2f1346c719
* Breaking up terrain FP wip - macro materials decoupled from meshes. Mesh creation and rendering pulled out from MeshFeatureProcessor into TerrainMeshManager. Stubs added for macro and detail material managers. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Separated macro material management from the terrain feature processor. Also separated bindless image array handling from terrain feature processor. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Detail materials separated from terrain feature processor. Also pulled out Aabb2i and Vector2i into their own simple classes Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Changed some classes so that when the SRG changes the classes don't need to be completely reinitialized and can instead just update their indices and push data to the new srg. Fixed an issue where MacroMaterialData wasn't being exposed to ScriptCanvas. Terrain shader reloads should now work correctly. Also added some debug logging to help catch an issue where sometimes detail materials don't seem to load. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Terrain PR reveiw updates Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Some small PR review fixes. More comments in TerrainDetailMaterialManager.h Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing unused variable causing clang failure Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing unused variable in release. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing a forward declare that oddly didn't work on linux. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing linux missing include... not sure why only linux failed on this. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Adding missing include Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
413 lines
18 KiB
C++
413 lines
18 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <TerrainRenderer/TerrainMacroMaterialManager.h>
|
|
|
|
namespace Terrain
|
|
{
|
|
namespace
|
|
{
|
|
[[maybe_unused]] static const char* TerrainMacroMaterialManagerName = "TerrainMacroMaterialManager";
|
|
}
|
|
|
|
namespace TerrainSrgInputs
|
|
{
|
|
static const char* const MacroMaterialData("m_macroMaterialData");
|
|
static const char* const MacroMaterialGrid("m_macroMaterialGrid");
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::Initialize(
|
|
const AZStd::shared_ptr<AZ::Render::BindlessImageArrayHandler>& bindlessImageHandler,
|
|
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
|
|
{
|
|
AZ_Error(TerrainMacroMaterialManagerName, bindlessImageHandler, "bindlessImageHandler must not be null.");
|
|
AZ_Error(TerrainMacroMaterialManagerName, terrainSrg, "terrainSrg must not be null.");
|
|
AZ_Error(TerrainMacroMaterialManagerName, !m_isInitialized, "Already initialized.");
|
|
|
|
if (!bindlessImageHandler || !terrainSrg || m_isInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UpdateSrgIndices(terrainSrg))
|
|
{
|
|
m_bindlessImageHandler = bindlessImageHandler;
|
|
|
|
OnTerrainDataChanged(AZ::Aabb::CreateNull(), TerrainDataChangedMask::Settings);
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
|
|
TerrainMacroMaterialNotificationBus::Handler::BusConnect();
|
|
|
|
m_terrainSizeChanged = true;
|
|
m_isInitialized = true;
|
|
}
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::Reset()
|
|
{
|
|
m_isInitialized = false;
|
|
|
|
m_macroMaterialDataBuffer = {};
|
|
|
|
m_macroMaterialShaderData.clear();
|
|
m_macroMaterialEntities.clear();
|
|
|
|
RemoveAllImages();
|
|
m_macroMaterials.clear();
|
|
|
|
m_bindlessImageHandler = {};
|
|
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
|
|
TerrainMacroMaterialNotificationBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
bool TerrainMacroMaterialManager::IsInitialized()
|
|
{
|
|
return m_isInitialized;
|
|
}
|
|
|
|
bool TerrainMacroMaterialManager::UpdateSrgIndices(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
|
|
{
|
|
const AZ::RHI::ShaderResourceGroupLayout* terrainSrgLayout = terrainSrg->GetLayout();
|
|
|
|
m_macroMaterialGridIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::MacroMaterialGrid));
|
|
AZ_Error(TerrainMacroMaterialManagerName, m_macroMaterialGridIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::MacroMaterialGrid);
|
|
|
|
AZ::Render::GpuBufferHandler::Descriptor desc;
|
|
|
|
// Set up the gpu buffer for macro material data
|
|
desc.m_bufferName = "Macro Material Data";
|
|
desc.m_bufferSrgName = TerrainSrgInputs::MacroMaterialData;
|
|
desc.m_elementSize = sizeof(MacroMaterialShaderData);
|
|
desc.m_srgLayout = terrainSrgLayout;
|
|
m_macroMaterialDataBuffer = AZ::Render::GpuBufferHandler(desc);
|
|
|
|
m_bufferNeedsUpdate = true;
|
|
|
|
return m_macroMaterialDataBuffer.IsValid() && m_macroMaterialGridIndex.IsValid();
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion [[maybe_unused]], TerrainDataChangedMask dataChangedMask)
|
|
{
|
|
if ((dataChangedMask & TerrainDataChangedMask::Settings) != 0)
|
|
{
|
|
AZ::Aabb worldBounds = AZ::Aabb::CreateNull();
|
|
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
|
worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
|
|
|
|
m_terrainSizeChanged = m_terrainSizeChanged || m_terrainBounds != worldBounds;
|
|
m_terrainBounds = worldBounds;
|
|
}
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::OnTerrainMacroMaterialCreated(AZ::EntityId entityId, const MacroMaterialData& newMaterialData)
|
|
{
|
|
AZ_Assert(!m_macroMaterials.contains(entityId),
|
|
"OnTerrainMacroMaterialCreated called for a macro material that already exists. This indicates that either the bus is incorrectly sending out "
|
|
"OnCreated announcements for existing materials, or the terrain feature processor isn't properly cleaning up macro materials.");
|
|
|
|
MacroMaterial& macroMaterial = m_macroMaterials[entityId];
|
|
macroMaterial.m_data = newMaterialData;
|
|
if (newMaterialData.m_colorImage)
|
|
{
|
|
macroMaterial.m_colorIndex = m_bindlessImageHandler->AppendBindlessImage(newMaterialData.m_colorImage->GetImageView());
|
|
}
|
|
if (newMaterialData.m_normalImage)
|
|
{
|
|
macroMaterial.m_normalIndex = m_bindlessImageHandler->AppendBindlessImage(newMaterialData.m_normalImage->GetImageView());
|
|
}
|
|
|
|
ForMacroMaterialsInBounds(newMaterialData.m_bounds,
|
|
[&](uint16_t idx, [[maybe_unused]] const AZ::Vector2& corner)
|
|
{
|
|
for (uint16_t offset = 0; offset < MacroMaterialsPerTile; ++offset)
|
|
{
|
|
MacroMaterialShaderData& macroMaterialShaderData = m_macroMaterialShaderData.at(idx + offset);
|
|
if ((macroMaterialShaderData.m_flags & MacroMaterialShaderFlags::IsUsed) == 0)
|
|
{
|
|
UpdateMacroMaterialShaderEntry(idx + offset, macroMaterial);
|
|
break;
|
|
}
|
|
AZ_Assert(m_macroMaterialEntities.at(idx + offset) != entityId, "Found existing macro material tile for what should be a completely new macro material.");
|
|
}
|
|
}
|
|
);
|
|
|
|
m_bufferNeedsUpdate = true;
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::OnTerrainMacroMaterialChanged(AZ::EntityId entityId, const MacroMaterialData& newMaterialData)
|
|
{
|
|
AZ_Assert(m_macroMaterials.contains(entityId),
|
|
"OnTerrainMacroMaterialChanged called for a macro material that TerrainFeatureProcessor isn't tracking. This indicates that either the bus is sending out "
|
|
"Changed announcements for materials that haven't had a OnCreated event sent, or the terrain feature processor isn't properly tracking macro materials.");
|
|
|
|
MacroMaterial& macroMaterial = m_macroMaterials[entityId];
|
|
macroMaterial.m_data = newMaterialData;
|
|
|
|
auto UpdateImageIndex = [&](uint16_t& indexRef, const AZ::Data::Instance<AZ::RPI::Image>& imageView)
|
|
{
|
|
if (indexRef)
|
|
{
|
|
if (imageView)
|
|
{
|
|
m_bindlessImageHandler->UpdateBindlessImage(indexRef, imageView->GetImageView());
|
|
}
|
|
else
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(indexRef);
|
|
indexRef = 0xFFFF;
|
|
}
|
|
}
|
|
else if (imageView)
|
|
{
|
|
indexRef = m_bindlessImageHandler->AppendBindlessImage(imageView->GetImageView());
|
|
}
|
|
};
|
|
|
|
UpdateImageIndex(macroMaterial.m_colorIndex, newMaterialData.m_colorImage);
|
|
UpdateImageIndex(macroMaterial.m_normalIndex, newMaterialData.m_normalImage);
|
|
|
|
ForMacroMaterialsInBounds(newMaterialData.m_bounds,
|
|
[&](uint16_t idx, [[maybe_unused]] const AZ::Vector2& corner)
|
|
{
|
|
for (uint16_t offset = 0; offset < MacroMaterialsPerTile; ++offset)
|
|
{
|
|
if (m_macroMaterialEntities.at(idx + offset) == entityId)
|
|
{
|
|
UpdateMacroMaterialShaderEntry(idx + offset, macroMaterial);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
m_bufferNeedsUpdate = true;
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::OnTerrainMacroMaterialRegionChanged(
|
|
AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
|
|
{
|
|
AZ_Assert(m_macroMaterials.contains(entityId),
|
|
"OnTerrainMacroMaterialChanged called for a macro material that TerrainFeatureProcessor isn't tracking. This indicates that either the bus is sending out "
|
|
"Changed announcements for materials that haven't had a OnCreated event sent, or the terrain feature processor isn't properly tracking macro materials.");
|
|
|
|
MacroMaterial& macroMaterial = m_macroMaterials[entityId];
|
|
macroMaterial.m_data.m_bounds = newRegion;
|
|
|
|
AZ::Aabb changedRegion = oldRegion;
|
|
changedRegion.AddAabb(newRegion);
|
|
|
|
ForMacroMaterialsInBounds(changedRegion,
|
|
[&](uint16_t idx, const AZ::Vector2& corner)
|
|
{
|
|
AZ::Aabb tileAabb = AZ::Aabb::CreateFromMinMaxValues(
|
|
corner.GetX(), corner.GetY(), m_terrainBounds.GetMin().GetZ(),
|
|
corner.GetX() + MacroMaterialGridSize, corner.GetY() + MacroMaterialGridSize, m_terrainBounds.GetMax().GetZ());
|
|
|
|
bool overlapsNew = tileAabb.Overlaps(newRegion);
|
|
uint16_t end = idx + MacroMaterialsPerTile;
|
|
|
|
for (; idx < end; ++idx)
|
|
{
|
|
if (m_macroMaterialEntities.at(idx) == entityId)
|
|
{
|
|
if (overlapsNew)
|
|
{
|
|
// Update the macro material entry from this tile.
|
|
UpdateMacroMaterialShaderEntry(idx, macroMaterial);
|
|
}
|
|
else
|
|
{
|
|
// Remove the macro material entry from this tile.
|
|
RemoveMacroMaterialShaderEntry(idx);
|
|
}
|
|
break;
|
|
}
|
|
else if (overlapsNew && (m_macroMaterialShaderData.at(idx).m_flags & MacroMaterialShaderFlags::IsUsed) == 0)
|
|
{
|
|
// Add a macro material entry from this tile. (!overlapsOld && overlapsNew)
|
|
UpdateMacroMaterialShaderEntry(idx, macroMaterial);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
m_bufferNeedsUpdate = true;
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId)
|
|
{
|
|
AZ_Assert(m_macroMaterials.contains(entityId),
|
|
"OnTerrainMacroMaterialChanged called for a macro material that TerrainFeatureProcessor isn't tracking. This indicates that either the bus is sending out "
|
|
"Changed announcements for materials that haven't had a OnCreated event sent, or the terrain feature processor isn't properly tracking macro materials.");
|
|
|
|
const MacroMaterial& macroMaterial = m_macroMaterials[entityId];
|
|
|
|
ForMacroMaterialsInBounds(macroMaterial.m_data.m_bounds,
|
|
[&](uint16_t idx, [[maybe_unused]] const AZ::Vector2& corner)
|
|
{
|
|
uint16_t end = idx + MacroMaterialsPerTile;
|
|
|
|
for (; idx < end; ++idx)
|
|
{
|
|
if (m_macroMaterialEntities.at(idx) == entityId)
|
|
{
|
|
RemoveMacroMaterialShaderEntry(idx);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
if (macroMaterial.m_colorIndex != 0xFFFF)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(macroMaterial.m_colorIndex);
|
|
}
|
|
if (macroMaterial.m_normalIndex != 0xFFFF)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(macroMaterial.m_normalIndex);
|
|
}
|
|
|
|
m_macroMaterials.erase(entityId);
|
|
m_bufferNeedsUpdate = true;
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::UpdateMacroMaterialShaderEntry(uint16_t shaderDataIdx, const MacroMaterial& macroMaterial)
|
|
{
|
|
m_macroMaterialEntities.at(shaderDataIdx) = macroMaterial.m_data.m_entityId;
|
|
MacroMaterialShaderData& macroMaterialShaderData = m_macroMaterialShaderData.at(shaderDataIdx);
|
|
|
|
macroMaterialShaderData.m_flags = (MacroMaterialShaderFlags)(
|
|
MacroMaterialShaderFlags::IsUsed |
|
|
(macroMaterial.m_data.m_normalFlipX ? MacroMaterialShaderFlags::FlipMacroNormalX : 0) |
|
|
(macroMaterial.m_data.m_normalFlipY ? MacroMaterialShaderFlags::FlipMacroNormalY : 0)
|
|
);
|
|
|
|
macroMaterialShaderData.m_normalFactor = macroMaterial.m_data.m_normalFactor;
|
|
macroMaterialShaderData.m_boundsMin = { macroMaterial.m_data.m_bounds.GetMin().GetX(), macroMaterial.m_data.m_bounds.GetMin().GetY() };
|
|
macroMaterialShaderData.m_boundsMax = { macroMaterial.m_data.m_bounds.GetMax().GetX(), macroMaterial.m_data.m_bounds.GetMax().GetY() };
|
|
macroMaterialShaderData.m_colorMapId = macroMaterial.m_colorIndex;
|
|
macroMaterialShaderData.m_normalMapId = macroMaterial.m_normalIndex;
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::RemoveMacroMaterialShaderEntry(uint16_t shaderDataIdx)
|
|
{
|
|
// Remove the macro material entry from this tile by copying the remaining entries on top.
|
|
for (++shaderDataIdx; shaderDataIdx % MacroMaterialsPerTile != 0; ++shaderDataIdx)
|
|
{
|
|
m_macroMaterialEntities.at(shaderDataIdx - 1) = m_macroMaterialEntities.at(shaderDataIdx);
|
|
m_macroMaterialShaderData.at(shaderDataIdx - 1) = m_macroMaterialShaderData.at(shaderDataIdx);
|
|
}
|
|
// Disable the last entry.
|
|
m_macroMaterialEntities.at(shaderDataIdx - 1) = AZ::EntityId();
|
|
m_macroMaterialShaderData.at(shaderDataIdx - 1).m_flags = MacroMaterialShaderFlags(0);
|
|
}
|
|
|
|
template<typename Callback>
|
|
void TerrainMacroMaterialManager::ForMacroMaterialsInBounds(const AZ::Aabb& bounds, Callback callback)
|
|
{
|
|
// Get the macro material bounds relative to the terrain
|
|
float yStart = bounds.GetMin().GetY() - m_terrainBounds.GetMin().GetY();
|
|
float yEnd = bounds.GetMax().GetY() - m_terrainBounds.GetMin().GetY();
|
|
float xStart = bounds.GetMin().GetX() - m_terrainBounds.GetMin().GetX();
|
|
float xEnd = bounds.GetMax().GetX() - m_terrainBounds.GetMin().GetX();
|
|
|
|
// Clamp the bounds to the terrain
|
|
uint16_t yStartIdx = yStart > 0.0f ? uint16_t(yStart / MacroMaterialGridSize) : 0;
|
|
uint16_t yEndIdx = yEnd > 0.0f ? AZStd::GetMin<uint16_t>(uint16_t(yEnd / MacroMaterialGridSize) + 1, m_tilesY) : 0;
|
|
uint16_t xStartIdx = xStart > 0.0f ? uint16_t(xStart / MacroMaterialGridSize) : 0;
|
|
uint16_t xEndIdx = xEnd > 0.0f ? AZStd::GetMin<uint16_t>(uint16_t(xEnd / MacroMaterialGridSize) + 1, m_tilesX) : 0;
|
|
|
|
AZ::Vector2 gridCorner = AZ::Vector2(
|
|
floor(m_terrainBounds.GetMin().GetX() / MacroMaterialGridSize) * MacroMaterialGridSize,
|
|
floor(m_terrainBounds.GetMin().GetY() / MacroMaterialGridSize) * MacroMaterialGridSize);
|
|
|
|
for (uint16_t y = yStartIdx; y < yEndIdx; ++y)
|
|
{
|
|
for (uint16_t x = xStartIdx; x < xEndIdx; ++x)
|
|
{
|
|
uint16_t idx = (y * m_tilesX + x) * MacroMaterialsPerTile;
|
|
const AZ::Vector2 corner = gridCorner + AZ::Vector2(x * MacroMaterialGridSize, y * MacroMaterialGridSize);
|
|
callback(idx, corner);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::Update(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
|
|
{
|
|
if (m_terrainSizeChanged)
|
|
{
|
|
m_terrainSizeChanged = false;
|
|
|
|
// Rebuild the macro material tiles from scratch when the world size changes. This could be made more efficient
|
|
// but is fine for now since world resizes are rare.
|
|
|
|
RemoveAllImages();
|
|
m_macroMaterials.clear();
|
|
|
|
m_macroMaterialShaderData.clear();
|
|
m_macroMaterialEntities.clear();
|
|
|
|
m_tilesX = aznumeric_cast<uint16_t>(m_terrainBounds.GetXExtent() / MacroMaterialGridSize) + 1;
|
|
m_tilesY = aznumeric_cast<uint16_t>(m_terrainBounds.GetYExtent() / MacroMaterialGridSize) + 1;
|
|
const uint32_t macroMaterialTileCount = m_tilesX * m_tilesY * MacroMaterialsPerTile;
|
|
|
|
m_macroMaterialShaderData.resize(macroMaterialTileCount);
|
|
m_macroMaterialEntities.resize(macroMaterialTileCount);
|
|
|
|
TerrainMacroMaterialRequestBus::EnumerateHandlers(
|
|
[&](TerrainMacroMaterialRequests* handler)
|
|
{
|
|
MacroMaterialData macroMaterial = handler->GetTerrainMacroMaterialData();
|
|
AZ::EntityId entityId = *(Terrain::TerrainMacroMaterialRequestBus::GetCurrentBusId());
|
|
OnTerrainMacroMaterialCreated(entityId, macroMaterial);
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
if (m_bufferNeedsUpdate)
|
|
{
|
|
m_bufferNeedsUpdate = false;
|
|
m_macroMaterialDataBuffer.UpdateBuffer(m_macroMaterialShaderData.data(), aznumeric_cast<uint32_t>(m_macroMaterialShaderData.size()));
|
|
|
|
MacroMaterialGridShaderData macroMaterialGridShaderData;
|
|
macroMaterialGridShaderData.m_offset = { m_terrainBounds.GetMin().GetX(), m_terrainBounds.GetMin().GetY() };
|
|
macroMaterialGridShaderData.m_resolution = (m_tilesX << 16) | m_tilesY;
|
|
macroMaterialGridShaderData.m_tileSize = MacroMaterialGridSize;
|
|
|
|
if (terrainSrg)
|
|
{
|
|
m_macroMaterialDataBuffer.UpdateSrg(terrainSrg.get());
|
|
terrainSrg->SetConstant(m_macroMaterialGridIndex, macroMaterialGridShaderData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::RemoveAllImages()
|
|
{
|
|
for (const auto& [entity, macroMaterial] : m_macroMaterials)
|
|
{
|
|
RemoveImagesForMaterial(macroMaterial);
|
|
}
|
|
}
|
|
|
|
void TerrainMacroMaterialManager::RemoveImagesForMaterial(const MacroMaterial& macroMaterial)
|
|
{
|
|
if (macroMaterial.m_colorIndex != 0xFFFF)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(macroMaterial.m_colorIndex);
|
|
}
|
|
if (macroMaterial.m_normalIndex != 0xFFFF)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(macroMaterial.m_normalIndex);
|
|
}
|
|
}
|
|
|
|
}
|