3
0

SkinnedMeshInputBuffers.cpp 44 KB

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