3
0

MaterialFunctorTests.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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 <AzTest/AzTest.h>
  9. #include <Common/RPITestFixture.h>
  10. #include <Common/JsonTestUtils.h>
  11. #include <Common/ErrorMessageFinder.h>
  12. #include <Common/ShaderAssetTestUtils.h>
  13. #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
  14. #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
  15. #include <Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h>
  16. #include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
  17. #include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
  18. #include <Atom/RPI.Reflect/Shader/ShaderAssetCreator.h>
  19. #include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h>
  20. #include <Atom/RPI.Public/Material/Material.h>
  21. #include <Material/MaterialAssetTestUtils.h>
  22. namespace UnitTest
  23. {
  24. using namespace AZ;
  25. using namespace RPI;
  26. class MaterialFunctorTests
  27. : public RPITestFixture
  28. {
  29. public:
  30. class SetShaderOptionFunctor final
  31. : public MaterialFunctor
  32. {
  33. public:
  34. AZ_CLASS_ALLOCATOR(SetShaderOptionFunctor, SystemAllocator)
  35. AZ_RTTI(SetShaderOptionFunctor, "{6316D98D-D2DD-4E9C-808C-58118DC9FF73}", MaterialFunctor);
  36. SetShaderOptionFunctor(Name shaderOptionName, ShaderOptionValue shaderOptionValue)
  37. : m_shaderOptionName(shaderOptionName)
  38. , m_shaderOptionValue(shaderOptionValue)
  39. {
  40. }
  41. using MaterialFunctor::Process;
  42. void Process(MaterialFunctorAPI::RuntimeContext& context) override
  43. {
  44. m_processResult = context.SetShaderOptionValue(m_shaderOptionName, m_shaderOptionValue);
  45. }
  46. // Note a real functor wouldn't do this, it's just for testing
  47. bool GetProcessResult()
  48. {
  49. return m_processResult;
  50. }
  51. private:
  52. Name m_shaderOptionName;
  53. ShaderOptionValue m_shaderOptionValue;
  54. bool m_processResult = false;
  55. };
  56. class PropertyDependencyTestFunctor final
  57. : public MaterialFunctor
  58. {
  59. public:
  60. AZ_CLASS_ALLOCATOR(PropertyDependencyTestFunctor, SystemAllocator)
  61. MOCK_METHOD0(ProcessCalled, void());
  62. using MaterialFunctor::Process;
  63. void Process(RPI::MaterialFunctorAPI::RuntimeContext& context) override
  64. {
  65. ProcessCalled();
  66. context.GetMaterialPropertyValue<int32_t>(m_registedPropertyIndex);
  67. context.GetMaterialPropertyValue<int32_t>(m_registedPropertyName);
  68. // Should report error in the call.
  69. context.GetMaterialPropertyValue<int32_t>(m_unregistedPropertyIndex);
  70. context.GetMaterialPropertyValue<int32_t>(m_unregistedPropertyName);
  71. }
  72. MaterialPropertyIndex m_registedPropertyIndex;
  73. MaterialPropertyIndex m_unregistedPropertyIndex;
  74. AZ::Name m_registedPropertyName;
  75. AZ::Name m_unregistedPropertyName;
  76. };
  77. class PropertyDependencyTestFunctorSourceData final
  78. : public MaterialFunctorSourceData
  79. {
  80. public:
  81. AZ_CLASS_ALLOCATOR(PropertyDependencyTestFunctorSourceData, AZ::SystemAllocator)
  82. using MaterialFunctorSourceData::CreateFunctor;
  83. FunctorResult CreateFunctor(const RuntimeContext& context) const override
  84. {
  85. Ptr<PropertyDependencyTestFunctor> functor = aznew PropertyDependencyTestFunctor;
  86. functor->m_registedPropertyIndex = context.FindMaterialPropertyIndex(Name{ m_registedPropertyName });
  87. EXPECT_TRUE(!functor->m_registedPropertyIndex.IsNull());
  88. AddMaterialPropertyDependency(functor, functor->m_registedPropertyIndex);
  89. functor->m_unregistedPropertyIndex = context.FindMaterialPropertyIndex(Name{ m_unregistedPropertyName });
  90. EXPECT_TRUE(!functor->m_unregistedPropertyIndex.IsNull());
  91. // Intended missing registration to m_materialPropertyDependencies
  92. functor->m_registedPropertyName = m_registedPropertyName;
  93. functor->m_unregistedPropertyName = m_unregistedPropertyName;
  94. return Success(Ptr<MaterialFunctor>(functor));
  95. }
  96. AZStd::string m_registedPropertyName;
  97. AZStd::string m_unregistedPropertyName;
  98. };
  99. protected:
  100. void SetUp() override
  101. {
  102. RPITestFixture::SetUp();
  103. }
  104. void TearDown() override
  105. {
  106. RPITestFixture::TearDown();
  107. }
  108. };
  109. TEST_F(MaterialFunctorTests, MaterialFunctor_RuntimeContext_ShaderOptionNotOwned)
  110. {
  111. using namespace AZ::RPI;
  112. AZStd::vector<RPI::ShaderOptionValuePair> boolOptionValues = CreateBoolShaderOptionValues();
  113. AZ::RPI::Ptr<AZ::RPI::ShaderOptionGroupLayout> shaderOptions = RPI::ShaderOptionGroupLayout::Create();
  114. shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_optionA"}, ShaderOptionType::Boolean, 0, 0, boolOptionValues, Name{"False"}});
  115. shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_optionB"}, ShaderOptionType::Boolean, 1, 1, boolOptionValues, Name{"False"}});
  116. shaderOptions->AddShaderOption(ShaderOptionDescriptor{Name{"o_optionC"}, ShaderOptionType::Boolean, 2, 2, boolOptionValues, Name{"False"}});
  117. shaderOptions->Finalize();
  118. Data::Asset<MaterialTypeAsset> materialTypeAsset;
  119. // Note we don't actually need any properties or functors in the material type. We just need to set up some sample data
  120. // structures that we can pass to the functors below, especially the shader with shader options.
  121. MaterialTypeAssetCreator materialTypeCreator;
  122. materialTypeCreator.Begin(Uuid::CreateRandom());
  123. materialTypeCreator.AddShader(CreateTestShaderAsset(Uuid::CreateRandom(), CreateCommonTestMaterialSrgLayout(), shaderOptions));
  124. // We claim ownership of options A and B, but not C. So C is a globally accessible option, not owned by the material.
  125. materialTypeCreator.ClaimShaderOptionOwnership(Name{"o_optionA"});
  126. materialTypeCreator.ClaimShaderOptionOwnership(Name{"o_optionB"});
  127. EXPECT_TRUE(materialTypeCreator.End(materialTypeAsset));
  128. SetShaderOptionFunctor testFunctorSetOptionA{Name{"o_optionA"}, ShaderOptionValue{1}};
  129. SetShaderOptionFunctor testFunctorSetOptionB{Name{"o_optionB"}, ShaderOptionValue{1}};
  130. SetShaderOptionFunctor testFunctorSetOptionC{Name{"o_optionC"}, ShaderOptionValue{1}};
  131. SetShaderOptionFunctor testFunctorSetOptionInvalid{Name{"o_optionInvalid"}, ShaderOptionValue{1}};
  132. // Most of this data can be empty since this particular functor doesn't access it.
  133. AZStd::vector<MaterialPropertyValue> unusedPropertyValues;
  134. MaterialPropertyCollection properties;
  135. properties.Init(materialTypeAsset->GetMaterialPropertiesLayout(), unusedPropertyValues);
  136. MaterialPipelineDataMap unusedPipelineData;
  137. ShaderResourceGroup* unusedSrg = nullptr;
  138. ShaderCollection shaderCollectionCopy = materialTypeAsset->GetGeneralShaderCollection();
  139. {
  140. // Successfully set o_optionA
  141. MaterialFunctorAPI::RuntimeContext runtimeContext = MaterialFunctorAPI::RuntimeContext{
  142. properties,
  143. & testFunctorSetOptionA.GetMaterialPropertyDependencies(),
  144. AZ::RPI::MaterialPropertyPsoHandling::Allowed,
  145. unusedSrg,
  146. &shaderCollectionCopy,
  147. &unusedPipelineData
  148. };
  149. testFunctorSetOptionA.Process(runtimeContext);
  150. EXPECT_TRUE(testFunctorSetOptionA.GetProcessResult());
  151. EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 0 }).GetIndex());
  152. EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 1 }).GetIndex());
  153. EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 2 }).GetIndex());
  154. }
  155. {
  156. // Successfully set o_optionB
  157. MaterialFunctorAPI::RuntimeContext runtimeContext = MaterialFunctorAPI::RuntimeContext{
  158. properties,
  159. &testFunctorSetOptionB.GetMaterialPropertyDependencies(),
  160. AZ::RPI::MaterialPropertyPsoHandling::Allowed,
  161. unusedSrg,
  162. &shaderCollectionCopy,
  163. &unusedPipelineData
  164. };
  165. testFunctorSetOptionB.Process(runtimeContext);
  166. EXPECT_TRUE(testFunctorSetOptionB.GetProcessResult());
  167. EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 0 }).GetIndex());
  168. EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 1 }).GetIndex());
  169. EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{ 2 }).GetIndex());
  170. }
  171. {
  172. // Fail to set o_optionC because it is not owned by the material type
  173. AZ_TEST_START_TRACE_SUPPRESSION;
  174. MaterialFunctorAPI::RuntimeContext runtimeContext = MaterialFunctorAPI::RuntimeContext{
  175. properties,
  176. &testFunctorSetOptionC.GetMaterialPropertyDependencies(),
  177. AZ::RPI::MaterialPropertyPsoHandling::Allowed,
  178. unusedSrg,
  179. &shaderCollectionCopy,
  180. &unusedPipelineData
  181. };
  182. testFunctorSetOptionC.Process(runtimeContext);
  183. EXPECT_FALSE(testFunctorSetOptionC.GetProcessResult());
  184. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  185. }
  186. {
  187. // Fail to set option index that is out of range
  188. AZ_TEST_START_TRACE_SUPPRESSION;
  189. MaterialFunctorAPI::RuntimeContext runtimeContext = MaterialFunctorAPI::RuntimeContext{
  190. properties,
  191. & testFunctorSetOptionInvalid.GetMaterialPropertyDependencies(),
  192. AZ::RPI::MaterialPropertyPsoHandling::Allowed,
  193. unusedSrg,
  194. &shaderCollectionCopy,
  195. &unusedPipelineData
  196. };
  197. testFunctorSetOptionInvalid.Process(runtimeContext);
  198. EXPECT_FALSE(testFunctorSetOptionInvalid.GetProcessResult());
  199. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  200. }
  201. EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{0}).GetIndex());
  202. EXPECT_EQ(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{1}).GetIndex());
  203. EXPECT_NE(1, shaderCollectionCopy[0].GetShaderOptions()->GetValue(ShaderOptionIndex{2}).GetIndex());
  204. }
  205. TEST_F(MaterialFunctorTests, ReprocessTest)
  206. {
  207. Data::Asset<MaterialTypeAsset> m_testMaterialTypeAsset;
  208. Data::Asset<MaterialAsset> m_testMaterialAsset;
  209. AZ::Name registedPropertyName("PropA");
  210. AZ::Name unregistedPropertyName("PropB");
  211. AZ::Name unrelatedPropertyName("PropC");
  212. MaterialTypeAssetCreator materialTypeCreator;
  213. materialTypeCreator.Begin(Uuid::CreateRandom());
  214. materialTypeCreator.BeginMaterialProperty(registedPropertyName, AZ::RPI::MaterialPropertyDataType::Int);
  215. materialTypeCreator.EndMaterialProperty();
  216. materialTypeCreator.BeginMaterialProperty(unregistedPropertyName, AZ::RPI::MaterialPropertyDataType::Int);
  217. materialTypeCreator.EndMaterialProperty();
  218. materialTypeCreator.BeginMaterialProperty(unrelatedPropertyName, AZ::RPI::MaterialPropertyDataType::Int);
  219. materialTypeCreator.EndMaterialProperty();
  220. materialTypeCreator.SetPropertyValue(registedPropertyName, 42);
  221. materialTypeCreator.SetPropertyValue(unregistedPropertyName, 42);
  222. materialTypeCreator.SetPropertyValue(unrelatedPropertyName, 42);
  223. PropertyDependencyTestFunctorSourceData functorSourceData;
  224. functorSourceData.m_registedPropertyName = registedPropertyName.GetStringView();
  225. functorSourceData.m_unregistedPropertyName = unregistedPropertyName.GetStringView();
  226. MaterialNameContext nameContext;
  227. MaterialFunctorSourceData::FunctorResult result = functorSourceData.CreateFunctor(
  228. MaterialFunctorSourceData::RuntimeContext(
  229. "Dummy.materialtype",
  230. materialTypeCreator.GetMaterialPropertiesLayout(),
  231. materialTypeCreator.GetMaterialShaderResourceGroupLayout(),
  232. &nameContext
  233. )
  234. );
  235. EXPECT_TRUE(result.IsSuccess());
  236. Ptr<MaterialFunctor>& functor = result.GetValue();
  237. EXPECT_TRUE(functor != nullptr);
  238. materialTypeCreator.AddMaterialFunctor(functor);
  239. materialTypeCreator.End(m_testMaterialTypeAsset);
  240. MaterialAssetCreator materialCreator;
  241. materialCreator.Begin(Uuid::CreateRandom(), m_testMaterialTypeAsset);
  242. materialCreator.SetPropertyValue(registedPropertyName, 42);
  243. materialCreator.SetPropertyValue(unregistedPropertyName, 42);
  244. materialCreator.SetPropertyValue(unrelatedPropertyName, 42);
  245. materialCreator.End(m_testMaterialAsset);
  246. EXPECT_TRUE(m_testMaterialAsset->GetMaterialFunctors().size() == 1u);
  247. PropertyDependencyTestFunctor* testFunctor = static_cast<PropertyDependencyTestFunctor*>(m_testMaterialAsset->GetMaterialFunctors()[0].get());
  248. ErrorMessageFinder errorMessageFinder;
  249. // Expect creation will call functor process once.
  250. EXPECT_CALL(*testFunctor, ProcessCalled()).Times(1);
  251. // Suppress 1 error as we know an unregistered dependent property will be accessed.
  252. errorMessageFinder.Reset();
  253. errorMessageFinder.AddExpectedErrorMessage("Material functor accessing an unregistered material property", 2);
  254. Data::Instance<Material> material = Material::FindOrCreate(m_testMaterialAsset);
  255. errorMessageFinder.CheckExpectedErrorsFound();
  256. material->SetPropertyValue(material->FindPropertyIndex(registedPropertyName), int32_t(24));
  257. // Expect dependent property change will call functor process once.
  258. EXPECT_CALL(*testFunctor, ProcessCalled()).Times(1);
  259. // Suppress 1 error as we know an unregistered dependent property will be accessed.
  260. errorMessageFinder.Reset();
  261. errorMessageFinder.AddExpectedErrorMessage("Material functor accessing an unregistered material property", 2);
  262. material->Compile();
  263. errorMessageFinder.CheckExpectedErrorsFound();
  264. // Expect unrelated property change won't call functor process.
  265. material->SetPropertyValue(material->FindPropertyIndex(unrelatedPropertyName), int32_t(24));
  266. EXPECT_CALL(*testFunctor, ProcessCalled()).Times(0);
  267. material->Compile();
  268. m_testMaterialTypeAsset = {};
  269. m_testMaterialAsset = {};
  270. }
  271. class FindPropertyIndexTestFunctor : public MaterialFunctor
  272. {
  273. public:
  274. AZ_CLASS_ALLOCATOR(FindPropertyIndexTestFunctor, AZ::SystemAllocator)
  275. MaterialPropertyIndex m_foundIndex;
  276. };
  277. class FindPropertyIndexTestFunctorSourceData : public MaterialFunctorSourceData
  278. {
  279. public:
  280. AZ_CLASS_ALLOCATOR(FindPropertyIndexTestFunctorSourceData, AZ::SystemAllocator)
  281. Name m_materialPropertyName;
  282. using MaterialFunctorSourceData::CreateFunctor;
  283. FunctorResult CreateFunctor(const RuntimeContext& runtimeContext) const override
  284. {
  285. RPI::Ptr<FindPropertyIndexTestFunctor> functor = aznew FindPropertyIndexTestFunctor;
  286. functor->m_foundIndex = runtimeContext.FindMaterialPropertyIndex(m_materialPropertyName);
  287. return Success(RPI::Ptr<MaterialFunctor>(functor));
  288. }
  289. };
  290. TEST_F(MaterialFunctorTests, UseNameContextInFunctorSourceData_PropertyLookup)
  291. {
  292. Data::Asset<MaterialTypeAsset> materialTypeAsset;
  293. MaterialTypeAssetCreator materialTypeCreator;
  294. materialTypeCreator.Begin(Uuid::CreateRandom());
  295. materialTypeCreator.BeginMaterialProperty(Name{"layer1.baseColor.factor"}, MaterialPropertyDataType::Float);
  296. materialTypeCreator.EndMaterialProperty();
  297. materialTypeCreator.End(materialTypeAsset);
  298. FindPropertyIndexTestFunctorSourceData sourceData;
  299. sourceData.m_materialPropertyName = "factor";
  300. MaterialNameContext nameContext;
  301. nameContext.ExtendPropertyIdContext("layer1");
  302. nameContext.ExtendPropertyIdContext("baseColor");
  303. MaterialFunctorSourceData::RuntimeContext createFunctorContext(
  304. "",
  305. materialTypeAsset->GetMaterialPropertiesLayout(),
  306. nullptr,
  307. &nameContext);
  308. RPI::Ptr<MaterialFunctor> functor = sourceData.CreateFunctor(createFunctorContext).TakeValue();
  309. EXPECT_TRUE(reinterpret_cast<FindPropertyIndexTestFunctor*>(functor.get())->m_foundIndex.IsValid());
  310. }
  311. class FindShaderInputIndexTestFunctor : public MaterialFunctor
  312. {
  313. public:
  314. AZ_CLASS_ALLOCATOR(FindShaderInputIndexTestFunctor, SystemAllocator)
  315. RHI::ShaderInputConstantIndex m_foundConstantIndex;
  316. RHI::ShaderInputImageIndex m_foundImageIndex;
  317. };
  318. class FindShaderInputIndexTestFunctorSourceData : public MaterialFunctorSourceData
  319. {
  320. public:
  321. AZ_CLASS_ALLOCATOR(FindShaderInputIndexTestFunctorSourceData, AZ::SystemAllocator)
  322. Name m_shaderConstantName;
  323. Name m_shaderImageName;
  324. using MaterialFunctorSourceData::CreateFunctor;
  325. FunctorResult CreateFunctor(const RuntimeContext& runtimeContext) const override
  326. {
  327. RPI::Ptr<FindShaderInputIndexTestFunctor> functor = aznew FindShaderInputIndexTestFunctor;
  328. functor->m_foundConstantIndex = runtimeContext.FindShaderInputConstantIndex(m_shaderConstantName);
  329. functor->m_foundImageIndex = runtimeContext.FindShaderInputImageIndex(m_shaderImageName);
  330. return Success(RPI::Ptr<MaterialFunctor>(functor));
  331. }
  332. };
  333. TEST_F(MaterialFunctorTests, UseNameContextInFunctorSourceData_ShaderConstantLookup)
  334. {
  335. AZ::RHI::Ptr<AZ::RHI::ShaderResourceGroupLayout> srgLayout = RHI::ShaderResourceGroupLayout::Create();
  336. srgLayout->SetName(Name("MaterialSrg"));
  337. srgLayout->SetUniqueId(Uuid::CreateRandom().ToString<AZStd::string>()); // Any random string will suffice.
  338. srgLayout->SetBindingSlot(SrgBindingSlot::Material);
  339. srgLayout->AddShaderInput(RHI::ShaderInputConstantDescriptor{Name{ "m_layer1_baseColor_factor" }, 0, 4, 0, 0});
  340. srgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{Name{ "m_layer1_baseColor_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1, 1});
  341. srgLayout->Finalize();
  342. Data::Asset<ShaderAsset> shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), srgLayout);
  343. Data::Asset<MaterialTypeAsset> materialTypeAsset;
  344. MaterialTypeAssetCreator materialTypeCreator;
  345. materialTypeCreator.Begin(Uuid::CreateRandom());
  346. materialTypeCreator.AddShader(shaderAsset);
  347. materialTypeCreator.End(materialTypeAsset);
  348. FindShaderInputIndexTestFunctorSourceData sourceData;
  349. sourceData.m_shaderConstantName = "factor";
  350. sourceData.m_shaderImageName = "texture";
  351. MaterialNameContext nameContext;
  352. nameContext.ExtendSrgInputContext("m_layer1_baseColor_");
  353. MaterialFunctorSourceData::RuntimeContext createFunctorContext(
  354. "",
  355. nullptr,
  356. srgLayout.get(),
  357. &nameContext);
  358. RPI::Ptr<MaterialFunctor> functor = sourceData.CreateFunctor(createFunctorContext).TakeValue();
  359. EXPECT_TRUE(reinterpret_cast<FindShaderInputIndexTestFunctor*>(functor.get())->m_foundConstantIndex.IsValid());
  360. EXPECT_TRUE(reinterpret_cast<FindShaderInputIndexTestFunctor*>(functor.get())->m_foundImageIndex.IsValid());
  361. }
  362. }