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/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp

495 lines
22 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 <Atom/RPI.Public/Model/ModelLod.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RHI/Factory.h>
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
#include <AzCore/Debug/EventTrace.h>
#include <AtomCore/Instance/InstanceDatabase.h>
namespace AZ
{
namespace RPI
{
Data::Instance<ModelLod> ModelLod::FindOrCreate(const Data::Asset<ModelLodAsset>& lodAsset)
{
return Data::InstanceDatabase<ModelLod>::Instance().FindOrCreate(
Data::InstanceId::CreateFromAssetId(lodAsset.GetId()),
lodAsset);
}
AZStd::array_view<ModelLod::Mesh> ModelLod::GetMeshes() const
{
return m_meshes;
}
Data::Instance<ModelLod> ModelLod::CreateInternal(ModelLodAsset& lodAsset)
{
Data::Instance<ModelLod> lod = aznew ModelLod();
const RHI::ResultCode resultCode = lod->Init(lodAsset);
if (resultCode == RHI::ResultCode::Success)
{
return lod;
}
return nullptr;
}
RHI::ResultCode ModelLod::Init(ModelLodAsset& lodAsset)
{
AZ_TRACE_METHOD();
for (const ModelLodAsset::Mesh& mesh : lodAsset.GetMeshes())
{
Mesh meshInstance;
const BufferAssetView& indexBufferAssetView = mesh.GetIndexBufferAssetView();
const Data::Asset<BufferAsset>& indexBufferAsset = indexBufferAssetView.GetBufferAsset();
if (indexBufferAsset)
{
Data::Instance<Buffer> indexBuffer = Buffer::FindOrCreate(indexBufferAsset);
if (!indexBuffer)
{
return RHI::ResultCode::Fail;
}
const RHI::BufferViewDescriptor& bufferViewDescriptor = indexBufferAssetView.GetBufferViewDescriptor();
RHI::IndexFormat indexFormat = RHI::IndexFormat::Uint32;
if (bufferViewDescriptor.m_elementSize == sizeof(uint16_t))
{
indexFormat = RHI::IndexFormat::Uint16;
}
else if (bufferViewDescriptor.m_elementSize != sizeof(uint32_t))
{
AZ_Error("ModelLod", false, "Index buffer format is invalid. Only 16 or 32 bit indices are supported.");
return RHI::ResultCode::InvalidOperation;
}
meshInstance.m_indexBufferView = RHI::IndexBufferView(
*indexBuffer->GetRHIBuffer(),
bufferViewDescriptor.m_elementOffset * bufferViewDescriptor.m_elementSize,
bufferViewDescriptor.m_elementCount * bufferViewDescriptor.m_elementSize,
indexFormat);
RHI::DrawIndexed drawIndexed;
drawIndexed.m_indexCount = bufferViewDescriptor.m_elementCount;
drawIndexed.m_instanceCount = 1;
meshInstance.m_drawArguments = drawIndexed;
TrackBuffer(indexBuffer);
}
// [GFX TODO][ATOM-838]: We need to figure out how to load only the required streams from disk rather than all available streams.
for (const auto& streamBufferInfo : mesh.GetStreamBufferInfoList())
{
if (!SetMeshInstanceData(streamBufferInfo, meshInstance))
{
return RHI::ResultCode::InvalidOperation;
}
}
auto& materialAsset = mesh.GetMaterialAsset();
if (materialAsset.IsReady())
{
meshInstance.m_material = Material::FindOrCreate(materialAsset);
}
m_meshes.emplace_back(AZStd::move(meshInstance));
}
m_isUploadPending = true;
return RHI::ResultCode::Success;
}
ModelLod::StreamInfoList::const_iterator ModelLod::FindFirstUvStreamFromMesh(size_t meshIndex) const
{
const Mesh& mesh = m_meshes[meshIndex];
auto firstUv = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [](const StreamBufferInfo& info) {
return info.m_semantic.m_name.GetStringView().starts_with(RHI::ShaderSemantic::UvStreamSemantic);
});
return firstUv;
}
ModelLod::StreamInfoList::const_iterator ModelLod::FindDefaultUvStream(size_t meshIndex, const MaterialUvNameMap& materialUvNameMap) const
{
const Mesh& mesh = m_meshes[meshIndex];
// The default UV is used for cases that there are more UVs defined in the material than in the model.
// The unmatched UV slots will be filled with the default UV.
// The default UV is the first one matched in the shader input contract.
auto defaultUv = mesh.m_streamInfo.end();
for (const auto& materialUvNamePair : materialUvNameMap)
{
const AZ::Name& uvCustomName = materialUvNamePair.m_uvName;
const RHI::ShaderSemantic& shaderInput = materialUvNamePair.m_shaderInput;
// Use name matching first. Empty name can't be used because it will match other non-UV streams.
if (!uvCustomName.IsEmpty())
{
defaultUv = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [&uvCustomName](const StreamBufferInfo& info)
{
return info.m_customName == uvCustomName;
});
}
// Use semantic matching second if previous matching failed.
if (defaultUv == mesh.m_streamInfo.end())
{
defaultUv = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [&shaderInput](const StreamBufferInfo& info)
{
return info.m_semantic == shaderInput;
});
}
// Select the first matching
if (defaultUv != mesh.m_streamInfo.end())
{
break;
}
}
return defaultUv;
}
ModelLod::StreamInfoList::const_iterator ModelLod::FindMatchingStream(
size_t meshIndex,
const MaterialModelUvOverrideMap& materialModelUvMap,
const MaterialUvNameMap& materialUvNameMap,
const ShaderInputContract::StreamChannelInfo& contractStreamChannel,
StreamInfoList::const_iterator defaultUv,
StreamInfoList::const_iterator firstUv,
UvStreamTangentBitmask* uvStreamTangentBitmaskOut) const
{
const Mesh& mesh = m_meshes[meshIndex];
auto iter = mesh.m_streamInfo.end();
// Special matching for UV sets, we will match each UV shader input by following steps:
// 1. The custom mapping from the name in material to the name in model (modelUvMap)
// 2. The exact name matching between material and model (uvCustomNames <=> mesh.m_streamInfo.m_customName)
// 3. The exact semantic matching between material and model (uvDefaultNames <=> mesh.m_streamInfo.m_semantic)
// 4. If no matching found from the model, then the first applied model UV fills the slot.
// e.g. (In practice, custom mapping should have the same size as material's UV, or empty if in places like material editor)
// Material Model model UV map Final Mapping
// UV0: Unwrapped UV0: Packed Unwrapped = Packed UV0: Unwrapped = UV0: Packed (rule 1: custom mapping)
// UV1: Packed UV1: Unwrapped UV1: Packed = UV0: Packed (rule 2: default name mapping)
// UV2: Tiled UV2: Repeated UV2: Tiled = UV2: Repeated (rule 3: semantic name mapping)
// UV3: Extra UV3: Extra = UV0: Packed (rule 4: first filling)
// ensure the semantic is a UV, otherwise skip name matching
auto materialUvIter = AZStd::find_if(materialUvNameMap.begin(), materialUvNameMap.end(),
[&contractStreamChannel](const UvNamePair& uvNamePair)
{
// Cost of linear search UV names is low because the size is extremely limited.
return uvNamePair.m_shaderInput == contractStreamChannel.m_semantic;
});
const bool isUv = materialUvIter != materialUvNameMap.end();
if (isUv)
{
const AZ::Name& materialUvName = materialUvIter->m_uvName;
auto modelUvMapIter = materialModelUvMap.find(materialUvIter->m_shaderInput);
if (modelUvMapIter != materialModelUvMap.end())
{
const AZ::Name& modelUvName = modelUvMapIter->second;
// Empty name can't be used because it will match other non-UV streams.
if (!modelUvName.IsEmpty())
{
iter = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [&modelUvName](const StreamBufferInfo& info)
{
return info.m_customName == modelUvName
|| info.m_semantic.ToString() == modelUvName.GetStringView(); // For unnamed UVs, use the semantic instead.
});
}
}
if (iter == mesh.m_streamInfo.end())
{
// Empty name can't be used because it will match other non-UV streams.
if (!materialUvName.IsEmpty())
{
iter = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [&materialUvName](const StreamBufferInfo& info)
{
return info.m_customName == materialUvName;
});
}
}
}
if (iter == mesh.m_streamInfo.end())
{
iter = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [&contractStreamChannel](const StreamBufferInfo& info)
{
return info.m_semantic == contractStreamChannel.m_semantic;
});
}
if (iter == mesh.m_streamInfo.end() && isUv)
{
iter = defaultUv;
}
if (isUv && uvStreamTangentBitmaskOut)
{
uvStreamTangentBitmaskOut->ApplyTangent(iter == firstUv ? 0 : UvStreamTangentBitmask::UnassignedTangent);
}
return iter;
}
bool ModelLod::GetStreamsForMesh(
RHI::InputStreamLayout& layoutOut,
StreamBufferViewList& streamBufferViewsOut,
UvStreamTangentBitmask* uvStreamTangentBitmaskOut,
const ShaderInputContract& contract,
size_t meshIndex,
const MaterialModelUvOverrideMap& materialModelUvMap,
const MaterialUvNameMap& materialUvNameMap) const
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
streamBufferViewsOut.clear();
RHI::InputStreamLayoutBuilder layoutBuilder;
const Mesh& mesh = m_meshes[meshIndex];
bool success = true;
// Searching for the first UV in the mesh, so it can be used to paired with tangent/bitangent stream
auto firstUv = FindFirstUvStreamFromMesh(meshIndex);
auto defaultUv = FindDefaultUvStream(meshIndex, materialUvNameMap);
if (uvStreamTangentBitmaskOut)
{
uvStreamTangentBitmaskOut->Reset();
}
for (auto& contractStreamChannel : contract.m_streamChannels)
{
auto iter = FindMatchingStream(meshIndex, materialModelUvMap, materialUvNameMap, contractStreamChannel, defaultUv, firstUv, uvStreamTangentBitmaskOut);
if (iter == mesh.m_streamInfo.end())
{
if (contractStreamChannel.m_isOptional)
{
RHI::Format formatDoesntReallyMatter = RHI::Format::R8_UNORM;
layoutBuilder.AddBuffer()->Channel(contractStreamChannel.m_semantic, formatDoesntReallyMatter);
// We can't just use a null buffer pointer here because vulkan will occasionally crash. So we bind some valid non-null buffer and view it with length 0.
RHI::StreamBufferView dummyBuffer{*mesh.m_indexBufferView.GetBuffer(), 0, 0, 1};
streamBufferViewsOut.push_back(dummyBuffer);
// Note that all of the below scenarios seem to work find on PC, for both dx12 and vulkan. If the above approach proves to be incompatible
// with another platform, consider trying one of the approaches below.
//RHI::Format formatDoesntReallyMatter = RHI::Format::R8_UNORM;
//layoutBuilder.AddBuffer(RHI::StreamStepFunction::PerInstance)->Channel(contractStreamChannel.m_semantic, formatDoesntReallyMatter);
//RHI::StreamBufferView dummyBuffer{*mesh.m_indexBufferView.GetBuffer(), 0, 0, 0};
//streamBufferViewsOut.push_back(dummyBuffer);
//RHI::Format formatDoesntReallyMatter = RHI::Format::R8G8B8A8_UINT;
//layoutBuilder.AddBuffer(RHI::StreamStepFunction::PerInstance)->Channel(contractStreamChannel.m_semantic, formatDoesntReallyMatter);
//RHI::StreamBufferView dummyBuffer{*mesh.m_indexBufferView.GetBuffer(), 0, 4, 4};
//streamBufferViewsOut.push_back(dummyBuffer);
//RHI::Format formatDoesntMatter = RHI::Format::R32G32B32A32_FLOAT;
//layoutBuilder.AddBuffer()->Channel(contractStreamChannel.m_semantic, formatDoesntMatter);
//RHI::StreamBufferView emptyBuffer{*m_buffers[0]->GetRHIBuffer(), 0, 16, 16};
//streamBufferViewsOut.push_back(emptyBuffer);
//RHI::Format formatDoesntMatter = RHI::Format::R32G32B32A32_FLOAT;
//layoutBuilder.AddBuffer()->Channel(contractStreamChannel.m_semantic, formatDoesntMatter);
//RHI::StreamBufferView emptyBuffer{*m_buffers[0]->GetRHIBuffer(), 0, 0, 16};
//streamBufferViewsOut.push_back(emptyBuffer);
}
else
{
AZ_Warning("Mesh", false, "Mesh does not have all the required input streams. Missing '%s'.", contractStreamChannel.m_semantic.ToString().c_str());
success = false;
}
}
else
{
// Note, we may need to iterate on the details of this validation. It might not be correct for all use cases.
if (RHI::GetFormatComponentCount(iter->m_format) < contractStreamChannel.m_componentCount)
{
AZ_Error("Mesh", false, "Mesh format (%s) for stream '%s' provides %d components but the shader requires %d.",
RHI::ToString(iter->m_format),
contractStreamChannel.m_semantic.ToString().c_str(),
RHI::GetFormatComponentCount(iter->m_format),
contractStreamChannel.m_componentCount);
success = false;
}
else
{
// Note, don't use iter->m_semantic as it can be a UV name matching.
layoutBuilder.AddBuffer()->Channel(contractStreamChannel.m_semantic, iter->m_format);
RHI::StreamBufferView bufferView(*m_buffers[iter->m_bufferIndex]->GetRHIBuffer(), iter->m_byteOffset, iter->m_byteCount, iter->m_stride);
streamBufferViewsOut.push_back(bufferView);
}
}
}
if (success)
{
layoutOut = layoutBuilder.End();
success &= RHI::ValidateStreamBufferViews(layoutOut, streamBufferViewsOut);
}
return success;
}
void ModelLod::CheckOptionalStreams(
ShaderOptionGroup& shaderOptions,
const ShaderInputContract& contract,
size_t meshIndex,
const MaterialModelUvOverrideMap& materialModelUvMap,
const MaterialUvNameMap& materialUvNameMap) const
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
const Mesh& mesh = m_meshes[meshIndex];
auto defaultUv = FindDefaultUvStream(meshIndex, materialUvNameMap);
auto firstUv = FindFirstUvStreamFromMesh(meshIndex);
for (auto& contractStreamChannel : contract.m_streamChannels)
{
if (!contractStreamChannel.m_isOptional)
{
continue;
}
AZ_Assert(contractStreamChannel.m_streamBoundIndicatorIndex.IsValid(), "m_streamBoundIndicatorIndex was invalid for an optional shader input stream");
auto iter = FindMatchingStream(meshIndex, materialModelUvMap, materialUvNameMap, contractStreamChannel, defaultUv, firstUv, nullptr);
ShaderOptionValue isStreamBound = (iter == mesh.m_streamInfo.end()) ? ShaderOptionValue{0} : ShaderOptionValue{1};
shaderOptions.SetValue(contractStreamChannel.m_streamBoundIndicatorIndex, isStreamBound);
}
}
bool ModelLod::SetMeshInstanceData(
const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo,
Mesh& meshInstance)
{
AZ_TRACE_METHOD();
const Data::Asset<BufferAsset>& streamBufferAsset = streamBufferInfo.m_bufferAssetView.GetBufferAsset();
const Data::Instance<Buffer>& streamBuffer = Buffer::FindOrCreate(streamBufferAsset);
if (streamBuffer == nullptr)
{
AZ_Error("ModelLod", false, "Failed to create stream buffer! Possibly out of memory!");
return false;
}
const RHI::BufferViewDescriptor& bufferViewDescriptor = streamBufferInfo.m_bufferAssetView.GetBufferViewDescriptor();
StreamBufferInfo info;
info.m_semantic = streamBufferInfo.m_semantic;
info.m_customName = streamBufferInfo.m_customName;
info.m_format = bufferViewDescriptor.m_elementFormat;
info.m_byteOffset = bufferViewDescriptor.m_elementOffset * bufferViewDescriptor.m_elementSize;
info.m_byteCount = bufferViewDescriptor.m_elementCount * bufferViewDescriptor.m_elementSize;
info.m_stride = bufferViewDescriptor.m_elementSize;
info.m_bufferIndex = TrackBuffer(streamBuffer);
meshInstance.m_streamInfo.push_back(info);
return true;
}
void ModelLod::WaitForUpload()
{
if (m_isUploadPending)
{
for (const Data::Instance<Buffer>& buffer : m_buffers)
{
buffer->WaitForUpload();
}
m_isUploadPending = false;
}
}
uint32_t ModelLod::TrackBuffer(const Data::Instance<Buffer>& buffer)
{
for (uint32_t i = 0; i < m_buffers.size(); ++i)
{
auto& existingBuffer = m_buffers[i];
if (existingBuffer.get() == buffer)
{
return i;
}
}
m_buffers.emplace_back(buffer);
return static_cast<uint32_t>(m_buffers.size() - 1);
}
uint32_t UvStreamTangentBitmask::GetFullTangentBitmask() const
{
return m_mask;
}
uint32_t UvStreamTangentBitmask::GetUvStreamCount() const
{
return m_mask >> (sizeof(m_mask) * CHAR_BIT - BitsForUvIndex);
}
uint32_t UvStreamTangentBitmask::GetTangentAtUv(uint32_t uvIndex) const
{
return (m_mask >> (BitsPerTangent * uvIndex)) & 0b1111u;
}
void UvStreamTangentBitmask::ApplyTangent(uint32_t tangentIndex)
{
uint32_t currentSlot = GetUvStreamCount();
if (currentSlot >= MaxUvSlots)
{
AZ_Error("UV Stream", false, "Reaching the max of avaiblable stream slots.");
return;
}
if (tangentIndex > UnassignedTangent)
{
AZ_Warning(
"UV Stream", false,
"Tangent index must use %d bits as defined in UvStreamTangentIndex::m_flag. Unassigned index will be applied.",
BitsPerTangent);
tangentIndex = UnassignedTangent;
}
uint32_t clearMask = 0b1111u << (BitsPerTangent * currentSlot);
clearMask = ~clearMask;
// Clear the writing bits in case
m_mask &= clearMask;
// Write the bits to the slot
m_mask |= (tangentIndex << (BitsPerTangent * currentSlot));
// Increase the index
m_mask += (1u << (sizeof(m_mask) * CHAR_BIT - BitsForUvIndex));
}
void UvStreamTangentBitmask::Reset()
{
m_mask = 0;
}
} // namespace RPI
} // namespace AZ