3
0

MeshDrawPacket.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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/RPI.Public/MeshDrawPacket.h>
  9. #include <Atom/RPI.Public/RPIUtils.h>
  10. #include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
  11. #include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
  12. #include <Atom/RPI.Public/Scene.h>
  13. #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
  14. #include <Atom/RHI/DrawPacketBuilder.h>
  15. #include <Atom/RHI/RHISystemInterface.h>
  16. #include <AzCore/Console/Console.h>
  17. #include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
  18. namespace AZ
  19. {
  20. namespace RPI
  21. {
  22. AZ_CVAR(bool,
  23. r_forceRootShaderVariantUsage,
  24. false,
  25. [](const bool&) { AZ::Interface<AZ::IConsole>::Get()->PerformCommand("MeshFeatureProcessor.ForceRebuildDrawPackets"); },
  26. ConsoleFunctorFlags::Null,
  27. "(For Testing) Forces usage of root shader variant in the mesh draw packet level, ignoring any other shader variants that may exist."
  28. );
  29. MeshDrawPacket::MeshDrawPacket(
  30. ModelLod& modelLod,
  31. size_t modelLodMeshIndex,
  32. Data::Instance<Material> materialOverride,
  33. Data::Instance<ShaderResourceGroup> objectSrg,
  34. const MaterialModelUvOverrideMap& materialModelUvMap
  35. )
  36. : m_modelLod(&modelLod)
  37. , m_modelLodMeshIndex(modelLodMeshIndex)
  38. , m_objectSrg(objectSrg)
  39. , m_material(materialOverride)
  40. , m_materialModelUvMap(materialModelUvMap)
  41. {
  42. if (!m_material)
  43. {
  44. m_material = GetMesh().m_material;
  45. }
  46. // set to all true so no items would be skipped
  47. m_drawListFilter.set();
  48. }
  49. Data::Instance<Material> MeshDrawPacket::GetMaterial() const
  50. {
  51. return m_material;
  52. }
  53. const ModelLod::Mesh& MeshDrawPacket::GetMesh() const
  54. {
  55. AZ_Assert(m_modelLodMeshIndex < m_modelLod->GetMeshes().size(), "m_modelLodMeshIndex %zu is out of range %zu", m_modelLodMeshIndex, m_modelLod->GetMeshes().size());
  56. return m_modelLod->GetMeshes()[m_modelLodMeshIndex];
  57. }
  58. void MeshDrawPacket::ForValidShaderOptionName(const Name& shaderOptionName, const AZStd::function<bool(const ShaderCollection::Item&, ShaderOptionIndex)>& callback)
  59. {
  60. m_material->ForAllShaderItems(
  61. [&](const Name&, const ShaderCollection::Item& shaderItem)
  62. {
  63. const ShaderOptionGroupLayout* layout = shaderItem.GetShaderOptions()->GetShaderOptionLayout();
  64. ShaderOptionIndex index = layout->FindShaderOptionIndex(shaderOptionName);
  65. if (index.IsValid())
  66. {
  67. bool shouldContinue = callback(shaderItem, index);
  68. if (!shouldContinue)
  69. {
  70. return false;
  71. }
  72. }
  73. return true;
  74. });
  75. }
  76. void MeshDrawPacket::SetStencilRef(uint8_t stencilRef)
  77. {
  78. if (m_stencilRef != stencilRef)
  79. {
  80. m_needUpdate = true;
  81. m_stencilRef = stencilRef;
  82. }
  83. }
  84. void MeshDrawPacket::SetSortKey(RHI::DrawItemSortKey sortKey)
  85. {
  86. if (m_sortKey != sortKey)
  87. {
  88. m_needUpdate = true;
  89. m_sortKey = sortKey;
  90. }
  91. }
  92. bool MeshDrawPacket::SetShaderOption(const Name& shaderOptionName, ShaderOptionValue value)
  93. {
  94. // check if the material owns this option in any of its shaders, if so it can't be set externally
  95. if (m_material->MaterialOwnsShaderOption(shaderOptionName))
  96. {
  97. return false;
  98. }
  99. // Try to find an existing option entry in the list
  100. for (ShaderOptionPair& shaderOptionPair : m_shaderOptions)
  101. {
  102. if (shaderOptionPair.first == shaderOptionName)
  103. {
  104. shaderOptionPair.second = value;
  105. m_needUpdate = true;
  106. return true;
  107. }
  108. }
  109. // Shader option isn't on the list, look to see if it's even valid for at least one shader item, and if so, add it.
  110. ForValidShaderOptionName(shaderOptionName,
  111. [&]([[maybe_unused]] const ShaderCollection::Item& shaderItem, [[maybe_unused]] ShaderOptionIndex index)
  112. {
  113. // Store the option name and value, they will be used in DoUpdate() to select the appropriate shader variant
  114. m_shaderOptions.push_back({ shaderOptionName, value });
  115. return false; // stop checking other shader items.
  116. }
  117. );
  118. m_needUpdate = true;
  119. return true;
  120. }
  121. bool MeshDrawPacket::UnsetShaderOption(const Name& shaderOptionName)
  122. {
  123. // try to find an existing option entry in the list, then remove it by swapping it with the back.
  124. for (ShaderOptionPair& shaderOptionPair : m_shaderOptions)
  125. {
  126. if (shaderOptionPair.first == shaderOptionName)
  127. {
  128. shaderOptionPair = m_shaderOptions.back();
  129. m_shaderOptions.pop_back();
  130. m_needUpdate = true;
  131. return true;
  132. }
  133. }
  134. return false;
  135. }
  136. void MeshDrawPacket::ClearShaderOptions()
  137. {
  138. m_needUpdate = m_shaderOptions.size() > 0;
  139. m_shaderOptions.clear();
  140. }
  141. void MeshDrawPacket::SetEnableDraw(RHI::DrawListTag drawListTag, bool enableDraw)
  142. {
  143. if (drawListTag.IsNull())
  144. {
  145. return;
  146. }
  147. uint8_t index = drawListTag.GetIndex();
  148. if (m_drawListFilter[index] != enableDraw)
  149. {
  150. m_needUpdate = true;
  151. m_drawListFilter[index] = enableDraw;
  152. }
  153. }
  154. RHI::DrawListMask MeshDrawPacket::GetDrawListFilter()
  155. {
  156. return m_drawListFilter;
  157. }
  158. void MeshDrawPacket::ClearDrawListFilter()
  159. {
  160. m_drawListFilter.set();
  161. m_needUpdate = true;
  162. }
  163. bool MeshDrawPacket::Update(const Scene& parentScene, bool forceUpdate /*= false*/)
  164. {
  165. // Setup the Shader variant handler when update this MeshDrawPacket the first time .
  166. // This is because the MeshDrawPacket data can be copied or moved right after it's created.
  167. // The m_shaderVariantHandler won't be copied correctly due to the capture of 'this' pointer.
  168. // Instead of override all the copy and move operators, this might be a better solution.
  169. if (!m_shaderVariantHandler.IsConnected())
  170. {
  171. m_shaderVariantHandler = Material::OnMaterialShaderVariantReadyEvent::Handler(
  172. [this]()
  173. {
  174. this->m_needUpdate = true;
  175. });
  176. m_material->ConnectEvent(m_shaderVariantHandler);
  177. }
  178. // Why we need to check "!m_material->NeedsCompile()"...
  179. // Frame A:
  180. // - Material::SetPropertyValue("foo",...). This bumps the material's CurrentChangeId()
  181. // - Material::Compile() updates all the material's outputs (SRG data, shader selection, shader options, etc).
  182. // - Material::SetPropertyValue("bar",...). This bumps the materials' CurrentChangeId() again.
  183. // - We do not process Material::Compile() a second time because you can only call SRG::Compile() once per frame. Material::Compile()
  184. // will be processed on the next frame. (See implementation of Material::Compile())
  185. // - MeshDrawPacket::Update() is called. It runs DoUpdate() to rebuild the draw packet, but everything is still in the state when "foo" was
  186. // set. The "bar" changes haven't been applied yet. It also sets m_materialChangeId to GetCurrentChangeId(), which corresponds to "bar" not "foo".
  187. // Frame B:
  188. // - Something calls Material::Compile(). This finally updates the material's outputs with the latest data corresponding to "bar".
  189. // - MeshDrawPacket::Update() is called. But since the GetCurrentChangeId() hasn't changed since last time, DoUpdate() is not called.
  190. // - The mesh continues rendering with only the "foo" change applied, indefinitely.
  191. if (forceUpdate || (!m_material->NeedsCompile() && m_materialChangeId != m_material->GetCurrentChangeId())
  192. || m_needUpdate)
  193. {
  194. DoUpdate(parentScene);
  195. m_materialChangeId = m_material->GetCurrentChangeId();
  196. m_needUpdate = false;
  197. DebugOutputShaderVariants();
  198. return true;
  199. }
  200. return false;
  201. }
  202. static bool HasRootConstants(const RHI::ConstantsLayout* rootConstantsLayout)
  203. {
  204. return rootConstantsLayout && rootConstantsLayout->GetDataSize() > 0;
  205. }
  206. void MeshDrawPacket::DebugOutputShaderVariants()
  207. {
  208. #ifdef DEBUG_MESH_SHADERVARIANTS
  209. uint32_t index = 0;
  210. AZ::Data::AssetInfo assetInfo;
  211. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, m_modelLod->GetAssetId());
  212. AZ_TracePrintf("MeshDrawPacket", "Mesh: %s", assetInfo.m_relativePath.data());
  213. for (const auto& variant : m_shaderVariantNames)
  214. {
  215. AZ_TracePrintf("MeshDrawPacket", "%d: %s", index++, variant.data());
  216. }
  217. #endif
  218. }
  219. bool MeshDrawPacket::DoUpdate(const Scene& parentScene)
  220. {
  221. const auto meshes = m_modelLod->GetMeshes();
  222. const ModelLod::Mesh& mesh = meshes[m_modelLodMeshIndex];
  223. if (!m_material)
  224. {
  225. AZ_Warning("MeshDrawPacket", false, "No material provided for mesh. Skipping.");
  226. return false;
  227. }
  228. ShaderReloadDebugTracker::ScopedSection reloadSection("MeshDrawPacket::DoUpdate");
  229. RHI::DrawPacketBuilder drawPacketBuilder;
  230. drawPacketBuilder.Begin(nullptr);
  231. drawPacketBuilder.SetDrawArguments(mesh.m_drawArguments);
  232. drawPacketBuilder.SetIndexBufferView(mesh.m_indexBufferView);
  233. drawPacketBuilder.AddShaderResourceGroup(m_objectSrg->GetRHIShaderResourceGroup());
  234. drawPacketBuilder.AddShaderResourceGroup(m_material->GetRHIShaderResourceGroup());
  235. // We build the list of used shaders in a local list rather than m_activeShaders so that
  236. // if DoUpdate() fails it won't modify any member data.
  237. MeshDrawPacket::ShaderList shaderList;
  238. shaderList.reserve(m_activeShaders.size());
  239. // We have to keep a list of these outside the loops that collect all the shaders because the DrawPacketBuilder
  240. // keeps pointers to StreamBufferViews until DrawPacketBuilder::End() is called. And we use a fixed_vector to guarantee
  241. // that the memory won't be relocated when new entries are added.
  242. AZStd::fixed_vector<ModelLod::StreamBufferViewList, RHI::DrawPacketBuilder::DrawItemCountMax> streamBufferViewsPerShader;
  243. // The root constants are shared by all draw items in the draw packet. We must populate them with default values.
  244. // The draw packet builder needs to know where the data is coming from during appendShader, but it's not actually read
  245. // until drawPacketBuilder.End(), so store the default data out here.
  246. AZStd::vector<uint8_t> rootConstants;
  247. bool isFirstShaderItem = true;
  248. m_perDrawSrgs.clear();
  249. #ifdef DEBUG_MESH_SHADERVARIANTS
  250. m_shaderVariantNames.clear();
  251. #endif
  252. auto appendShader = [&](const ShaderCollection::Item& shaderItem, const Name& materialPipelineName)
  253. {
  254. // Skip the shader item without creating the shader instance
  255. // if the mesh is not going to be rendered based on the draw tag
  256. RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get();
  257. RHI::DrawListTagRegistry* drawListTagRegistry = rhiSystem->GetDrawListTagRegistry();
  258. // Use the explicit draw list override if exists.
  259. RHI::DrawListTag drawListTag = shaderItem.GetDrawListTagOverride();
  260. if (drawListTag.IsNull())
  261. {
  262. Data::Asset<RPI::ShaderAsset> shaderAsset = shaderItem.GetShaderAsset();
  263. if (!shaderAsset.IsReady())
  264. {
  265. // The shader asset needs to be loaded before we can check the draw tag.
  266. // If it's not loaded yet, the instance database will do a blocking load
  267. // when we create the instance below, so might as well load it now.
  268. shaderAsset.QueueLoad();
  269. if (shaderAsset.IsLoading())
  270. {
  271. shaderAsset.BlockUntilLoadComplete();
  272. }
  273. }
  274. drawListTag = drawListTagRegistry->FindTag(shaderAsset->GetDrawListName());
  275. }
  276. // draw list tag is filtered out. skip this item
  277. if (drawListTag.IsNull() || !m_drawListFilter[drawListTag.GetIndex()])
  278. {
  279. return false;
  280. }
  281. if (!parentScene.HasOutputForPipelineState(drawListTag))
  282. {
  283. // drawListTag not found in this scene, so don't render this item
  284. return false;
  285. }
  286. Data::Instance<Shader> shader = RPI::Shader::FindOrCreate(shaderItem.GetShaderAsset());
  287. if (!shader)
  288. {
  289. AZ_Error("MeshDrawPacket", false, "Shader '%s'. Failed to find or create instance", shaderItem.GetShaderAsset()->GetName().GetCStr());
  290. return false;
  291. }
  292. RPI::ShaderOptionGroup shaderOptions = *shaderItem.GetShaderOptions();
  293. // Set all unspecified shader options to default values, so that we get the most specialized variant possible.
  294. // (because FindVariantStableId treats unspecified options as a request specifically for a variant that doesn't specify those options)
  295. // [GFX TODO][ATOM-3883] We should consider updating the FindVariantStableId algorithm to handle default values for us, and remove this step here.
  296. // This might not be necessary anymore though, since ShaderAsset::GetDefaultShaderOptions() does this when the material type builder is creating the ShaderCollection.
  297. shaderOptions.SetUnspecifiedToDefaultValues();
  298. // [GFX_TODO][ATOM-14476]: according to this usage, we should make the shader input contract uniform across all shader variants.
  299. m_modelLod->CheckOptionalStreams(
  300. shaderOptions,
  301. shader->GetInputContract(),
  302. m_modelLodMeshIndex,
  303. m_materialModelUvMap,
  304. m_material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap());
  305. // apply shader options from this draw packet to the ShaderItem
  306. for (auto& meshShaderOption : m_shaderOptions)
  307. {
  308. Name& name = meshShaderOption.first;
  309. RPI::ShaderOptionValue& value = meshShaderOption.second;
  310. ShaderOptionIndex index = shaderOptions.FindShaderOptionIndex(name);
  311. // Shader options will be applied to any shader item that supports it, even if
  312. // not all the shader items in the draw packet support it
  313. if (index.IsValid())
  314. {
  315. shaderOptions.SetValue(name, value);
  316. }
  317. }
  318. const ShaderVariantId requestedVariantId = shaderOptions.GetShaderVariantId();
  319. const ShaderVariant& variant = r_forceRootShaderVariantUsage ? shader->GetRootVariant() : shader->GetVariant(requestedVariantId);
  320. #ifdef DEBUG_MESH_SHADERVARIANTS
  321. m_shaderVariantNames.push_back(variant.GetShaderVariantAsset().GetHint());
  322. #endif
  323. RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
  324. variant.ConfigurePipelineState(pipelineStateDescriptor);
  325. // Render states need to merge the runtime variation.
  326. // This allows materials to customize the render states that the shader uses.
  327. const RHI::RenderStates& renderStatesOverlay = *shaderItem.GetRenderStatesOverlay();
  328. RHI::MergeStateInto(renderStatesOverlay, pipelineStateDescriptor.m_renderStates);
  329. auto& streamBufferViews = streamBufferViewsPerShader.emplace_back();
  330. UvStreamTangentBitmask uvStreamTangentBitmask;
  331. if (!m_modelLod->GetStreamsForMesh(
  332. pipelineStateDescriptor.m_inputStreamLayout,
  333. streamBufferViews,
  334. &uvStreamTangentBitmask,
  335. shader->GetInputContract(),
  336. m_modelLodMeshIndex,
  337. m_materialModelUvMap,
  338. m_material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap()))
  339. {
  340. return false;
  341. }
  342. Data::Instance<ShaderResourceGroup> drawSrg = shader->CreateDrawSrgForShaderVariant(shaderOptions, false);
  343. if (drawSrg)
  344. {
  345. // Pass UvStreamTangentBitmask to the shader if the draw SRG has it.
  346. AZ::Name shaderUvStreamTangentBitmask = AZ::Name(UvStreamTangentBitmask::SrgName);
  347. auto index = drawSrg->FindShaderInputConstantIndex(shaderUvStreamTangentBitmask);
  348. if (index.IsValid())
  349. {
  350. drawSrg->SetConstant(index, uvStreamTangentBitmask.GetFullTangentBitmask());
  351. }
  352. drawSrg->Compile();
  353. }
  354. parentScene.ConfigurePipelineState(drawListTag, pipelineStateDescriptor);
  355. const RHI::PipelineState* pipelineState = shader->AcquirePipelineState(pipelineStateDescriptor);
  356. if (!pipelineState)
  357. {
  358. AZ_Error("MeshDrawPacket", false, "Shader '%s'. Failed to acquire default pipeline state", shaderItem.GetShaderAsset()->GetName().GetCStr());
  359. return false;
  360. }
  361. const RHI::ConstantsLayout* rootConstantsLayout =
  362. pipelineStateDescriptor.m_pipelineLayoutDescriptor->GetRootConstantsLayout();
  363. if(isFirstShaderItem)
  364. {
  365. if (HasRootConstants(rootConstantsLayout))
  366. {
  367. m_rootConstantsLayout = rootConstantsLayout;
  368. rootConstants.resize(m_rootConstantsLayout->GetDataSize());
  369. drawPacketBuilder.SetRootConstants(rootConstants);
  370. }
  371. isFirstShaderItem = false;
  372. }
  373. else
  374. {
  375. AZ_Error(
  376. "MeshDrawPacket",
  377. (!m_rootConstantsLayout && !HasRootConstants(rootConstantsLayout)) ||
  378. (m_rootConstantsLayout && rootConstantsLayout && m_rootConstantsLayout->GetHash() == rootConstantsLayout->GetHash()),
  379. "Shader %s has mis-matched root constant layout in material %s. "
  380. "All draw items in a draw packet need to share the same root constants layout. This means that each pass "
  381. "(e.g. Depth, Shadows, Forward, MotionVectors) for a given materialtype should use the same layout.",
  382. shaderItem.GetShaderAsset()->GetName().GetCStr(),
  383. m_material->GetAsset().ToString<AZStd::string>().c_str());
  384. }
  385. RHI::DrawPacketBuilder::DrawRequest drawRequest;
  386. drawRequest.m_listTag = drawListTag;
  387. drawRequest.m_pipelineState = pipelineState;
  388. drawRequest.m_streamBufferViews = streamBufferViews;
  389. drawRequest.m_stencilRef = m_stencilRef;
  390. drawRequest.m_sortKey = m_sortKey;
  391. if (drawSrg)
  392. {
  393. drawRequest.m_uniqueShaderResourceGroup = drawSrg->GetRHIShaderResourceGroup();
  394. // Hold on to a reference to the drawSrg so the refcount doesn't drop to zero
  395. m_perDrawSrgs.push_back(drawSrg);
  396. }
  397. if (materialPipelineName != MaterialPipelineNone)
  398. {
  399. RHI::DrawFilterTag pipelineTag = parentScene.GetDrawFilterTagRegistry()->AcquireTag(materialPipelineName);
  400. AZ_Assert(pipelineTag.IsValid(), "Could not acquire pipeline filter tag '%s'.", materialPipelineName.GetCStr());
  401. drawRequest.m_drawFilterMask = 1 << pipelineTag.GetIndex();
  402. }
  403. drawPacketBuilder.AddDrawItem(drawRequest);
  404. ShaderData shaderData;
  405. shaderData.m_shader = AZStd::move(shader);
  406. shaderData.m_materialPipelineName = materialPipelineName;
  407. shaderData.m_shaderTag = shaderItem.GetShaderTag();
  408. shaderData.m_requestedShaderVariantId = requestedVariantId;
  409. shaderData.m_activeShaderVariantId = variant.GetShaderVariantId();
  410. shaderData.m_activeShaderVariantStableId = variant.GetStableId();
  411. shaderList.emplace_back(AZStd::move(shaderData));
  412. return true;
  413. };
  414. m_material->ApplyGlobalShaderOptions();
  415. // TODO(MaterialPipeline): We might want to detect duplicate ShaderItem objects here, and merge them to avoid redundant RHI DrawItems.
  416. m_material->ForAllShaderItems(
  417. [&](const Name& materialPipelineName, const ShaderCollection::Item& shaderItem)
  418. {
  419. if (shaderItem.IsEnabled())
  420. {
  421. if (shaderList.size() == RHI::DrawPacketBuilder::DrawItemCountMax)
  422. {
  423. AZ_Error("MeshDrawPacket", false, "Material has more than the limit of %d active shader items.", RHI::DrawPacketBuilder::DrawItemCountMax);
  424. return false;
  425. }
  426. appendShader(shaderItem, materialPipelineName);
  427. }
  428. return true;
  429. });
  430. m_drawPacket = drawPacketBuilder.End();
  431. if (m_drawPacket)
  432. {
  433. m_activeShaders = shaderList;
  434. m_materialSrg = m_material->GetRHIShaderResourceGroup();
  435. return true;
  436. }
  437. else
  438. {
  439. return false;
  440. }
  441. }
  442. const RHI::ConstPtr<RHI::ConstantsLayout> MeshDrawPacket::GetRootConstantsLayout() const
  443. {
  444. return m_rootConstantsLayout;
  445. }
  446. } // namespace RPI
  447. } // namespace AZ