EditorStateMeshDrawPacket.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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 <Draw/EditorStateMeshDrawPacket.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. namespace AZ::Render
  18. {
  19. EditorStateMeshDrawPacket::EditorStateMeshDrawPacket(
  20. RPI::ModelLod& modelLod,
  21. size_t modelLodMeshIndex,
  22. Data::Instance<RPI::Material> materialOverride,
  23. AZ::Name drawList,
  24. Data::Instance<RPI::ShaderResourceGroup> objectSrg,
  25. const RPI::MaterialModelUvOverrideMap& materialModelUvMap
  26. )
  27. : m_modelLod(&modelLod)
  28. , m_modelLodMeshIndex(modelLodMeshIndex)
  29. , m_objectSrg(objectSrg)
  30. , m_material(materialOverride)
  31. , m_materialModelUvMap(materialModelUvMap)
  32. {
  33. if (!m_material)
  34. {
  35. const auto meshes = m_modelLod->GetMeshes();
  36. const RPI::ModelLod::Mesh& mesh = meshes[m_modelLodMeshIndex];
  37. m_material = mesh.m_material;
  38. }
  39. RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get();
  40. RHI::DrawListTagRegistry* drawListTagRegistry = rhiSystem->GetDrawListTagRegistry();
  41. m_drawListTag = drawListTagRegistry->AcquireTag(drawList);
  42. }
  43. Data::Instance<RPI::Material> EditorStateMeshDrawPacket::GetMaterial()
  44. {
  45. return m_material;
  46. }
  47. bool EditorStateMeshDrawPacket::SetShaderOption(const Name& shaderOptionName, RPI::ShaderOptionValue value)
  48. {
  49. // check if the material owns this option in any of its shaders, if so it can't be set externally
  50. if (m_material->MaterialOwnsShaderOption(shaderOptionName))
  51. {
  52. return false;
  53. }
  54. m_material->ForAllShaderItems(
  55. [&](const Name&, const RPI::ShaderCollection::Item& shaderItem)
  56. {
  57. const RPI::ShaderOptionGroupLayout* layout = shaderItem.GetShaderOptions()->GetShaderOptionLayout();
  58. RPI::ShaderOptionIndex index = layout->FindShaderOptionIndex(shaderOptionName);
  59. if (index.IsValid())
  60. {
  61. // try to find an existing option entry in the list
  62. auto itEntry = AZStd::find_if(m_shaderOptions.begin(), m_shaderOptions.end(), [&shaderOptionName](const ShaderOptionPair& entry)
  63. {
  64. return entry.first == shaderOptionName;
  65. });
  66. // store the option name and value, they will be used in DoUpdate() to select the appropriate shader variant
  67. if (itEntry == m_shaderOptions.end())
  68. {
  69. m_shaderOptions.push_back({shaderOptionName, value});
  70. }
  71. else
  72. {
  73. itEntry->second = value;
  74. }
  75. }
  76. return true;
  77. });
  78. return true;
  79. }
  80. bool EditorStateMeshDrawPacket::Update(const RPI::Scene& parentScene, bool forceUpdate /*= false*/)
  81. {
  82. // Why we need to check "!m_material->NeedsCompile()"...
  83. // Frame A:
  84. // - Material::SetPropertyValue("foo",...). This bumps the material's CurrentChangeId()
  85. // - Material::Compile() updates all the material's outputs (SRG data, shader selection, shader options, etc).
  86. // - Material::SetPropertyValue("bar",...). This bumps the materials' CurrentChangeId() again.
  87. // - We do not process Material::Compile() a second time because because you can only call SRG::Compile() once per frame. Material::Compile()
  88. // will be processed on the next frame. (See implementation of Material::Compile())
  89. // - EditorStateMeshDrawPacket::Update() is called. It runs DoUpdate() to rebuild the draw packet, but everything is still in the state when "foo" was
  90. // set. The "bar" changes haven't been applied yet. It also sets m_materialChangeId to GetCurrentChangeId(), which corresponds to "bar" not "foo".
  91. // Frame B:
  92. // - Something calls Material::Compile(). This finally updates the material's outputs with the latest data corresponding to "bar".
  93. // - EditorStateMeshDrawPacket::Update() is called. But since the GetCurrentChangeId() hasn't changed since last time, DoUpdate() is not called.
  94. // - The mesh continues rendering with only the "foo" change applied, indefinitely.
  95. if (forceUpdate || (!m_material->NeedsCompile() && m_materialChangeId != m_material->GetCurrentChangeId()))
  96. {
  97. DoUpdate(parentScene);
  98. m_materialChangeId = m_material->GetCurrentChangeId();
  99. return true;
  100. }
  101. return false;
  102. }
  103. bool EditorStateMeshDrawPacket::DoUpdate(const RPI::Scene& parentScene)
  104. {
  105. const auto meshes = m_modelLod->GetMeshes();
  106. const RPI::ModelLod::Mesh& mesh = meshes[m_modelLodMeshIndex];
  107. if (!m_material)
  108. {
  109. AZ_Warning("EditorStateMeshDrawPacket", false, "No material provided for mesh. Skipping.");
  110. return false;
  111. }
  112. RHI::DrawPacketBuilder drawPacketBuilder{RHI::MultiDevice::AllDevices};
  113. drawPacketBuilder.Begin(nullptr);
  114. drawPacketBuilder.SetDrawArguments(mesh.m_drawArguments);
  115. drawPacketBuilder.SetIndexBufferView(mesh.m_indexBufferView);
  116. drawPacketBuilder.AddShaderResourceGroup(m_objectSrg->GetRHIShaderResourceGroup());
  117. drawPacketBuilder.AddShaderResourceGroup(m_material->GetRHIShaderResourceGroup());
  118. // We build the list of used shaders in a local list rather than m_activeShaders so that
  119. // if DoUpdate() fails it won't modify any member data.
  120. EditorStateMeshDrawPacket::ShaderList shaderList;
  121. shaderList.reserve(m_activeShaders.size());
  122. // We have to keep a list of these outside the loops that collect all the shaders because the DrawPacketBuilder
  123. // keeps pointers to StreamBufferViews until DrawPacketBuilder::End() is called. And we use a fixed_vector to guarantee
  124. // that the memory won't be relocated when new entries are added.
  125. AZStd::fixed_vector<RPI::ModelLod::StreamBufferViewList, RHI::DrawPacketBuilder::DrawItemCountMax> streamBufferViewsPerShader;
  126. m_perDrawSrgs.clear();
  127. auto appendShader = [&](const RPI::ShaderCollection::Item& shaderItem, const Name& materialPipelineName)
  128. {
  129. if (!parentScene.HasOutputForPipelineState(m_drawListTag))
  130. {
  131. // drawListTag not found in this scene, so don't render this item
  132. return false;
  133. }
  134. Data::Instance<RPI::Shader> shader = RPI::Shader::FindOrCreate(shaderItem.GetShaderAsset());
  135. if (!shader)
  136. {
  137. AZ_Error("EditorStateMeshDrawPacket", false, "Shader '%s'. Failed to find or create instance", shaderItem.GetShaderAsset()->GetName().GetCStr());
  138. return false;
  139. }
  140. // Set all unspecified shader options to default values, so that we get the most specialized variant possible.
  141. // (because FindVariantStableId treats unspecified options as a request specifically for a variant that doesn't specify those options)
  142. // [GFX TODO][ATOM-3883] We should consider updating the FindVariantStableId algorithm to handle default values for us, and remove this step here.
  143. RPI::ShaderOptionGroup shaderOptions = *shaderItem.GetShaderOptions();
  144. shaderOptions.SetUnspecifiedToDefaultValues();
  145. // [GFX_TODO][ATOM-14476]: according to this usage, we should make the shader input contract uniform across all shader variants.
  146. m_modelLod->CheckOptionalStreams(
  147. shaderOptions,
  148. shader->GetInputContract(),
  149. m_modelLodMeshIndex,
  150. m_materialModelUvMap,
  151. m_material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap());
  152. // apply shader options from this draw packet to the ShaderItem
  153. for (auto& meshShaderOption : m_shaderOptions)
  154. {
  155. Name& name = meshShaderOption.first;
  156. RPI::ShaderOptionValue& value = meshShaderOption.second;
  157. RPI::ShaderOptionIndex index = shaderOptions.FindShaderOptionIndex(name);
  158. if (index.IsValid())
  159. {
  160. shaderOptions.SetValue(name, value);
  161. }
  162. }
  163. const RPI::ShaderVariantId finalVariantId = shaderOptions.GetShaderVariantId();
  164. const RPI::ShaderVariant& variant = shader->GetVariant(finalVariantId);
  165. RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
  166. variant.ConfigurePipelineState(pipelineStateDescriptor, shaderOptions);
  167. // Render states need to merge the runtime variation.
  168. // This allows materials to customize the render states that the shader uses.
  169. const RHI::RenderStates& renderStatesOverlay = *shaderItem.GetRenderStatesOverlay();
  170. RHI::MergeStateInto(renderStatesOverlay, pipelineStateDescriptor.m_renderStates);
  171. streamBufferViewsPerShader.emplace_back();
  172. auto& streamBufferViews = streamBufferViewsPerShader.back();
  173. RPI::UvStreamTangentBitmask uvStreamTangentBitmask;
  174. if (!m_modelLod->GetStreamsForMesh(
  175. pipelineStateDescriptor.m_inputStreamLayout,
  176. streamBufferViews,
  177. &uvStreamTangentBitmask,
  178. shader->GetInputContract(),
  179. m_modelLodMeshIndex,
  180. m_materialModelUvMap,
  181. m_material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap()))
  182. {
  183. return false;
  184. }
  185. auto drawSrgLayout = shader->GetAsset()->GetDrawSrgLayout(shader->GetSupervariantIndex());
  186. Data::Instance<RPI::ShaderResourceGroup> drawSrg;
  187. if (drawSrgLayout)
  188. {
  189. // If the DrawSrg exists we must create and bind it, otherwise the CommandList will fail validation for SRG being null
  190. drawSrg = RPI::ShaderResourceGroup::Create(shader->GetAsset(), shader->GetSupervariantIndex(), drawSrgLayout->GetName());
  191. if (variant.UseKeyFallback() && drawSrgLayout->HasShaderVariantKeyFallbackEntry())
  192. {
  193. drawSrg->SetShaderVariantKeyFallbackValue(shaderOptions.GetShaderVariantKeyFallbackValue());
  194. }
  195. // Pass UvStreamTangentBitmask to the shader if the draw SRG has it.
  196. {
  197. AZ::Name shaderUvStreamTangentBitmask = AZ::Name(RPI::UvStreamTangentBitmask::SrgName);
  198. auto index = drawSrg->FindShaderInputConstantIndex(shaderUvStreamTangentBitmask);
  199. if (index.IsValid())
  200. {
  201. drawSrg->SetConstant(index, uvStreamTangentBitmask.GetFullTangentBitmask());
  202. }
  203. }
  204. drawSrg->Compile();
  205. }
  206. parentScene.ConfigurePipelineState(m_drawListTag, pipelineStateDescriptor);
  207. const RHI::PipelineState* pipelineState = shader->AcquirePipelineState(pipelineStateDescriptor);
  208. if (!pipelineState)
  209. {
  210. AZ_Error("EditorStateMeshDrawPacket", false, "Shader '%s'. Failed to acquire default pipeline state", shaderItem.GetShaderAsset()->GetName().GetCStr());
  211. return false;
  212. }
  213. RHI::DrawPacketBuilder::DrawRequest drawRequest;
  214. drawRequest.m_listTag = m_drawListTag;
  215. drawRequest.m_pipelineState = pipelineState;
  216. drawRequest.m_streamBufferViews = streamBufferViews;
  217. drawRequest.m_stencilRef = m_stencilRef;
  218. drawRequest.m_sortKey = m_sortKey;
  219. if (drawSrg)
  220. {
  221. drawRequest.m_uniqueShaderResourceGroup = drawSrg->GetRHIShaderResourceGroup();
  222. m_perDrawSrgs.push_back(drawSrg);
  223. }
  224. if (materialPipelineName != RPI::MaterialPipelineNone)
  225. {
  226. RHI::DrawFilterTag pipelineTag = parentScene.GetDrawFilterTagRegistry()->AcquireTag(materialPipelineName);
  227. AZ_Assert(pipelineTag.IsValid(), "Could not acquire pipeline filter tag '%s'.", materialPipelineName.GetCStr());
  228. drawRequest.m_drawFilterMask = 1 << pipelineTag.GetIndex();
  229. }
  230. drawPacketBuilder.AddDrawItem(drawRequest);
  231. shaderList.emplace_back(AZStd::move(shader));
  232. return true;
  233. };
  234. // [GFX TODO][ATOM-5625] This really needs to be optimized to put the burden on setting global shader options, not applying global shader options.
  235. // For example, make the shader system collect a map of all shaders and ShaderVaraintIds, and look up the shader option names at set-time.
  236. RPI::ShaderSystemInterface* shaderSystem = RPI::ShaderSystemInterface::Get();
  237. for (auto iter : shaderSystem->GetGlobalShaderOptions())
  238. {
  239. const AZ::Name& shaderOptionName = iter.first;
  240. RPI::ShaderOptionValue value = iter.second;
  241. if (!m_material->SetSystemShaderOption(shaderOptionName, value).IsSuccess())
  242. {
  243. AZ_Warning("EditorStateMeshDrawPacket", false, "Shader option '%s' is owned by this this material. Global value for this option was ignored.", shaderOptionName.GetCStr());
  244. }
  245. }
  246. // TODO(MaterialPipeline): We might want to detect duplicate ShaderItem objects here, and merge them to avoid redundant RHI DrawItems.
  247. m_material->ForAllShaderItems(
  248. [&](const Name& materialPipelineName, const RPI::ShaderCollection::Item& shaderItem)
  249. {
  250. if (shaderItem.IsEnabled())
  251. {
  252. if (shaderList.size() == RHI::DrawPacketBuilder::DrawItemCountMax)
  253. {
  254. AZ_Error("MeshDrawPacket", false, "Material has more than the limit of %d active shader items.", RHI::DrawPacketBuilder::DrawItemCountMax);
  255. return false;
  256. }
  257. appendShader(shaderItem, materialPipelineName);
  258. }
  259. return true;
  260. });
  261. m_drawPacket = drawPacketBuilder.End();
  262. if (m_drawPacket)
  263. {
  264. m_activeShaders = shaderList;
  265. m_materialSrg = m_material->GetRHIShaderResourceGroup();
  266. return true;
  267. }
  268. else
  269. {
  270. AZ_Error("EditorStateMeshDrawPacket", false, "Invalid draw packet generated.");
  271. return false;
  272. }
  273. }
  274. const RHI::DrawPacket* EditorStateMeshDrawPacket::GetRHIDrawPacket() const
  275. {
  276. return m_drawPacket.get();
  277. }
  278. } // namespace AZ::Render