From c29c1825cb519551356da38ac7cbfabfa500b4ab Mon Sep 17 00:00:00 2001 From: Doug McDiarmid Date: Mon, 24 May 2021 01:19:37 -0700 Subject: [PATCH 01/23] Added RayTracingPass and RayTracingPassData --- .../Code/Source/CommonSystemComponent.cpp | 6 + .../Code/Source/RayTracing/RayTracingPass.cpp | 326 ++++++++++++++++++ .../Code/Source/RayTracing/RayTracingPass.h | 81 +++++ .../Source/RayTracing/RayTracingPassData.h | 73 ++++ .../Code/atom_feature_common_files.cmake | 3 + .../RPI/Code/Include/Atom/RPI.Reflect/Base.h | 3 + 6 files changed, 492 insertions(+) create mode 100644 Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp create mode 100644 Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h create mode 100644 Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h diff --git a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp index 089a6168b1..d4053d0b31 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp @@ -91,6 +91,8 @@ #include #include +#include +#include #include #include #include @@ -132,6 +134,7 @@ namespace AZ SMAAFeatureProcessor::Reflect(context); PostProcessFeatureProcessor::Reflect(context); ImGuiPassData::Reflect(context); + RayTracingPassData::Reflect(context); LightingPreset::Reflect(context); ModelPreset::Reflect(context); @@ -275,6 +278,9 @@ namespace AZ passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurChildPass"), &Render::ReflectionScreenSpaceBlurChildPass::Create); passSystem->AddPassCreator(Name("ReflectionCopyFrameBufferPass"), &Render::ReflectionCopyFrameBufferPass::Create); + // Add RayTracing pas + passSystem->AddPassCreator(Name("RayTracingPass"), &Render::RayTracingPass::Create); + // setup handler for load pass template mappings m_loadTemplatesHandler = RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); }); RPI::PassSystemInterface::Get()->ConnectEvent(m_loadTemplatesHandler); diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp new file mode 100644 index 0000000000..2c0c7e986e --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp @@ -0,0 +1,326 @@ +/* +* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + RPI::Ptr RayTracingPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew RayTracingPass(descriptor); + return pass; + } + + RayTracingPass::RayTracingPass(const RPI::PassDescriptor& descriptor) + : RenderPass(descriptor) + , m_passDescriptor(descriptor) + { + RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); + if (device->GetFeatures().m_rayTracing == false) + { + // raytracing is not supported on this platform + SetEnabled(false); + } + + Init(); + } + + RayTracingPass::~RayTracingPass() + { + RPI::ShaderReloadNotificationBus::MultiHandler::BusDisconnect(); + } + + void RayTracingPass::Init() + { + RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); + + m_passData = RPI::PassUtils::GetPassData(m_passDescriptor); + if (m_passData == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Invalid RayTracingPassData", GetPathName().GetCStr()); + return; + } + + // ray generation shader + m_rayGenerationShader = LoadShader(m_passData->m_rayGenerationShaderAssetReference); + if (m_rayGenerationShader == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load RayGeneration shader [%s]", GetPathName().GetCStr(), m_passData->m_rayGenerationShaderAssetReference.m_filePath.data()); + return; + } + + auto shaderVariant = m_rayGenerationShader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId); + RHI::PipelineStateDescriptorForRayTracing rayGenerationShaderDescriptor; + shaderVariant.ConfigurePipelineState(rayGenerationShaderDescriptor); + + // closest hit shader + m_closestHitShader = LoadShader(m_passData->m_closestHitShaderAssetReference); + if (m_closestHitShader == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load ClosestHit shader [%s]", GetPathName().GetCStr(), m_passData->m_closestHitShaderAssetReference.m_filePath.data()); + return; + } + + shaderVariant = m_closestHitShader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId); + RHI::PipelineStateDescriptorForRayTracing closestHitShaderDescriptor; + shaderVariant.ConfigurePipelineState(closestHitShaderDescriptor); + + // miss shader + m_missShader = LoadShader(m_passData->m_missShaderAssetReference); + if (m_missShader == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load Miss shader [%s]", GetPathName().GetCStr(), m_passData->m_missShaderAssetReference.m_filePath.data()); + return; + } + + shaderVariant = m_missShader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId); + RHI::PipelineStateDescriptorForRayTracing missShaderDescriptor; + shaderVariant.ConfigurePipelineState(missShaderDescriptor); + + // retrieve global pipeline state + m_globalPipelineState = m_rayGenerationShader->AcquirePipelineState(rayGenerationShaderDescriptor); + AZ_Assert(m_globalPipelineState, "Failed to acquire ray tracing global pipeline state"); + + // create global srg + static const uint32_t RayTracingGlobalSrgBindingSlot = 0; + Data::Asset globalSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RayTracingGlobalSrgBindingSlot); + AZ_Error("PassSystem", globalSrgAsset.GetId().IsValid(), "RayTracingPass [%s] Failed to find RayTracingGlobalSrg asset", GetPathName().GetCStr()); + AZ_Error("PassSystem", globalSrgAsset.IsReady(), "RayTracingPass [%s] asset is not loaded for shader", GetPathName().GetCStr()); + + m_shaderResourceGroup = RPI::ShaderResourceGroup::Create(globalSrgAsset); + AZ_Assert(m_shaderResourceGroup, "RayTracingPass [%s]: Failed to create RayTracingGlobalSrg", GetPathName().GetCStr()); + RPI::PassUtils::BindDataMappingsToSrg(m_passDescriptor, m_shaderResourceGroup.get()); + + // check to see if the shader requires a ViewSrg + Data::Asset viewSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RPI::SrgBindingSlot::View); + m_requiresViewSrg = viewSrgAsset.GetId().IsValid(); + + // build the ray tracing pipeline state descriptor + RHI::RayTracingPipelineStateDescriptor descriptor; + descriptor.Build() + ->PipelineState(m_globalPipelineState.get()) + ->MaxPayloadSize(m_passData->m_maxPayloadSize) + ->MaxAttributeSize(m_passData->m_maxAttributeSize) + ->MaxRecursionDepth(m_passData->m_maxRecursionDepth) + ->ShaderLibrary(rayGenerationShaderDescriptor) + ->RayGenerationShaderName(AZ::Name(m_passData->m_rayGenerationShaderName.c_str())) + ->ShaderLibrary(missShaderDescriptor) + ->MissShaderName(AZ::Name(m_passData->m_missShaderName.c_str())) + ->ShaderLibrary(closestHitShaderDescriptor) + ->ClosestHitShaderName(AZ::Name(m_passData->m_closestHitShaderName.c_str())) + ->HitGroup(AZ::Name("HitGroup")) + ->ClosestHitShaderName(AZ::Name(m_passData->m_closestHitShaderName.c_str())); + + // create the ray tracing pipeline state object + m_rayTracingPipelineState = RHI::Factory::Get().CreateRayTracingPipelineState(); + m_rayTracingPipelineState->Init(*device.get(), &descriptor); + + // make sure the shader table rebuilds if we're hotreloading + m_rayTracingRevision = 0; + + RPI::ShaderReloadNotificationBus::MultiHandler::BusDisconnect(); + RPI::ShaderReloadNotificationBus::MultiHandler::BusConnect(m_passData->m_rayGenerationShaderAssetReference.m_assetId); + RPI::ShaderReloadNotificationBus::MultiHandler::BusConnect(m_passData->m_closestHitShaderAssetReference.m_assetId); + RPI::ShaderReloadNotificationBus::MultiHandler::BusConnect(m_passData->m_missShaderAssetReference.m_assetId); + } + + Data::Instance RayTracingPass::LoadShader(const RPI::AssetReference& shaderAssetReference) + { + Data::Asset shaderAsset; + if (shaderAssetReference.m_assetId.IsValid()) + { + shaderAsset = RPI::FindShaderAsset(shaderAssetReference.m_assetId, shaderAssetReference.m_filePath); + } + + if (!shaderAsset.GetId().IsValid()) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load shader asset [%s]", GetPathName().GetCStr(), shaderAssetReference.m_filePath.data()); + return nullptr; + } + + return RPI::Shader::FindOrCreate(shaderAsset); + } + + void RayTracingPass::FrameBeginInternal(FramePrepareParams params) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + if (!rayTracingFeatureProcessor) + { + return; + } + + if (!m_rayTracingShaderTable) + { + RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); + RHI::RayTracingBufferPools& rayTracingBufferPools = rayTracingFeatureProcessor->GetBufferPools(); + + m_rayTracingShaderTable = RHI::Factory::Get().CreateRayTracingShaderTable(); + m_rayTracingShaderTable->Init(*device.get(), rayTracingBufferPools); + } + + RPI::RenderPass::FrameBeginInternal(params); + } + + void RayTracingPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) + { + RPI::RenderPass::SetupFrameGraphDependencies(frameGraph); + frameGraph.SetEstimatedItemCount(1); + } + + void RayTracingPass::CompileResources(const RHI::FrameGraphCompileContext& context) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + AZ_Assert(rayTracingFeatureProcessor, "RayTracingPass requires the RayTracingFeatureProcessor"); + + if (m_shaderResourceGroup != nullptr) + { + BindPassSrg(context, m_shaderResourceGroup); + m_shaderResourceGroup->Compile(); + } + + uint32_t rayTracingRevision = rayTracingFeatureProcessor->GetRevision(); + if (m_rayTracingRevision != rayTracingRevision) + { + // scene changed, need to rebuild the shader table + m_rayTracingRevision = rayTracingRevision; + + AZStd::shared_ptr descriptor = AZStd::make_shared(); + + if (rayTracingFeatureProcessor->GetSubMeshCount()) + { + // build the ray tracing shader table descriptor + RHI::RayTracingShaderTableDescriptor* descriptorBuild = descriptor->Build(AZ::Name("RayTracingShaderTable"), m_rayTracingPipelineState) + ->RayGenerationRecord(AZ::Name(m_passData->m_rayGenerationShaderName.c_str())) + ->MissRecord(AZ::Name(m_passData->m_missShaderName.c_str())); + + // add a hit group for each mesh to the shader table + for (uint32_t i = 0; i < rayTracingFeatureProcessor->GetSubMeshCount(); ++i) + { + descriptorBuild->HitGroupRecord(AZ::Name("HitGroup")); + } + } + + m_rayTracingShaderTable->Build(descriptor); + } + } + + void RayTracingPass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + AZ_Assert(rayTracingFeatureProcessor, "RayTracingPass requires the RayTracingFeatureProcessor"); + + if (!rayTracingFeatureProcessor || + !rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer() || + !rayTracingFeatureProcessor->GetSubMeshCount() || + !m_rayTracingShaderTable) + { + return; + } + + RHI::DispatchRaysItem dispatchRaysItem; + + // calculate thread counts if this is a full screen raytracing pass + if (m_passData->m_makeFullscreenPass) + { + RPI::PassAttachment* outputAttachment = nullptr; + + if (GetOutputCount() > 0) + { + outputAttachment = GetOutputBinding(0).m_attachment.get(); + } + else if (GetInputOutputCount() > 0) + { + outputAttachment = GetInputOutputBinding(0).m_attachment.get(); + } + + AZ_Assert(outputAttachment != nullptr, "[RayTracingPass '%s']: A fullscreen RayTracing pass must have a valid output or input/output.", GetPathName().GetCStr()); + AZ_Assert(outputAttachment->GetAttachmentType() == RHI::AttachmentType::Image, "[RayTracingPass '%s']: The output of a fullscreen RayTracing pass must be an image.", GetPathName().GetCStr()); + + RHI::Size imageSize = outputAttachment->m_descriptor.m_image.m_size; + + dispatchRaysItem.m_width = imageSize.m_width; + dispatchRaysItem.m_height = imageSize.m_height; + dispatchRaysItem.m_depth = imageSize.m_depth; + } + else + { + dispatchRaysItem.m_width = m_passData->m_threadCountX; + dispatchRaysItem.m_height = m_passData->m_threadCountY; + dispatchRaysItem.m_depth = m_passData->m_threadCountZ; + } + + // bind RayTracingGlobal, RayTracingScene, and View Srgs + // [GFX TODO][ATOM-15610] Add RenderPass::SetSrgsForRayTracingDispatch + AZStd::vector shaderResourceGroups = + { + m_shaderResourceGroup->GetRHIShaderResourceGroup(), + rayTracingFeatureProcessor->GetRayTracingSceneSrg()->GetRHIShaderResourceGroup() + }; + + if (m_requiresViewSrg) + { + const AZStd::vector& views = m_pipeline->GetViews(m_passData->m_pipelineViewTag); + if (views.size() > 0) + { + shaderResourceGroups.push_back(views[0]->GetRHIShaderResourceGroup()); + } + } + + dispatchRaysItem.m_shaderResourceGroupCount = aznumeric_cast(shaderResourceGroups.size()); + dispatchRaysItem.m_shaderResourceGroups = shaderResourceGroups.data(); + dispatchRaysItem.m_rayTracingPipelineState = m_rayTracingPipelineState.get(); + dispatchRaysItem.m_rayTracingShaderTable = m_rayTracingShaderTable.get(); + dispatchRaysItem.m_globalPipelineState = m_globalPipelineState.get(); + + // submit the DispatchRays item + context.GetCommandList()->Submit(dispatchRaysItem); + } + + void RayTracingPass::OnShaderReinitialized([[maybe_unused]] const RPI::Shader& shader) + { + Init(); + } + + void RayTracingPass::OnShaderAssetReinitialized([[maybe_unused]] const Data::Asset& shaderAsset) + { + Init(); + } + + void RayTracingPass::OnShaderVariantReinitialized([[maybe_unused]] const RPI::Shader& shader, [[maybe_unused]] const RPI::ShaderVariantId& shaderVariantId, [[maybe_unused]] RPI::ShaderVariantStableId shaderVariantStableId) + { + Init(); + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h new file mode 100644 index 0000000000..935d034513 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h @@ -0,0 +1,81 @@ +/* +* 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 +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + struct RayTracingPassData; + + //! This pass executes a raytracing shader as specified in the PassData. + class RayTracingPass + : public RPI::RenderPass + , private RPI::ShaderReloadNotificationBus::MultiHandler + { + AZ_RPI_PASS(RayTracingPass); + + public: + AZ_RTTI(RayTracingPass, "{7A68A36E-956A-4258-93FE-38686042C4D9}", RPI::RenderPass); + AZ_CLASS_ALLOCATOR(RayTracingPass, SystemAllocator, 0); + virtual ~RayTracingPass(); + + //! Creates a RayTracingPass + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + RayTracingPass(const RPI::PassDescriptor& descriptor); + + // Pass overrides + void FrameBeginInternal(FramePrepareParams params) override; + + // Scope producer functions + void SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) override; + void CompileResources(const RHI::FrameGraphCompileContext& context) override; + void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; + + // ShaderReloadNotificationBus::Handler overrides + void OnShaderReinitialized(const RPI::Shader& shader) override; + void OnShaderAssetReinitialized(const Data::Asset& shaderAsset) override; + void OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId shaderVariantStableId) override; + + // load the raytracing shaders and setup pipeline states + void Init(); + + // helper for loading a shader from a shader asset reference + Data::Instance LoadShader(const RPI::AssetReference& shaderAssetReference); + + // pass data + RPI::PassDescriptor m_passDescriptor; + const RayTracingPassData* m_passData = nullptr; + + // revision number of the ray tracing TLAS when the shader table was built + uint32_t m_rayTracingRevision = 0; + + // raytracing shaders, pipeline states, and shader table + Data::Instance m_rayGenerationShader; + Data::Instance m_missShader; + Data::Instance m_closestHitShader; + RHI::Ptr m_rayTracingPipelineState; + RHI::ConstPtr m_globalPipelineState; + RHI::Ptr m_rayTracingShaderTable; + bool m_requiresViewSrg = false; + }; + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h new file mode 100644 index 0000000000..bc15a8372c --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h @@ -0,0 +1,73 @@ +/* +* 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 +#include + +namespace AZ +{ + namespace Render + { + //! Custom data for the RayTracingPass, specified in the PassRequest. + struct RayTracingPassData + : public RPI::RenderPassData + { + AZ_RTTI(RayTracingPassData, "{26C2E2FD-D30A-4142-82A3-0167BC94B3EE}", RPI::RenderPassData); + AZ_CLASS_ALLOCATOR(RayTracingPassData, SystemAllocator, 0); + + RayTracingPassData() = default; + virtual ~RayTracingPassData() = default; + + static void Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("RayGenerationShaderAsset", &RayTracingPassData::m_rayGenerationShaderAssetReference) + ->Field("RayGenerationShaderName", &RayTracingPassData::m_rayGenerationShaderName) + ->Field("ClosestHitShaderAsset", &RayTracingPassData::m_closestHitShaderAssetReference) + ->Field("ClosestHitShaderName", &RayTracingPassData::m_closestHitShaderName) + ->Field("MissShaderAsset", &RayTracingPassData::m_missShaderAssetReference) + ->Field("MissShaderName", &RayTracingPassData::m_missShaderName) + ->Field("MaxPayloadSize", &RayTracingPassData::m_maxPayloadSize) + ->Field("MaxAttributeSize", &RayTracingPassData::m_maxAttributeSize) + ->Field("MaxRecursionDepth", &RayTracingPassData::m_maxRecursionDepth) + ->Field("Thread Count X", &RayTracingPassData::m_threadCountX) + ->Field("Thread Count Y", &RayTracingPassData::m_threadCountY) + ->Field("Thread Count Z", &RayTracingPassData::m_threadCountZ) + ->Field("Make Fullscreen Pass", &RayTracingPassData::m_makeFullscreenPass) + ; + } + } + + RPI::AssetReference m_rayGenerationShaderAssetReference; + AZStd::string m_rayGenerationShaderName; + RPI::AssetReference m_closestHitShaderAssetReference; + AZStd::string m_closestHitShaderName; + RPI::AssetReference m_missShaderAssetReference; + AZStd::string m_missShaderName; + + uint32_t m_maxPayloadSize = 64; + uint32_t m_maxAttributeSize = 32; + uint32_t m_maxRecursionDepth = 1; + + uint32_t m_threadCountX = 1; + uint32_t m_threadCountY = 1; + uint32_t m_threadCountZ = 1; + + bool m_makeFullscreenPass = false; + }; + } // namespace RPI +} // namespace AZ + diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake index 8926b0c19f..064e49922d 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake @@ -252,6 +252,9 @@ set(FILES Source/RayTracing/RayTracingFeatureProcessor.cpp Source/RayTracing/RayTracingAccelerationStructurePass.cpp Source/RayTracing/RayTracingAccelerationStructurePass.h + Source/RayTracing/RayTracingPass.cpp + Source/RayTracing/RayTracingPass.h + Source/RayTracing/RayTracingPassData.h Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp Source/ReflectionProbe/ReflectionProbe.cpp Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h index c376112290..52b75c4157 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h @@ -59,7 +59,10 @@ namespace AZ static constexpr uint32_t Draw = 0; static constexpr uint32_t Object = 1; static constexpr uint32_t Material = 2; + static constexpr uint32_t SubPass = 3; static constexpr uint32_t Pass = 4; + static constexpr uint32_t View = 5; + static constexpr uint32_t Scene = 6; }; } } From 12d0d9e7b78edff156a4cdc3abfb42879126eab5 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Mon, 24 May 2021 12:49:38 -0700 Subject: [PATCH 02/23] The host will now have autonomy over the default player is has spawned for itself using the sv_defaultPlayerSpawnAsset cvar --- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index ef8627fe54..39bbf6644e 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -449,6 +449,7 @@ namespace Multiplayer { controlledEntity = entityList[0]; controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); + controlledEntity.GetNetBindComponent()->SetAllowAutonomy(true); } if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so From b5a0df00e1a14ad5f9aefb1063612953b3db8a5d Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Tue, 25 May 2021 17:21:03 -0700 Subject: [PATCH 03/23] Hosts which are not a dedicated server (meaning they also play the game) will spawn a default player for themselves --- .../Source/MultiplayerSystemComponent.cpp | 36 ++++++++++++++----- .../Code/Source/MultiplayerSystemComponent.h | 3 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 39bbf6644e..0818f605df 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -438,20 +438,16 @@ namespace Multiplayer m_connAcquiredEvent.Signal(datum); } + // Hosts will spawn a new default player prefab for the user that just connected if (GetAgentType() == MultiplayerAgentType::ClientServer || GetAgentType() == MultiplayerAgentType::DedicatedServer) { - PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); - INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); - - NetworkEntityHandle controlledEntity; - if (entityList.size() > 0) + NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(); + if (controlledEntity.Exists()) { - controlledEntity = entityList[0]; controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); - controlledEntity.GetNetBindComponent()->SetAllowAutonomy(true); } - + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so { connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); @@ -523,6 +519,17 @@ namespace Multiplayer } } m_agentType = multiplayerType; + + // Spawn the default player for this host since the host is also a player (not a dedicated server) + if (m_agentType == MultiplayerAgentType::ClientServer) + { + NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(); + if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent()) + { + controlledEntityNetBindComponent->SetAllowAutonomy(true); + } + } + AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType)); } @@ -630,6 +637,19 @@ namespace Multiplayer } } + NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab() + { + PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); + INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); + + NetworkEntityHandle controlledEntity; + if (entityList.size() > 0) + { + controlledEntity = entityList[0]; + } + return controlledEntity; + } + void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index db83c50fb5..e8e05a9d4c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -104,7 +104,8 @@ namespace Multiplayer void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector& commands); - + NetworkEntityHandle SpawnDefaultPlayerPrefab(); + AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; From 0fc9697e495d13ef8dd45bbc5723b79539336b06 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Tue, 25 May 2021 19:56:18 -0700 Subject: [PATCH 04/23] Allow Autonomous->Auth properties. Remove the ability of accessing properties Getters from the Component when ReplicateTo is Autonomous; in this case users must be using the controller to Get --- .../Source/AutoGen/AutoComponent_Header.jinja | 5 +++-- .../Source/AutoGen/AutoComponent_Source.jinja | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 71e81b6bfb..aeab2e88e1 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -365,6 +365,8 @@ namespace {{ Component.attrib['Namespace'] }} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Client', true)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', false)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', true)|indent(8) -}} + {{ DeclareNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', false)|indent(8) -}} + {{ DeclareNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} @@ -438,7 +440,7 @@ namespace {{ Component.attrib['Namespace'] }} {% endif %} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', false)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', false)|indent(8) -}} + {{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', false)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} @@ -463,7 +465,6 @@ namespace {{ Component.attrib['Namespace'] }} //! @} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', true)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', true)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 7d5295aabb..3ca4d854bb 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -767,18 +767,33 @@ enum class NetworkProperties return {{ Property.attrib['Type'] }}(); } - {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); + {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); if (!networkComponent) { AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) return {{ Property.attrib['Type'] }}(); } +{% if ReplicateTo == 'Autonomous' %} + // {{ UpperFirst(Property.attrib['Name']) }} is replicated to Automonous; we must go through the controller in order to get this property + {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); + if (!controller) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This property is replicated to autonomous network entities, because this entity doesn't have a controller, it must not be automonous. Please check your network context before attempting to get {{ UpperFirst(Property.attrib['Name']) }}.", entity->GetName().c_str(), id.ToString().c_str()) + return {{ Property.attrib['Type'] }}(); + } +{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %} + return controller->Get{{ UpperFirst(Property.attrib['Name']) }}(index); +{% else %} + return controller->Get{{ UpperFirst(Property.attrib['Name']) }}(); +{% endif %} +{% else %} {% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %} return networkComponent->Get{{ UpperFirst(Property.attrib['Name']) }}(index); {% else %} return networkComponent->Get{{ UpperFirst(Property.attrib['Name']) }}(); {% endif %} +{% endif %} }) {% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %} ->Method("Set{{ UpperFirst(Property.attrib['Name']) }}", [](AZ::EntityId id, int32_t index, const {{ Property.attrib['Type'] }}& value) -> void @@ -1457,11 +1472,9 @@ namespace {{ Component.attrib['Namespace'] }} } {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', false, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Authority', 'Autonomous', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', true, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Authority', 'Autonomous', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', true, ComponentBaseName)|indent(4) }} {{ DefineArchetypePropertyGets(Component, ClassType, ComponentBaseName)|indent(4) -}} From 40435fcb2e5326bc28cb750aaa3df270ba263ff9 Mon Sep 17 00:00:00 2001 From: Doug McDiarmid Date: Tue, 25 May 2021 23:52:19 -0700 Subject: [PATCH 05/23] Added RayTracingMaterialSrg. Added UV buffer to the RayTracingSceneSrg mesh buffers. Added RayTracingSceneUtils and RayTracingMaterialUtils shader includes. --- .../RayTracing/RayTracingMaterialSrg.azsli | 48 ++++ .../RayTracing/RayTracingMaterialUtils.azsli | 69 +++++ .../RayTracing/RayTracingSceneSrg.azsli | 33 ++- .../RayTracing/RayTracingSceneUtils.azsli | 126 +++++++++ .../Atom/Features/SrgSemantics.azsli | 5 + .../diffuseprobegridraytracing.azshader | Bin 79334 -> 79466 bytes ...probegridraytracing_dx12_0.azshadervariant | Bin 34086 -> 34738 bytes ...probegridraytracing_null_0.azshadervariant | Bin 4502 -> 4502 bytes ...obegridraytracing_vulkan_0.azshadervariant | Bin 36232 -> 36332 bytes ...fuseprobegridraytracingclosesthit.azshader | Bin 79344 -> 79476 bytes ...aytracingclosesthit_dx12_0.azshadervariant | Bin 16754 -> 17150 bytes ...aytracingclosesthit_null_0.azshadervariant | Bin 4502 -> 4502 bytes ...tracingclosesthit_vulkan_0.azshadervariant | Bin 9172 -> 9452 bytes ...raytracingcommon_raytracingglobalsrg.azsrg | 24 +- .../diffuseprobegridraytracingmiss.azshader | Bin 79338 -> 79470 bytes ...egridraytracingmiss_dx12_0.azshadervariant | Bin 16838 -> 17222 bytes ...egridraytracingmiss_null_0.azshadervariant | Bin 4502 -> 4502 bytes ...ridraytracingmiss_vulkan_0.azshadervariant | Bin 10364 -> 10364 bytes .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 213 +++++++++++++--- .../RayTracingAccelerationStructurePass.cpp | 4 +- .../RayTracing/RayTracingFeatureProcessor.cpp | 240 ++++++++++++++---- .../RayTracing/RayTracingFeatureProcessor.h | 111 +++++++- .../Code/Source/RayTracing/RayTracingPass.cpp | 11 +- .../Code/Source/RayTracing/RayTracingPass.h | 1 + 24 files changed, 777 insertions(+), 108 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli new file mode 100644 index 0000000000..40faba24f9 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli @@ -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. +* +*/ + +#include + +ShaderResourceGroup RayTracingMaterialSrg : SRG_RayTracingMaterial +{ + Sampler LinearSampler + { + AddressU = Wrap; + AddressV = Wrap; + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + MaxAnisotropy = 16; + }; + + // material info structured buffer + struct MaterialInfo + { + float4 m_baseColor; + float m_metallicFactor; + float m_roughnessFactor; + uint m_textureFlags; + uint m_textureStartIndex; + }; + + // hit shaders can retrieve the MaterialInfo for a mesh hit using: RayTracingMaterialSrg::m_materialInfo[InstanceIndex()] + StructuredBuffer m_materialInfo; + + // texture flag bits indicating if optional textures are present + #define TEXTURE_FLAG_BASECOLOR 1 + #define TEXTURE_FLAG_NORMAL 2 + #define TEXTURE_FLAG_METALLIC 4 + #define TEXTURE_FLAG_ROUGHNESS 8 + + // unbounded array of Material textures + Texture2D m_materialTextures[]; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli new file mode 100644 index 0000000000..d6dd77f4fa --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli @@ -0,0 +1,69 @@ +/* +* 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. +* +*/ + +struct TextureData +{ + float4 m_baseColor; + float3 m_normal; + float m_metallic; + float m_roughness; +}; + +TextureData GetHitTextureData(RayTracingMaterialSrg::MaterialInfo materialInfo, float2 uv) +{ + TextureData textureData = (TextureData)0; + + uint textureIndex = materialInfo.m_textureStartIndex; + + // base color + if (materialInfo.m_textureFlags & TEXTURE_FLAG_BASECOLOR) + { + textureData.m_baseColor = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_baseColor = materialInfo.m_baseColor; + } + + // normal + if (materialInfo.m_textureFlags & TEXTURE_FLAG_NORMAL) + { + textureData.m_normal = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_normal = float3(0.0f, 0.0f, 1.0f); + } + + // metallic + if (materialInfo.m_textureFlags & TEXTURE_FLAG_METALLIC) + { + textureData.m_metallic = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_metallic = materialInfo.m_metallicFactor; + } + + // roughness + if (materialInfo.m_textureFlags & TEXTURE_FLAG_ROUGHNESS) + { + textureData.m_roughness = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_roughness = materialInfo.m_roughnessFactor; + } + + return textureData; +} + \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli index fdf7ba92de..2352f5d09b 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli @@ -136,18 +136,35 @@ ShaderResourceGroup RayTracingSceneSrg : SRG_RayTracingScene uint m_indexOffset; uint m_positionOffset; uint m_normalOffset; + uint m_tangentOffset; + uint m_bitangentOffset; + uint m_uvOffset; + float m_padding0[2]; + float4 m_irradianceColor; float3x3 m_worldInvTranspose; + float m_padding1[1]; + + uint m_bufferFlags; + uint m_bufferStartIndex; }; - + + // hit shaders can retrieve the MeshInfo for a mesh hit using: RayTracingSceneSrg::m_meshInfo[InstanceIndex()] StructuredBuffer m_meshInfo; - // unbounded array of Index, VertexPosition, and VertexNormal buffers - // each mesh has three entries in this array starting at its InstanceIndex() * BUFFER_COUNT_PER_MESH - #define BUFFER_COUNT_PER_MESH 3 - #define MESH_INDEX_BUFFER_OFFSET 0 - #define MESH_POSITION_BUFFER_OFFSET 1 - #define MESH_NORMAL_BUFFER_OFFSET 2 - + // buffer array index offsets for buffers that are always present for each mesh + #define MESH_INDEX_BUFFER_OFFSET 0 + #define MESH_POSITION_BUFFER_OFFSET 1 + #define MESH_NORMAL_BUFFER_OFFSET 2 + #define MESH_TANGENT_BUFFER_OFFSET 3 + #define MESH_BITANGENT_BUFFER_OFFSET 4 + + // buffer flag bits indicating if optional buffers are present + #define MESH_BUFFER_FLAG_UV 1 + + // Unbounded array of mesh stream buffers: + // - Index, Position, Normal, Tangent, and Bitangent stream buffers are always present + // - Optional stream buffers such as UV are indicated in the MeshInfo.m_bufferFlags field + // - Buffers for a particular mesh start at MeshInfo.m_bufferStartIndex ByteAddressBuffer m_meshBuffers[]; } \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli new file mode 100644 index 0000000000..f858b9a11a --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli @@ -0,0 +1,126 @@ +/* +* 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. +* +*/ + +// returns the normalized camera view ray into the scene for this raytracing dispatch thread +float3 GetViewRayDirection(float4x4 viewProjectionInverseMatrix) +{ + float2 pixel = ((float2)DispatchRaysIndex().xy + float2(0.5f, 0.5f)) / (float2)DispatchRaysDimensions(); + float2 ndc = pixel * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f); + return normalize(mul(viewProjectionInverseMatrix, float4(ndc, 0.0f, 1.0f)).xyz); +} + +// returns the vertex indices for the primitive hit by the ray +// Note: usable only in a raytracing Hit shader +uint3 GetHitIndices(RayTracingSceneSrg::MeshInfo meshInfo) +{ + // compute the array index of the index buffer for this mesh in the m_meshBuffers unbounded array + uint meshIndexBufferArrayIndex = meshInfo.m_bufferStartIndex + MESH_INDEX_BUFFER_OFFSET; + + // compute the offset into the index buffer for this primitve of the mesh + uint offsetBytes = meshInfo.m_indexOffset + (PrimitiveIndex() * 12); + + // load the indices for this primitive from the index buffer + return RayTracingSceneSrg::m_meshBuffers[meshIndexBufferArrayIndex].Load3(offsetBytes); +} + +// returns the interpolated vertex data for the primitive hit by the ray +// Note: usable only in a raytracing hit shader +struct VertexData +{ + float3 m_position; + float3 m_normal; + float3 m_tangent; + float3 m_bitangent; + float2 m_uv; +}; + +VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, float2 builtInBarycentrics) +{ + // retrieve the poly indices + uint3 indices = GetHitIndices(meshInfo); + + // compute barycentrics + float3 barycentrics = float3((1.0f - builtInBarycentrics.x - builtInBarycentrics.y), builtInBarycentrics.x, builtInBarycentrics.y); + + // compute the vertex data using barycentric interpolation + VertexData vertexData = (VertexData)0; + for (uint i = 0; i < 3; ++i) + { + // position + { + // array index of the position buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexPositionArrayIndex = meshInfo.m_bufferStartIndex + MESH_POSITION_BUFFER_OFFSET; + + // offset into the position buffer for this vertex + uint positionOffset = meshInfo.m_positionOffset + (indices[i] * 12); + + // load the position data + vertexData.m_position += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexPositionArrayIndex].Load3(positionOffset)) * barycentrics[i]; + } + + // normal + { + // array index of the normal buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexNormalArrayIndex = meshInfo.m_bufferStartIndex + MESH_NORMAL_BUFFER_OFFSET; + + // offset into the normal buffer for this vertex + uint normalOffset = meshInfo.m_normalOffset + (indices[i] * 12); + + // load the normal data + vertexData.m_normal += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexNormalArrayIndex].Load3(normalOffset)) * barycentrics[i]; + } + + // tangent + { + // array index of the tangent buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexTangentArrayIndex = meshInfo.m_bufferStartIndex + MESH_TANGENT_BUFFER_OFFSET; + + // offset into the tangent buffer for this vertex + uint tangentOffset = meshInfo.m_tangentOffset + (indices[i] * 12); + + // load the tangent data + vertexData.m_tangent += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexTangentArrayIndex].Load3(tangentOffset)) * barycentrics[i]; + } + + // bitangent + { + // array index of the bitangent buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexBitangentArrayIndex = meshInfo.m_bufferStartIndex + MESH_BITANGENT_BUFFER_OFFSET; + + // offset into the bitangent buffer for this vertex + uint bitangentOffset = meshInfo.m_bitangentOffset + (indices[i] * 12); + + // load the bitangent data + vertexData.m_bitangent += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexBitangentArrayIndex].Load3(bitangentOffset)) * barycentrics[i]; + } + + // optional streams begin after MESH_BITANGENT_BUFFER_OFFSET + uint optionalBufferOffset = MESH_BITANGENT_BUFFER_OFFSET + 1; + + // UV + if (meshInfo.m_bufferFlags & MESH_BUFFER_FLAG_UV) + { + // array index of the UV buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexUVArrayIndex = meshInfo.m_bufferStartIndex + optionalBufferOffset++; + + // offset into the UV buffer for this vertex + uint uvOffset = meshInfo.m_uvOffset + (indices[i] * 8); + + // load the UV data + vertexData.m_uv += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexUVArrayIndex].Load2(uvOffset)) * barycentrics[i]; + } + } + + vertexData.m_normal = normalize(vertexData.m_normal); + + return vertexData; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli index c134a9d293..6d1b05d797 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli @@ -64,3 +64,8 @@ ShaderResourceGroupSemantic SRG_RayTracingScene { FrequencyId = 1; }; + +ShaderResourceGroupSemantic SRG_RayTracingMaterial +{ + FrequencyId = 2; +}; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader index 804b534277c99e83211f6276d08f434bc053295d..746ae357ee6493061a828bd02010fab4308b1164 100644 GIT binary patch delta 430 zcmaF%n&s6SmJOP0LhSOBjs?zccCK6<6U6v&)1iup$rEiv7#Su%ob0f^`U|^iQGwb!_^A}19 zIb<`$<35)+Gh>Fzb8!x*?l*6|oftQ7e(;f%9j9TyfOy9^`GF$K<`=(iS#nf93#i<5 e{&>^m?kp{q28e4`PhV`xXwBjG??Z48(18HC4!d;# delta 414 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 literal 36232 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 M+%~eyrkm9MA3e>YGXMYp diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader index c4a83a26dcc61d813d22d92798d7918b141877b3..0f42cd04d6971885c5d9acfd0d5665eee62d132a 100644 GIT binary patch delta 430 zcmezHn&rzImJP;iLhSOBjs?zccCK6<6U6v&)1iup$p@uG7#Sx0bl5!q;%3Il1}rR- z?@krn9KbP;d2)WS)Mmb->r9LZlNF^UH~%j=C(d#3V^5TA(UA$8&#SSr<1}y6&v#(6 zHs76k%aWrc)_9x9y&L;C@66<6WMrKD?)>Y?>$1CX>ZYx0PRev#asHkdG-JLH+z0shC7Tm{XApMalNq7;fb*1 g*2xFXYq2yiFn|JM_4Ku-jMf}}|Gub*GcYg!0Q-Em`Tzg` delta 492 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 VwsNenAw;~GzVgop{_cS1{{>MESCaq$ delta 7827 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 Rx-Q`=Vn@pQ;}D?1{{pwXlkfll diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant index b7bdabeb4e9dfea986d9adf30d0f57c1225bedd9..d9d30c99f7c6609f3be3cfba81ec330469fb67fe 100644 GIT binary patch delta 16 XcmbQHJWY8+pCE_dzb`7{3=9kaH0}jO delta 16 XcmbQHJWY8+pCHFZ->{jC3=9kaF>D05 diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant index 1eeeffa8be8df2020435b46d5b5de152d8ece66b..e2f2a13ca3738fe19122583db01704237b19ea4a 100644 GIT binary patch literal 9452 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 h(SV%BmzPvt9Y1)pbK=9&-}`>& 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 delta 451 zcmaF&hUL|3mJPaWf^71Wjs?zccCK6<6U6v&)1ivVbw3?8vtQcGIN4z?%Vhm&qMH*q z<}pvM=aQQIpp#>>0?&D-$+|bBHrL!LVVpd{mu2&p+h$yo1J)T%j((OjSwK#2a>8qg z%@d^ExjEQwmhS$ubB^z3!CqE&Oq({>{d@7HpdlXqunu{1C+uuN|-Wqii5(U0S)5(5JR0P`Ti AlmGw# diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant index 839efa72787ebf96ba18125c20d57da3997d6704..c144350c7ff1c7da6a32247b50d0e80f1d29ce31 100644 GIT binary patch delta 5097 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 A%m4rY delta 4676 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 delta 1362 zcmZvbJ7`l;7{^c2#HJX9Yx8K6Cimv%MM$GyC{c^FzNpfgroL-it@x-)B|ZiRhpUsD z4&UJFrs7b=!HPq1b`*rtK^G~milA7k|NqUolEJ`_@0{=P|IRu0%UWZt@uF^QE^Yjn zdVEektLaj0`MbR9?3Si6;{O_XwP>t|ou~?^s8Y%T{FU`S*4qBt$FcXd#e1+0wC#Jb z{sUsFLnUA>5JyH5mV4&1S0Ahew3g#KQgCztHfM4g@J9g?mOjS&)dqKOVa*&5uPy`M z!}s<1i1I7jjd!UqEHyl+#S1fTtEy5ZNOO|~6ygbUpi}QzkO%k;yKz}H%f5tkTch&L zipaVZ3c9xx$4WY^=23f~)DO!D@6M}KV5#N@eb`4}8RuK3Rj=9t%VSc*yLK6C*8C6V z5if?C?QT;#+Y8y4&C+;31D1_g#CM3wXl9$wlslQK(Hew+8i1dNOv+LwiL0(;9t|+R z29&zHv=vw60e1kd8QZDX)Yzrhwmh+moA|JUfR7iI6?<@kwZmWta1bU`(AC?cee6vi zwf1TsACW#AcAxeIJ)g{FOiqC#maIdjDGYed>RfmKVr)=o3qydoVl1RU0sU=?*Az{mV)z&yt8Y2OUi)L|@)5jJHZpNum5 zEI0!=%5JuN%2#KoeICpL{v6z&z6*eUum44?XMy)+m~%<{cxJC|PWv1Dr%JO>PVv51 z!4+^BP=RmPy8G)w)u7fjFb{ahD{gFYwW2H^W<0pINx s^bf%P)b(modelLods.size() - 1); const Data::Instance& modelLod = modelLods[rayTracingLod]; - // setup a stream layout and shader input contract for the position and normal streams + // setup a stream layout and shader input contract for the vertex streams static const char* PositionSemantic = "POSITION"; static const char* NormalSemantic = "NORMAL"; - static const RHI::Format StreamFormat = RHI::Format::R32G32B32_FLOAT; + static const char* TangentSemantic = "TANGENT"; + static const char* BitangentSemantic = "BITANGENT"; + static const char* UVSemantic = "UV"; + static const RHI::Format PositionStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format NormalStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format TangentStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format BitangentStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format UVStreamFormat = RHI::Format::R32G32_FLOAT; RHI::InputStreamLayoutBuilder layoutBuilder; - layoutBuilder.AddBuffer()->Channel(PositionSemantic, StreamFormat); - layoutBuilder.AddBuffer()->Channel(NormalSemantic, StreamFormat); + layoutBuilder.AddBuffer()->Channel(PositionSemantic, PositionStreamFormat); + layoutBuilder.AddBuffer()->Channel(NormalSemantic, NormalStreamFormat); + layoutBuilder.AddBuffer()->Channel(UVSemantic, UVStreamFormat); + layoutBuilder.AddBuffer()->Channel(TangentSemantic, TangentStreamFormat); + layoutBuilder.AddBuffer()->Channel(BitangentSemantic, BitangentStreamFormat); RHI::InputStreamLayout inputStreamLayout = layoutBuilder.End(); RPI::ShaderInputContract::StreamChannelInfo positionStreamChannelInfo; positionStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(PositionSemantic)); - positionStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(StreamFormat); + positionStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(PositionStreamFormat); RPI::ShaderInputContract::StreamChannelInfo normalStreamChannelInfo; normalStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(NormalSemantic)); - normalStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(StreamFormat); + normalStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(NormalStreamFormat); + + RPI::ShaderInputContract::StreamChannelInfo tangentStreamChannelInfo; + tangentStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(TangentSemantic)); + tangentStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(TangentStreamFormat); + + RPI::ShaderInputContract::StreamChannelInfo bitangentStreamChannelInfo; + bitangentStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(BitangentSemantic)); + bitangentStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(BitangentStreamFormat); + + RPI::ShaderInputContract::StreamChannelInfo uvStreamChannelInfo; + uvStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(UVSemantic)); + uvStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(UVStreamFormat); + uvStreamChannelInfo.m_isOptional = true; RPI::ShaderInputContract shaderInputContract; shaderInputContract.m_streamChannels.emplace_back(positionStreamChannelInfo); shaderInputContract.m_streamChannels.emplace_back(normalStreamChannelInfo); + shaderInputContract.m_streamChannels.emplace_back(tangentStreamChannelInfo); + shaderInputContract.m_streamChannels.emplace_back(bitangentStreamChannelInfo); + shaderInputContract.m_streamChannels.emplace_back(uvStreamChannelInfo); // setup the raytracing data for each sub-mesh const size_t meshCount = modelLod->GetMeshes().size(); @@ -739,26 +765,6 @@ namespace AZ { const RPI::ModelLod::Mesh& mesh = modelLod->GetMeshes()[meshIndex]; - // retrieve vertex/index buffers - RPI::ModelLod::StreamBufferViewList streamBufferViews; - [[maybe_unused]] bool result = modelLod->GetStreamsForMesh(inputStreamLayout, streamBufferViews, shaderInputContract, meshIndex); - AZ_Assert(result, "Failed to retrieve mesh stream buffer views"); - - // note that the element count is the size of the entire buffer, even though this mesh may only - // occupy a portion of the vertex buffer. This is necessary since we are accessing it using - // a ByteAddressBuffer in the raytracing shaders and passing the byte offset to the shader in a constant buffer. - uint32_t vertexBufferByteCount = const_cast(streamBufferViews[0].GetBuffer())->GetDescriptor().m_byteCount; - RHI::BufferViewDescriptor vertexBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, vertexBufferByteCount); - - const RHI::IndexBufferView& indexBufferView = mesh.m_indexBufferView; - uint32_t indexElementSize = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? 2 : 4; - uint32_t indexElementCount = (uint32_t)indexBufferView.GetBuffer()->GetDescriptor().m_byteCount / indexElementSize; - RHI::BufferViewDescriptor indexBufferDescriptor; - indexBufferDescriptor.m_elementOffset = 0; - indexBufferDescriptor.m_elementCount = indexElementCount; - indexBufferDescriptor.m_elementSize = indexElementSize; - indexBufferDescriptor.m_elementFormat = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? RHI::Format::R16_UINT : RHI::Format::R32_UINT; - // retrieve the material Data::Instance material = mesh.m_material; @@ -769,31 +775,162 @@ namespace AZ material = materialAssignment.m_materialInstance; } - AZ::Color irradianceColor(1.0f, 1.0f, 1.0f, 1.0f); + // retrieve vertex/index buffers + RPI::ModelLod::StreamBufferViewList streamBufferViews; + [[maybe_unused]] bool result = modelLod->GetStreamsForMesh( + inputStreamLayout, + streamBufferViews, + shaderInputContract, + meshIndex, + materialAssignment.m_matModUvOverrides, + material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap()); + AZ_Assert(result, "Failed to retrieve mesh stream buffer views"); + + // note that the element count is the size of the entire buffer, even though this mesh may only + // occupy a portion of the vertex buffer. This is necessary since we are accessing it using + // a ByteAddressBuffer in the raytracing shaders and passing the byte offset to the shader in a constant buffer. + uint32_t positionBufferByteCount = const_cast(streamBufferViews[0].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor positionBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, positionBufferByteCount); + + uint32_t normalBufferByteCount = const_cast(streamBufferViews[1].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor normalBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, normalBufferByteCount); + + uint32_t tangentBufferByteCount = const_cast(streamBufferViews[2].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor tangentBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, tangentBufferByteCount); + + uint32_t bitangentBufferByteCount = const_cast(streamBufferViews[3].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor bitangentBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, bitangentBufferByteCount); + + uint32_t uvBufferByteCount = const_cast(streamBufferViews[4].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor uvBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, uvBufferByteCount); + + const RHI::IndexBufferView& indexBufferView = mesh.m_indexBufferView; + uint32_t indexElementSize = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? 2 : 4; + uint32_t indexElementCount = (uint32_t)indexBufferView.GetBuffer()->GetDescriptor().m_byteCount / indexElementSize; + RHI::BufferViewDescriptor indexBufferDescriptor; + indexBufferDescriptor.m_elementOffset = 0; + indexBufferDescriptor.m_elementCount = indexElementCount; + indexBufferDescriptor.m_elementSize = indexElementSize; + indexBufferDescriptor.m_elementFormat = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? RHI::Format::R16_UINT : RHI::Format::R32_UINT; + + // set the SubMesh data to pass to the RayTracingFeatureProcessor, starting with vertex/index data + RayTracingFeatureProcessor::SubMesh subMesh; + subMesh.m_positionFormat = PositionStreamFormat; + subMesh.m_positionVertexBufferView = streamBufferViews[0]; + subMesh.m_positionShaderBufferView = const_cast(streamBufferViews[0].GetBuffer())->GetBufferView(positionBufferDescriptor); + + subMesh.m_normalFormat = NormalStreamFormat; + subMesh.m_normalVertexBufferView = streamBufferViews[1]; + subMesh.m_normalShaderBufferView = const_cast(streamBufferViews[1].GetBuffer())->GetBufferView(normalBufferDescriptor); + + subMesh.m_tangentFormat = TangentStreamFormat; + subMesh.m_tangentVertexBufferView = streamBufferViews[2]; + subMesh.m_tangentShaderBufferView = const_cast(streamBufferViews[2].GetBuffer())->GetBufferView(tangentBufferDescriptor); + + subMesh.m_bitangentFormat = BitangentStreamFormat; + subMesh.m_bitangentVertexBufferView = streamBufferViews[3]; + subMesh.m_bitangentShaderBufferView = const_cast(streamBufferViews[3].GetBuffer())->GetBufferView(bitangentBufferDescriptor); + + if (uvBufferByteCount > 0) + { + subMesh.m_bufferFlags |= RayTracingSubMeshBufferFlags::UV; + subMesh.m_uvFormat = UVStreamFormat; + subMesh.m_uvVertexBufferView = streamBufferViews[4]; + subMesh.m_uvShaderBufferView = const_cast(streamBufferViews[4].GetBuffer())->GetBufferView(uvBufferDescriptor); + } + + subMesh.m_indexBufferView = mesh.m_indexBufferView; + subMesh.m_indexShaderBufferView = const_cast(mesh.m_indexBufferView.GetBuffer())->GetBufferView(indexBufferDescriptor); + + // add material data if (material) { + // irradiance color RPI::MaterialPropertyIndex propertyIndex = material->FindPropertyIndex(AZ::Name("irradiance.color")); if (propertyIndex.IsValid()) { - irradianceColor = material->GetPropertyValue(propertyIndex); + subMesh.m_irradianceColor = material->GetPropertyValue(propertyIndex); } propertyIndex = material->FindPropertyIndex(AZ::Name("irradiance.factor")); if (propertyIndex.IsValid()) { - irradianceColor *= material->GetPropertyValue(propertyIndex); + subMesh.m_irradianceColor *= material->GetPropertyValue(propertyIndex); + } + + // base color + propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.color")); + if (propertyIndex.IsValid()) + { + subMesh.m_baseColor = material->GetPropertyValue(propertyIndex); + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.factor")); + if (propertyIndex.IsValid()) + { + subMesh.m_baseColor *= material->GetPropertyValue(propertyIndex); + } + + // metallic + propertyIndex = material->FindPropertyIndex(AZ::Name("metallic.factor")); + if (propertyIndex.IsValid()) + { + subMesh.m_metallicFactor = material->GetPropertyValue(propertyIndex); + } + + // roughness + propertyIndex = material->FindPropertyIndex(AZ::Name("roughness.factor")); + if (propertyIndex.IsValid()) + { + subMesh.m_roughnessFactor = material->GetPropertyValue(propertyIndex); + } + + // textures + propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::BaseColor; + subMesh.m_baseColorImageView = image->GetImageView(); + } + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("normal.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Normal; + subMesh.m_normalImageView = image->GetImageView(); + } + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("metallic.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Metallic; + subMesh.m_metallicImageView = image->GetImageView(); + } + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("roughness.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Roughness; + subMesh.m_roughnessImageView = image->GetImageView(); + } } } - RayTracingFeatureProcessor::SubMesh subMesh; - subMesh.m_vertexFormat = StreamFormat; - subMesh.m_positionVertexBufferView = streamBufferViews[0]; - subMesh.m_positionShaderBufferView = const_cast(streamBufferViews[0].GetBuffer())->GetBufferView(vertexBufferDescriptor); - subMesh.m_normalVertexBufferView = streamBufferViews[1]; - subMesh.m_normalShaderBufferView = const_cast(streamBufferViews[1].GetBuffer())->GetBufferView(vertexBufferDescriptor); - subMesh.m_indexBufferView = mesh.m_indexBufferView; - subMesh.m_indexShaderBufferView = const_cast(mesh.m_indexBufferView.GetBuffer())->GetBufferView(indexBufferDescriptor); - subMesh.m_irradianceColor = irradianceColor; subMeshes.push_back(subMesh); } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp index 2bb2fa2ac2..92cd41b4e8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp @@ -115,11 +115,11 @@ namespace AZ } } - // update and compile the RayTracingSceneSrg + // update and compile the RayTracingSceneSrg and RayTracingMaterialSrg // Note: the timing of this update is very important, it needs to be updated after the TLAS is allocated so it can // be set on the RayTracingSceneSrg for this frame, and the ray tracing mesh data in the RayTracingSceneSrg must // exactly match the TLAS. Any mismatch in this data may result in a TDR. - rayTracingFeatureProcessor->UpdateRayTracingSceneSrg(); + rayTracingFeatureProcessor->UpdateRayTracingSrgs(); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp index c4e9306dc9..c28ac32381 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp @@ -71,6 +71,13 @@ namespace AZ AZ_Assert(rayTracingSceneSrgAsset.IsReady(), "Failed to load RayTracingSceneSrg asset"); m_rayTracingSceneSrg = RPI::ShaderResourceGroup::Create(rayTracingSceneSrgAsset); + + // load the RayTracingMaterialSrg asset + Data::Asset rayTracingMaterialSrgAsset = + RPI::AssetUtils::LoadAssetByProductPath("shaderlib/atom/features/raytracing/raytracingmaterialsrg_raytracingmaterialsrg.azsrg", RPI::AssetUtils::TraceLevel::Error); + AZ_Assert(rayTracingMaterialSrgAsset.IsReady(), "Failed to load RayTracingMaterialSrg asset"); + + m_rayTracingMaterialSrg = RPI::ShaderResourceGroup::Create(rayTracingMaterialSrgAsset); } void RayTracingFeatureProcessor::SetMesh(const ObjectId objectId, const SubMeshVector& subMeshes) @@ -104,7 +111,7 @@ namespace AZ RHI::RayTracingBlasDescriptor blasDescriptor; blasDescriptor.Build() ->Geometry() - ->VertexFormat(subMesh.m_vertexFormat) + ->VertexFormat(subMesh.m_positionFormat) ->VertexBuffer(subMesh.m_positionVertexBufferView) ->IndexBuffer(subMesh.m_indexBufferView) ; @@ -124,6 +131,7 @@ namespace AZ m_subMeshCount += aznumeric_cast(subMeshes.size()); m_meshInfoBufferNeedsUpdate = true; + m_materialInfoBufferNeedsUpdate = true; } void RayTracingFeatureProcessor::RemoveMesh(const ObjectId objectId) @@ -142,6 +150,7 @@ namespace AZ } m_meshInfoBufferNeedsUpdate = true; + m_materialInfoBufferNeedsUpdate = true; } void RayTracingFeatureProcessor::SetMeshTransform(const ObjectId objectId, const AZ::Transform transform, const AZ::Vector3 nonUniformScale) @@ -162,14 +171,14 @@ namespace AZ m_meshInfoBufferNeedsUpdate = true; } - void RayTracingFeatureProcessor::UpdateRayTracingSceneSrg() + void RayTracingFeatureProcessor::UpdateRayTracingSrgs() { if (!m_tlas->GetTlasBuffer()) { return; } - if (m_rayTracingSceneSrg->IsQueuedForCompile()) + if (m_rayTracingSceneSrg->IsQueuedForCompile() || m_rayTracingMaterialSrg->IsQueuedForCompile()) { //[GFX TODO][ATOM-14792] AtomSampleViewer: Reset scene and feature processors before switching to sample return; @@ -178,7 +187,144 @@ namespace AZ // update the mesh info buffer with the latest ray tracing enabled meshes UpdateMeshInfoBuffer(); + // update the material info buffer with the latest ray tracing enabled meshes + UpdateMaterialInfoBuffer(); + // update the RayTracingSceneSrg + UpdateRayTracingSceneSrg(); + + // update the RayTracingMaterialSrg + UpdateRayTracingMaterialSrg(); + } + + void RayTracingFeatureProcessor::UpdateMeshInfoBuffer() + { + if (m_meshInfoBufferNeedsUpdate && (m_subMeshCount > 0)) + { + TransformServiceFeatureProcessor* transformFeatureProcessor = GetParentScene()->GetFeatureProcessor(); + + AZStd::vector meshInfos; + meshInfos.reserve(m_subMeshCount); + + uint32_t newMeshByteCount = m_subMeshCount * sizeof(MeshInfo); + + if (m_meshInfoBuffer == nullptr) + { + // allocate the MeshInfo structured buffer + RPI::CommonBufferDescriptor desc; + desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly; + desc.m_bufferName = "RayTracingMeshInfo"; + desc.m_byteCount = newMeshByteCount; + desc.m_elementSize = sizeof(MeshInfo); + m_meshInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc); + } + else if (m_meshInfoBuffer->GetBufferSize() < newMeshByteCount) + { + // resize for the new sub-mesh count + m_meshInfoBuffer->Resize(newMeshByteCount); + } + + // keep track of the start index of the buffers for each mesh, this is put into the MeshInfo + // entry for each mesh so it knows where to find the start of its buffers in the unbounded array + uint32_t bufferStartIndex = 0; + + for (const auto& mesh : m_meshes) + { + AZ::Transform meshTransform = transformFeatureProcessor->GetTransformForId(TransformServiceFeatureProcessorInterface::ObjectId(mesh.first)); + AZ::Transform noScaleTransform = meshTransform; + noScaleTransform.ExtractScale(); + AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform); + rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose(); + + const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes; + for (const auto& subMesh : subMeshes) + { + MeshInfo meshInfo; + meshInfo.m_indexOffset = subMesh.m_indexBufferView.GetByteOffset(); + meshInfo.m_positionOffset = subMesh.m_positionVertexBufferView.GetByteOffset(); + meshInfo.m_normalOffset = subMesh.m_normalVertexBufferView.GetByteOffset(); + meshInfo.m_tangentOffset = subMesh.m_tangentVertexBufferView.GetByteOffset(); + meshInfo.m_bitangentOffset = subMesh.m_bitangentVertexBufferView.GetByteOffset(); + + if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::UV)) + { + meshInfo.m_uvOffset = subMesh.m_uvVertexBufferView.GetByteOffset(); + } + + subMesh.m_irradianceColor.StoreToFloat4(meshInfo.m_irradianceColor.data()); + rotationMatrix.StoreToRowMajorFloat9(meshInfo.m_worldInvTranspose.data()); + meshInfo.m_bufferFlags = subMesh.m_bufferFlags; + meshInfo.m_bufferStartIndex = bufferStartIndex; + + // add the count of buffers present in this subMesh to the start index for the next subMesh + // note that the Index, Position, Normal, Tangent, and Bitangent buffers are always counted since they are guaranteed + static const uint32_t RayTracingSubMeshFixedStreamCount = 5; + bufferStartIndex += (RayTracingSubMeshFixedStreamCount + RHI::CountBitsSet(aznumeric_cast(meshInfo.m_bufferFlags))); + + meshInfos.emplace_back(meshInfo); + } + } + + m_meshInfoBuffer->UpdateData(meshInfos.data(), newMeshByteCount); + m_meshInfoBufferNeedsUpdate = false; + } + } + + void RayTracingFeatureProcessor::UpdateMaterialInfoBuffer() + { + if (m_materialInfoBufferNeedsUpdate && (m_subMeshCount > 0)) + { + AZStd::vector materialInfos; + materialInfos.reserve(m_subMeshCount); + + uint32_t newMaterialByteCount = m_subMeshCount * sizeof(MaterialInfo); + + if (m_materialInfoBuffer == nullptr) + { + // allocate the MaterialInfo structured buffer + RPI::CommonBufferDescriptor desc; + desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly; + desc.m_bufferName = "RayTracingMaterialInfo"; + desc.m_byteCount = newMaterialByteCount; + desc.m_elementSize = sizeof(MaterialInfo); + m_materialInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc); + } + else if (m_materialInfoBuffer->GetBufferSize() < newMaterialByteCount) + { + // resize for the new sub-mesh count + m_materialInfoBuffer->Resize(newMaterialByteCount); + } + + // keep track of the start index of the textures for each mesh, this is put into the MaterialInfo + // entry for each mesh so it knows where to find the start of its textures in the unbounded array + uint32_t textureStartIndex = 0; + + for (const auto& mesh : m_meshes) + { + const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes; + for (const auto& subMesh : subMeshes) + { + MaterialInfo materialInfo; + subMesh.m_baseColor.StoreToFloat4(materialInfo.m_baseColor.data()); + materialInfo.m_metallicFactor = subMesh.m_metallicFactor; + materialInfo.m_roughnessFactor = subMesh.m_roughnessFactor; + materialInfo.m_textureFlags = subMesh.m_textureFlags; + materialInfo.m_textureStartIndex = textureStartIndex; + + // add the count of textures present in this subMesh to the start index for the next subMesh + textureStartIndex += RHI::CountBitsSet(aznumeric_cast(materialInfo.m_textureFlags)); + + materialInfos.emplace_back(materialInfo); + } + } + + m_materialInfoBuffer->UpdateData(materialInfos.data(), newMaterialByteCount); + m_materialInfoBufferNeedsUpdate = false; + } + } + + void RayTracingFeatureProcessor::UpdateRayTracingSceneSrg() + { const RHI::ShaderResourceGroupLayout* srgLayout = m_rayTracingSceneSrg->GetLayout(); RHI::ShaderInputImageIndex imageIndex; RHI::ShaderInputBufferIndex bufferIndex; @@ -272,11 +418,18 @@ namespace AZ const SubMeshVector& subMeshes = mesh.second.m_subMeshes; for (const auto& subMesh : subMeshes) { - // add the index, position, and normal buffers for this sub-mesh to the mesh buffer list, this will - // go into the shader as an unbounded array in the Srg + // add the stream buffers for this sub-mesh to the mesh buffer list, + // this is sent to the shader as an unbounded array in the Srg meshBuffers.push_back(subMesh.m_indexShaderBufferView.get()); meshBuffers.push_back(subMesh.m_positionShaderBufferView.get()); meshBuffers.push_back(subMesh.m_normalShaderBufferView.get()); + meshBuffers.push_back(subMesh.m_tangentShaderBufferView.get()); + meshBuffers.push_back(subMesh.m_bitangentShaderBufferView.get()); + + if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::UV)) + { + meshBuffers.push_back(subMesh.m_uvShaderBufferView.get()); + } } } @@ -287,58 +440,53 @@ namespace AZ m_rayTracingSceneSrg->Compile(); } - void RayTracingFeatureProcessor::UpdateMeshInfoBuffer() + void RayTracingFeatureProcessor::UpdateRayTracingMaterialSrg() { - if (m_meshInfoBufferNeedsUpdate && (m_subMeshCount > 0)) + const RHI::ShaderResourceGroupLayout* srgLayout = m_rayTracingMaterialSrg->GetLayout(); + RHI::ShaderInputImageIndex imageIndex; + RHI::ShaderInputBufferIndex bufferIndex; + RHI::ShaderInputConstantIndex constantIndex; + + bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_materialInfo")); + m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialInfoBuffer->GetBufferView()); + + if (m_subMeshCount) { - TransformServiceFeatureProcessor* transformFeatureProcessor = GetParentScene()->GetFeatureProcessor(); - - AZStd::vector meshInfos; - meshInfos.reserve(m_subMeshCount); - - uint32_t newMeshByteCount = m_subMeshCount * sizeof(MeshInfo); - - if (m_meshInfoBuffer == nullptr) - { - // allocate the MeshInfo structured buffer - RPI::CommonBufferDescriptor desc; - desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly; - desc.m_bufferName = "RayTracingMeshInfo"; - desc.m_byteCount = newMeshByteCount; - desc.m_elementSize = sizeof(MeshInfo); - m_meshInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc); - } - else if (m_meshInfoBuffer->GetBufferSize() < newMeshByteCount) - { - // resize for the new sub-mesh count - m_meshInfoBuffer->Resize(newMeshByteCount); - } - + AZStd::vector materialTextures; for (const auto& mesh : m_meshes) { - AZ::Transform meshTransform = transformFeatureProcessor->GetTransformForId(TransformServiceFeatureProcessorInterface::ObjectId(mesh.first)); - AZ::Transform noScaleTransform = meshTransform; - noScaleTransform.ExtractScale(); - AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform); - rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose(); - - const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes; + const SubMeshVector& subMeshes = mesh.second.m_subMeshes; for (const auto& subMesh : subMeshes) { - MeshInfo meshInfo; - meshInfo.m_indexOffset = subMesh.m_indexBufferView.GetByteOffset(); - meshInfo.m_positionOffset = subMesh.m_positionVertexBufferView.GetByteOffset(); - meshInfo.m_normalOffset = subMesh.m_normalVertexBufferView.GetByteOffset(); - subMesh.m_irradianceColor.StoreToFloat4(meshInfo.m_irradianceColor.data()); - rotationMatrix.StoreToRowMajorFloat9(meshInfo.m_worldInvTranspose.data()); + // add the baseColor, normal, metallic, and roughness images for this sub-mesh to the material texture list, + // this is sent to the shader as an unbounded array in the Srg + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::BaseColor)) + { + materialTextures.push_back(subMesh.m_baseColorImageView.get()); + } - meshInfos.emplace_back(meshInfo); + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Normal)) + { + materialTextures.push_back(subMesh.m_normalImageView.get()); + } + + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Metallic)) + { + materialTextures.push_back(subMesh.m_metallicImageView.get()); + } + + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Roughness)) + { + materialTextures.push_back(subMesh.m_roughnessImageView.get()); + } } } - m_meshInfoBuffer->UpdateData(meshInfos.data(), newMeshByteCount); - m_meshInfoBufferNeedsUpdate = false; + RHI::ShaderInputImageUnboundedArrayIndex textureUnboundedArrayIndex = srgLayout->FindShaderInputImageUnboundedArrayIndex(AZ::Name("m_materialTextures")); + m_rayTracingMaterialSrg->SetImageViewUnboundedArray(textureUnboundedArrayIndex, materialTextures); } + + m_rayTracingMaterialSrg->Compile(); } } } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h index f317f1c096..d89b61b2f9 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,28 @@ namespace AZ { namespace Render { + static const uint32_t RayTracingGlobalSrgBindingSlot = 0; + static const uint32_t RayTracingSceneSrgBindingSlot = 1; + static const uint32_t RayTracingMaterialSrgBindingSlot = 2; + + enum class RayTracingSubMeshBufferFlags : uint32_t + { + None = 0, + + UV = AZ_BIT(0) + }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::Render::RayTracingSubMeshBufferFlags); + + enum class RayTracingSubMeshTextureFlags : uint32_t + { + None = 0, + BaseColor = AZ_BIT(0), + Normal = AZ_BIT(1), + Metallic = AZ_BIT(2), + Roughness = AZ_BIT(3) + }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::Render::RayTracingSubMeshTextureFlags); + //! This feature processor manages ray tracing data for a Scene class RayTracingFeatureProcessor : public RPI::FeatureProcessor @@ -42,20 +65,53 @@ namespace AZ //! Contains data for a single sub-mesh struct SubMesh { - // vertex/index buffer data - RHI::Format m_vertexFormat = RHI::Format::Unknown; + // vertex streams + RHI::Format m_positionFormat = RHI::Format::Unknown; RHI::StreamBufferView m_positionVertexBufferView; RHI::Ptr m_positionShaderBufferView; + + RHI::Format m_normalFormat = RHI::Format::Unknown; RHI::StreamBufferView m_normalVertexBufferView; RHI::Ptr m_normalShaderBufferView; + + RHI::Format m_tangentFormat = RHI::Format::Unknown; + RHI::StreamBufferView m_tangentVertexBufferView; + RHI::Ptr m_tangentShaderBufferView; + + RHI::Format m_bitangentFormat = RHI::Format::Unknown; + RHI::StreamBufferView m_bitangentVertexBufferView; + RHI::Ptr m_bitangentShaderBufferView; + + RHI::Format m_uvFormat = RHI::Format::Unknown; + RHI::StreamBufferView m_uvVertexBufferView; + RHI::Ptr m_uvShaderBufferView; + + // index buffer RHI::IndexBufferView m_indexBufferView; RHI::Ptr m_indexShaderBufferView; + // vertex buffer usage flags + RayTracingSubMeshBufferFlags m_bufferFlags = RayTracingSubMeshBufferFlags::None; + // color of the bounced light from this sub-mesh - AZ::Color m_irradianceColor; + AZ::Color m_irradianceColor = AZ::Color(1.0f); // ray tracing Blas RHI::Ptr m_blas; + + // material data + AZ::Color m_baseColor = AZ::Color(0.0f); + float m_metallicFactor = 0.0f; + float m_roughnessFactor = 0.0f; + + // material texture usage flags + RayTracingSubMeshTextureFlags m_textureFlags = RayTracingSubMeshTextureFlags::None; + + // material textures + RHI::Ptr m_baseColorImageView; + RHI::Ptr m_normalImageView; + RHI::Ptr m_metallicImageView; + RHI::Ptr m_roughnessImageView; }; using SubMeshVector = AZStd::vector; @@ -98,6 +154,9 @@ namespace AZ //! Retrieves the RayTracingSceneSrg Data::Instance GetRayTracingSceneSrg() const { return m_rayTracingSceneSrg; } + //! Retrieves the RayTracingMaterialSrg + Data::Instance GetRayTracingMaterialSrg() const { return m_rayTracingMaterialSrg; } + //! Retrieves the RayTracingTlas const RHI::Ptr& GetTlas() const { return m_tlas; } RHI::Ptr& GetTlas() { return m_tlas; } @@ -118,14 +177,20 @@ namespace AZ //! Retrieves the GPU buffer containing information for all ray tracing meshes. const Data::Instance GetMeshInfoBuffer() const { return m_meshInfoBuffer; } - //! Updates the RayTracingSceneSrg, called after the TLAS is allocated in the RayTracingAccelerationStructurePass - void UpdateRayTracingSceneSrg(); + //! Retrieves the GPU buffer containing information for all ray tracing materials. + const Data::Instance GetMaterialInfoBuffer() const { return m_materialInfoBuffer; } + + //! Updates the RayTracingSceneSrg and RayTracingMaterialSrg, called after the TLAS is allocated in the RayTracingAccelerationStructurePass + void UpdateRayTracingSrgs(); private: AZ_DISABLE_COPY_MOVE(RayTracingFeatureProcessor); void UpdateMeshInfoBuffer(); + void UpdateMaterialInfoBuffer(); + void UpdateRayTracingSceneSrg(); + void UpdateRayTracingMaterialSrg(); // flag indicating if RayTracing is enabled, currently based on device support bool m_rayTracingEnabled = false; @@ -143,6 +208,9 @@ namespace AZ // ray tracing scene Srg Data::Instance m_rayTracingSceneSrg; + // ray tracing material Srg + Data::Instance m_rayTracingMaterialSrg; + // current revision number of ray tracing data uint32_t m_revision = 0; @@ -158,18 +226,43 @@ namespace AZ // structure for data in the m_meshInfoBuffer, shaders that use the buffer must match this type struct MeshInfo { - uint32_t m_indexOffset; - uint32_t m_positionOffset; - uint32_t m_normalOffset; + uint32_t m_indexOffset; + uint32_t m_positionOffset; + uint32_t m_normalOffset; + uint32_t m_tangentOffset; + uint32_t m_bitangentOffset; + uint32_t m_uvOffset; + float m_padding0[2]; + AZStd::array m_irradianceColor; // float4 AZStd::array m_worldInvTranspose; // float3x3 + float m_padding1[1]; + + RayTracingSubMeshBufferFlags m_bufferFlags = RayTracingSubMeshBufferFlags::None; + uint32_t m_bufferStartIndex = 0; }; // buffer containing a MeshInfo for each sub-mesh Data::Instance m_meshInfoBuffer; - // flag indicating we need to update the mesh info GPU buffer + // structure for data in the m_materialInfoBuffer, shaders that use the buffer must match this type + struct MaterialInfo + { + AZStd::array m_baseColor; // float4 + float m_metallicFactor = 0.0f; + float m_roughnessFactor = 0.0f; + RayTracingSubMeshTextureFlags m_textureFlags = RayTracingSubMeshTextureFlags::None; + uint32_t m_textureStartIndex = 0; + }; + + // buffer containing a MaterialInfo for each sub-mesh + Data::Instance m_materialInfoBuffer; + + // flag indicating we need to update the meshInfo buffer bool m_meshInfoBufferNeedsUpdate = false; + + // flag indicating we need to update the materialInfo buffer + bool m_materialInfoBufferNeedsUpdate = false; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp index 2c0c7e986e..0fbe7dbc48 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp @@ -111,7 +111,6 @@ namespace AZ AZ_Assert(m_globalPipelineState, "Failed to acquire ray tracing global pipeline state"); // create global srg - static const uint32_t RayTracingGlobalSrgBindingSlot = 0; Data::Asset globalSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RayTracingGlobalSrgBindingSlot); AZ_Error("PassSystem", globalSrgAsset.GetId().IsValid(), "RayTracingPass [%s] Failed to find RayTracingGlobalSrg asset", GetPathName().GetCStr()); AZ_Error("PassSystem", globalSrgAsset.IsReady(), "RayTracingPass [%s] asset is not loaded for shader", GetPathName().GetCStr()); @@ -120,10 +119,13 @@ namespace AZ AZ_Assert(m_shaderResourceGroup, "RayTracingPass [%s]: Failed to create RayTracingGlobalSrg", GetPathName().GetCStr()); RPI::PassUtils::BindDataMappingsToSrg(m_passDescriptor, m_shaderResourceGroup.get()); - // check to see if the shader requires a ViewSrg + // check to see if the shader requires the View and RayTracingMaterial Srgs Data::Asset viewSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RPI::SrgBindingSlot::View); m_requiresViewSrg = viewSrgAsset.GetId().IsValid(); + Data::Asset rayTracingMaterialSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RayTracingMaterialSrgBindingSlot); + m_requiresRayTracingMaterialSrg = rayTracingMaterialSrgAsset.GetId().IsValid(); + // build the ray tracing pipeline state descriptor RHI::RayTracingPipelineStateDescriptor descriptor; descriptor.Build() @@ -298,6 +300,11 @@ namespace AZ } } + if (m_requiresRayTracingMaterialSrg) + { + shaderResourceGroups.push_back(rayTracingFeatureProcessor->GetRayTracingMaterialSrg()->GetRHIShaderResourceGroup()); + } + dispatchRaysItem.m_shaderResourceGroupCount = aznumeric_cast(shaderResourceGroups.size()); dispatchRaysItem.m_shaderResourceGroups = shaderResourceGroups.data(); dispatchRaysItem.m_rayTracingPipelineState = m_rayTracingPipelineState.get(); diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h index 935d034513..6ad082e894 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h @@ -76,6 +76,7 @@ namespace AZ RHI::ConstPtr m_globalPipelineState; RHI::Ptr m_rayTracingShaderTable; bool m_requiresViewSrg = false; + bool m_requiresRayTracingMaterialSrg = false; }; } // namespace RPI } // namespace AZ From 05a0e063a22edab79891fc6c7b34e61675334121 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 26 May 2021 10:38:04 -0700 Subject: [PATCH 06/23] Also making Auton->Auth Getters requiring controller --- .../Code/Source/AutoGen/AutoComponent_Header.jinja | 2 -- .../Code/Source/AutoGen/AutoComponent_Source.jinja | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index aeab2e88e1..96c5433ce0 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -440,7 +440,6 @@ namespace {{ Component.attrib['Namespace'] }} {% endif %} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', false)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', false)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} @@ -465,7 +464,6 @@ namespace {{ Component.attrib['Namespace'] }} //! @} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', true)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', true)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Client', false)|indent(8) -}} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 3ca4d854bb..b782d7e583 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -767,15 +767,15 @@ enum class NetworkProperties return {{ Property.attrib['Type'] }}(); } - {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); + {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); if (!networkComponent) { AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) return {{ Property.attrib['Type'] }}(); } -{% if ReplicateTo == 'Autonomous' %} +{% if (ReplicateTo == 'Autonomous') or (ReplicateFrom == 'Autonomous' and ReplicateTo == 'Authority') %} - // {{ UpperFirst(Property.attrib['Name']) }} is replicated to Automonous; we must go through the controller in order to get this property + // {{ UpperFirst(Property.attrib['Name']) }} is only sent and received between contoller objects (ie Authority, Autonomous); we must go through the controller in order to get this property {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); if (!controller) { @@ -1472,10 +1472,8 @@ namespace {{ Component.attrib['Namespace'] }} } {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', false, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', true, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', true, ComponentBaseName)|indent(4) }} {{ DefineArchetypePropertyGets(Component, ClassType, ComponentBaseName)|indent(4) -}} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', false)|indent(4) -}} From 0f258954fbd8a0bd2279b4a29369dc0667c3f2bf Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Thu, 27 May 2021 22:29:40 -0700 Subject: [PATCH 07/23] Fix for unit test. Checking that AssetManager is ready before spawning entities; MultiplayerSystemComponent will attempt to spawn a default player on init(), but during unit tests the AssetManager isn't stood up --- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index dda3f18ad4..eaf89f3489 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -402,8 +402,12 @@ namespace Multiplayer const AZ::Transform& transform ) { - INetworkEntityManager::EntityList returnList; - + EntityList returnList; + if (!AZ::Data::AssetManager::IsReady()) + { + return returnList; + } + auto spawnableAssetId = m_networkPrefabLibrary.GetAssetIdByName(prefabEntryId.m_prefabName); // Required for sync-instantiation. Todo: keep the reference in NetworkSpawnableLibrary auto netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset(spawnableAssetId, AZ::Data::AssetLoadBehavior::PreLoad); From fdc57cdaff7138f0e576978ce867ff6b099300fb Mon Sep 17 00:00:00 2001 From: Doug McDiarmid Date: Fri, 28 May 2021 00:08:13 -0700 Subject: [PATCH 08/23] Added TLAS dependency --- .../ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli | 2 +- .../Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli index 2352f5d09b..b8c97ef421 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli @@ -143,7 +143,7 @@ ShaderResourceGroup RayTracingSceneSrg : SRG_RayTracingScene float4 m_irradianceColor; float3x3 m_worldInvTranspose; - float m_padding1[1]; + float m_padding1; uint m_bufferFlags; uint m_bufferStartIndex; diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h index d89b61b2f9..90a9383ae6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h @@ -236,7 +236,7 @@ namespace AZ AZStd::array m_irradianceColor; // float4 AZStd::array m_worldInvTranspose; // float3x3 - float m_padding1[1]; + float m_padding1; RayTracingSubMeshBufferFlags m_bufferFlags = RayTracingSubMeshBufferFlags::None; uint32_t m_bufferStartIndex = 0; From 9ec4278f86d517c6ac830c2e60f106abe1bf891a Mon Sep 17 00:00:00 2001 From: Doug McDiarmid Date: Fri, 28 May 2021 00:09:00 -0700 Subject: [PATCH 09/23] Added TLAS dependency --- .../Code/Source/RayTracing/RayTracingPass.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp index 1ec2d9ae04..988870cc0e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp @@ -196,8 +196,36 @@ namespace AZ void RayTracingPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + AZ_Assert(rayTracingFeatureProcessor, "RayTracingPass requires the RayTracingFeatureProcessor"); + RPI::RenderPass::SetupFrameGraphDependencies(frameGraph); frameGraph.SetEstimatedItemCount(1); + + // TLAS + { + const RHI::Ptr& rayTracingTlasBuffer = rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer(); + if (rayTracingTlasBuffer) + { + AZ::RHI::AttachmentId tlasAttachmentId = rayTracingFeatureProcessor->GetTlasAttachmentId(); + if (frameGraph.GetAttachmentDatabase().IsAttachmentValid(tlasAttachmentId) == false) + { + [[maybe_unused]] RHI::ResultCode result = frameGraph.GetAttachmentDatabase().ImportBuffer(tlasAttachmentId, rayTracingTlasBuffer); + AZ_Assert(result == RHI::ResultCode::Success, "Failed to import ray tracing TLAS buffer with error %d", result); + } + + uint32_t tlasBufferByteCount = aznumeric_cast(rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer()->GetDescriptor().m_byteCount); + RHI::BufferViewDescriptor tlasBufferViewDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, tlasBufferByteCount); + + RHI::BufferScopeAttachmentDescriptor desc; + desc.m_attachmentId = tlasAttachmentId; + desc.m_bufferViewDescriptor = tlasBufferViewDescriptor; + desc.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Load; + + frameGraph.UseShaderAttachment(desc, RHI::ScopeAttachmentAccess::ReadWrite); + } + } } void RayTracingPass::CompileResources(const RHI::FrameGraphCompileContext& context) From bdf9da820dac872d39077c28f11e383346a1ec27 Mon Sep 17 00:00:00 2001 From: gallowj Date: Fri, 28 May 2021 09:49:44 -0500 Subject: [PATCH 10/23] removing folder with image data we don't own, can't license. --- .../sampleEnvironment/PaperMill_E_3k.exr | 3 - .../PaperMill_E_3k.exr.assetinfo | 69 ------------------- .../sampleEnvironment/exampleBrdf_lut.dds | 3 - .../exampleDiffuseHDR_cm.dds | 3 - .../exampleSpecularHDR_cm.dds | 3 - .../sampleEnvironment/example_iblskyboxcm.dds | 3 - .../sampleEnvironment/papermill_license.txt | 10 --- 7 files changed, 94 deletions(-) delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds delete mode 100644 Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr deleted file mode 100644 index 0fcbcc4746..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc9981393c88c6d30a0a5a6837e6f6246a9f550042b3c4be39dd34e479b90569 -size 16793931 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo deleted file mode 100644 index 16cb0dd668..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds deleted file mode 100644 index f2b2ce550d..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:440bcb1579d4ad667c040bda914ed3121980526a94f23879a0c482c799fd5132 -size 1310848 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds deleted file mode 100644 index 9585c25dd1..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf4b22481726214c062ac27fb2d9d8a49e760a76d8fff03330ff9c5f9275a8da -size 1966208 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds deleted file mode 100644 index a35fac45fb..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b4c99faffc34988c268613948f2004f40e9bd51de915461a4cb74edc5e8bae6 -size 134217920 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds deleted file mode 100644 index a89dfdbd3d..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7edea42ded8143764654f12c19e0c9b74c74afb21f435ebb59c0a4d203892a3 -size 536871104 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt deleted file mode 100644 index 83cfe08ab9..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt +++ /dev/null @@ -1,10 +0,0 @@ -The papermill 'Image base lighting' (IBL) images are modified from the following: - -http://www.hdrlabs.com/sibl/archive.html -'Papermill Ruins E' - -All sIBL-sets on this page, including the images within, are licensed under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 License. - -Creative Commons License: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ - -Remember: Do what you want with them, but always mention where you got them from... \ No newline at end of file From de4e6957e8606fb3ca7fa49cc0fefaf81f8af357 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Fri, 28 May 2021 13:38:56 -0700 Subject: [PATCH 11/23] Made a bunch of display name and description updates to core material types. - Renamed "Details" group to "Overview". - Renamed "UV Names" group to "UV Sets". - Renamed "General" group to "General Settings". - Renamed "Parallax" group to "Displacement". - Renamed "Texture Map" properties to just "Texture". In cases where a specific type of texture is mentioned like "roughness texture map" I called this "roughness map" (which is more common according to google). - Renamed "Heightmap" to "Height map" (which is more common according to google). ATOM-14002 [Material Editor] Revisit user facing organization and layout of material types --- .../Materials/Types/EnhancedPBR.materialtype | 128 ++++----- .../Assets/Materials/Types/Skin.materialtype | 72 ++--- .../Types/StandardMultilayerPBR.materialtype | 256 +++++++++--------- .../Materials/Types/StandardPBR.materialtype | 114 ++++---- .../RPI.Edit/Material/MaterialSourceData.h | 2 +- .../Document/MaterialDocumentRequestBus.h | 2 +- .../Code/Source/Document/MaterialDocument.cpp | 10 +- .../MaterialInspector/MaterialInspector.cpp | 16 +- .../MaterialInspector/MaterialInspector.h | 2 +- .../EditorMaterialComponentInspector.cpp | 2 +- 10 files changed, 302 insertions(+), 302 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index 3696188514..3b5a653f43 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -60,8 +60,8 @@ }, { "id": "parallax", - "displayName": "Parallax Mapping", - "description": "Properties for parallax effect produced by depthmap." + "displayName": "Displacement", + "description": "Properties for parallax effect produced by a height map." }, { "id": "subsurfaceScattering", @@ -86,7 +86,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." } ], @@ -197,7 +197,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -208,14 +208,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -227,7 +227,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -253,7 +253,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -264,14 +264,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -284,8 +284,8 @@ "roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -295,14 +295,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -315,7 +315,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -329,7 +329,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -409,8 +409,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -420,14 +420,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -472,7 +472,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -482,14 +482,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -514,7 +514,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -524,14 +524,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -573,7 +573,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -586,8 +586,8 @@ "normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -597,14 +597,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -653,7 +653,7 @@ { "id": "mode", "displayName": "Opacity Mode", - "description": "Opacity mode for this texture.", + "description": "Indicates the general approach how transparency is to be applied.", "type": "Enum", "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], "defaultValue": "Opaque", @@ -665,7 +665,7 @@ { "id": "alphaSource", "displayName": "Alpha Source", - "description": "Source texture of alpha value.", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", "type": "Enum", "enumValues": [ "Packed", "Split", "None" ], "defaultValue": "Packed", @@ -676,8 +676,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface opacity.", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", "type": "Image", "connection": { "type": "ShaderInput", @@ -687,7 +687,7 @@ { "id": "textureMapUv", "displayName": "UV", - "description": "Opacity texture map UV set", + "description": "Opacity map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -782,7 +782,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -792,14 +792,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -824,7 +824,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -834,14 +834,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -904,8 +904,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -915,14 +915,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -935,8 +935,8 @@ "parallax": [ { "id": "textureMap", - "displayName": "Heightmap", - "description": "Displacement heightmap to create parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", @@ -946,14 +946,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the heightmap.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Heightmap UV set", + "description": "Height map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -964,8 +964,8 @@ }, { "id": "factor", - "displayName": "Heightmap Scale", - "description": "The total height of the heightmap in local model units.", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, @@ -1026,7 +1026,7 @@ { "id": "showClipping", "displayName": "Show Clipping", - "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "description": "Highlight areas where the height map is clipped by the mesh surface.", "type": "Bool", "defaultValue": false, "connection": { @@ -1063,7 +1063,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", + "description": "Texture for controlling the strength of subsurface scattering", "type": "Image", "connection": { "type": "ShaderInput", @@ -1073,7 +1073,7 @@ { "id": "useInfluenceMap", "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", + "description": "Whether to use the influence map.", "type": "Bool", "defaultValue": true }, @@ -1142,7 +1142,7 @@ { "id": "thicknessMap", "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", + "description": "Texture for controlling per pixel thickness", "type": "Image", "connection": { "type": "ShaderInput", @@ -1171,7 +1171,7 @@ { "id": "transmissionTint", "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", + "description": "Color of the volume light traveling through", "type": "Color", "defaultValue": [ 1.0, 0.8, 0.6 ] }, @@ -1246,7 +1246,7 @@ { "id": "enableDetailMaskTexture", "displayName": " Use Texture", - "description": "Enable detail mask texture", + "description": "Enable detail blend mask", "type": "Bool", "defaultValue": true }, @@ -1265,7 +1265,7 @@ { "id": "textureMapUv", "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map texture sampling", + "description": "Which UV set to use for detail map sampling", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1283,8 +1283,8 @@ }, { "id": "baseColorDetailMap", - "displayName": " Texture Map", - "description": "Detailed Base Color Texture map", + "displayName": " Texture", + "description": "Detailed Base Color Texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -1307,7 +1307,7 @@ { "id": "enableNormals", "displayName": "Enable Normal", - "description": "Enable detail normal texture to be used for fine detail normal such as scratches and small dents", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", "type": "Bool", "defaultValue": false }, @@ -1326,8 +1326,8 @@ }, { "id": "normalDetailMap", - "displayName": " Texture Map", - "description": "Detailed Normal Texture map", + "displayName": " Texture", + "description": "Detailed Normal map", "type": "Image", "connection": { "type": "ShaderInput", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index f8c49d579c..ab36853723 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -50,7 +50,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." } ], @@ -150,7 +150,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -161,14 +161,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -180,7 +180,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -193,8 +193,8 @@ "roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -204,14 +204,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -224,7 +224,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -238,7 +238,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -279,8 +279,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -290,14 +290,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -321,8 +321,8 @@ "normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -332,14 +332,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -388,7 +388,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -398,14 +398,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -430,7 +430,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -440,14 +440,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -498,7 +498,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", + "description": "Texture for controlling the strength of subsurface scattering", "type": "Image", "connection": { "type": "ShaderInput", @@ -508,7 +508,7 @@ { "id": "useInfluenceMap", "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", + "description": "Whether to use the influence map.", "type": "Bool", "defaultValue": true }, @@ -577,7 +577,7 @@ { "id": "thicknessMap", "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", + "description": "Texture for controlling per pixel thickness", "type": "Image", "connection": { "type": "ShaderInput", @@ -606,7 +606,7 @@ { "id": "transmissionTint", "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", + "description": "Color of the volume light traveling through", "type": "Color", "defaultValue": [ 1.0, 0.8, 0.6 ] }, @@ -792,7 +792,7 @@ { "id": "enableDetailMaskTexture", "displayName": " Use Texture", - "description": "Enable detail mask texture", + "description": "Enable detail blend mask", "type": "Bool", "defaultValue": true }, @@ -811,7 +811,7 @@ { "id": "textureMapUv", "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map texture sampling", + "description": "Which UV set to use for detail map sampling", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -829,8 +829,8 @@ }, { "id": "baseColorDetailMap", - "displayName": " Texture Map", - "description": "Detailed Base Color Texture map", + "displayName": " Texture", + "description": "Detailed Base Color Texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -853,7 +853,7 @@ { "id": "enableNormals", "displayName": "Enable Normal", - "description": "Enable detail normal texture to be used for fine detail normal such as scratches and small dents", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", "type": "Bool", "defaultValue": false }, @@ -872,8 +872,8 @@ }, { "id": "normalDetailMap", - "displayName": " Texture Map", - "description": "Detailed Normal Texture map", + "displayName": " Texture", + "description": "Detailed Normal map", "type": "Image", "connection": { "type": "ShaderInput", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index ca6cb77b0a..5b6e5f30bc 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -26,7 +26,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." }, //############################################################################################## @@ -428,7 +428,7 @@ { "id": "showClipping", "displayName": "Show Clipping", - "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "description": "Highlight areas where the height map is clipped by the mesh surface.", "type": "Bool", "defaultValue": false, "connection": { @@ -550,7 +550,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -561,14 +561,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -580,7 +580,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -606,7 +606,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -617,14 +617,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -637,8 +637,8 @@ "layer1_roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -648,14 +648,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -668,7 +668,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -682,7 +682,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -723,8 +723,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -734,14 +734,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -754,8 +754,8 @@ "layer1_normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -765,14 +765,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -841,7 +841,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -851,14 +851,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -883,7 +883,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -893,14 +893,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -942,7 +942,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -956,7 +956,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -966,14 +966,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -998,7 +998,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1008,14 +1008,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1078,8 +1078,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1089,14 +1089,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1109,8 +1109,8 @@ "layer1_parallax": [ { "id": "textureMap", - "displayName": "Heightmap", - "description": "Displacement heightmap, which can be used for layer blending and/or a parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map, which can be used for layer blending and/or a parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1120,14 +1120,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the heightmap.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "factor", "displayName": "Scale", - "description": "The total height of the heightmap in local model units.", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, @@ -1245,7 +1245,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -1256,14 +1256,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1275,7 +1275,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -1301,7 +1301,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -1312,14 +1312,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1332,8 +1332,8 @@ "layer2_roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1343,14 +1343,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1363,7 +1363,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -1377,7 +1377,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -1418,8 +1418,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1429,14 +1429,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1449,8 +1449,8 @@ "layer2_normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1460,14 +1460,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1536,7 +1536,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -1546,14 +1546,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1578,7 +1578,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -1588,14 +1588,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1637,7 +1637,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1651,7 +1651,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1661,14 +1661,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1693,7 +1693,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1703,14 +1703,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1773,8 +1773,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1784,14 +1784,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1804,8 +1804,8 @@ "layer2_parallax": [ { "id": "textureMap", - "displayName": "Heightmap", - "description": "Displacement heightmap, which can be used for layer blending and/or a parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map, which can be used for layer blending and/or a parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1815,14 +1815,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the heightmap.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "factor", "displayName": "Scale", - "description": "The total height of the heightmap in local model units.", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, @@ -1940,7 +1940,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -1951,14 +1951,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1970,7 +1970,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -1996,7 +1996,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -2007,14 +2007,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2027,8 +2027,8 @@ "layer3_roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2038,14 +2038,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2058,7 +2058,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -2072,7 +2072,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -2113,8 +2113,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2124,14 +2124,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2144,8 +2144,8 @@ "layer3_normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2155,14 +2155,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2231,7 +2231,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -2241,14 +2241,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2273,7 +2273,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -2283,14 +2283,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2332,7 +2332,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2346,7 +2346,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2356,14 +2356,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2388,7 +2388,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2398,14 +2398,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2468,8 +2468,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2479,14 +2479,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2499,8 +2499,8 @@ "layer3_parallax": [ { "id": "textureMap", - "displayName": "Heightmap", - "description": "Displacement heightmap, which can be used for layer blending and/or a parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map, which can be used for layer blending and/or a parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2510,14 +2510,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the heightmap.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "factor", "displayName": "Scale", - "description": "The total height of the heightmap in local model units.", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 183cddd4cb..0904302085 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -55,8 +55,8 @@ }, { "id": "parallax", - "displayName": "Parallax Mapping", - "description": "Properties for parallax effect produced by depthmap." + "displayName": "Displacement", + "description": "Properties for parallax effect produced by a height map." }, { "id": "subsurfaceScattering", @@ -71,7 +71,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." } ], @@ -182,7 +182,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -193,14 +193,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -212,7 +212,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -238,7 +238,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -249,14 +249,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -269,8 +269,8 @@ "roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -280,14 +280,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -300,7 +300,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -314,7 +314,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -355,8 +355,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -366,14 +366,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -418,7 +418,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -428,14 +428,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -460,7 +460,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -470,14 +470,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -519,7 +519,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -532,8 +532,8 @@ "normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -543,14 +543,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -599,7 +599,7 @@ { "id": "mode", "displayName": "Opacity Mode", - "description": "Opacity mode for this texture.", + "description": "Indicates the general approach how transparency is to be applied.", "type": "Enum", "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], "defaultValue": "Opaque", @@ -611,7 +611,7 @@ { "id": "alphaSource", "displayName": "Alpha Source", - "description": "Source texture of alpha value.", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", "type": "Enum", "enumValues": [ "Packed", "Split", "None" ], "defaultValue": "Packed", @@ -622,8 +622,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface opacity.", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", "type": "Image", "connection": { "type": "ShaderInput", @@ -633,7 +633,7 @@ { "id": "textureMapUv", "displayName": "UV", - "description": "Opacity texture map UV set", + "description": "Opacity map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -728,7 +728,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -738,14 +738,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -770,7 +770,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -780,14 +780,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -850,8 +850,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -861,14 +861,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -881,8 +881,8 @@ "parallax": [ { "id": "textureMap", - "displayName": "Heightmap", - "description": "Displacement heightmap to create parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", @@ -892,14 +892,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the heightmap.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Heightmap UV set", + "description": "Height map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -910,8 +910,8 @@ }, { "id": "factor", - "displayName": "Heightmap Scale", - "description": "The total height of the heightmap in local model units.", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, @@ -972,7 +972,7 @@ { "id": "showClipping", "displayName": "Show Clipping", - "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "description": "Highlight areas where the height map is clipped by the mesh surface.", "type": "Bool", "defaultValue": false, "connection": { @@ -1009,7 +1009,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", + "description": "Texture for controlling the strength of subsurface scattering", "type": "Image", "connection": { "type": "ShaderInput", @@ -1019,7 +1019,7 @@ { "id": "useInfluenceMap", "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", + "description": "Whether to use the influence map.", "type": "Bool", "defaultValue": true }, @@ -1088,7 +1088,7 @@ { "id": "thicknessMap", "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", + "description": "Texture for controlling per pixel thickness", "type": "Image", "connection": { "type": "ShaderInput", @@ -1117,7 +1117,7 @@ { "id": "transmissionTint", "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", + "description": "Color of the volume light traveling through", "type": "Color", "defaultValue": [ 1.0, 0.8, 0.6 ] }, diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h index 9baf80bae0..c8b406f94b 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h @@ -32,7 +32,7 @@ namespace AZ //! In the source data, properties and UV names are loaded separately. //! However, treating UV names as a special property group can greatly simplify the editor code. //! See MaterialInspector::AddUvNamesGroup() for more details. - static constexpr const char UvGroupName[] = "UvNames"; + static constexpr const char UvGroupName[] = "uvSets"; class MaterialAsset; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h index c71d500d8c..7f36d3fabc 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h @@ -33,7 +33,7 @@ namespace AZ namespace MaterialEditor { //! UVs are processed in a property group but will be handled differently. - static constexpr const char UvGroupName[] = "UvNames"; + static constexpr const char UvGroupName[] = "uvSets"; class MaterialDocumentRequests : public AZ::EBusTraits diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 301fd69025..2242d3af1b 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -819,10 +819,10 @@ namespace MaterialEditor // is implemented. AtomToolsFramework::DynamicPropertyConfig propertyConfig; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; - propertyConfig.m_id = "details.materialType"; + propertyConfig.m_id = "overview.materialType"; propertyConfig.m_nameId = "materialType"; propertyConfig.m_displayName = "Material Type"; - propertyConfig.m_groupName = "Details"; + propertyConfig.m_groupName = "Overview"; propertyConfig.m_description = "The material type defines the layout, properties, default values, shader connections, and other " "data needed to create and edit a derived material."; propertyConfig.m_defaultValue = AZStd::any(materialTypeAsset); @@ -834,10 +834,10 @@ namespace MaterialEditor propertyConfig = {}; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; - propertyConfig.m_id = "details.parentMaterial"; + propertyConfig.m_id = "overview.parentMaterial"; propertyConfig.m_nameId = "parentMaterial"; propertyConfig.m_displayName = "Parent Material"; - propertyConfig.m_groupName = "Details"; + propertyConfig.m_groupName = "Overview"; propertyConfig.m_description = "The parent material provides an initial configuration whose properties are inherited and overriden by a derived material."; propertyConfig.m_defaultValue = AZStd::any(parentMaterialAsset); @@ -860,7 +860,7 @@ namespace MaterialEditor propertyConfig.m_id = MaterialPropertyId(UvGroupName, shaderInput).GetCStr(); propertyConfig.m_nameId = shaderInput; propertyConfig.m_displayName = shaderInput; - propertyConfig.m_groupName = "UV Names"; + propertyConfig.m_groupName = "UV Sets"; propertyConfig.m_description = shaderInput; propertyConfig.m_defaultValue = uvName; propertyConfig.m_originalValue = uvName; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index 706d365027..560c82be9b 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -79,8 +79,8 @@ namespace MaterialEditor if (!m_documentId.IsNull() && isOpen) { - // Create the top group for displaying details about the material - AddDetailsGroup(); + // Create the top group for displaying overview info about the material + AddOverviewGroup(); // Create groups for displaying editable UV names AddUvNamesGroup(); // Create groups for displaying editable properties @@ -105,25 +105,25 @@ namespace MaterialEditor return property && AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue); } - void MaterialInspector::AddDetailsGroup() + void MaterialInspector::AddOverviewGroup() { const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr; MaterialDocumentRequestBus::EventResult( materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData); - const AZStd::string groupNameId = "details"; - const AZStd::string groupDisplayName = "Details"; + const AZStd::string groupNameId = "overview"; + const AZStd::string groupDisplayName = "Overview"; const AZStd::string groupDescription = materialTypeSourceData->m_description; auto& group = m_groups[groupNameId]; AtomToolsFramework::DynamicProperty property; MaterialDocumentRequestBus::EventResult( - property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("details.materialType")); + property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("overview.materialType")); group.m_properties.push_back(property); property = {}; MaterialDocumentRequestBus::EventResult( - property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("details.parentMaterial")); + property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("overview.parentMaterial")); group.m_properties.push_back(property); // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties @@ -139,7 +139,7 @@ namespace MaterialEditor MaterialDocumentRequestBus::EventResult(materialAsset, m_documentId, &MaterialDocumentRequestBus::Events::GetAsset); const AZStd::string groupNameId = UvGroupName; - const AZStd::string groupDisplayName = "UV Names"; + const AZStd::string groupDisplayName = "UV Sets"; const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model."; auto& group = m_groups[groupNameId]; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h index 4080430ff5..65d095f42f 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h @@ -52,7 +52,7 @@ namespace MaterialEditor bool CompareInstanceNodeProperties( const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target) const; - void AddDetailsGroup(); + void AddOverviewGroup(); void AddUvNamesGroup(); void AddPropertiesGroup(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 3192900ca4..229606a238 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -184,7 +184,7 @@ namespace AZ void MaterialPropertyInspector::AddUvNamesGroup() { const AZStd::string groupNameId = AZ::RPI::UvGroupName; - const AZStd::string groupDisplayName = "UV Names"; + const AZStd::string groupDisplayName = "UV Sets"; const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model."; auto& group = m_groups[groupNameId]; From fe8803291a759db566cfaa09a4ad64454dc50583 Mon Sep 17 00:00:00 2001 From: sconel Date: Fri, 28 May 2021 13:53:09 -0700 Subject: [PATCH 12/23] Fix for referencing now deprecated AZ::Transform constructor --- .../Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp index b93844b989..21d1fc5264 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp @@ -118,7 +118,7 @@ namespace ScriptCanvas::Nodeables::Spawning AZ::Vector3 rotationCopy = rotation; AZ::Quaternion rotationQuat = AZ::Quaternion::CreateFromEulerAnglesDegrees(rotationCopy); - entityTransform->SetWorldTM(AZ::Transform(translation, rotationQuat, AZ::Vector3(scale, scale, scale))); + entityTransform->SetWorldTM(AZ::Transform(translation, rotationQuat, scale)); } }; From 2112da5f85b91533e6f9f3f804f2ccdc9d850c3f Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Fri, 28 May 2021 14:34:07 -0700 Subject: [PATCH 13/23] Reordered material property groups according to design review. - Base Color - Metallic - Roughness - Specular Reflectance F0 - Normal - Occlusion - Emissive - Subsurface - Clear Coat - Displacement - Opacity - UVs - Irradiance - General Settings ATOM-14002 [Material Editor] Revisit user facing organization and layout of material types --- .../Materials/Types/EnhancedPBR.materialtype | 54 +++++++++---------- .../Assets/Materials/Types/Skin.materialtype | 20 +++---- .../Types/StandardMultilayerPBR.materialtype | 30 +++++------ .../Materials/Types/StandardPBR.materialtype | 36 ++++++------- 4 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index 3b5a653f43..79c4ca3cc3 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -13,11 +13,6 @@ "displayName": "Metallic", "description": "Properties for configuring whether the surface is metallic or not." }, - { - "id": "anisotropy", - "displayName": "Anisotropic Material Response", - "description": "How much is this material response anisotropic." - }, { "id": "roughness", "displayName": "Roughness", @@ -28,25 +23,25 @@ "displayName": "Specular Reflectance f0", "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, - { - "id": "clearCoat", - "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "normal", "displayName": "Normal", "description": "Properties related to configuring surface normal." }, { - "id": "opacity", - "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." + "id": "detailLayerGroup", + "displayName": "Detail Layer", + "description": "Properties for Fine Details Layer." }, { - "id": "uv", - "displayName": "UVs", - "description": "Properties for configuring UV transforms." + "id": "detailUV", + "displayName": "Detail Layer UV", + "description": "Properties for modifying detail layer UV." + }, + { + "id": "anisotropy", + "displayName": "Anisotropic Material Response", + "description": "How much is this material response anisotropic." }, { "id": "occlusion", @@ -58,25 +53,30 @@ "displayName": "Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, - { - "id": "parallax", - "displayName": "Displacement", - "description": "Properties for parallax effect produced by a height map." - }, { "id": "subsurfaceScattering", "displayName": "Subsurface Scattering", "description": "Properties for configuring subsurface scattering effects." }, { - "id": "detailLayerGroup", - "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer." + "id": "clearCoat", + "displayName": "Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, + { + "id": "parallax", + "displayName": "Displacement", + "description": "Properties for parallax effect produced by a height map." }, { - "id": "detailUV", - "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV." + "id": "opacity", + "displayName": "Opacity", + "description": "Properties for configuring the materials transparency." + }, + { + "id": "uv", + "displayName": "UVs", + "description": "Properties for configuring UV transforms." }, { // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index ab36853723..9ead0376bb 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -23,6 +23,16 @@ "displayName": "Normal", "description": "Properties related to configuring surface normal." }, + { + "id": "detailLayerGroup", + "displayName": "Detail Layer", + "description": "Properties for Fine Details Layer." + }, + { + "id": "detailUV", + "displayName": "Detail Layer UV", + "description": "Properties for modifying detail layer UV." + }, { "id": "occlusion", "displayName": "Occlusion", @@ -38,16 +48,6 @@ "displayName": "Wrinkle Layers", "description": "Properties for wrinkle maps to support morph animation, using vertex color blend weights." }, - { - "id": "detailLayerGroup", - "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer." - }, - { - "id": "detailUV", - "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV." - }, { "id": "general", "displayName": "General Settings", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 5b6e5f30bc..c07eac3d47 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -57,11 +57,6 @@ "displayName": "Layer 1: Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "layer1_clearCoat", - "displayName": "Layer 1: Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "layer1_occlusion", "displayName": "Layer 1: Occlusion", @@ -72,6 +67,11 @@ "displayName": "Layer 1: Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "layer1_clearCoat", + "displayName": "Layer 1: Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "layer1_parallax", "displayName": "Layer 1: Displacement", @@ -110,11 +110,6 @@ "displayName": "Layer 2: Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "layer2_clearCoat", - "displayName": "Layer 2: Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "layer2_occlusion", "displayName": "Layer 2: Occlusion", @@ -125,6 +120,11 @@ "displayName": "Layer 2: Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "layer2_clearCoat", + "displayName": "Layer 2: Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "layer2_parallax", "displayName": "Layer 2: Displacement", @@ -163,11 +163,6 @@ "displayName": "Layer 3: Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "layer3_clearCoat", - "displayName": "Layer 3: Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "layer3_occlusion", "displayName": "Layer 3: Occlusion", @@ -178,6 +173,11 @@ "displayName": "Layer 3: Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "layer3_clearCoat", + "displayName": "Layer 3: Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "layer3_parallax", "displayName": "Layer 3: Displacement", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 0904302085..658aaeeee9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -23,26 +23,11 @@ "displayName": "Specular Reflectance f0", "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, - { - "id": "clearCoat", - "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "normal", "displayName": "Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "opacity", - "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." - }, - { - "id": "uv", - "displayName": "UVs", - "description": "Properties for configuring UV transforms." - }, { "id": "occlusion", "displayName": "Occlusion", @@ -53,15 +38,30 @@ "displayName": "Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "subsurfaceScattering", + "displayName": "Subsurface Scattering", + "description": "Properties for configuring subsurface scattering effects." + }, + { + "id": "clearCoat", + "displayName": "Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "parallax", "displayName": "Displacement", "description": "Properties for parallax effect produced by a height map." }, { - "id": "subsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." + "id": "opacity", + "displayName": "Opacity", + "description": "Properties for configuring the materials transparency." + }, + { + "id": "uv", + "displayName": "UVs", + "description": "Properties for configuring UV transforms." }, { // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader From f7e03a2f37b96ffb8ebd96994848f573838ea091 Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Fri, 28 May 2021 16:48:24 -0500 Subject: [PATCH 14/23] Updating the README.md to account for the O3DE as an SDK changes (#1041) Moved the registration of the engine to the "Setting up new projects" section. The engine is no longer required to be registered in order to build it. --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e0442adc16..0c59837a62 100644 --- a/README.md +++ b/README.md @@ -118,11 +118,6 @@ If you have the Git credential manager core or other credential helpers installe ``` python\get_python.bat ``` - -1. While still within the repo folder, register the engine with this command: - ``` - scripts\o3de.bat register --this-engine - ``` 1. Configure the source into a solution using this command line, replacing and <3rdParty cache path> to a path you've created: ``` @@ -146,7 +141,11 @@ If you have the Git credential manager core or other credential helpers installe 1. This will compile after some time and binaries will be available in the build path you've specified -### Setting up new projects +### Setting up new projects +1. While still within the repo folder, register the engine with this command: + ``` + scripts\o3de.bat register --this-engine + ``` 1. Setup new projects using the `o3de create-project` command. In the 0.5 branch, the project directory must be a subdirectory in the repo folder. ``` \scripts\o3de.bat create-project --project-path @@ -160,10 +159,10 @@ If you have the Git credential manager core or other credential helpers installe cmake -B -S -G "Visual Studio 16" -DLY_3RDPARTY_PATH=<3rdParty cache path> // For the 0.5 branch, you must build a new Editor for each project: - cmake --build --target Editor --config profile -- /m + cmake --build --target .GameLauncher Editor --config profile -- /m // For all other branches, just build the project: - cmake --build --target --config profile -- /m + cmake --build --target .GameLauncher --config profile -- /m ``` For a tutorial on project configuration, see [Creating Projects Using the Command Line](https://docs.o3de.org/docs/welcome-guide/get-started/project-config/creating-projects-using-cli) in the documentation. From 75cb293b2a0309335d9b7aea78ca9f0cd61fa41a Mon Sep 17 00:00:00 2001 From: hershey5045 <43485729+hershey5045@users.noreply.github.com> Date: Fri, 28 May 2021 16:40:44 -0700 Subject: [PATCH 15/23] Png fix for vulkan rhi (#962) * Add supported formats for pngs in frame capture system. Add conversion logic from bgra to rgba. --- .../Source/FrameCaptureSystemComponent.cpp | 65 ++++++++++++++++--- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index 90499be8b8..f8c1258fc0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include @@ -55,6 +57,43 @@ namespace AZ FrameCaptureOutputResult PngFrameCaptureOutput( const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) { + AZStd::shared_ptr> buffer = readbackResult.m_dataBuffer; + + // convert bgra to rgba by swapping channels + const int numChannels = AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format); + if (readbackResult.m_imageDescriptor.m_format == RHI::Format::B8G8R8A8_UNORM) + { + buffer = AZStd::make_shared>(readbackResult.m_dataBuffer->size()); + AZStd::copy(readbackResult.m_dataBuffer->begin(), readbackResult.m_dataBuffer->end(), buffer->begin()); + + AZ::JobCompletion jobCompletion; + const int numThreads = 8; + const int numPixelsPerThread = buffer->size() / numChannels / numThreads; + for (int i = 0; i < numThreads; ++i) + { + int startPixel = i * numPixelsPerThread; + + AZ::Job* job = AZ::CreateJobFunction( + [&, startPixel, numPixelsPerThread]() + { + for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset) + { + if (startPixel * numChannels + numChannels < buffer->size()) + { + AZStd::swap( + buffer->data()[(startPixel + pixelOffset) * numChannels], + buffer->data()[(startPixel + pixelOffset) * numChannels + 2] + ); + } + } + }, true, nullptr); + + job->SetDependent(&jobCompletion); + job->Start(); + } + jobCompletion.StartAndWaitForCompletion(); + } + using namespace OIIO; AZStd::unique_ptr out = ImageOutput::create(outputFilePath.c_str()); if (out) @@ -62,13 +101,13 @@ namespace AZ ImageSpec spec( readbackResult.m_imageDescriptor.m_size.m_width, readbackResult.m_imageDescriptor.m_size.m_height, - AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format) + numChannels ); spec.attribute("png:compressionLevel", r_pngCompressionLevel); if (out->open(outputFilePath.c_str(), spec)) { - out->write_image(TypeDesc::UINT8, readbackResult.m_dataBuffer->data()); + out->write_image(TypeDesc::UINT8, buffer->data()); out->close(); return FrameCaptureOutputResult{FrameCaptureResult::Success, AZStd::nullopt}; } @@ -460,13 +499,23 @@ namespace AZ #if defined(OPEN_IMAGE_IO_ENABLED) else if (extension == "png") { - AZStd::string folderPath; - AzFramework::StringFunc::Path::GetFolderPath(m_outputFilePath.c_str(), folderPath); - AZ::IO::SystemFile::CreateDir(folderPath.c_str()); + if (readbackResult.m_imageDescriptor.m_format == RHI::Format::R8G8B8A8_UNORM || + readbackResult.m_imageDescriptor.m_format == RHI::Format::B8G8R8A8_UNORM) + { + AZStd::string folderPath; + AzFramework::StringFunc::Path::GetFolderPath(m_outputFilePath.c_str(), folderPath); + AZ::IO::SystemFile::CreateDir(folderPath.c_str()); - const auto frameCaptureResult = PngFrameCaptureOutput(m_outputFilePath, readbackResult); - m_result = frameCaptureResult.m_result; - m_latestCaptureInfo = frameCaptureResult.m_errorMessage.value_or(""); + const auto frameCaptureResult = PngFrameCaptureOutput(m_outputFilePath, readbackResult); + m_result = frameCaptureResult.m_result; + m_latestCaptureInfo = frameCaptureResult.m_errorMessage.value_or(""); + } + else + { + m_latestCaptureInfo = AZStd::string::format( + "Can't save image with format %s to a png file", RHI::ToString(readbackResult.m_imageDescriptor.m_format)); + m_result = FrameCaptureResult::UnsupportedFormat; + } } #endif else From 50a9e94eca9d5cc68567c30cfe3142b50dee5e58 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Fri, 28 May 2021 18:28:29 -0700 Subject: [PATCH 16/23] Fix old method call (#1049) --- .../Code/Source/RayTracing/RayTracingFeatureProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp index f51fa4b81b..10c7c2d378 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp @@ -234,7 +234,7 @@ namespace AZ { AZ::Transform meshTransform = transformFeatureProcessor->GetTransformForId(TransformServiceFeatureProcessorInterface::ObjectId(mesh.first)); AZ::Transform noScaleTransform = meshTransform; - noScaleTransform.ExtractScale(); + noScaleTransform.ExtractUniformScale(); AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform); rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose(); From 74ec7a362b25cd55d9429142188892e1cbe937ba Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Fri, 28 May 2021 23:24:51 -0700 Subject: [PATCH 17/23] Update Android Project Generation to support AGP 4.2.0, Cmake 3.20, and newer versions of NDK&SDK - build.gradle format updates for newer Android Gradle Plugin - Remove hard coded Android Gradle Plugin version 3.6.4 to be passed in from command args - Set Android Gradle Plugin min version 4.2.0 in order to support Min CMake version 3.20 - Add ability to use the android sdk to install missing components if needed rather than doing it externally - Removed argument to pass in the NDK folder to use the android, use the android-sdk instead. Can request specific NDK versions if possible - Android Gradle Plugin has dependencies by version and is being managed - More defaults based on tools on path, agp version made possible so they are no longer needed in the command args --- .../Android/ProjectBuilder/build.gradle.in | 4 +- .../ProjectBuilder/local.properties.in | 1 - .../ProjectBuilder/root.build.gradle.in | 7 +- .../Tools/Platform/Android/android_support.py | 615 +++++++++--------- .../Android/generate_android_project.py | 164 +++-- .../unit_test_generate_android_project.py | 114 ---- cmake/Tools/common.py | 37 +- .../build/Platform/Android/build_config.json | 6 +- .../build/Platform/Android/gradle_windows.cmd | 39 +- scripts/build/Platform/Android/pipeline.json | 4 +- 10 files changed, 490 insertions(+), 501 deletions(-) diff --git a/Code/Tools/Android/ProjectBuilder/build.gradle.in b/Code/Tools/Android/ProjectBuilder/build.gradle.in index 66f58294ab..5980984516 100644 --- a/Code/Tools/Android/ProjectBuilder/build.gradle.in +++ b/Code/Tools/Android/ProjectBuilder/build.gradle.in @@ -15,14 +15,14 @@ android { ${SIGNING_CONFIGS} compileSdkVersion sdkVer buildToolsVersion buildToolsVer - + ndkVersion ndkPlatformVer lintOptions { abortOnError false checkReleaseBuilds false } defaultConfig { - minSdkVersion ndkPlatformVer + minSdkVersion minSdkVer targetSdkVersion sdkVer ${NATIVE_CMAKE_SECTION_DEFAULT_CONFIG} } diff --git a/Code/Tools/Android/ProjectBuilder/local.properties.in b/Code/Tools/Android/ProjectBuilder/local.properties.in index 559ea67bcb..4e82cb2940 100644 --- a/Code/Tools/Android/ProjectBuilder/local.properties.in +++ b/Code/Tools/Android/ProjectBuilder/local.properties.in @@ -16,6 +16,5 @@ # For customization when using a Version Control System, please read the # header note. # ${GENERATION_TIMESTAMP} -ndk.dir=${ANDROID_NDK_PATH} sdk.dir=${ANDROID_SDK_PATH} ${CMAKE_DIR_LINE} diff --git a/Code/Tools/Android/ProjectBuilder/root.build.gradle.in b/Code/Tools/Android/ProjectBuilder/root.build.gradle.in index 782a1f26b5..dfce99c3c7 100644 --- a/Code/Tools/Android/ProjectBuilder/root.build.gradle.in +++ b/Code/Tools/Android/ProjectBuilder/root.build.gradle.in @@ -12,10 +12,9 @@ buildscript { repositories { google() jcenter() - } dependencies { - classpath 'com.android.tools.build:gradle:3.6.4' + classpath 'com.android.tools.build:gradle:${ANDROID_GRADLE_PLUGIN_VERSION}' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -26,14 +25,14 @@ allprojects { repositories { google() jcenter() - } } subprojects { ext { + minSdkVer = ${MIN_SDK_VER} sdkVer = ${SDK_VER} - ndkPlatformVer = ${NDK_PLATFORM_VER} + ndkPlatformVer = '${NDK_VERSION}' buildToolsVer = '${SDK_BUILD_TOOL_VER}' lyEngineRoot = '${LY_ENGINE_ROOT}' } diff --git a/cmake/Tools/Platform/Android/android_support.py b/cmake/Tools/Platform/Android/android_support.py index 6443077457..75e0cd2970 100755 --- a/cmake/Tools/Platform/Android/android_support.py +++ b/cmake/Tools/Platform/Android/android_support.py @@ -10,7 +10,9 @@ # import imghdr +import configparser import datetime +import fnmatch import logging import os import json @@ -33,6 +35,13 @@ if ROOT_DEV_PATH not in sys.path: from cmake.Tools import common +ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP = { + '4.2.0': {'min_gradle_version': '6.7.1', + 'sdk_build': '30.0.2', + 'default_ndk': '21.4.7075529', + 'min_cmake_version': '3.20'} +} + APP_NAME = 'app' ANDROID_MANIFEST_FILE = 'AndroidManifest.xml' ANDROID_LIBRARIES_JSON_FILE = 'android_libraries.json' @@ -86,83 +95,93 @@ PYTHON_SCRIPT = 'python.cmd' if platform.system() == 'Windows' else 'python.sh' ANDROID_LAUNCHER_NAME_PATTERN = "{project_name}.GameLauncher" + class AndroidProjectManifestEnvironment(object): """ - This class manages the environment for the AndroidManifiest.xml template file, based on project settings and environments + This class manages the environment for the AndroidManifest.xml template file, based on project settings and environments that were passed in or calculated from the command line arguments. """ - def __init__(self, engine_root, project_path, android_sdk_version_number, android_ndk_platform_number, is_test:bool): + def __init__(self, engine_root, project_path, android_sdk_version_number, is_test:bool): """ Initialize the object with the project specific parameters and values for the game project :param engine_root: The path where the engine is located :param project_path: The path were the project is located :param android_sdk_version_number: The android SDK platform version - :param android_ndk_platform_number: The android NDK platform version :param is_test: Indicates if theAzTestRunner application should be run """ - if is_test: - # The AzTestRunner project.json is located under {engine_root}/Code/Tools/AzTestRunner/Platform/Android/android_project.json - project_properties_path = engine_root / 'Code' / 'Tools' / 'AzTestRunner' / 'Platform' / 'Android' / 'android_project.json' - else: - # The project.json file is located under the game name folder - project_properties_path = project_path / 'project.json' - # Read and parse the project.json file into a dictionary to process the specific attributes needed for the manifest template - project_properties_content = project_properties_path.resolve(strict=True)\ - .read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, - errors=common.ENCODING_ERROR_HANDLINGS) - self.project_path = project_path + try: + if is_test: + # The AzTestRunner project.json is located under {engine_root}/Code/Tools/AzTestRunner/Platform/Android/android_project.json + project_properties_path = engine_root / 'Code' / 'Tools' / 'AzTestRunner' / 'Platform' / 'Android' / 'android_project.json' + assert project_properties_path.is_file(), f'Missing required android settings file {project_properties_path.resolve()}' + project_properties_content = project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, + errors=common.ENCODING_ERROR_HANDLINGS) + project_json = json.loads(project_properties_content) - # Extract the key attributes we need to process and build up our environment table - project_json = json.loads(project_properties_content) + android_settings = project_json['android_settings'] - project_name = project_json.get('project_name') - if not project_name: - raise common.LmbrCmdError(f"Missing required 'project_name' from project.json for project at '{str(project_path)}'") - product_name = project_json.get('product_name', project_name) + else: + # O3DE projects have both a project.json and an android_project.json files (unless its internal) + project_properties_path = project_path / 'project.json' + assert project_properties_path.is_file(), f'Missing required project settings file {project_properties_path.resolve()}' + project_properties_content = project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, + errors=common.ENCODING_ERROR_HANDLINGS) + project_json = json.loads(project_properties_content) - game_project_android_settings = project_json['android_settings'] + android_project_properties_path = project_path / 'Platform' / 'Android' / 'android_project.json' + if android_project_properties_path.is_file(): + android_project_properties_content = android_project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, + errors=common.ENCODING_ERROR_HANDLINGS) + android_project_json = json.loads(android_project_properties_content) + android_settings = android_project_json['android_settings'] + else: + android_settings = project_json['android_settings'] - package_name = game_project_android_settings["package_name"] + self.project_path = project_path - package_path = package_name.replace('.', '/') + project_name = project_json['project_name'] + product_name = project_json.get('product_name', project_name) + package_name = android_settings["package_name"] + package_path = package_name.replace('.', '/') - project_activity = f'{TEST_RUNNER_PROJECT}Activity' if is_test else f'{project_name}Activity' + project_activity = f'{TEST_RUNNER_PROJECT}Activity' if is_test else f'{project_name}Activity' - # Multiview options require special processing - multi_window_options = AndroidProjectManifestEnvironment.process_android_multi_window_options(game_project_android_settings) + # Multiview options require special processing + multi_window_options = AndroidProjectManifestEnvironment.process_android_multi_window_options(android_settings) - self.internal_dict = { - 'ANDROID_PACKAGE': package_name, - 'ANDROID_PACKAGE_PATH': package_path, - 'ANDROID_VERSION_NUMBER': game_project_android_settings["version_number"], - "ANDROID_VERSION_NAME": game_project_android_settings["version_name"], - "ANDROID_SCREEN_ORIENTATION": game_project_android_settings["orientation"], - 'ANDROID_APP_NAME': TEST_RUNNER_PROJECT if is_test else product_name, # external facing name - 'ANDROID_PROJECT_NAME': TEST_RUNNER_PROJECT if is_test else project_name, # internal facing name - 'ANDROID_PROJECT_ACTIVITY': project_activity, - 'ANDROID_LAUNCHER_NAME': TEST_RUNNER_PROJECT if is_test else ANDROID_LAUNCHER_NAME_PATTERN.format(project_name=project_name), - 'ANDROID_CONFIG_CHANGES': multi_window_options['ANDROID_CONFIG_CHANGES'], - 'ANDROID_APP_PUBLIC_KEY': game_project_android_settings.get('app_public_key', 'NoKey'), - 'ANDROID_APP_OBFUSCATOR_SALT': game_project_android_settings.get('app_obfuscator_salt', ''), - 'ANDROID_USE_MAIN_OBB': game_project_android_settings.get('use_main_obb', 'false'), - 'ANDROID_USE_PATCH_OBB': game_project_android_settings.get('use_patch_obb', 'false'), - 'ANDROID_ENABLE_KEEP_SCREEN_ON': game_project_android_settings.get('enable_keep_screen_on', 'false'), - 'ANDROID_DISABLE_IMMERSIVE_MODE': game_project_android_settings.get('disable_immersive_mode', 'false'), - 'ANDROID_MIN_SDK_VERSION': android_ndk_platform_number, - 'ANDROID_TARGET_SDK_VERSION': android_sdk_version_number, - 'ICONS': game_project_android_settings.get('icons', None), - 'SPLASH_SCREEN': game_project_android_settings.get('splash_screen', None), + self.internal_dict = { + 'ANDROID_PACKAGE': package_name, + 'ANDROID_PACKAGE_PATH': package_path, + 'ANDROID_VERSION_NUMBER': android_settings["version_number"], + "ANDROID_VERSION_NAME": android_settings["version_name"], + "ANDROID_SCREEN_ORIENTATION": android_settings["orientation"], + 'ANDROID_APP_NAME': TEST_RUNNER_PROJECT if is_test else product_name, # external facing name + 'ANDROID_PROJECT_NAME': TEST_RUNNER_PROJECT if is_test else project_name, # internal facing name + 'ANDROID_PROJECT_ACTIVITY': project_activity, + 'ANDROID_LAUNCHER_NAME': TEST_RUNNER_PROJECT if is_test else ANDROID_LAUNCHER_NAME_PATTERN.format(project_name=project_name), + 'ANDROID_CONFIG_CHANGES': multi_window_options['ANDROID_CONFIG_CHANGES'], + 'ANDROID_APP_PUBLIC_KEY': android_settings.get('app_public_key', 'NoKey'), + 'ANDROID_APP_OBFUSCATOR_SALT': android_settings.get('app_obfuscator_salt', ''), + 'ANDROID_USE_MAIN_OBB': android_settings.get('use_main_obb', 'false'), + 'ANDROID_USE_PATCH_OBB': android_settings.get('use_patch_obb', 'false'), + 'ANDROID_ENABLE_KEEP_SCREEN_ON': android_settings.get('enable_keep_screen_on', 'false'), + 'ANDROID_DISABLE_IMMERSIVE_MODE': android_settings.get('disable_immersive_mode', 'false'), + 'ANDROID_TARGET_SDK_VERSION': android_sdk_version_number, + 'ICONS': android_settings.get('icons', None), + 'SPLASH_SCREEN': android_settings.get('splash_screen', None), - 'ANDROID_MULTI_WINDOW': multi_window_options['ANDROID_MULTI_WINDOW'], - 'ANDROID_MULTI_WINDOW_PROPERTIES': multi_window_options['ANDROID_MULTI_WINDOW_PROPERTIES'], + 'ANDROID_MULTI_WINDOW': multi_window_options['ANDROID_MULTI_WINDOW'], + 'ANDROID_MULTI_WINDOW_PROPERTIES': multi_window_options['ANDROID_MULTI_WINDOW_PROPERTIES'], - 'SAMSUNG_DEX_KEEP_ALIVE': multi_window_options['SAMSUNG_DEX_KEEP_ALIVE'], - 'SAMSUNG_DEX_LAUNCH_WIDTH': multi_window_options['SAMSUNG_DEX_LAUNCH_WIDTH'], - 'SAMSUNG_DEX_LAUNCH_HEIGHT': multi_window_options['SAMSUNG_DEX_LAUNCH_HEIGHT'] - } + 'SAMSUNG_DEX_KEEP_ALIVE': multi_window_options['SAMSUNG_DEX_KEEP_ALIVE'], + 'SAMSUNG_DEX_LAUNCH_WIDTH': multi_window_options['SAMSUNG_DEX_LAUNCH_WIDTH'], + 'SAMSUNG_DEX_LAUNCH_HEIGHT': multi_window_options['SAMSUNG_DEX_LAUNCH_HEIGHT'] + } + except KeyError as e: + raise common.LmbrCmdError(f"Missing key from android project settings for project at {project_path}:'{e}' ") def __getitem__(self, item): return self.internal_dict.get(item) @@ -306,6 +325,7 @@ asset_deploy_type={asset_type} android_sdk_path={android_sdk_path} embed_assets_in_apk={embed_assets_in_apk} is_unit_test={is_unit_test} +android_gradle_plugin={android_gradle_plugin_version} """ NATIVE_CMAKE_SECTION_ANDROID_FORMAT = """ @@ -425,26 +445,28 @@ class AndroidProjectGenerator(object): Class the manages the process to generate an android project folder in order to build with gradle/android studio """ - def __init__(self, engine_root, build_dir, android_ndk_path, android_sdk_path, android_sdk_version, android_ndk_platform, - project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, override_ninja_path, - android_sdk_build_tool_version, include_assets_in_apk, asset_mode, asset_type, signing_config, is_test_project=False, + def __init__(self, engine_root, build_dir, android_sdk_path, build_tool, android_sdk_platform, android_native_api_level, android_ndk, + project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, gradle_version, gradle_plugin_version, + override_ninja_path, include_assets_in_apk, asset_mode, asset_type, signing_config, is_test_project=False, overwrite_existing=True): """ Initialize the object with all the required parameters needed to create an Android Project. The parameters should be verified before initializing this object - + :param engine_root: The engine root that contains the engine :param build_dir: The target folder under the where the android project folder will be created - :param android_ndk_path: The path to the ANDROID_NDK used for building the native android code :param android_sdk_path: The path to the ANDROID_SDK used for building the android java code - :param android_sdk_version: The android platform version number to use for the Android SDK related builds - :param android_ndk_platform: The android platform version number to use for the Android NDK related builds + :param build_tool: The android SDK build-tool version. + :param android_sdk_platform: The android sdk platform version number to use for the Android SDK related builds + :param android_native_api_level:The android native API level (ANDROID_NATIVE_API_LEVEL) to set + :param android_ndk: The android ndk version number to use for the native builds :param project_path: The path to the project :param third_party_path: The required path to the lumberyard 3rd party path :param cmake_version: The version number of cmake that will be used by gradle :param override_cmake_path: The override path to cmake if it does not exists in the system path :param override_gradle_path: The override path to gradle if it does not exists in the system path + :param gradle_version: The detected version of gradle being used + :param gradle_plugin_version: The android gradle plugin version :param override_ninja_path: The override path to ninja if it does not exists in the system path - :param android_sdk_build_tool_version: The preferred android SDK build-tool version. Will default to the first one detected in the android sdk path :param include_assets_in_apk: :param asset_mode: :param asset_type: @@ -458,17 +480,16 @@ class AndroidProjectGenerator(object): self.build_dir = build_dir - self.android_ndk_path = android_ndk_path - self.android_sdk_path = android_sdk_path self.android_project_builder_path = self.engine_root / 'Code/Tools/Android/ProjectBuilder' - self.android_sdk_version = android_sdk_version + self.android_sdk_platform = android_sdk_platform + self.android_sdk_build_tool_version = build_tool.version - self.android_sdk_build_tool_version = android_sdk_build_tool_version - - self.android_ndk_platform = android_ndk_platform + self.android_ndk = android_ndk + self.android_ndk_version = android_ndk.version + self.android_native_api_level = android_native_api_level self.project_path = project_path @@ -480,6 +501,10 @@ class AndroidProjectGenerator(object): self.override_gradle_path = override_gradle_path + self.gradle_version = gradle_version + + self.gradle_plugin_version = gradle_plugin_version + self.override_ninja_path = override_ninja_path self.include_assets_in_apk = include_assets_in_apk @@ -511,8 +536,10 @@ class AndroidProjectGenerator(object): project_names.extend(self.create_lumberyard_app(project_names)) root_gradle_env = { - 'SDK_VER': self.android_sdk_version, - 'NDK_PLATFORM_VER': self.android_ndk_platform, + 'ANDROID_GRADLE_PLUGIN_VERSION': str(self.gradle_plugin_version), + 'SDK_VER': self.android_sdk_platform, + 'MIN_SDK_VER': self.android_sdk_platform, + 'NDK_VERSION': self.android_ndk_version, 'SDK_BUILD_TOOL_VER': self.android_sdk_build_tool_version, 'LY_ENGINE_ROOT': common.normalize_path_for_settings(self.engine_root) } @@ -557,7 +584,7 @@ class AndroidProjectGenerator(object): if self.override_gradle_path: gradle_wrapper_cmd = [self.override_gradle_path] else: - gradle_wrapper_cmd = ['gradle.bat' if platform.system() == 'Windows' else 'gradle'] + gradle_wrapper_cmd = ['gradle'] gradle_wrapper_cmd.extend(['wrapper', '-p', str(self.build_dir.resolve())]) @@ -580,7 +607,8 @@ class AndroidProjectGenerator(object): asset_type='', android_sdk_path=str(self.android_sdk_path), embed_assets_in_apk=True, - is_unit_test=True) + is_unit_test=True, + android_gradle_plugin_version=self.gradle_plugin_version) else: platform_settings_content = PLATFORM_SETTINGS_FORMAT.format(generation_timestamp=str(datetime.datetime.now().strftime("%c")), platform='android', @@ -589,16 +617,28 @@ class AndroidProjectGenerator(object): asset_type=self.asset_type, android_sdk_path=str(self.android_sdk_path), embed_assets_in_apk=str(self.include_assets_in_apk), - is_unit_test=False) + is_unit_test=False, + android_gradle_plugin_version=self.gradle_plugin_version) platform_settings_file = self.build_dir / 'platform.settings' + + # Check if there already exists the build folder and a 'platform.settings' file. If there is an android gradle + # plugin version set and it is different than the one configured here, we will always overwrite it since + # there could be significant differences from one plug-in to the next + if platform_settings_file.is_file(): + config = configparser.ConfigParser() + config.read([str(platform_settings_file.resolve(strict=True))]) + if config.has_option('android', 'android_gradle_plugin'): + exist_agp_version = config.get('android', 'android_gradle_plugin') + if exist_agp_version != self.gradle_plugin_version: + self.overwrite_existing = True + platform_settings_file.open('w').write(platform_settings_content) def create_default_local_properties(self): """ Create the default 'local.properties' file in the build folder """ - template_android_ndk_path = common.normalize_path_for_settings(self.android_ndk_path, True) template_android_sdk_path = common.normalize_path_for_settings(self.android_sdk_path, True) if self.override_cmake_path: # The cmake dir references the base cmake folder, not the executable path itself, so resolve to the base folder @@ -608,7 +648,6 @@ class AndroidProjectGenerator(object): local_properties_env = { "GENERATION_TIMESTAMP": str(datetime.datetime.now().strftime("%c")), - "ANDROID_NDK_PATH": template_android_ndk_path, "ANDROID_SDK_PATH": template_android_sdk_path, "CMAKE_DIR_LINE": f'cmake.dir={template_cmake_path}' if template_cmake_path else '' } @@ -626,8 +665,7 @@ class AndroidProjectGenerator(object): # before we can process it. android_libraries_substitution_table = { "ANDROID_SDK_HOME": common.normalize_path_for_settings(self.android_sdk_path, False), - "ANDROID_NDK_HOME": common.normalize_path_for_settings(self.android_ndk_path, False), - "ANDROID_SDK_VERSION": "android-".format(self.android_sdk_version) + "ANDROID_SDK_VERSION": f"android-{self.android_sdk_platform}" } android_libraries_template_json_path = self.android_project_builder_path / ANDROID_LIBRARIES_JSON_FILE @@ -717,7 +755,7 @@ class AndroidProjectGenerator(object): template_engine_root = common.normalize_path_for_settings(self.engine_root) template_third_party_path = common.normalize_path_for_settings(self.third_party_path) - template_ndk_path = common.normalize_path_for_settings(self.android_ndk_path) + template_ndk_path = common.normalize_path_for_settings(os.path.join(self.android_sdk_path, self.android_ndk.location)) gradle_build_env = dict() @@ -733,7 +771,6 @@ class AndroidProjectGenerator(object): gradle_build_env['OVERRIDE_JAVA_SOURCESET'] = OVERRIDE_JAVA_SOURCESET_STR.format(absolute_azandroid_path=absolute_azandroid_path) - gradle_build_env['OPTIONAL_JNI_SRC_LIB_SET'] = ', "outputs/native-lib"' for native_config in BUILD_CONFIGURATIONS: @@ -755,7 +792,7 @@ class AndroidProjectGenerator(object): cmake_argument_list.append('"-DLY_TEST_PROJECT=1"') cmake_argument_list.extend([ - f'"-DANDROID_NATIVE_API_LEVEL={self.android_ndk_platform}"', + f'"-DANDROID_NATIVE_API_LEVEL={self.android_native_api_level}"', f'"-DLY_NDK_DIR={template_ndk_path}"', '"-DANDROID_STL=c++_shared"', '"-Wno-deprecated"', @@ -835,8 +872,7 @@ class AndroidProjectGenerator(object): dest_src_main_path.mkdir(parents=True) az_android_package_env = AndroidProjectManifestEnvironment(engine_root=self.engine_root, project_path=self.project_path, - android_sdk_version_number=self.android_sdk_version, - android_ndk_platform_number=self.android_ndk_platform, + android_sdk_version_number=self.android_sdk_platform, is_test=self.is_test_project) self.create_file_from_project_template(src_template_file=ANDROID_MANIFEST_FILE, template_env=az_android_package_env, @@ -1304,218 +1340,7 @@ class AndroidProjectGenerator(object): self.new = new -ANDROID_PLATFORM_PATTERN = re.compile(r'([\w\d]*-)?(\d+\d*)') # Regex to handle android platform naming for both SDKs and NDKs - - -def validate_android_platform_input(input_android_platform, platform_variable_type, min_version, max_version): - """ - Helper tool to support android platform number inputs and perform min/max version validation - - :param input_android_platform: The inpuit argument to evaluate - :param platform_variable_type: The type of platform version to validate (android sdk / android ndk) - :param min_version: The minimum version to validate against - :param max_version: The maximum version to validate against - :return: The int version of the extracted platform number from the input - """ - # Validate the platform number's format and against the supported versions - platform_number_match = ANDROID_PLATFORM_PATTERN.search(input_android_platform) - if not platform_number_match or not platform_number_match.group(2) or (platform_number_match.group(1) and platform_number_match.group(1) != 'android-'): - raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}). It must be " - f"either 'XX' or android-'XX' where 'XX' is a platform number.", - common.ERROR_CODE_INVALID_PARAMETER) - - android_platform_number = int(platform_number_match.group(2)) - if android_platform_number < min_version: - raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}) is less than the minimum " - f"supported version ({min_version}).", - common.ERROR_CODE_INVALID_PARAMETER) - if android_platform_number > max_version: - raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}) is greater than the maximum " - f"supported version ({max_version}).", - common.ERROR_CODE_INVALID_PARAMETER) - return android_platform_number - - ANDROID_SDK_ENV_NAME = 'ANDROID_SDK' -ANDROID_SDK_MIN_PLATFORM = 28 -ANDROID_SDK_MAX_PLATFORM = 29 - - -def verify_android_sdk(android_sdk_platform, argument_name, override_android_sdk_path=None, preferred_sdk_build_tools_ver=None): - """ - Verify the android sdk and the requested platform platform against the android sdk path - - :param android_sdk_platform: The android sdk platform to use (e.g. '28' or 'android-28') - :param argument_name: The name of the argument for descriptive errors to present - :param override_android_sdk_path: The location of the android SDK path if not set through the environment variable - :param preferred_sdk_build_tools_ver: Option prefered built tool version under the android SDK if available. Will fallback to the first one discovered - :returns tuple of the verified android sdk platform number, path to the Android SDK path and the build tool version - """ - android_sdk_platform_number = validate_android_platform_input(input_android_platform=android_sdk_platform, - platform_variable_type='android sdk', - min_version=ANDROID_SDK_MIN_PLATFORM, - max_version=ANDROID_SDK_MAX_PLATFORM) - - # Get the candidate android sdk path from either the override argument or the system environment variable - if override_android_sdk_path: - check_android_sdk_path = override_android_sdk_path - else: - check_android_sdk_path = os.environ.get(ANDROID_SDK_ENV_NAME) - if not check_android_sdk_path: - raise common.LmbrCmdError(f"Android SDK path not set. Make sure that either the '{ANDROID_SDK_ENV_NAME}' environment is " - f"set or it is passed in through the {argument_name} argument") - - # The android sdk folder structure is expected to have a 'platforms' sub folder based on the android sdk-platform number - check_android_sdk_path = pathlib.Path(check_android_sdk_path) - android_sdk_platforms_path = check_android_sdk_path / 'platforms' - if not android_sdk_platforms_path.is_dir(): - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Missing 'platforms' directory.") - - # Collect the available platform numbers from the platforms subdirectory - validated_android_platforms = [] - for dir_item in android_sdk_platforms_path.iterdir(): - if not dir_item.is_dir(): - continue - check_file = dir_item / 'package.xml' - if check_file.is_file(): - validated_android_platforms.append(dir_item.name) - - if not validated_android_platforms: - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any android platforms.") - - # Normalize the android_sdk argument to fit the same folder name pattern - android_sdk_platform_name = f'android-{android_sdk_platform_number}' - if android_sdk_platform_name not in validated_android_platforms: - raise common.LmbrCmdError(f"Android SDK platform {android_sdk_platform_name} is not a valid for the android SDK located under '{str(check_android_sdk_path)}'") - - # Enumerate through the build tools under android sdk - android_sdk_build_tools_dir = check_android_sdk_path / 'build-tools' - if not android_sdk_build_tools_dir.is_dir(): - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any built-tools folder.") - supported_build_tools = [str(build_tool.name) for build_tool in android_sdk_build_tools_dir.iterdir() if build_tool.is_dir()] - if not supported_build_tools: - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any built-tools.") - if preferred_sdk_build_tools_ver: - if preferred_sdk_build_tools_ver in supported_build_tools: - validated_build_tool = preferred_sdk_build_tools_ver - else: - validated_build_tool = supported_build_tools[0] - logging.warning("Unable to locate android sdk build tool version {preferred_sdk_build_tools_ver}. Defaulting to version {validated_build_tool}") - - else: - validated_build_tool = supported_build_tools[0] - - return android_sdk_platform_number, check_android_sdk_path, validated_build_tool - - -ANDROID_NDK_ENV_NAME = 'ANDROID_NDK' -ANDROID_NDK_MIN_PLATFORM = 21 -ANDROID_NDK_MAX_PLATFORM = 29 -ANDROID_NDK_SOURCE_PROPERTIES_REVISION_PATTERN = re.compile(r'Pkg.Revision\s*=\s*(\d+.\d+.\d+)') - - -def verify_android_ndk(android_ndk_platform, argument_name, override_android_ndk_path=None): - """ - Verify the android ndk and requested platform against the android ndk path - - :param android_ndk_platform: The android ndk platform to use (e.g. '21' or 'android-21') - :param argument_name: The name of the argument for descriptive errors to present - :param override_android_ndk_path: The location of the android NDK path if not set through the environment variable - :returns tuple of the verified android ndk platform number and the Path to the Android SDK path and the - """ - - android_ndk_platform_number = validate_android_platform_input(input_android_platform=android_ndk_platform, - platform_variable_type='android ndk', - min_version=ANDROID_NDK_MIN_PLATFORM, - max_version=ANDROID_NDK_MAX_PLATFORM) - - # Get the candidate android ndk path from either the override argument or the system environment variable - if override_android_ndk_path: - check_android_ndk_path = str(override_android_ndk_path) - else: - check_android_ndk_path = os.environ.get(ANDROID_NDK_ENV_NAME) - if not check_android_ndk_path: - raise common.LmbrCmdError(f"Android NDK path not set. Make sure that either the {ANDROID_NDK_ENV_NAME} environment " - f"is set or it is passed in through the {argument_name} argument") - check_android_ndk_path = pathlib.Path(check_android_ndk_path) - - # Validate the android ndk path - - # Determine the NDK revision by reading the source.properties file - ndk_source_properties_file = check_android_ndk_path / 'source.properties' - if not ndk_source_properties_file.is_file(): - raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Missing 'source.properties' file.", - common.ERROR_CODE_INVALID_PARAMETER) - ndk_source_properties_file_content = ndk_source_properties_file.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, - errors=common.ENCODING_ERROR_HANDLINGS) - - ndk_revision_match = ANDROID_NDK_SOURCE_PROPERTIES_REVISION_PATTERN.search(ndk_source_properties_file_content) - if not ndk_revision_match: - raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Unable to extract version from 'source.properties' file.", - common.ERROR_CODE_INVALID_PARAMETER) - ndk_revision_number = LooseVersion(ndk_revision_match.group(1)) - logging.info(f"Detected Android NDK Revision {str(ndk_revision_number)}") - - # Collect the supported android platforms from the required 'platforms' folder under the ndk path - android_ndk_platforms_path = check_android_ndk_path / 'platforms' - if not android_ndk_platforms_path.is_dir(): - raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Missing 'platforms' folder.", - common.ERROR_CODE_INVALID_PARAMETER) - - validated_android_platforms = [] - for dir_item in android_ndk_platforms_path.iterdir(): - if not dir_item.is_dir(): - continue - api_version_match = ANDROID_PLATFORM_PATTERN.search(dir_item.name) - if not api_version_match or api_version_match.group(1) != 'android-': - continue - - check_lib_path = dir_item / 'arch-arm64/usr/lib' - if check_lib_path.is_dir(): - validated_android_platforms.append(dir_item.name) - - # For NDK revisions 19 and up, there is a mapping file for version numbers that map to other version. - platforms_map_aliases = {} - if ndk_revision_number >= LooseVersion('19.0.0'): - platforms_map_file = check_android_ndk_path / 'meta/platforms.json' - if platforms_map_file.exists(): - with open(platforms_map_file, 'r') as platforms_map_file_handle: - platforms_map_file_json = json.load(platforms_map_file_handle) - platforms_map_aliases = platforms_map_file_json['aliases'] - elif validated_android_platforms: - # Revisions before 19 does not have a mapping file for API versions, they fall back to the previous one - # So we need to make a mapping file that does the same - platforms_map_aliases = {} - validated_android_platforms.sort() - max_supported_api_number = int(ANDROID_PLATFORM_PATTERN.search(validated_android_platforms[-1]).group(2)) - for validated_android_platform in validated_android_platforms: - current_api_version = int(ANDROID_PLATFORM_PATTERN.search(validated_android_platform).group(2)) - next_api_version = current_api_version + 1 - while f'android-{next_api_version}' not in validated_android_platforms and next_api_version <= max_supported_api_number: - platforms_map_aliases[str(next_api_version)] = current_api_version - next_api_version += 1 - - # Go through the aliases and add to the validated platforms if it is mapped to an existing platform - for alias_key, alias_value in platforms_map_aliases.items(): - if not ANDROID_PLATFORM_PATTERN.search(f'android-{alias_key}'): - # Skip any non android-XX (XX = number) aliases - continue - aliased_platform_key = f'android-{alias_value}' - if aliased_platform_key in validated_android_platforms: - validated_android_platforms.append(f'android-{alias_key}') - - if not validated_android_platforms: - raise common.LmbrCmdError(f"Invalid Android NDK path {str(check_android_ndk_path)}") - - # Verify the ndk platform against the ndk path - android_ndk_platform_name = f'android-{android_ndk_platform_number}' - if android_ndk_platform_name not in validated_android_platforms: - raise common.LmbrCmdError(f"Android NDK platform {android_ndk_platform_name} is not a valid for the Android NDK located under '{str(check_android_ndk_path)}'") - - return android_ndk_platform_number, check_android_ndk_path - - -ADB_TARGET = 'adb.exe' if platform.system() == 'Windows' else 'adb' def resolve_adb_tool(android_sdk_path): @@ -1528,9 +1353,16 @@ def resolve_adb_tool(android_sdk_path): if isinstance(android_sdk_path, str): android_sdk_path = pathlib.Path(android_sdk_path) - check_adb_target = android_sdk_path / 'platform-tools' / ADB_TARGET - if not check_adb_target.exists(): - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(android_sdk_path)}': Unable to locate '{ADB_TARGET}'.") + file_found = False + for executable_path_ext in common.PLATFORM_EXECUTABLE_EXTENSIONS: + check_adb_target = android_sdk_path / 'platform-tools' / f'adb{executable_path_ext}' + if check_adb_target.is_file(): + file_found = True + break + + if not file_found: + raise common.LmbrCmdError(f"Invalid Android SDK path '{str(android_sdk_path)}': Unable to locate 'adb'.") + return check_adb_target @@ -1633,3 +1465,196 @@ class AdbTool(common.CommandLineExec): else: adb_params = arguments return super().popen(adb_params, cwd) + + +class AndroidGradlePluginInfo(object): + + def __init__(self, android_gradle_plugin_version): + + if android_gradle_plugin_version not in ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP.keys(): + raise common.LmbrCmdError(f"Android Gradle Plugin version {android_gradle_plugin_version} is not supported. " + f"Only the following version(s) are supported: {','.join(ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP.keys())}") + + details = ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP[android_gradle_plugin_version] + self.default_sdk_build_tools_version = LooseVersion(details.get('sdk_build')) + + self.default_ndk_version = LooseVersion(details.get('default_ndk')) + + self.min_gradle_version = LooseVersion(details.get('min_gradle_version')) + + self.min_cmake_version = LooseVersion(details.get('min_cmake_version')) + + max_cmake_version_number = details.get('max_cmake_version') + self.max_cmake_version = None if max_cmake_version_number is None else LooseVersion(max_cmake_version_number) + + +class AndroidSDKResolver(object): + """ + Class that manages the Android SDK tool to validate, install packages (e.g. built tools, sdk platforms, ndk, etc) + """ + + class InstalledPackage(object): + def __init__(self, installed_package_components): + assert len(installed_package_components) == 4, '4 sections expected for installed package components (path, version, description, location)' + self.path = installed_package_components[0] + self.version = LooseVersion(installed_package_components[1]) + self.description = installed_package_components[2] + self.location = installed_package_components[3] + + class AvailablePackage(object): + def __init__(self, available_package_components): + assert len(available_package_components) == 3, '3 sections expected for installed package components (path, version, description)' + self.path = available_package_components[0] + self.version = LooseVersion(available_package_components[1]) + self.description = available_package_components[2] + + class AvailableUpdate(object): + def __init__(self, available_update_components): + assert len(available_update_components) == 3, '3 sections expected for installed package components (path, version, available)' + self.path = available_update_components[0] + self.version = LooseVersion(available_update_components[1]) + self.available = available_update_components[2] + + def __init__(self, android_sdk_path): + + self.android_sdk_path = android_sdk_path or os.environ.get(ANDROID_SDK_ENV_NAME) + if not self.android_sdk_path: + raise common.LmbrCmdError(f"Android SDK path not set or it was not passed into the command to generate the android project") + if not os.path.isdir(self.android_sdk_path): + raise common.LmbrCmdError(f"Android SDK path {self.android_sdk_path} is not valid") + if platform.system() == 'Windows': + self.sdk_manager_path = pathlib.Path(self.android_sdk_path) / 'tools' / 'bin' / 'sdkmanager.bat' + else: + raise common.LmbrCmdError(f"This tool is not supported on the current platform {platform.system()}") + if not self.sdk_manager_path.is_file(): + raise common.LmbrCmdError(f"Android SDK path {self.android_sdk_path} is not valid or complete. Missing {self.sdk_manager_path}") + + self.sdk_manager = common.CommandLineExec(str(self.sdk_manager_path.resolve())) + + self.installed_packages = {} + self.available_packages = {} + self.available_updates = {} + self.refresh_sdk_installation() + + def refresh_sdk_installation(self): + """ + Utilize the sdk_manager command line tool from the Android SDK to collect / refresh the list of + installed, available, and updateable packages that are managed by the android SDK. + """ + self.installed_packages = {} + self.available_packages = {} + self.available_updates = {} + + def _factory_installed_package(package_map, item_components): + package_map[item_components[0]] = AndroidSDKResolver.InstalledPackage(item_components) + + def _factory_available_package(package_map, item_components): + package_map[item_components[0]] = AndroidSDKResolver.AvailablePackage(item_components) + + def _factory_available_update(package_map, item_components): + package_map[item_components[0]] = AndroidSDKResolver.AvailableUpdate(item_components) + + # Use the SDK manager to collect the available and installed packages + result_code, result_stdout, result_stderr = self.sdk_manager.exec(['--list'], capture_stdout=True, suppress_stderr=True) + + current_append_map = None + current_item_factory = None + for package_item in result_stdout.split('\n'): + package_item_stripped = package_item.strip() + if not package_item_stripped: + continue + if '|' not in package_item_stripped: + if package_item_stripped.upper() == 'INSTALLED PACKAGES:': + current_append_map = self.installed_packages + current_item_factory = _factory_installed_package + elif package_item_stripped.upper() == 'AVAILABLE PACKAGES:': + current_append_map = self.available_packages + current_item_factory = _factory_available_package + elif package_item_stripped.upper() == 'AVAILABLE UPDATES:': + current_append_map = self.available_updates + current_item_factory = _factory_available_update + else: + current_append_map = None + current_item_factory = None + continue + item_parts = [split.strip() for split in package_item_stripped.split('|')] + if len(item_parts) < 3: + continue + elif item_parts[1].upper() in ('VERSION', 'INSTALLED', '-------'): + continue + elif current_append_map is None: + continue + if current_append_map is not None and current_item_factory is not None: + current_item_factory(current_append_map, item_parts) + + def is_package_installed(self, search_package_path): + """ + Check if a package path to see if its a package that is installed. The path can use wildcard '*'s + The function will return a list of the results that match the package paths, ordered by the newest version first + """ + def _package_sort(package): + return package.version + package_detail_result_list = [] + for installed_package_path, installed_package_details in self.installed_packages.items(): + if fnmatch.fnmatch(installed_package_path, search_package_path): + package_detail_result_list.append(installed_package_details) + package_detail_result_list.sort(reverse=True, key=_package_sort) + return package_detail_result_list + + def is_package_available(self, search_package_path): + """ + Check if a package path to see if its an available package to install. The path can use wildcard '*'s + The function will return a list of the results that match the package paths, ordered by the newest version first + """ + def _package_sort(package): + return package.version + package_detail_result_list = [] + for available_package_path, available_package_details in self.available_packages.items(): + if fnmatch.fnmatch(available_package_path, search_package_path): + package_detail_result_list.append(available_package_details) + package_detail_result_list.sort(reverse=True, key=_package_sort) + return package_detail_result_list + + def install_package(self, package_install_path, package_description): + """ + Install a package based on the path of an available android sdk package + """ + + # Skip installation if the package is already installed + package_result_list = self.is_package_installed(package_install_path) + if package_result_list: + installed_package_detail = package_result_list[0] + logging.info(f"{installed_package_detail.description} (version {installed_package_detail.version}) Detected") + return installed_package_detail + + # Make sure the package name is available + package_result_list = self.is_package_available(package_install_path) + if not package_result_list: + raise common.LmbrCmdError(f"Invalid Android SDK Package {package_description}: Bad package path {package_install_path}") + + # Reverse sort and pick the first item, which should be the latest (if the install path contains wildcards) + def _available_sort(item): + return item.path + + package_result_list.sort(reverse=True, key=_available_sort) + + available_package_to_install = package_result_list[0] # For multiple hits, resolve to the first item which will be the latest version + + # Perform the package installation + logging.info(f"Installing {available_package_to_install.description} ...") + result_code, result_stdout, result_stderr = self.sdk_manager.exec(['--install', available_package_to_install.path], capture_stdout=True, suppress_stderr=True) + if result_code != 0: + raise common.LmbrCmdError(f"Error installing package {available_package_to_install.path}: \n{result_stderr}") + + # Refresh the tracked SDK Contents + self.refresh_sdk_installation() + + # Get the package details to verify + package_result_list = self.is_package_installed(package_install_path) + if package_result_list: + installed_package_detail = package_result_list[0] + logging.info(f"{installed_package_detail.description} (version {installed_package_detail.version}) Installed") + return installed_package_detail + else: + raise common.LmbrCmdError(f"Error installing package {available_package_to_install.path}: \n{result_stderr}") + diff --git a/cmake/Tools/Platform/Android/generate_android_project.py b/cmake/Tools/Platform/Android/generate_android_project.py index 9a0e2760f5..d25b62dde8 100755 --- a/cmake/Tools/Platform/Android/generate_android_project.py +++ b/cmake/Tools/Platform/Android/generate_android_project.py @@ -27,7 +27,7 @@ from cmake.Tools import common from cmake.Tools.Platform.Android import android_support GRADLE_ARGUMENT_NAME = '--gradle-install-path' -GRADLE_MIN_VERSION = LooseVersion('4.10.1') +GRADLE_MIN_VERSION = LooseVersion('6.5') GRADLE_MAX_VERSION = LooseVersion('7.0.0') GRADLE_VERSION_REGEX = re.compile(r"Gradle\s(\d+.\d+.?\d*)") GRADLE_EXECUTABLE = 'gradle.bat' if platform.system() == 'Windows' else 'gradle' @@ -48,9 +48,9 @@ def verify_gradle(override_gradle_path=None): CMAKE_ARGUMENT_NAME = '--cmake-install-path' -CMAKE_MIN_VERSION = LooseVersion('3.17.0') +CMAKE_MIN_VERSION = LooseVersion('3.19.0') CMAKE_VERSION_REGEX = re.compile(r'cmake version (\d+.\d+.?\d*)') -CMAKE_EXECUTABLE = 'cmake.exe' if platform.system() == 'Windows' else 'cmake' +CMAKE_EXECUTABLE = 'cmake' def verify_cmake(override_cmake_path=None): @@ -69,7 +69,7 @@ def verify_cmake(override_cmake_path=None): NINJA_ARGUMENT_NAME = '--ninja-install-path' NINJA_VERSION_REGEX = re.compile(r'(\d+.\d+.?\d*)') -NINJA_EXECUTABLE = 'ninja.exe' if platform.system() == 'Windows' else 'ninja' +NINJA_EXECUTABLE = 'ninja' def verify_ninja(override_ninja_path=None): @@ -78,7 +78,7 @@ def verify_ninja(override_ninja_path=None): """ return common.verify_tool(override_tool_path=override_ninja_path, tool_name='ninja', - tool_filename='ninja.exe' if platform.system() == 'Windows' else 'ninja', + tool_filename='ninja', argument_name=NINJA_ARGUMENT_NAME, tool_version_argument='--version', tool_version_regex=NINJA_VERSION_REGEX, @@ -103,13 +103,21 @@ def build_optional_signing_profile(store_file, store_password, key_alias, key_pa ANDROID_SDK_ARGUMENT_NAME = '--android-sdk-path' -ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-version' +ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-platform' ANDROID_SDK_PREFERRED_TOOL_VER = '--android-sdk-build-tool-version' +ANDROID_NATIVE_API_LEVEL = '--android-native-api-level' + + +MIN_ANDROID_SDK_PLATFORM = 28 # The minimum platform/api level that is supported for the SDK Platform +MIN_NATIVE_API_LEVEL = 24 # The minimum Native API level that is supported for the NDK + -ANDROID_NDK_ARGUMENT_NAME = '--android-ndk-path' ANDROID_NDK_PLATFORM_ARGUMENT_NAME = '--android-ndk-version' +ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME = '--gradle-plugin-version' +ANDROID_GRADLE_MIN_PLUGIN_VERSION = LooseVersion("4.2.0") + # Constants for asset-related options for APK generation INCLUDE_APK_ASSETS_ARGUMENT_NAME = "--include-apk-assets" ASSET_MODE_ARGUMENT_NAME = "--asset-mode" @@ -147,6 +155,7 @@ def main(args): parser = argparse.ArgumentParser(description="Prepare the android studio subfolder") + # Required Arguments parser.add_argument('--engine-root', help='The path to the engine root. Defaults to the current working directory.', default=os.getcwd()) @@ -160,32 +169,42 @@ def main(args): help='The path to the 3rd Party root directory', required=True) - parser.add_argument(ANDROID_NDK_ARGUMENT_NAME, - help='The path to the android NDK', - required=True) - parser.add_argument(ANDROID_SDK_ARGUMENT_NAME, help='The path to the android SDK', required=True) - parser.add_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME, - help='The android SDK version', + parser.add_argument('-g', '--project-path', + help='The project path to generate an android project', required=True) + parser.add_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME, + help=f'The android SDK platform number version to use for the APK. (Minimum {MIN_ANDROID_SDK_PLATFORM})', + type=int, + default=-1) + + parser.add_argument(ANDROID_NATIVE_API_LEVEL, + help=f'The android native API level to use for the APK. If not set, this will default to the android SDK platform. (Minimum {MIN_ANDROID_SDK_PLATFORM})', + type=int, + default=-1) + + # Override arguments parser.add_argument(ANDROID_SDK_PREFERRED_TOOL_VER, - help='The preferred android sdk build version (i.e. 28.0.3). Will default to the first one detected under the android sdk', - default=None, + help='The android SDK build tools version.', required=False) parser.add_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME, help='The android NDK version', - required=True) + required=False) parser.add_argument(GRADLE_ARGUMENT_NAME, help=f'The path to installed gradle. The version of gradle must fall in between {str(GRADLE_MIN_VERSION)} and {str(GRADLE_MAX_VERSION)}.', default=None, required=False) + parser.add_argument(ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME, + help=f'The version of the android gradle plugin to use. Defaults to the minimum version ({ANDROID_GRADLE_MIN_PLUGIN_VERSION})', + default=str(ANDROID_GRADLE_MIN_PLUGIN_VERSION)) + parser.add_argument(CMAKE_ARGUMENT_NAME, help=f'The path to cmake build tool if not installed on the system path. The version of cmake must be at least version {str(CMAKE_MIN_VERSION)}.', default=None, @@ -196,9 +215,6 @@ def main(args): default=None, required=False) - parser.add_argument('-g', '--project-path', - help='The project path to generate an android project') - # Asset Options parser.add_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME, action='store_true', @@ -207,11 +223,11 @@ def main(args): parser.add_argument(ASSET_MODE_ARGUMENT_NAME, choices=ALL_ASSET_MODES, default=ASSET_MODE_LOOSE, - help='Asset Mode (vfs|pak|loose) to use when including assets into the APK') + help=f'Asset Mode (vfs|pak|loose) to use when including assets into the APK. (Defaults to {ASSET_MODE_LOOSE})') parser.add_argument(ASSET_TYPE_ARGUMENT_NAME, default=DEFAULT_ASSET_TYPE, - help='Asset Type to use when including assets into the APK') + help=f'Asset Type to use when including assets into the APK. (Defaults to {DEFAULT_ASSET_TYPE})') parser.add_argument('--debug', action='store_true', @@ -260,16 +276,81 @@ def main(args): ninja_version, override_ninja_path = verify_ninja(override_ninja_path=parsed_args.get_argument(NINJA_ARGUMENT_NAME)) logging.info("Detected Ninja version %s", str(ninja_version)) - # Verify the android sdk path and sdk version - verified_android_sdk_platform, verified_android_sdk_path, android_sdk_build_tool_ver = android_support.verify_android_sdk(android_sdk_platform=parsed_args.get_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME), - argument_name=ANDROID_SDK_ARGUMENT_NAME, - override_android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME), - preferred_sdk_build_tools_ver=parsed_args.get_argument(ANDROID_SDK_PREFERRED_TOOL_VER)) + # Get the android sdk platform version to use from the arguments, but also handle the deprecated argument name + android_sdk_platform_version = parsed_args.get_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME) - # Verify the android ndk path and ndk version - verified_android_ndk_platform, verified_android_ndk_path = android_support.verify_android_ndk(android_ndk_platform=parsed_args.get_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME), - argument_name=ANDROID_NDK_ARGUMENT_NAME, - override_android_ndk_path=parsed_args.get_argument(ANDROID_NDK_ARGUMENT_NAME)) + # Get the gradle plugin details and validate against the current environment + android_gradle_plugin_version = parsed_args.get_argument(ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME) + android_gradle_plugin = android_support.AndroidGradlePluginInfo(android_gradle_plugin_version) + logging.info(f"Generating Android Gradle Plugin version {android_gradle_plugin_version} based project") + + if gradle_version < android_gradle_plugin.min_gradle_version: + raise common.LmbrCmdError(f"The current version of gradle ({gradle_version}) does not satisfy the minimum version " + f"({android_gradle_plugin.min_gradle_version}) needed for the android gradle plugin " + f"({android_gradle_plugin_version}). Please upgrade your gradle.") + if cmake_version < android_gradle_plugin.min_cmake_version: + raise common.LmbrCmdError(f"The current version of cmake ({cmake_version}) does not satisfy the minimum version " + f"({android_gradle_plugin.min_cmake_version}) needed for the android gradle plugin " + f"({android_gradle_plugin_version}). Please upgrade your cmake.") + if android_gradle_plugin.max_cmake_version and cmake_version > android_gradle_plugin.max_cmake_version: + raise common.LmbrCmdError(f"The current version of cmake ({cmake_version}) exceeds the maximum version " + f"({android_gradle_plugin.max_cmake_version}) of the android gradle plugin " + f"({android_gradle_plugin_version}).") + + # Use the SDK Resolver to make sure the build tools and ndk + android_sdk = android_support.AndroidSDKResolver(android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME)) + + # If no SDK platform is provided, check for any installed one + if android_sdk_platform_version < 0: + android_sdk_platform_version = MIN_ANDROID_SDK_PLATFORM + installed_android_sdk_platforms = android_sdk.is_package_installed('platforms;*') + if installed_android_sdk_platforms: + # If there are installed platforms, check the most recent one + latest_platform_version = -1 + for installed_android_sdk_platform in installed_android_sdk_platforms: + platform_number_match = re.match(r'platforms;android-([0-9]*)', installed_android_sdk_platform.path) + if not platform_number_match: + continue + check_platform_version = int(platform_number_match.group(1)) + if check_platform_version > latest_platform_version: + latest_platform_version = check_platform_version + if latest_platform_version >= MIN_ANDROID_SDK_PLATFORM: + android_sdk_platform_version = latest_platform_version + else: + if android_sdk_platform_version < MIN_ANDROID_SDK_PLATFORM: + raise common.LmbrCmdError(f"Invalid argument for {ANDROID_SDK_PLATFORM_ARGUMENT_NAME} ({android_sdk_platform_version}). Must be greater than the minimum value supported {MIN_ANDROID_SDK_PLATFORM}.") + + # Get the android native api level from the arguments. Default to the sdk platform version if not provided + android_native_api_level = parsed_args.get_argument(ANDROID_NATIVE_API_LEVEL) + if android_native_api_level < 0: + android_native_api_level = android_sdk_platform_version + else: + if android_native_api_level < MIN_NATIVE_API_LEVEL: + raise common.LmbrCmdError(f"Invalid argument for {ANDROID_NATIVE_API_LEVEL} ({android_native_api_level}). Must be greater than the minimum value supported {MIN_NATIVE_API_LEVEL}.") + + # Check and make sure that the requested sdk platform exists, download if necessary + platform_package_name = f"platforms;android-{android_sdk_platform_version}" + android_sdk.install_package(package_install_path=platform_package_name, + package_description=f'Android SDK Platform {android_sdk_platform_version}') + + # Make sure we have the extra android packages "market_apk_expansion" and "market_licensing" which is needed by the APK + android_sdk.install_package(package_install_path='extras;google;market_apk_expansion', + package_description='Google APK Expansion Library') + + android_sdk.install_package(package_install_path='extras;google;market_licensing', + package_description='Google Play Licensing Library') + + # Install either the requested SDK build tools or the default one for the android gradle plugin version + build_tools_version = parsed_args.get_argument(ANDROID_SDK_PREFERRED_TOOL_VER) or android_gradle_plugin.default_sdk_build_tools_version + build_tools_package_name = f'build-tools;{build_tools_version}' + build_tools_package = android_sdk.install_package(package_install_path=build_tools_package_name, + package_description='Android SDK Build Tools') + + # Install either the requested NDK version or the default one for the android gradle plugin version + android_ndk_version = parsed_args.get_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME) or android_gradle_plugin.default_ndk_version + android_ndk_package_name = f'ndk;{android_ndk_version}' + android_ndk_package = android_sdk.install_package(package_install_path=android_ndk_package_name, + package_description='Android NDK') # Verify the engine root path and project path verified_project_path, verified_engine_root = common.verify_project_and_engine_root(project_root=parsed_args.project_path, @@ -277,10 +358,9 @@ def main(args): is_test_project = parsed_args.unit_test # Verify the 3rd Party Root Path - third_party_path = pathlib.Path(parsed_args.third_party_path) / '3rdParty.txt' - if not third_party_path.is_file(): - raise common.LmbrCmdError("Invalid --third-party-path '{}'. Make sure it exists and contains " - "3rdParty.txt".format(parsed_args.third_party_path), + third_party_path = pathlib.Path(parsed_args.third_party_path) + if not third_party_path.is_dir(): + raise common.LmbrCmdError(f"Invalid --third-party-path '{parsed_args.third_party_path}'.", common.ERROR_CODE_INVALID_PARAMETER) third_party_path = third_party_path.parent @@ -293,23 +373,23 @@ def main(args): logging.debug("Engine Root : %s", str(verified_engine_root.resolve())) logging.debug("Build Path : %s", str(build_dir.resolve())) - logging.debug("Android NDK Path : %s", str(verified_android_ndk_path.resolve())) - logging.debug("Android SDK Path : %s", str(verified_android_sdk_path.resolve())) # Prepare the generator and execute generator = android_support.AndroidProjectGenerator(engine_root=verified_engine_root, - project_path=verified_project_path, build_dir=build_dir, - android_sdk_path=verified_android_sdk_path, - android_ndk_path=verified_android_ndk_path, - android_sdk_version=verified_android_sdk_platform, - android_ndk_platform=verified_android_ndk_platform, + android_sdk_path=android_sdk.android_sdk_path, + build_tool=build_tools_package, + android_sdk_platform=android_sdk_platform_version, + android_native_api_level=android_native_api_level, + android_ndk=android_ndk_package, + project_path=verified_project_path, third_party_path=third_party_path, cmake_version=cmake_version, override_cmake_path=override_cmake_path, override_gradle_path=override_gradle_path, + gradle_version=gradle_version, + gradle_plugin_version=android_gradle_plugin_version, override_ninja_path=override_ninja_path, - android_sdk_build_tool_version=android_sdk_build_tool_ver, include_assets_in_apk=parsed_args.get_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME), asset_mode=parsed_args.get_argument(ASSET_MODE_ARGUMENT_NAME), asset_type=parsed_args.get_argument(ASSET_TYPE_ARGUMENT_NAME), diff --git a/cmake/Tools/Platform/Android/unit_test_generate_android_project.py b/cmake/Tools/Platform/Android/unit_test_generate_android_project.py index 5598942046..0cd0f16eaf 100755 --- a/cmake/Tools/Platform/Android/unit_test_generate_android_project.py +++ b/cmake/Tools/Platform/Android/unit_test_generate_android_project.py @@ -170,117 +170,3 @@ def test_verify_ninja(tmpdir, from_override, version_str, expected_result): finally: subprocess.check_output = orig_check_output - -TEST_VALIDATE_VERSION_MIN = 19 -TEST_VALIDATE_VERSION_MAX = 21 - - -@pytest.mark.parametrize( - "test_input, expected", [ - pytest.param('20', 20), - pytest.param('android-20', 20), - pytest.param('bad-21', "android-'XX'"), - pytest.param('10', "minimum"), - pytest.param('30', "maximum") - ] -) -def test_validate_android_platform_input(test_input, expected): - try: - result = android_support.validate_android_platform_input(input_android_platform=test_input, - platform_variable_type='test', - min_version=TEST_VALIDATE_VERSION_MIN, - max_version=TEST_VALIDATE_VERSION_MAX) - assert isinstance(expected, int) - assert result == expected - except Exception as e: - assert expected in str(e) - - -def test_verify_android_sdk_success(tmpdir): - - test_android_path = 'android_sdk' - sdk_version_number = 28 - sdk_version = f'android-{sdk_version_number}' - - tmpdir.ensure(f'{test_android_path}/platforms/{sdk_version}/package.xml') - - tmpdir.ensure(f'{test_android_path}/build-tools/28.0.3/package.xml') - tmpdir.ensure(f'{test_android_path}/build-tools/29.0.3/package.xml') - - input_sdk_path = tmpdir.join(test_android_path).realpath() - argument_name = '--android-sdk' - - requested_build_tool_version = '29.0.3' - - result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version, - argument_name=argument_name, - override_android_sdk_path=input_sdk_path, - preferred_sdk_build_tools_ver=requested_build_tool_version) - assert result_sdk_version == sdk_version_number - assert result_sdk_path == input_sdk_path - assert result_build_tool_version == requested_build_tool_version - - sdk_version_number_only = str(sdk_version_number) - result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version_number_only, - argument_name=argument_name, - override_android_sdk_path=input_sdk_path) - assert result_sdk_version == sdk_version_number - assert result_sdk_path == input_sdk_path - assert result_build_tool_version == '28.0.3' - - requested_build_tool_version = '30.0.3' - result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version, - argument_name=argument_name, - override_android_sdk_path=input_sdk_path, - preferred_sdk_build_tools_ver=requested_build_tool_version) - assert result_sdk_version == sdk_version_number - assert result_sdk_path == input_sdk_path - assert result_build_tool_version == '28.0.3' - - -@pytest.mark.parametrize( - "desired_ndk_version_number, available_ndk_revisions, pkg_revision, mappings, expect_error", [ - pytest.param(21, [21, 22, 24], '15.2.4203891', None, False, id='preNdk19ExactMatch'), - pytest.param(23, [21, 22, 24], '15.2.4203891', None, False, id='preNdk19FallbackMatch'), - pytest.param(22, [21, 22, 24], '19.2.4203891', {'23': 21}, False, id='postNdk19ExactMatch'), - pytest.param(23, [21, 22, 24], '21.2.4203891', {'23': 21}, False, id='postNdk19MappingMatch'), - pytest.param(android_support.ANDROID_NDK_MIN_PLATFORM-1, [21, 22, 24], '15.2.4203891', None, True, id='preNdk19BelowMinVer'), - pytest.param(android_support.ANDROID_NDK_MAX_PLATFORM+1, [21, 22, 24], '15.2.4203891', None, True, id='preNdk19AboveMaxVer'), - pytest.param(25, [21, 22, 24], '19.2.4203891', {'23': 21}, True, id='postNdk19NoMatch') - ] -) -def test_verify_android_ndk_success(tmpdir, desired_ndk_version_number, available_ndk_revisions, pkg_revision, mappings, expect_error): - - test_android_path = 'android_ndk' - for ndk_number in available_ndk_revisions: - tmpdir.ensure(f'{test_android_path}/platforms/android-{ndk_number}/arch-arm64/usr/lib/libc.so') - - tmpdir.ensure(f'{test_android_path}/source.properties') - test_ndk_source_properties_file = tmpdir / test_android_path / 'source.properties' - test_ndk_source_properties_file.write_text(f'Pkg.Desc = Android NDK\nPkg.Revision = {pkg_revision}\n', encoding='ASCII') - - if mappings: - platform_mapping = { - # min and max are arbitrary for now since we dont use it during evaluation, but if we do, parameterize it here as well - "min": 16, # - "max": 29, - "aliases": {} - } - for key, value in mappings.items(): - platform_mapping['aliases'][key] = value - tmpdir.ensure(f'{test_android_path}/meta/platforms.json') - platform_mapping_file = tmpdir / test_android_path / 'meta/platforms.json' - platform_mapping_file.write_text(json.dumps(platform_mapping), encoding='ASCII') - - input_ndk_path = tmpdir.join(test_android_path).realpath() - - try: - android_ndk_platform_number, android_ndk_path = android_support.verify_android_ndk(android_ndk_platform=str(desired_ndk_version_number), - argument_name="--android-ndk", - override_android_ndk_path=input_ndk_path) - assert not expect_error - assert android_ndk_platform_number == desired_ndk_version_number - assert android_ndk_path == input_ndk_path - except Exception: - assert expect_error - diff --git a/cmake/Tools/common.py b/cmake/Tools/common.py index c6a3e89e67..9c0d31cd53 100755 --- a/cmake/Tools/common.py +++ b/cmake/Tools/common.py @@ -55,6 +55,7 @@ ENGINE_ROOT_CHECK_FILE = 'engine.json' HASH_CHUNK_SIZE = 200000 + class LmbrCmdError(Exception): """ Wrapper class to the general exception class where will absorb and prevent the printing of stack. @@ -244,6 +245,19 @@ def load_template_file(template_file_path, template_env): raise FileNotFoundError(f"Invalid file path. Cannot find template file located at {str(template_file_path)}") +# Determine the possible file extensions for executable files based on the host platform +PLATFORM_EXECUTABLE_EXTENSIONS = [''] # Files without extensions are always considered + +if platform.system() == 'Windows': + # Windows manages its executable extensions through the %PATHEXT% environment variable + path_extensions_str = os.environ.get('PATHEXT', default='.EXE;.COM;.BAT;.CMD') + PLATFORM_EXECUTABLE_EXTENSIONS.extend([pathext.lower() for pathext in path_extensions_str.split(';')]) +elif platform.system() == 'Linux': + PLATFORM_EXECUTABLE_EXTENSIONS = ['', '.out'] +else: + PLATFORM_EXECUTABLE_EXTENSIONS = [''] + + def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, tool_version_argument, tool_version_regex, min_version, max_version): """ Support method to validate a required system tool needed for the build either through an installed tool in the @@ -270,12 +284,21 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too elif not isinstance(override_tool_path, pathlib.Path): raise LmbrCmdError(f"Invalid {tool_name} path argument. '{override_tool_path}' must be a string or Path", ERROR_CODE_INVALID_PARAMETER) - check_tool_path = override_tool_path / tool_filename - if not check_tool_path.is_file(): - check_tool_path = pathlib.Path(override_tool_path) / 'bin' / tool_filename + file_found = False + for executable_path_ext in PLATFORM_EXECUTABLE_EXTENSIONS: + check_tool_filename = f'{tool_filename}{executable_path_ext}' - if not check_tool_path.is_file(): + check_tool_path = override_tool_path / check_tool_filename + if check_tool_path.is_file(): + file_found = True + break + check_tool_path = override_tool_path / 'bin' / check_tool_filename + if check_tool_path.is_file(): + file_found = True + break + + if not file_found: raise LmbrCmdError(f"Invalid {tool_name} path argument. '{override_tool_path}' is not a valid {tool_name} path", ERROR_CODE_INVALID_PARAMETER) resolved_override_tool_path = str(check_tool_path.resolve()) @@ -284,7 +307,7 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too else: resolved_override_tool_path = None tool_source = tool_name - tool_desc = "installed gradle in the system path" + tool_desc = f"installed {tool_name} in the system path" # Extract the version and verify version_output = subprocess.check_output([tool_source, tool_version_argument], @@ -296,10 +319,10 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too result_version = LooseVersion(str(version_match.group(1)).strip()) if min_version and result_version < min_version: - raise LmbrCmdError(f"The {tool_desc} does not meet the minimum version of gradle required ({str(min_version)}).", + raise LmbrCmdError(f"The {tool_desc} does not meet the minimum version of {tool_name} required ({str(min_version)}).", ERROR_CODE_ENVIRONMENT_ERROR) elif max_version and result_version > max_version: - raise LmbrCmdError(f"The {tool_desc} exceeds maximum version of gradle supported ({str(max_version)}).", + raise LmbrCmdError(f"The {tool_desc} exceeds maximum version of {tool_name} supported ({str(max_version)}).", ERROR_CODE_ENVIRONMENT_ERROR) return result_version, resolved_override_tool_path diff --git a/scripts/build/Platform/Android/build_config.json b/scripts/build/Platform/Android/build_config.json index adaa417380..b871670cd0 100644 --- a/scripts/build/Platform/Android/build_config.json +++ b/scripts/build/Platform/Android/build_config.json @@ -141,10 +141,8 @@ "COMMAND":"gradle_windows.cmd", "PARAMETERS": { "CONFIGURATION":"profile", - "OUTPUT_DIRECTORY":"build\\android_gradle", + "OUTPUT_DIRECTORY":"build\\ad_grd", "GAME_PROJECT": "AutomatedTesting", - "ANDROID_NDK_PLATFORM": "21", - "ANDROID_SDK_PLATFORM": "29", "SIGN_APK": "false", "GRADLE_BUILD_CMD": "build", "ADDITIONAL_GENERATE_ARGS": "" @@ -158,8 +156,6 @@ "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android_unittest", "GAME_PROJECT": "AutomatedTesting", - "ANDROID_NDK_PLATFORM": "21", - "ANDROID_SDK_PLATFORM": "29", "SIGN_APK": "true", "GRADLE_BUILD_CMD": "assemble", "ADDITIONAL_GENERATE_ARGS": "--unit-test" diff --git a/scripts/build/Platform/Android/gradle_windows.cmd b/scripts/build/Platform/Android/gradle_windows.cmd index 56423af95a..dd5285bdbf 100644 --- a/scripts/build/Platform/Android/gradle_windows.cmd +++ b/scripts/build/Platform/Android/gradle_windows.cmd @@ -17,20 +17,12 @@ IF NOT EXIST "%LY_3RDPARTY_PATH%" ( GOTO :error ) -IF NOT EXIST "%GRADLE_HOME%" ( +IF NOT EXIST "%GRADLE_BUILD_HOME%" ( REM This is the default for developers - SET GRADLE_HOME=C:\Gradle\gradle-5.6.4 + SET GRADLE_BUILD_HOME=C:\Gradle\gradle-7.0 ) -IF NOT EXIST "%GRADLE_HOME%" ( - ECHO [ci_build] FAIL: GRADLE_HOME=%GRADLE_HOME% - GOTO :error -) - -IF NOT EXIST "%CMAKE_HOME%" ( - SET CMAKE_HOME=%LY_3RDPARTY_PATH%/CMake/3.19.1/Windows/ -) -IF NOT EXIST "%CMAKE_HOME%" ( - ECHO [ci_build] FAIL: CMAKE_HOME=%CMAKE_HOME% +IF NOT EXIST "%GRADLE_BUILD_HOME%" ( + ECHO [ci_build] FAIL: GRADLE_BUILD_HOME=%GRADLE_BUILD_HOME% GOTO :error ) @@ -50,20 +42,9 @@ ECHO Ninja wasnt in the call path, add the value set by LY_NINJA_PATH SET PATH=%PATH%;%LY_NINJA_PATH% :ninja_on_path -IF NOT EXIST "%LY_ANDROID_SDK%" ( - SET LY_ANDROID_SDK=!LY_3RDPARTY_PATH!/android-sdk/platform-29 -) -IF NOT EXIST "%LY_ANDROID_SDK%" ( - ECHO [ci_build] FAIL: LY_ANDROID_SDK=!LY_ANDROID_SDK! - GOTO :error -) -IF NOT EXIST "%LY_ANDROID_NDK%" ( - set LY_ANDROID_NDK=!LY_3RDPARTY_PATH!/android-ndk/r21d -) -IF NOT EXIST "%LY_ANDROID_NDK%" ( - ECHO [ci_build] LY_ANDROID_NDK=!LY_ANDROID_NDK! - GOTO :error +IF NOT "%ANDROID_GRADLE_PLUGIN%" == "" ( + set ANDROID_GRADLE_PLUGIN_OPTION=--gradle-plugin-version=%ANDROID_GRADLE_PLUGIN% ) IF NOT EXIST %OUTPUT_DIRECTORY% ( @@ -154,11 +135,11 @@ IF "%GENERATE_SIGNED_APK%"=="true" ( ECHO Using keystore file at %CI_ANDROID_KEYSTORE_FILE_ABS% ) - ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %OPTIONAL_TEST_FLAG% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing - CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing ) ELSE ( - ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing - CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% %GRADLE_OVERRIDE_OPTION% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing ) REM Validate the android project generation diff --git a/scripts/build/Platform/Android/pipeline.json b/scripts/build/Platform/Android/pipeline.json index ed10e7022d..551374a027 100644 --- a/scripts/build/Platform/Android/pipeline.json +++ b/scripts/build/Platform/Android/pipeline.json @@ -1,7 +1,7 @@ { "ENV": { - "GRADLE_HOME": "C:/Gradle/gradle-5.6.4", - "NODE_LABEL": "windows-047e5cdf", + "GRADLE_HOME": "C:/Gradle/gradle-7.0", + "NODE_LABEL": "windows-b3c8994f1", "LY_3RDPARTY_PATH": "C:/ly/3rdParty", "TIMEOUT": 30, "WORKSPACE": "D:/workspace", From 370f28f69cc06a471534c69f691e3b8e906669c1 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Tue, 1 Jun 2021 01:28:13 -0700 Subject: [PATCH 18/23] Missing PHYSX_ENABLE_MULTI_THREADING for PhysX.Editor --- Gems/PhysX/Code/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gems/PhysX/Code/CMakeLists.txt b/Gems/PhysX/Code/CMakeLists.txt index b0318af9f2..d281270ce4 100644 --- a/Gems/PhysX/Code/CMakeLists.txt +++ b/Gems/PhysX/Code/CMakeLists.txt @@ -128,6 +128,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AUTOMOC FILES_CMAKE physx_editor_shared_files.cmake + COMPILE_DEFINITIONS + PUBLIC + PHYSX_ENABLE_MULTI_THREADING INCLUDE_DIRECTORIES PRIVATE . From 3947dcf213e0b5326d7d33b25868e2363497869a Mon Sep 17 00:00:00 2001 From: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> Date: Tue, 1 Jun 2021 09:36:00 +0100 Subject: [PATCH 19/23] Add some extra cvars to control orbit point appearance and remove unused ones (#1032) --- .../AzFramework/Viewport/CameraInput.cpp | 3 +-- .../ModularViewportCameraController.cpp | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index d5f02c957c..559f7ce460 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -29,7 +29,7 @@ namespace AzFramework AZ_CVAR(float, ed_cameraSystemOrbitDollyScrollSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemOrbitDollyCursorSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemScrollTranslateSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 6.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemLookSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemTranslateSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); @@ -37,7 +37,6 @@ namespace AzFramework AZ_CVAR(float, ed_cameraSystemPanSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(bool, ed_cameraSystemPanInvertX, true, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(bool, ed_cameraSystemPanInvertY, true, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemLookDeadzone, 2.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR( AZ::CVarFixedString, ed_cameraSystemTranslateForwardKey, "keyboard_key_alphanumeric_W", nullptr, AZ::ConsoleFunctorFlags::Null, ""); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 6fb3edfa22..896d9f8043 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,11 @@ namespace AtomToolsFramework { + AZ_CVAR( + AZ::Color, ed_cameraSystemOrbitPointColor, AZ::Color::CreateFromRgba(255, 255, 255, 255), nullptr, AZ::ConsoleFunctorFlags::Null, + ""); + AZ_CVAR(float, ed_cameraSystemOrbitPointSize, 0.5f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + // debug void DrawPreviewAxis(AzFramework::DebugDisplayRequests& display, const AZ::Transform& transform, const float axisLength) { @@ -73,7 +79,8 @@ namespace AtomToolsFramework if (auto viewportContext = RetrieveViewportContext(GetViewportId())) { - auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&) { + auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&) + { if (!m_updatingTransform) { UpdateCameraFromTransform(m_targetCamera, viewportContext->GetCameraTransform()); @@ -137,7 +144,10 @@ namespace AtomToolsFramework } else if (m_cameraMode == CameraMode::Animation) { - const auto smootherStepFn = [](const float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; + const auto smootherStepFn = [](const float t) + { + return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); + }; const float transitionT = smootherStepFn(m_animationT); const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation( @@ -169,8 +179,9 @@ namespace AtomToolsFramework { if (const float alpha = AZStd::min(-m_camera.m_lookDist / 5.0f, 1.0f); alpha > AZ::Constants::FloatEpsilon) { - debugDisplay.SetColor(1.0f, 1.0f, 1.0f, alpha); - debugDisplay.DrawWireSphere(m_camera.m_lookAt, 0.5f); + const AZ::Color orbitPointColor = ed_cameraSystemOrbitPointColor; + debugDisplay.SetColor(orbitPointColor.GetR(), orbitPointColor.GetG(), orbitPointColor.GetB(), alpha); + debugDisplay.DrawWireSphere(m_camera.m_lookAt, ed_cameraSystemOrbitPointSize); } } From 99ba89a02b82a408089ea55983bab77b78d6ec31 Mon Sep 17 00:00:00 2001 From: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> Date: Tue, 1 Jun 2021 09:37:02 +0100 Subject: [PATCH 20/23] Add console function to print entity name from entity id (#1021) * Add console function to print entity name from entity id * update name of console function an improve description --- .../AzCore/Component/ComponentApplication.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index 1010ae3473..7b33359023 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -77,6 +77,27 @@ #endif // defined(AZ_ENABLE_DEBUG_TOOLS) #include +#include + +static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments) +{ + if (arguments.empty()) + { + return; + } + + const auto entityIdStr = AZStd::string(arguments.front()); + const auto entityIdValue = AZStd::stoull(entityIdStr); + + AZStd::string entityName; + AZ::ComponentApplicationBus::BroadcastResult( + entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, AZ::EntityId(entityIdValue)); + + AZ_Printf("Entity Debug", "EntityId: %" PRIu64 ", Entity Name: %s", entityIdValue, entityName.c_str()); +} + +AZ_CONSOLEFREEFUNC( + PrintEntityName, AZ::ConsoleFunctorFlags::Null, "Parameter: EntityId value, Prints the name of the entity to the console"); namespace AZ { From c03669df72886ce3c51ecb9d0526976a16fbcc81 Mon Sep 17 00:00:00 2001 From: Aaron Ruiz Mora Date: Tue, 1 Jun 2021 15:16:50 +0100 Subject: [PATCH 21/23] Updating default physics material library with the latest materials (#1056) --- .../surfacetypemateriallibrary.physmaterial | 153 ++++++++++++++++-- 1 file changed, 144 insertions(+), 9 deletions(-) diff --git a/AutomatedTesting/surfacetypemateriallibrary.physmaterial b/AutomatedTesting/surfacetypemateriallibrary.physmaterial index 434d673998..481cd2fbfa 100644 --- a/AutomatedTesting/surfacetypemateriallibrary.physmaterial +++ b/AutomatedTesting/surfacetypemateriallibrary.physmaterial @@ -4,17 +4,152 @@ - - - - - - - - + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7d1fedc10c442269811ce4531bf9434e653acb2c Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Tue, 1 Jun 2021 08:29:35 -0700 Subject: [PATCH 22/23] LYN-4128 EditorPythonBindings.Editor in debug does not load (missing python_d.dll) --- Gems/PythonAssetBuilder/Code/CMakeLists.txt | 26 ++++++------------- .../Common/RuntimeDependencies_common.cmake | 2 +- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Gems/PythonAssetBuilder/Code/CMakeLists.txt b/Gems/PythonAssetBuilder/Code/CMakeLists.txt index 4af266f56d..73fa90ab67 100644 --- a/Gems/PythonAssetBuilder/Code/CMakeLists.txt +++ b/Gems/PythonAssetBuilder/Code/CMakeLists.txt @@ -13,24 +13,11 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) return() endif() -set(static_files pythonassetbuilder_common_files.cmake) -set(editor_files pythonassetbuilder_editor_files.cmake) -set(shared_files pythonassetbuilder_shared_files.cmake) -set(static_dependencies - 3rdParty::Python - Gem::EditorPythonBindings.Static - AZ::AssetBuilderSDK -) -set(editor_dependencies - Gem::EditorPythonBindings.Static - AZ::AssetBuilderSDK -) - ly_add_target( NAME PythonAssetBuilder.Static STATIC NAMESPACE Gem FILES_CMAKE - ${static_files} + pythonassetbuilder_common_files.cmake PLATFORM_INCLUDE_FILES Source/Platform/Common/${PAL_TRAIT_COMPILER_ID}/pythonassetbuilder_static_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake INCLUDE_DIRECTORIES @@ -43,7 +30,9 @@ ly_add_target( PRIVATE AZ::AzCore PUBLIC - ${static_dependencies} + 3rdParty::Python + Gem::EditorPythonBindings.Static + AZ::AssetBuilderSDK AZ::AzToolsFramework ) @@ -52,8 +41,8 @@ ly_add_target( NAMESPACE Gem FILES_CMAKE - ${editor_files} - ${shared_files} + pythonassetbuilder_editor_files.cmake + pythonassetbuilder_shared_files.cmake PLATFORM_INCLUDE_FILES Source/Platform/Common/${PAL_TRAIT_COMPILER_ID}/pythonassetbuilder_static_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake INCLUDE_DIRECTORIES @@ -64,7 +53,8 @@ ly_add_target( Include BUILD_DEPENDENCIES PRIVATE - ${editor_dependencies} + Gem::EditorPythonBindings.Static + AZ::AssetBuilderSDK RUNTIME_DEPENDENCIES Gem::EditorPythonBindings.Editor ) diff --git a/cmake/Platform/Common/RuntimeDependencies_common.cmake b/cmake/Platform/Common/RuntimeDependencies_common.cmake index d9d0fe4c7f..4ae914744f 100644 --- a/cmake/Platform/Common/RuntimeDependencies_common.cmake +++ b/cmake/Platform/Common/RuntimeDependencies_common.cmake @@ -105,7 +105,7 @@ function(ly_get_runtime_dependencies ly_RUNTIME_DEPENDENCIES ly_TARGET) set(skip_imported TRUE) endif() endif() - if(target_type MATCHES "(INTERFACE_LIBRARY|STATIC_LIBRARY)") + if(target_type MATCHES "(STATIC_LIBRARY)") # No need to copy these dependencies since the outputs are not used at runtime set(skip_imported TRUE) endif() From 895bbafa9e61cb5366e8a36a74b5b0664b73fcf3 Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Tue, 1 Jun 2021 10:50:10 -0500 Subject: [PATCH 23/23] Fixed CreatePrefab to use correct absolute path (#1044) The initial CreatePrefab flow was trying to go from absolute -> relative -> absolute path before the file had ever been saved, so the relative -> absolute path conversion generated an error and always produced a project-relative path, even if the initial path was in a gem. For example, trying to save "c:/o3de/Gems/Camera/Assets/Entity1.prefab" would instead create "c:/o3de/AutomatedTesting/Entity1.prefab". This change preserves the absolute path throughout the initial creation flow so that the file is saved in the correct location. --- .../AzToolsFramework/Prefab/PrefabLoader.cpp | 39 +++++++++++++++++++ .../AzToolsFramework/Prefab/PrefabLoader.h | 10 +++++ .../Prefab/PrefabLoaderInterface.h | 10 +++++ .../Prefab/PrefabPublicHandler.cpp | 9 +++-- .../Prefab/PrefabPublicHandler.h | 2 +- .../Prefab/PrefabPublicInterface.h | 4 +- .../UI/Prefab/PrefabIntegrationManager.cpp | 3 +- 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp index e4507227b5..d7de634c11 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp @@ -303,6 +303,45 @@ namespace AzToolsFramework return true; } + bool PrefabLoader::SaveTemplateToFile(TemplateId templateId, AZ::IO::PathView absolutePath) + { + AZ_Assert(absolutePath.IsAbsolute(), "SaveTemplateToFile requires an absolute path for saving the initial prefab file."); + + const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId); + if (!domAndFilepath) + { + return false; + } + + // Verify that the absolute path provided to this matches the relative path saved in the template. + // Otherwise, the saved prefab won't be able to be loaded. + auto relativePath = GenerateRelativePath(absolutePath); + if (relativePath != domAndFilepath->second) + { + AZ_Error( + "Prefab", false, + "PrefabLoader::SaveTemplateToFile - " + "Failed to save template '%s' to location '%.*s'." + "Error: Relative path '%.*s' for location didn't match template name.", + domAndFilepath->second.c_str(), AZ_STRING_ARG(absolutePath.Native()), AZ_STRING_ARG(relativePath.Native())); + return false; + } + + auto outcome = AzFramework::FileFunc::WriteJsonFile(domAndFilepath->first, absolutePath); + if (!outcome.IsSuccess()) + { + AZ_Error( + "Prefab", false, + "PrefabLoader::SaveTemplateToFile - " + "Failed to save template '%s' to location '%.*s'." + "Error: %s", + domAndFilepath->second.c_str(), AZ_STRING_ARG(absolutePath.Native()), outcome.GetError().c_str()); + return false; + } + m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, false); + return true; + } + bool PrefabLoader::SaveTemplateToString(TemplateId templateId, AZStd::string& output) { const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h index aed24e153e..3722e14a97 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h @@ -72,6 +72,16 @@ namespace AzToolsFramework */ bool SaveTemplate(TemplateId templateId) override; + /** + * Saves a Prefab Template to the provided absolute source path, which needs to match the relative path in the template. + * Converts Prefab Template form into .prefab form by collapsing nested Template info + * into a source path and patches. + * @param templateId Id of the template to be saved + * @param absolutePath Absolute path to save the file to + * @return bool on whether the operation succeeded or not + */ + bool SaveTemplateToFile(TemplateId templateId, AZ::IO::PathView absolutePath) override; + /** * Saves a Prefab Template into the provided output string. * Converts Prefab Template form into .prefab form by collapsing nested Template info diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h index d71fbff80f..0e551cee6b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h @@ -60,6 +60,16 @@ namespace AzToolsFramework */ virtual bool SaveTemplate(TemplateId templateId) = 0; + /** + * Saves a Prefab Template to the provided absolute source path, which needs to match the relative path in the template. + * Converts Prefab Template form into .prefab form by collapsing nested Template info + * into a source path and patches. + * @param templateId Id of the template to be saved + * @param absolutePath Absolute path to save the file to + * @return bool on whether the operation succeeded or not + */ + virtual bool SaveTemplateToFile(TemplateId templateId, AZ::IO::PathView absolutePath) = 0; + /** * Saves a Prefab Template into the provided output string. * Converts Prefab Template form into .prefab form by collapsing nested Template info diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 9dd5199ea2..fadcc1b81f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -64,7 +64,7 @@ namespace AzToolsFramework m_prefabUndoCache.Destroy(); } - PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView filePath) + PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView absolutePath) { EntityList inputEntityList, topLevelEntities; AZ::EntityId commonRootEntityId; @@ -76,6 +76,8 @@ namespace AzToolsFramework return findCommonRootOutcome; } + AZ_Assert(absolutePath.IsAbsolute(), "CreatePrefab requires an absolute path for saving the initial prefab file."); + InstanceOptionalReference instanceToCreate; { // Initialize Undo Batch object @@ -144,7 +146,8 @@ namespace AzToolsFramework // Create the Prefab instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab( - entities, AZStd::move(instancePtrs), filePath, commonRootEntityOwningInstance); + entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(absolutePath), + commonRootEntityOwningInstance); if (!instanceToCreate) { @@ -254,7 +257,7 @@ namespace AzToolsFramework } // Save Template to file - m_prefabLoaderInterface->SaveTemplate(instanceToCreate->get().GetTemplateId()); + m_prefabLoaderInterface->SaveTemplateToFile(instanceToCreate->get().GetTemplateId(), absolutePath); return AZ::Success(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 7e2357dd44..e68a3e0b1e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -46,7 +46,7 @@ namespace AzToolsFramework void UnregisterPrefabPublicHandlerInterface(); // PrefabPublicInterface... - PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView filePath) override; + PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView absolutePath) override; PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override; PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override; PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 0750c4d264..2e9152fd1b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -46,10 +46,10 @@ namespace AzToolsFramework * Create a prefab out of the entities provided, at the path provided. * Automatically detects descendants of entities, and discerns between entities and child instances. * @param entityIds The entities that should form the new prefab (along with their descendants). - * @param filePath The path for the new prefab file. + * @param filePath The absolute path for the new prefab file. * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView filePath) = 0; + virtual PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView absolutePath) = 0; /** * Instantiate a prefab from a prefab file. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 61d4433c0e..021b97a7dd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -333,8 +333,7 @@ namespace AzToolsFramework } } - auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab( - selectedEntities, s_prefabLoaderInterface->GenerateRelativePath(prefabFilePath.data())); + auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, prefabFilePath.data()); if (!createPrefabOutcome.IsSuccess()) {