SkinnedMeshInputBuffers.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
  9. #include <Atom/Feature/MorphTargets/MorphTargetInputBuffers.h>
  10. #include <SkinnedMesh/SkinnedMeshOutputStreamManager.h>
  11. #include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
  12. #include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
  13. #include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
  14. #include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
  15. #include <Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h>
  16. #include <Atom/RPI.Reflect/Model/MorphTargetDelta.h>
  17. #include <Atom/RPI.Public/Base.h>
  18. #include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
  19. #include <Atom/RPI.Public/Model/Model.h>
  20. #include <Atom/RHI/Factory.h>
  21. #include <AzCore/std/algorithm.h>
  22. #include <AzCore/Math/PackedVector3.h>
  23. #include <inttypes.h>
  24. namespace AZ
  25. {
  26. namespace Render
  27. {
  28. Data::Asset<RPI::BufferAsset> CreateBufferAsset(const void* data, const RHI::BufferViewDescriptor& viewDescriptor, RHI::BufferBindFlags bindFlags, Data::Asset<RPI::ResourcePoolAsset> resourcePoolAsset, const char* bufferName)
  29. {
  30. const uint32_t bufferSize = viewDescriptor.m_elementCount * viewDescriptor.m_elementSize;
  31. Data::Asset<RPI::BufferAsset> asset;
  32. {
  33. RHI::BufferDescriptor bufferDescriptor;
  34. bufferDescriptor.m_bindFlags = bindFlags;
  35. bufferDescriptor.m_byteCount = bufferSize;
  36. bufferDescriptor.m_alignment = viewDescriptor.m_elementSize;
  37. RPI::BufferAssetCreator creator;
  38. Uuid uuid = Uuid::CreateRandom();
  39. creator.Begin(uuid);
  40. creator.SetPoolAsset(resourcePoolAsset);
  41. creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor);
  42. // Create a unique buffer name by combining the given, friendly buffer name with the uuid. Use isBrackents=false and isDashes=false to make it look less like some kind of AssetId that has any meaning.
  43. creator.SetBufferName(AZStd::string::format("%s_%s", bufferName, uuid.ToString<AZStd::string>(false,false).c_str()));
  44. creator.SetBufferViewDescriptor(viewDescriptor);
  45. creator.End(asset);
  46. }
  47. return asset;
  48. }
  49. RHI::BufferViewDescriptor SkinnedMeshInputLod::CreateInputViewDescriptor(SkinnedMeshInputVertexStreams inputStream, RHI::Format elementFormat, const RHI::StreamBufferView &streamBufferView)
  50. {
  51. RHI::BufferViewDescriptor descriptor;
  52. uint32_t elementOffset = streamBufferView.GetByteOffset() / streamBufferView.GetByteStride();
  53. uint32_t elementCount = streamBufferView.GetByteCount() / streamBufferView.GetByteStride();
  54. if (inputStream == SkinnedMeshInputVertexStreams::BlendIndices)
  55. {
  56. // Create a descriptor for a raw view from the StreamBufferView
  57. descriptor = RHI::BufferViewDescriptor::CreateRaw(streamBufferView.GetByteOffset(), streamBufferView.GetByteCount());
  58. }
  59. else if (elementFormat == RHI::Format::R32G32B32_FLOAT)
  60. {
  61. // 3-component float buffers are not supported on metal for non-input assembly buffer views,
  62. // so use a float view instead
  63. descriptor =
  64. RHI::BufferViewDescriptor::CreateTyped(elementOffset * 3, elementCount * 3, RHI::Format::R32_FLOAT);
  65. }
  66. else
  67. {
  68. // Create a descriptor for a typed buffer view from the StreamBufferView
  69. descriptor =
  70. RHI::BufferViewDescriptor::CreateTyped(elementOffset, elementCount, elementFormat);
  71. }
  72. return descriptor;
  73. }
  74. SkinnedMeshInputLod::HasInputStreamArray SkinnedMeshInputLod::CreateInputBufferViews(
  75. uint32_t lodIndex,
  76. uint32_t meshIndex,
  77. const RHI::InputStreamLayout& inputLayout,
  78. RPI::ModelLod::Mesh& mesh,
  79. const RHI::StreamBufferIndices& streamIndices,
  80. const char* modelName)
  81. {
  82. SkinnedSubMeshProperties& skinnedSubMesh = m_meshes[meshIndex];
  83. const auto modelLodAssetMeshes = m_modelLodAsset->GetMeshes();
  84. const RPI::ModelLodAsset::Mesh& modelLodAssetMesh = modelLodAssetMeshes[meshIndex];
  85. // Keep track of whether or not an input stream exists
  86. HasInputStreamArray meshHasInputStream{ false };
  87. // Loop variables
  88. auto streamIter = mesh.CreateStreamIterator(streamIndices);
  89. u8 meshStreamIndex = 0;
  90. // Create a buffer view for each input stream in the current mesh
  91. for (; !streamIter.HasEnded(); ++streamIter, ++meshStreamIndex)
  92. {
  93. // `IsValid` would return false for dummy buffers, since the index of those buffers equals the size of the buffer view.
  94. // We shall skip dummy buffers to avoid empty pointers in the following code.
  95. if (!streamIter.IsValid())
  96. {
  97. continue;
  98. }
  99. // Get the semantic from the input layout, and use that to get the SkinnedMeshStreamInfo
  100. const SkinnedMeshVertexStreamInfo* streamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamInfo(
  101. inputLayout.GetStreamChannels()[meshStreamIndex].m_semantic);
  102. const RHI::StreamBufferView& streamBufferView = *streamIter;
  103. if (streamInfo && streamBufferView.GetByteCount() > 0)
  104. {
  105. RHI::BufferViewDescriptor descriptor =
  106. CreateInputViewDescriptor(streamInfo->m_enum, streamInfo->m_elementFormat, streamBufferView);
  107. AZ::RHI::Ptr<AZ::RHI::BufferView> bufferView =
  108. const_cast<RHI::Buffer*>(streamBufferView.GetBuffer())->GetBufferView(descriptor);
  109. {
  110. // Initialize the buffer view
  111. AZStd::string bufferViewName = AZStd::string::format(
  112. "%s_lod%" PRIu32 "_mesh%" PRIu32 "_%s", modelName, lodIndex, meshIndex,
  113. streamInfo->m_shaderResourceGroupName.GetCStr());
  114. bufferView->SetName(Name(bufferViewName));
  115. // Keep track of which streams exist for the current mesh
  116. meshHasInputStream[static_cast<uint8_t>(streamInfo->m_enum)] = true;
  117. }
  118. // Add the buffer view along with the shader resource group name, which will be used to bind it to the srg later
  119. skinnedSubMesh.m_inputBufferViews.push_back(
  120. SkinnedSubMeshProperties::SrgNameViewPair{ streamInfo->m_shaderResourceGroupName, bufferView });
  121. if (streamInfo->m_enum == SkinnedMeshInputVertexStreams::BlendWeights)
  122. {
  123. uint32_t elementCount = streamBufferView.GetByteCount() / streamBufferView.GetByteStride();
  124. skinnedSubMesh.m_skinInfluenceCountPerVertex = elementCount / modelLodAssetMesh.GetVertexCount();
  125. }
  126. }
  127. }
  128. return meshHasInputStream;
  129. }
  130. void SkinnedMeshInputLod::CreateOutputOffsets(
  131. uint32_t meshIndex,
  132. const HasInputStreamArray& meshHasInputStream,
  133. SkinnedMeshOutputVertexOffsets& currentMeshOffsetFromStreamStart)
  134. {
  135. SkinnedSubMeshProperties& skinnedSubMesh = m_meshes[meshIndex];
  136. const auto modelLodAssetMeshes = m_modelLodAsset->GetMeshes();
  137. const RPI::ModelLodAsset::Mesh& modelLodAssetMesh = modelLodAssetMeshes[meshIndex];
  138. for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams);
  139. ++outputStreamIndex)
  140. {
  141. const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo =
  142. SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(
  143. static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex));
  144. // If there is no input to be skinned, then we won't need to bind the output stream
  145. if (meshHasInputStream[static_cast<uint8_t>(outputStreamInfo.m_correspondingInputVertexStream)])
  146. {
  147. // Keep track of the offset for the individual mesh
  148. skinnedSubMesh.m_vertexOffsetsFromStreamStartInBytes[outputStreamIndex] =
  149. currentMeshOffsetFromStreamStart[outputStreamIndex];
  150. currentMeshOffsetFromStreamStart[outputStreamIndex] +=
  151. modelLodAssetMesh.GetVertexCount() * outputStreamInfo.m_elementSize;
  152. // Keep track of the total for the whole lod
  153. m_outputVertexCountsByStream[outputStreamIndex] += modelLodAssetMesh.GetVertexCount();
  154. }
  155. }
  156. }
  157. void SkinnedMeshInputLod::TrackStaticBufferViews(uint32_t meshIndex)
  158. {
  159. SkinnedSubMeshProperties& skinnedSubMesh = m_meshes[meshIndex];
  160. const auto modelLodAssetMeshes = m_modelLodAsset->GetMeshes();
  161. const RPI::ModelLodAsset::Mesh& modelLodAssetMesh = modelLodAssetMeshes[meshIndex];
  162. for (const RPI::ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo : modelLodAssetMesh.GetStreamBufferInfoList())
  163. {
  164. // If it is not part of the skinning compute shader input or output, then it is a static buffer used for rendering instead
  165. // of skinning
  166. bool isStaticStream = !SkinnedMeshVertexStreamPropertyInterface::Get()->GetInputStreamInfo(streamBufferInfo.m_semantic) &&
  167. !SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(streamBufferInfo.m_semantic);
  168. if (isStaticStream)
  169. {
  170. skinnedSubMesh.m_staticBufferInfo.push_back(streamBufferInfo);
  171. // If the buffer asset isn't already tracked by the lod from another mesh, add it here
  172. if (AZStd::find(
  173. begin(m_staticBufferAssets), end(m_staticBufferAssets), streamBufferInfo.m_bufferAssetView.GetBufferAsset()) ==
  174. end(m_staticBufferAssets))
  175. {
  176. m_staticBufferAssets.push_back(streamBufferInfo.m_bufferAssetView.GetBufferAsset());
  177. }
  178. }
  179. }
  180. }
  181. void SkinnedMeshInputLod::CreateFromModelLod(
  182. const Data::Asset<RPI::ModelAsset>& modelAsset, const Data::Instance<RPI::Model>& model, uint32_t lodIndex)
  183. {
  184. m_modelLodAsset = modelAsset->GetLodAssets()[lodIndex];
  185. const auto modelLods = model->GetLods();
  186. const Data::Instance<RPI::ModelLod>& modelLod = modelLods[lodIndex];
  187. // Collect the vertex count for each output stream
  188. m_outputVertexCountsByStream = SkinnedMeshOutputVertexCounts{ 0 };
  189. SkinnedMeshOutputVertexOffsets currentMeshOffsetFromStreamStart = { 0 };
  190. m_meshes.resize(modelLod->GetMeshes().size());
  191. for (uint32_t meshIndex = 0; meshIndex < modelLod->GetMeshes().size(); ++meshIndex)
  192. {
  193. RPI::ModelLod::Mesh mesh = modelLod->GetMeshes()[meshIndex];
  194. SkinnedSubMeshProperties& skinnedSubMesh = m_meshes[meshIndex];
  195. skinnedSubMesh.m_vertexOffsetsFromStreamStartInBytes = SkinnedMeshOutputVertexOffsets{ 0 };
  196. // Get the source mesh
  197. const auto modelLodAssetMeshes = m_modelLodAsset->GetMeshes();
  198. const RPI::ModelLodAsset::Mesh& modelLodAssetMesh = modelLodAssetMeshes[meshIndex];
  199. skinnedSubMesh.m_vertexCount = modelLodAssetMesh.GetVertexCount();
  200. // Get all of the streams potentially used as input to the skinning compute shader
  201. RHI::InputStreamLayout inputLayout;
  202. RHI::StreamBufferIndices streamIndices;
  203. [[maybe_unused]] bool success = modelLod->GetStreamsForMesh(
  204. inputLayout, streamIndices, nullptr,
  205. SkinnedMeshVertexStreamPropertyInterface::Get()->GetComputeShaderInputContract(), meshIndex);
  206. AZ_Assert( success, "SkinnedMeshInputLod failed to get Streams for model '%s'", modelAsset.GetHint().c_str());
  207. HasInputStreamArray meshHasInputStream = CreateInputBufferViews(lodIndex, meshIndex, inputLayout,
  208. mesh, streamIndices, modelAsset->GetName().GetCStr());
  209. CreateOutputOffsets(meshIndex, meshHasInputStream, currentMeshOffsetFromStreamStart);
  210. TrackStaticBufferViews(meshIndex);
  211. }
  212. }
  213. Data::Asset<RPI::ModelLodAsset> SkinnedMeshInputLod::GetModelLodAsset() const
  214. {
  215. return m_modelLodAsset;
  216. }
  217. uint32_t SkinnedMeshInputLod::GetVertexCount() const
  218. {
  219. return m_outputVertexCountsByStream[aznumeric_caster(SkinnedMeshOutputVertexStreams::Position)];
  220. }
  221. void SkinnedMeshInputLod::AddMorphTarget(
  222. const RPI::MorphTargetMetaAsset::MorphTarget& morphTarget,
  223. const RPI::BufferAssetView* morphBufferAssetView,
  224. const AZStd::string& bufferNamePrefix,
  225. float minWeight = 0.0f,
  226. float maxWeight = 1.0f)
  227. {
  228. m_morphTargetComputeMetaDatas.push_back(MorphTargetComputeMetaData{
  229. minWeight, maxWeight, morphTarget.m_minPositionDelta, morphTarget.m_maxPositionDelta, morphTarget.m_numVertices, morphTarget.m_meshIndex });
  230. // Create a view into the larger per-lod morph buffer for this particular morph
  231. // The morphTarget itself refers to an offset from within the mesh, so combine that
  232. // with the mesh offset to get the view within the lod buffer
  233. RHI::BufferViewDescriptor morphView = morphBufferAssetView->GetBufferViewDescriptor();
  234. morphView.m_elementOffset += morphTarget.m_startIndex;
  235. morphView.m_elementCount = morphTarget.m_numVertices;
  236. RPI::BufferAssetView morphTargetDeltaView{ morphBufferAssetView->GetBufferAsset(), morphView };
  237. m_morphTargetInputBuffers.push_back(aznew MorphTargetInputBuffers{ morphTargetDeltaView, bufferNamePrefix });
  238. }
  239. const AZStd::vector<MorphTargetComputeMetaData>& SkinnedMeshInputLod::GetMorphTargetComputeMetaDatas() const
  240. {
  241. return m_morphTargetComputeMetaDatas;
  242. }
  243. const AZStd::vector<AZStd::intrusive_ptr<MorphTargetInputBuffers>>& SkinnedMeshInputLod::GetMorphTargetInputBuffers() const
  244. {
  245. return m_morphTargetInputBuffers;
  246. }
  247. void SkinnedMeshInputLod::CalculateMorphTargetIntegerEncodings()
  248. {
  249. AZStd::vector<float> ranges(m_meshes.size(), 0.0f);
  250. // The accumulation buffer must be stored as an int to support InterlockedAdd in AZSL
  251. // Conservatively determine the largest value, positive or negative across the entire skinned mesh lod, which is used for encoding/decoding the accumulation buffer
  252. for (const MorphTargetComputeMetaData& metaData : m_morphTargetComputeMetaDatas)
  253. {
  254. float maxWeight = AZStd::max(std::abs(metaData.m_minWeight), std::abs(metaData.m_maxWeight));
  255. float maxDelta = AZStd::max(std::abs(metaData.m_minDelta), std::abs(metaData.m_maxDelta));
  256. // Normal, Tangent, and Bitangent deltas can be as high as 2
  257. maxDelta = AZStd::max(maxDelta, 2.0f);
  258. // Since multiple morphs can be fully active at once, sum the maximum offset in either positive or negative direction
  259. // that can be applied each individual morph to get the maximum offset that could be applied across all morphs
  260. ranges[metaData.m_meshIndex] += maxWeight * maxDelta;
  261. }
  262. // Calculate the final encoding value
  263. for (size_t i = 0; i < ranges.size(); ++i)
  264. {
  265. if (ranges[i] < std::numeric_limits<float>::epsilon())
  266. {
  267. // There are no morph targets for this mesh
  268. ranges[i] = -1.0f;
  269. }
  270. else
  271. {
  272. // Given a conservative maximum value of a delta (minimum if negated), set a value for encoding a float as an integer that maximizes precision
  273. // while still being able to represent the entire range of possible offset values for this instance
  274. // For example, if at most all the deltas accumulated fell between a -1 and 1 range, we'd encode it as an integer by multiplying it by 2,147,483,647.
  275. // If the delta has a larger range, we multiply it by a smaller number, increasing the range of representable values but decreasing the precision
  276. m_meshes[i].m_morphTargetIntegerEncoding = static_cast<float>(std::numeric_limits<int>::max()) / ranges[i];
  277. }
  278. }
  279. }
  280. bool SkinnedMeshInputLod::HasMorphTargetsForMesh(uint32_t meshIndex) const
  281. {
  282. return m_meshes[meshIndex].m_morphTargetIntegerEncoding > 0.0f;
  283. }
  284. SkinnedMeshInputBuffers::SkinnedMeshInputBuffers() = default;
  285. SkinnedMeshInputBuffers::~SkinnedMeshInputBuffers() = default;
  286. void SkinnedMeshInputBuffers::CreateFromModelAsset(const Data::Asset<RPI::ModelAsset>& modelAsset)
  287. {
  288. if (!modelAsset.IsReady())
  289. {
  290. AZ_Error("SkinnedMeshInputBuffers", false, "Trying to create a skinned mesh from a model '%s' that isn't loaded.", modelAsset.GetHint().c_str());
  291. return;
  292. }
  293. m_modelAsset = modelAsset;
  294. m_model = RPI::Model::FindOrCreate(m_modelAsset);
  295. if (m_model)
  296. {
  297. m_lods.resize(m_model->GetLodCount());
  298. for (uint32_t lodIndex = 0; lodIndex < m_model->GetLodCount(); ++lodIndex)
  299. {
  300. // Add a new lod to the SkinnedMeshInputBuffers
  301. SkinnedMeshInputLod& skinnedMeshLod = m_lods[lodIndex];
  302. skinnedMeshLod.CreateFromModelLod(m_modelAsset, m_model, lodIndex);
  303. }
  304. }
  305. }
  306. Data::Asset<RPI::ModelAsset> SkinnedMeshInputBuffers::GetModelAsset() const
  307. {
  308. return m_modelAsset;
  309. }
  310. Data::Instance<RPI::Model> SkinnedMeshInputBuffers::GetModel() const
  311. {
  312. return m_model;
  313. }
  314. uint32_t SkinnedMeshInputBuffers::GetMeshCount(uint32_t lodIndex) const
  315. {
  316. return aznumeric_caster(m_lods[lodIndex].m_meshes.size());
  317. }
  318. uint32_t SkinnedMeshInputBuffers::GetLodCount() const
  319. {
  320. return aznumeric_caster(m_lods.size());
  321. }
  322. const SkinnedMeshInputLod& SkinnedMeshInputBuffers::GetLod(uint32_t lodIndex) const
  323. {
  324. AZ_Assert(lodIndex < m_lods.size(), "Attempting to get lod at index %" PRIu32 " in SkinnedMeshInputBuffers, which is outside the range of %zu.", lodIndex, m_lods.size());
  325. return m_lods[lodIndex];
  326. }
  327. uint32_t SkinnedMeshInputBuffers::GetVertexCount(uint32_t lodIndex, uint32_t meshIndex) const
  328. {
  329. return m_lods[lodIndex].m_meshes[meshIndex].m_vertexCount;
  330. }
  331. uint32_t SkinnedMeshInputBuffers::GetInfluenceCountPerVertex(uint32_t lodIndex, uint32_t meshIndex) const
  332. {
  333. return m_lods[lodIndex].m_meshes[meshIndex].m_skinInfluenceCountPerVertex;
  334. }
  335. const AZStd::vector<MorphTargetComputeMetaData>& SkinnedMeshInputBuffers::GetMorphTargetComputeMetaDatas(uint32_t lodIndex) const
  336. {
  337. return m_lods[lodIndex].m_morphTargetComputeMetaDatas;
  338. }
  339. const AZStd::vector<AZStd::intrusive_ptr<MorphTargetInputBuffers>>& SkinnedMeshInputBuffers::GetMorphTargetInputBuffers(uint32_t lodIndex) const
  340. {
  341. return m_lods[lodIndex].m_morphTargetInputBuffers;
  342. }
  343. float SkinnedMeshInputBuffers::GetMorphTargetIntegerEncoding(uint32_t lodIndex, uint32_t meshIndex) const
  344. {
  345. return m_lods[lodIndex].m_meshes[meshIndex].m_morphTargetIntegerEncoding;
  346. }
  347. void SkinnedMeshInputBuffers::AddMorphTarget(
  348. uint32_t lodIndex,
  349. const RPI::MorphTargetMetaAsset::MorphTarget& morphTarget,
  350. const RPI::BufferAssetView* morphBufferAssetView,
  351. const AZStd::string& bufferNamePrefix,
  352. float minWeight,
  353. float maxWeight)
  354. {
  355. m_lods[lodIndex].AddMorphTarget(morphTarget, morphBufferAssetView, bufferNamePrefix, minWeight, maxWeight);
  356. }
  357. void SkinnedMeshInputBuffers::Finalize()
  358. {
  359. for (SkinnedMeshInputLod& lod : m_lods)
  360. {
  361. lod.CalculateMorphTargetIntegerEncodings();
  362. }
  363. }
  364. void SkinnedMeshInputBuffers::SetBufferViewsOnShaderResourceGroup(
  365. uint32_t lodIndex, uint32_t meshIndex, const Data::Instance<RPI::ShaderResourceGroup>& perInstanceSRG)
  366. {
  367. AZ_Assert(lodIndex < m_lods.size() && meshIndex < m_lods[lodIndex].m_modelLodAsset->GetMeshes().size(), "Lod %" PRIu32 " Mesh %" PRIu32 " out of range for model '%s'", lodIndex, meshIndex, m_modelAsset->GetName().GetCStr());
  368. // Loop over each input buffer view and set it on the srg
  369. for (const SkinnedSubMeshProperties::SrgNameViewPair& nameViewPair :
  370. m_lods[lodIndex].m_meshes[meshIndex].m_inputBufferViews)
  371. {
  372. RHI::ShaderInputBufferIndex srgIndex = perInstanceSRG->FindShaderInputBufferIndex(nameViewPair.m_srgName);
  373. AZ_Error(
  374. "SkinnedMeshInputBuffers", srgIndex.IsValid(),
  375. "Failed to find shader input index for '%s' in the skinning compute shader per-instance SRG.",
  376. nameViewPair.m_srgName.GetCStr());
  377. [[maybe_unused]] bool success = perInstanceSRG->SetBufferView(srgIndex, nameViewPair.m_bufferView.get());
  378. AZ_Error("SkinnedMeshInputBuffers", success, "Failed to bind buffer view for %s", nameViewPair.m_srgName.GetCStr());
  379. }
  380. RHI::ShaderInputConstantIndex srgConstantIndex;
  381. // Set the vertex count
  382. srgConstantIndex = perInstanceSRG->FindShaderInputConstantIndex(Name{ "m_numVertices" });
  383. AZ_Error(
  384. "SkinnedMeshInputBuffers", srgConstantIndex.IsValid(),
  385. "Failed to find shader input index for m_numVerticies in the skinning compute shader per-instance SRG.");
  386. perInstanceSRG->SetConstant(srgConstantIndex, m_lods[lodIndex].m_meshes[meshIndex].m_vertexCount);
  387. // Set the max influences per vertex for the mesh
  388. srgConstantIndex = perInstanceSRG->FindShaderInputConstantIndex(Name{ "m_numInfluencesPerVertex" });
  389. AZ_Error(
  390. "SkinnedMeshInputBuffers", srgConstantIndex.IsValid(),
  391. "Failed to find shader input index for m_numInfluencesPerVertex in the skinning compute shader per-instance SRG.");
  392. perInstanceSRG->SetConstant(srgConstantIndex, m_lods[lodIndex].m_meshes[meshIndex].m_skinInfluenceCountPerVertex);
  393. }
  394. // Create a resource view that has a different type than the data it is viewing
  395. static RHI::BufferViewDescriptor CreateResourceViewWithDifferentFormat(
  396. uint64_t offsetInBytes,
  397. uint32_t realElementCount,
  398. uint32_t realElementSize,
  399. RHI::Format format,
  400. RHI::BufferBindFlags overrideBindFlags)
  401. {
  402. RHI::BufferViewDescriptor viewDescriptor;
  403. uint64_t elementOffset = offsetInBytes / RHI::GetFormatSize(format);
  404. AZ_Assert(elementOffset <= std::numeric_limits<uint32_t>().max(), "The offset in bytes from the start of the SkinnedMeshOutputStream buffer is too large to be expressed as a uint32_t element offset in the BufferViewDescriptor.");
  405. viewDescriptor.m_elementOffset = aznumeric_cast<uint32_t>(elementOffset);
  406. viewDescriptor.m_elementCount = realElementCount * (realElementSize / RHI::GetFormatSize(format));
  407. viewDescriptor.m_elementFormat = format;
  408. viewDescriptor.m_elementSize = RHI::GetFormatSize(format);
  409. viewDescriptor.m_overrideBindFlags = overrideBindFlags;
  410. return viewDescriptor;
  411. }
  412. static bool AllocateLodStream(
  413. uint8_t outputStreamIndex,
  414. size_t vertexCount,
  415. AZStd::intrusive_ptr<SkinnedMeshInstance> instance,
  416. SkinnedMeshOutputVertexOffsets& streamOffsetsFromBufferStart,
  417. AZStd::vector<AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation>>& lodAllocations)
  418. {
  419. const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex));
  420. // Positions use 2x the number of vertices to hold both the current frame and previous frame's data
  421. size_t positionMultiplier = static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex) == SkinnedMeshOutputVertexStreams::Position ? 2u : 1u;
  422. AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation> allocation = SkinnedMeshOutputStreamManagerInterface::Get()->Allocate(vertexCount * static_cast<size_t>(outputStreamInfo.m_elementSize) * positionMultiplier);
  423. if (!allocation)
  424. {
  425. // Suppress the OnMemoryFreed signal when releasing the previous successful allocations
  426. // The memory was already free before this function was called, so it's not really newly available memory
  427. AZ_Error("SkinnedMeshInputBuffers", false, "Out of memory to create a skinned mesh instance. Consider increasing r_skinnedMeshInstanceMemoryPoolSize");
  428. instance->m_allocations.push_back(lodAllocations);
  429. instance->SuppressSignalOnDeallocate();
  430. return false;
  431. }
  432. lodAllocations.push_back(allocation);
  433. streamOffsetsFromBufferStart[outputStreamIndex] = aznumeric_cast<uint32_t>(allocation->GetVirtualAddress().m_ptr);
  434. return true;
  435. }
  436. static bool AllocateMorphTargetsForLod(const SkinnedMeshInputLod& lod, AZStd::intrusive_ptr<SkinnedMeshInstance> instance, AZStd::vector<AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation>>& lodAllocations)
  437. {
  438. AZStd::vector<MorphTargetInstanceMetaData> instanceMetaDatas;
  439. for (uint32_t meshIndex = 0; meshIndex < lod.GetModelLodAsset()->GetMeshes().size(); ++meshIndex)
  440. {
  441. uint32_t vertexCount = lod.GetModelLodAsset()->GetMeshes()[meshIndex].GetVertexCount();
  442. // If this skinned mesh has morph targets, allocate a buffer for the accumulated deltas that come from the morph target pass
  443. if (lod.HasMorphTargetsForMesh(meshIndex))
  444. {
  445. // Naively, we're going to allocate enough memory to store the accumulated delta for every vertex.
  446. // This makes it simple for the skinning shader to index into the buffer, but the memory cost
  447. // could be reduced by keeping a buffer that maps from vertexId to morph target delta offset ATOM-14427
  448. // We're also using the skinned mesh output buffer, since it gives us a read-write pool of memory that can be
  449. // used for dependency tracking between passes. This can be switched to a transient memory pool so that the memory is free
  450. // later in the frame once skinning is finished ATOM-14429
  451. size_t perVertexSizeInBytes = static_cast<size_t>(MorphTargetConstants::s_unpackedMorphTargetDeltaSizeInBytes) * MorphTargetConstants::s_morphTargetDeltaTypeCount;
  452. AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation> allocation = SkinnedMeshOutputStreamManagerInterface::Get()->Allocate(vertexCount * perVertexSizeInBytes);
  453. if (!allocation)
  454. {
  455. // Suppress the OnMemoryFreed signal when releasing the previous successful allocations
  456. // The memory was already free before this function was called, so it's not really newly available memory
  457. AZ_Error("SkinnedMeshInputBuffers", false, "Out of memory to create a skinned mesh instance. Consider increasing r_skinnedMeshInstanceMemoryPoolSize");
  458. instance->m_allocations.push_back(lodAllocations);
  459. instance->SuppressSignalOnDeallocate();
  460. return false;
  461. }
  462. else
  463. {
  464. // We're using an offset into a global buffer to be able to access the morph target offsets in a bindless manner.
  465. // The offset can at most be a 32-bit uint until AZSL supports 64-bit uints. This gives us a 4GB limit for where the
  466. // morph target deltas can live. In practice, the offsets could end up outside that range even if less that 4GB is used
  467. // if the memory becomes fragmented. To address it, we can split morph target deltas into their own buffer, allocate
  468. // memory in pages with a buffer for each page, or create and bind a buffer view
  469. // so we are not doing an offset from the beginning of the buffer
  470. AZ_Error("SkinnedMeshInputBuffers", allocation->GetVirtualAddress().m_ptr < static_cast<uintptr_t>(std::numeric_limits<uint32_t>::max()), "Morph target deltas allocated from the skinned mesh memory pool are outside the range that can be accessed from the skinning shader");
  471. MorphTargetInstanceMetaData instanceMetaData;
  472. // Positions start at the beginning of the allocation
  473. instanceMetaData.m_accumulatedPositionDeltaOffsetInBytes = static_cast<int32_t>(allocation->GetVirtualAddress().m_ptr);
  474. uint32_t deltaStreamSizeInBytes = static_cast<uint32_t>(vertexCount * MorphTargetConstants::s_unpackedMorphTargetDeltaSizeInBytes);
  475. // Followed by normals, tangents, and bitangents
  476. instanceMetaData.m_accumulatedNormalDeltaOffsetInBytes = instanceMetaData.m_accumulatedPositionDeltaOffsetInBytes + deltaStreamSizeInBytes;
  477. instanceMetaData.m_accumulatedTangentDeltaOffsetInBytes = instanceMetaData.m_accumulatedNormalDeltaOffsetInBytes + deltaStreamSizeInBytes;
  478. instanceMetaData.m_accumulatedBitangentDeltaOffsetInBytes = instanceMetaData.m_accumulatedTangentDeltaOffsetInBytes + deltaStreamSizeInBytes;
  479. // Track both the allocation and the metadata in the instance
  480. instanceMetaDatas.push_back(instanceMetaData);
  481. lodAllocations.push_back(allocation);
  482. }
  483. }
  484. else
  485. {
  486. // Use invalid offsets to indicate there are no morph targets for this mesh
  487. // This allows the SkinnedMeshDispatchItem to know it doesn't need to consume morph target deltas during skinning.
  488. MorphTargetInstanceMetaData instanceMetaData{ MorphTargetConstants::s_invalidDeltaOffset, MorphTargetConstants::s_invalidDeltaOffset, MorphTargetConstants::s_invalidDeltaOffset, MorphTargetConstants::s_invalidDeltaOffset };
  489. instanceMetaDatas.push_back(instanceMetaData);
  490. }
  491. }
  492. instance->m_morphTargetInstanceMetaData.push_back(instanceMetaDatas);
  493. return true;
  494. }
  495. static void AddSubMeshViewToModelLodCreator(
  496. uint8_t outputStreamIndex,
  497. uint32_t lodVertexCount,
  498. uint32_t submeshVertexCount,
  499. Data::Asset<RPI::BufferAsset> skinnedMeshOutputBufferAsset,
  500. const SkinnedMeshOutputVertexOffsets& streamOffsetsFromBufferStart,
  501. SkinnedMeshOutputVertexOffsets& subMeshOffsetsFromStreamStart,
  502. RPI::ModelLodAssetCreator& modelLodCreator)
  503. {
  504. const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex));
  505. // For the purpose of the model, which is fed to the static mesh feature processor, these buffer views are only going to be used as input assembly.
  506. // The underlying buffer is still writable and will be written to by the skinning shader.
  507. RHI::BufferViewDescriptor viewDescriptor = CreateResourceViewWithDifferentFormat(
  508. aznumeric_cast<uint64_t>(streamOffsetsFromBufferStart[outputStreamIndex]) + aznumeric_cast<uint64_t>(subMeshOffsetsFromStreamStart[outputStreamIndex]),
  509. submeshVertexCount, outputStreamInfo.m_elementSize, outputStreamInfo.m_elementFormat, RHI::BufferBindFlags::InputAssembly);
  510. AZ_Assert(streamOffsetsFromBufferStart[outputStreamIndex] % outputStreamInfo.m_elementSize == 0, "The SkinnedMeshOutputStreamManager is supposed to guarantee that offsets can always align.");
  511. RPI::BufferAssetView bufferView{ skinnedMeshOutputBufferAsset, viewDescriptor };
  512. modelLodCreator.AddMeshStreamBuffer(outputStreamInfo.m_semantic, AZ::Name(), bufferView);
  513. if (static_cast<SkinnedMeshOutputVertexStreams>(outputStreamIndex) == SkinnedMeshOutputVertexStreams::Position)
  514. {
  515. // Add stream buffer for position history
  516. size_t positionHistoryBufferOffsetInBytes = streamOffsetsFromBufferStart[outputStreamIndex] + subMeshOffsetsFromStreamStart[outputStreamIndex] + lodVertexCount * outputStreamInfo.m_elementSize;
  517. viewDescriptor.m_elementOffset = aznumeric_cast<uint32_t>(positionHistoryBufferOffsetInBytes / outputStreamInfo.m_elementSize);
  518. bufferView = { skinnedMeshOutputBufferAsset, viewDescriptor };
  519. modelLodCreator.AddMeshStreamBuffer(RHI::ShaderSemantic{ Name{"POSITIONT"} }, AZ::Name(), bufferView);
  520. }
  521. subMeshOffsetsFromStreamStart[outputStreamIndex] += viewDescriptor.m_elementCount * viewDescriptor.m_elementSize;
  522. }
  523. AZStd::intrusive_ptr<SkinnedMeshInstance> SkinnedMeshInputBuffers::CreateSkinnedMeshInstance() const
  524. {
  525. // This function creates a SkinnedMeshInstance which describes all the buffer views needed to write the output of the skinned mesh compute shader
  526. // and a model which can be rendered by the MeshFeatureProcessor
  527. // Static data that doesn't get modified during skinning (e.g. index buffer, uvs) is shared between all instances that use the same SkinnedMeshInputBuffers
  528. // The buffers for this static data and the per sub-mesh views into these buffers were created when the SkinnedMeshInputBuffers was created.
  529. // This function adds those views to the model when creating it
  530. // For the output of the skinned mesh shader, each instance has unique vertex data that exists in a single buffer managed by the SkinnedMeshOutputStreamManager
  531. // For a given stream all of the vertices for an entire lod is contiguous in memory, allowing the entire lod to be skinned at once in as part of a single dispatch
  532. // The streams are de-interleaved, and each stream may reside independently within the output buffer as determined by the best fit allocator
  533. // E.g. the positions may or may not be adjacent to normals, but all of the positions for a single lod with be contiguous
  534. // To support multiple sub-meshes, views into each stream for each lod are created for the sub-meshes
  535. // SkinnedMeshOutputBuffer[.....................................................................................................................................]
  536. // lod0 Positions[^ ^] lod0Normals[^ ^] lod1Positions[^ ^] lod1Normals[^ ^]
  537. // lod0 subMesh0+1 Positions[^ ^^ ^] lod0 subMesh0+1 Normals[^ ^^ ^] lod1 sm0+1 pos[^ ^^ ^] lod1 sm0+1 norm[^ ^^ ^]
  538. AZ_PROFILE_SCOPE(AzRender, "SkinnedMeshInputBuffers: CreateSkinnedMeshInstance");
  539. AZStd::intrusive_ptr<SkinnedMeshInstance> instance = aznew SkinnedMeshInstance;
  540. // Each model gets a unique, random ID, so if the same source model is used for multiple instances, multiple target models will be created.
  541. RPI::ModelAssetCreator modelCreator;
  542. modelCreator.Begin(Uuid::CreateRandom());
  543. // Use the name from the original model
  544. modelCreator.SetName(m_modelAsset->GetName().GetStringView());
  545. Data::Asset<RPI::BufferAsset> skinnedMeshOutputBufferAsset = SkinnedMeshOutputStreamManagerInterface::Get()->GetBufferAsset();
  546. size_t lodIndex = 0;
  547. for (const SkinnedMeshInputLod& lod : m_lods)
  548. {
  549. RPI::ModelLodAssetCreator modelLodCreator;
  550. modelLodCreator.Begin(Data::AssetId(Uuid::CreateRandom()));
  551. //
  552. // Lod
  553. //
  554. Data::Asset<RPI::ModelLodAsset> inputLodAsset = m_modelAsset->GetLodAssets()[lodIndex];
  555. // Add a reference to the shared index buffer
  556. modelLodCreator.AddLodStreamBuffer(inputLodAsset->GetIndexBufferAsset());
  557. // There is only one underlying buffer that houses all of the skinned mesh output streams for all skinned mesh instances
  558. modelLodCreator.AddLodStreamBuffer(skinnedMeshOutputBufferAsset);
  559. // Add any shared static buffers
  560. for (const Data::Asset<RPI::BufferAsset>& staticBufferAsset : lod.m_staticBufferAssets)
  561. {
  562. modelLodCreator.AddLodStreamBuffer(staticBufferAsset);
  563. }
  564. // Track offsets for each stream, so that the sub-meshes know where to begin
  565. SkinnedMeshOutputVertexOffsets streamOffsetsFromBufferStart = {0};
  566. AZStd::vector<AZStd::intrusive_ptr<SkinnedMeshOutputStreamAllocation>> lodAllocations;
  567. // The skinning shader doesn't differentiate between sub-meshes, it just writes all the vertices at once.
  568. // So we want to pack all the positions for each sub-mesh together, all the normals together, etc.
  569. for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex)
  570. {
  571. if (!AllocateLodStream(outputStreamIndex, lod.m_outputVertexCountsByStream[outputStreamIndex], instance, streamOffsetsFromBufferStart, lodAllocations))
  572. {
  573. return nullptr;
  574. }
  575. }
  576. if (!AllocateMorphTargetsForLod(lod, instance, lodAllocations))
  577. {
  578. return nullptr;
  579. }
  580. instance->m_allocations.push_back(lodAllocations);
  581. //
  582. // Submesh
  583. //
  584. AZStd::vector<SkinnedMeshOutputVertexOffsets> meshOffsetsFromBufferStartInBytes;
  585. meshOffsetsFromBufferStartInBytes.reserve(lod.m_meshes.size());
  586. AZStd::vector<uint32_t> meshPositionHistoryBufferOffsetsInBytes;
  587. meshPositionHistoryBufferOffsetsInBytes.reserve(lod.m_meshes.size());
  588. AZStd::vector<bool> isSkinningEnabledPerMesh;
  589. isSkinningEnabledPerMesh.reserve(lod.m_meshes.size());
  590. SkinnedMeshOutputVertexOffsets currentMeshOffsetsFromStreamStartInBytes = {0};
  591. // Iterate over each sub-mesh for the lod to create views into the buffers
  592. for (size_t i = 0; i < lod.m_meshes.size(); ++i)
  593. {
  594. modelLodCreator.BeginMesh();
  595. // Set the index buffer view
  596. const auto inputMeshes = lod.m_modelLodAsset->GetMeshes();
  597. const RPI::ModelLodAsset::Mesh& inputMesh = inputMeshes[i];
  598. modelLodCreator.SetMeshIndexBuffer(inputMesh.GetIndexBufferAssetView());
  599. // Track the offsets from the start of the global output buffer
  600. // for the current mesh to feed to the skinning shader so it
  601. // knows where to write to
  602. SkinnedMeshOutputVertexOffsets currentMeshOffsetsFromBufferStartInBytes = {0};
  603. for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex)
  604. {
  605. currentMeshOffsetsFromBufferStartInBytes[outputStreamIndex] = streamOffsetsFromBufferStart[outputStreamIndex] + currentMeshOffsetsFromStreamStartInBytes[outputStreamIndex];
  606. }
  607. meshOffsetsFromBufferStartInBytes.push_back(currentMeshOffsetsFromBufferStartInBytes);
  608. // Track the offset for the position history buffer
  609. uint32_t meshPositionHistoryBufferOffsetInBytes =
  610. currentMeshOffsetsFromBufferStartInBytes[static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::Position)] +
  611. lod.GetVertexCount() *
  612. SkinnedMeshVertexStreamPropertyInterface::Get()
  613. ->GetOutputStreamInfo(SkinnedMeshOutputVertexStreams::Position)
  614. .m_elementSize;
  615. meshPositionHistoryBufferOffsetsInBytes.push_back(meshPositionHistoryBufferOffsetInBytes);
  616. // Create and set the views into the skinning output buffers
  617. for (uint8_t outputStreamIndex = 0; outputStreamIndex < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); ++outputStreamIndex)
  618. {
  619. // Add a buffer view to the output model so it knows where to read the final skinned vertex data from
  620. AddSubMeshViewToModelLodCreator(
  621. outputStreamIndex, lod.m_outputVertexCountsByStream[static_cast<uint8_t>(outputStreamIndex)],
  622. lod.m_meshes[i].m_vertexCount, skinnedMeshOutputBufferAsset, streamOffsetsFromBufferStart,
  623. currentMeshOffsetsFromStreamStartInBytes, modelLodCreator);
  624. }
  625. // Set the views into the static buffers
  626. for (const RPI::ModelLodAsset::Mesh::StreamBufferInfo& staticBufferInfo : lod.m_meshes[i].m_staticBufferInfo)
  627. {
  628. modelLodCreator.AddMeshStreamBuffer(staticBufferInfo.m_semantic, staticBufferInfo.m_customName, staticBufferInfo.m_bufferAssetView);
  629. }
  630. // Skip the skinning dispatch if there are no skin influences.
  631. isSkinningEnabledPerMesh.push_back(lod.m_meshes[i].m_skinInfluenceCountPerVertex > 0);
  632. Aabb localAabb = inputMesh.GetAabb();
  633. modelLodCreator.SetMeshAabb(localAabb);
  634. modelCreator.AddMaterialSlot(m_modelAsset->FindMaterialSlot(inputMesh.GetMaterialSlotId()));
  635. modelLodCreator.SetMeshMaterialSlot(inputMesh.GetMaterialSlotId());
  636. modelLodCreator.EndMesh();
  637. }
  638. // Add all the mesh offsets for the lod
  639. instance->m_outputStreamOffsetsInBytes.push_back(meshOffsetsFromBufferStartInBytes);
  640. instance->m_positionHistoryBufferOffsetsInBytes.push_back(meshPositionHistoryBufferOffsetsInBytes);
  641. instance->m_isSkinningEnabled.push_back(isSkinningEnabledPerMesh);
  642. Data::Asset<RPI::ModelLodAsset> lodAsset;
  643. modelLodCreator.End(lodAsset);
  644. if (!lodAsset.IsReady())
  645. {
  646. // [GFX TODO] During mesh reload the modelLodCreator could report errors and result in the lodAsset not ready.
  647. return nullptr;
  648. }
  649. modelCreator.AddLodAsset(AZStd::move(lodAsset));
  650. lodIndex++;
  651. }
  652. Data::Asset<RPI::ModelAsset> modelAsset;
  653. modelCreator.End(modelAsset);
  654. instance->m_model = RPI::Model::FindOrCreate(modelAsset);
  655. return instance;
  656. }
  657. } // namespace Render
  658. }// namespace AZ