You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp

344 lines
14 KiB
C++

/*
* 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 <Decals/DecalFeatureProcessor.h>
#include <AzCore/Debug/EventTrace.h>
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI/Factory.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h>
#include <AzCore/Math/Quaternion.h>
#include <AtomCore/std/containers/array_view.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
namespace AZ
{
namespace Render
{
DecalFeatureProcessor::DecalFeatureProcessor()
: m_baseColorMapShaderName("m_decalBaseColorMaps")
, m_opacityMapShaderName("m_opacityMaps")
, m_baseColorMapPropertyName("baseColor.textureMap")
, m_opacityMapPropertyName("opacity.textureMap")
{
}
void DecalFeatureProcessor::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext
->Class<DecalFeatureProcessor, RPI::FeatureProcessor>()
->Version(0);
}
}
void DecalFeatureProcessor::Activate()
{
GpuBufferHandler::Descriptor desc;
desc.m_bufferName = "DecalBuffer";
desc.m_bufferSrgName = "m_decals";
desc.m_elementCountSrgName = "m_decalCount";
desc.m_elementSize = sizeof(DecalData);
desc.m_srgLayout = RPI::RPISystemInterface::Get()->GetViewSrgAsset()->GetLayout();
m_decalBufferHandler = GpuBufferHandler(desc);
CacheShaderIndices();
}
void DecalFeatureProcessor::Deactivate()
{
m_decalData.Clear();
m_decalBufferHandler.Release();
}
DecalFeatureProcessor::DecalHandle DecalFeatureProcessor::AcquireDecal()
{
uint16_t id = m_decalData.GetFreeSlotIndex();
if (id == DataVector::NoFreeSlot)
{
return DecalHandle(DecalHandle::NullIndex);
}
else
{
m_deviceBufferNeedsUpdate = true;
return DecalHandle(id);
}
}
bool DecalFeatureProcessor::ReleaseDecal(DecalHandle decal)
{
if (decal.IsValid())
{
m_decalData.RemoveIndex(decal.GetIndex());
m_deviceBufferNeedsUpdate = true;
return true;
}
return false;
}
DecalFeatureProcessor::DecalHandle DecalFeatureProcessor::CloneDecal(DecalHandle sourceDecal)
{
AZ_Assert(sourceDecal.IsValid(), "Invalid DecalHandle passed to DecalFeatureProcessor::CloneDecal().");
DecalHandle decal = AcquireDecal();
if (decal.IsValid())
{
m_decalData.GetData<0>(decal.GetIndex()) = m_decalData.GetData<0>(sourceDecal.GetIndex());
m_decalData.GetData<1>(decal.GetIndex()) = m_decalData.GetData<1>(sourceDecal.GetIndex());
m_deviceBufferNeedsUpdate = true;
}
return decal;
}
void DecalFeatureProcessor::Simulate(const RPI::FeatureProcessor::SimulatePacket& packet)
{
AZ_ATOM_PROFILE_FUNCTION("RPI", "DecalFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
{
[[maybe_unused]] bool success = m_decalBufferHandler.UpdateBuffer(m_decalData.GetDataVector<0>());
AZ_Error(FeatureProcessorName, success, "Unable to update buffer during Simulate().");
m_deviceBufferNeedsUpdate = false;
}
}
AZStd::array_view<Data::Instance<RPI::Image>> DecalFeatureProcessor::GetImageArray() const
{
// [GFX TODO][ATOM-4445] Replace this hardcoded constant with atlasing / bindless so we can have far more than 8 decal textures
// Note this constant also is defined in View.srg
const size_t MaxDecals = 8;
size_t numImages = AZStd::min(MaxDecals, m_decalData.GetDataCount());
AZStd::array_view<ImagePtr> imageArrayView(m_decalData.GetDataVector<1>().begin(), m_decalData.GetDataVector<1>().begin() + numImages);
return imageArrayView;
}
void DecalFeatureProcessor::Render(const RPI::FeatureProcessor::RenderPacket& packet)
{
AZ_ATOM_PROFILE_FUNCTION("RPI", "DecalFeatureProcessor: Render");
AZStd::array_view<Data::Instance<RPI::Image>> baseMaps = GetImagesFromDecalData<1>();
AZStd::array_view<Data::Instance<RPI::Image>> opacityMaps = GetImagesFromDecalData<2>();
for (const RPI::ViewPtr& view : packet.m_views)
{
m_decalBufferHandler.UpdateSrg(view->GetShaderResourceGroup().get());
if (baseMaps.empty() == false)
{
view->GetShaderResourceGroup()->SetImageArray(m_baseColorMapsIndex, baseMaps);
}
if (opacityMaps.empty() == false)
{
view->GetShaderResourceGroup()->SetImageArray(m_opacityMapsIndex, opacityMaps);
}
}
}
void DecalFeatureProcessor::SetDecalData(DecalHandle handle, const DecalData& data)
{
if (handle.IsValid())
{
m_decalData.GetData<0>(handle.GetIndex()) = data;
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetDecalData().");
}
}
const Data::Instance<RPI::Buffer> DecalFeatureProcessor::GetDecalBuffer()const
{
return m_decalBufferHandler.GetBuffer();
}
uint32_t DecalFeatureProcessor::GetDecalCount() const
{
return m_decalBufferHandler.GetElementCount();
}
void DecalFeatureProcessor::SetDecalPosition(DecalHandle handle, const AZ::Vector3& position)
{
if (handle.IsValid())
{
AZStd::array<float, 3>& writePos = m_decalData.GetData<0>(handle.GetIndex()).m_position;
position.StoreToFloat3(writePos.data());
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetDecalPosition().");
}
}
void DecalFeatureProcessor::SetDecalOrientation(DecalHandle handle, const AZ::Quaternion& orientation)
{
if (handle.IsValid())
{
orientation.StoreToFloat4(m_decalData.GetData<0>(handle.GetIndex()).m_quaternion.data());
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetDecalOrientation().");
}
}
void DecalFeatureProcessor::SetDecalHalfSize(DecalHandle handle, const Vector3& halfSize)
{
if (handle.IsValid())
{
halfSize.StoreToFloat3(m_decalData.GetData<0>(handle.GetIndex()).m_halfSize.data());
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetDecalHalfSize().");
}
}
void DecalFeatureProcessor::SetDecalAttenuationAngle(DecalHandle handle, float angleAttenuation)
{
if (handle.IsValid())
{
m_decalData.GetData<0>(handle.GetIndex()).m_angleAttenuation = angleAttenuation;
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", handle.IsValid(), "Invalid handle passed to DecalFeatureProcessor::SetDecalAttenuationAngle().");
}
}
void DecalFeatureProcessor::SetDecalOpacity(DecalHandle handle, float opacity)
{
if (handle.IsValid())
{
m_decalData.GetData<0>(handle.GetIndex()).m_opacity = opacity;
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetDecalOpacity().");
}
}
void DecalFeatureProcessor::SetDecalSortKey(DecalHandle handle, uint8_t sortKey)
{
if (handle.IsValid())
{
m_decalData.GetData<0>(handle.GetIndex()).m_sortKey = sortKey;
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetDecalSortKey().");
}
}
void DecalFeatureProcessor::SetDecalTransform(DecalHandle handle, const AZ::Transform& world)
{
SetDecalTransform(handle, world, AZ::Vector3::CreateOne());
}
void DecalFeatureProcessor::SetDecalTransform(DecalHandle handle, const AZ::Transform& world, const AZ::Vector3& nonUniformScale)
{
// ATOM-4330
// Original Open 3D Engine uploads a 4x4 matrix rather than quaternion, rotation, scale.
// That is more memory but less calculation because it is doing a matrix inverse rather than a polar decomposition
// I've done some experiments and uploading a 3x4 transform matrix with 3x3 matrix inverse should be possible
// I am putting it as part of a separate Jira because I would have to upload different data to the light culling system
// (not a bad thing, but it would make this commit quite a bit more complex)
if (handle.IsValid())
{
Quaternion orientation = world.GetRotation();
Vector3 scale = world.GetUniformScale() * nonUniformScale;
SetDecalHalfSize(handle, scale);
SetDecalPosition(handle, world.GetTranslation());
SetDecalOrientation(handle, orientation);
m_deviceBufferNeedsUpdate = true;
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetTransform().");
}
}
Data::Instance<RPI::Image> DecalFeatureProcessor::GetImageFromMaterial(const AZ::Name& materialPropertyName, Data::Instance<RPI::Material> materialInstance) const
{
Data::Instance<RPI::Image> image;
RPI::MaterialPropertyIndex index = materialInstance->FindPropertyIndex(materialPropertyName);
if (index.IsValid())
{
image = materialInstance->GetPropertyValue<Data::Instance<RPI::Image>>(index);
}
else
{
AZ_Warning("DecalFeatureProcessor", false, "Unable to find %s in material.", materialPropertyName.GetCStr());
}
return image;
}
void DecalFeatureProcessor::SetDecalMaterial(DecalHandle handle, const AZ::Data::AssetId materialAssetId)
{
if (handle.IsNull())
{
AZ_Warning("DecalFeatureProcessor", false, "Invalid handle passed to DecalFeatureProcessor::SetMaterial().");
return;
}
if (materialAssetId.IsValid())
{
const auto materialAsset = AZ::RPI::AssetUtils::LoadAssetById<AZ::RPI::MaterialAsset>(materialAssetId);
Data::Instance<RPI::Material> materialInstance = AZ::RPI::Material::FindOrCreate(materialAsset);
if (materialInstance)
{
const auto baseColorImage = GetImageFromMaterial(m_baseColorMapPropertyName, materialInstance);
const auto opacityImage = GetImageFromMaterial(m_opacityMapPropertyName, materialInstance);
m_decalData.GetData<1>(handle.GetIndex()) = baseColorImage;
m_decalData.GetData<2>(handle.GetIndex()) = opacityImage;
}
}
}
void DecalFeatureProcessor::CacheShaderIndices()
{
const RHI::ShaderResourceGroupLayout* viewSrgLayout = RPI::RPISystemInterface::Get()->GetViewSrgAsset()->GetLayout();
m_baseColorMapsIndex = viewSrgLayout->FindShaderInputImageIndex(m_baseColorMapShaderName);
AZ_Warning("DecalFeatureProcessor", m_baseColorMapsIndex.IsValid(), "Unable to find baseColorMaps in decal shader.");
m_opacityMapsIndex = viewSrgLayout->FindShaderInputImageIndex(m_opacityMapShaderName);
AZ_Warning("DecalFeatureProcessor", m_opacityMapsIndex.IsValid(), "Unable to find opacityMaps in decal shader.");
}
} // namespace Render
} // namespace AZ