SkinnedMeshInputBuffers.cpp 45 KB

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