UiMaskComponent.cpp 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  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 "UiMaskComponent.h"
  9. #include <LyShine/IDraw2d.h>
  10. #include <AzCore/Math/Crc.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Serialization/EditContext.h>
  13. #include <AzCore/RTTI/BehaviorContext.h>
  14. #include "RenderToTextureBus.h"
  15. #include "RenderGraph.h"
  16. #include <LyShine/Bus/UiTransformBus.h>
  17. #include <LyShine/Bus/UiElementBus.h>
  18. #include <LyShine/Bus/UiRenderBus.h>
  19. #include <LyShine/Bus/UiVisualBus.h>
  20. #include <LyShine/Bus/UiCanvasBus.h>
  21. #include <Atom/RPI.Public/Image/AttachmentImage.h>
  22. #include <AtomCore/Instance/Instance.h>
  23. ////////////////////////////////////////////////////////////////////////////////////////////////////
  24. // PUBLIC MEMBER FUNCTIONS
  25. ////////////////////////////////////////////////////////////////////////////////////////////////////
  26. ////////////////////////////////////////////////////////////////////////////////////////////////////
  27. UiMaskComponent::UiMaskComponent()
  28. : m_enableMasking(true)
  29. , m_drawMaskVisualBehindChildren(false)
  30. , m_drawMaskVisualInFrontOfChildren(false)
  31. , m_useAlphaTest(false)
  32. , m_maskInteraction(true)
  33. {
  34. m_cachedPrimitive.m_vertices = nullptr;
  35. m_cachedPrimitive.m_numVertices = 0;
  36. m_cachedPrimitive.m_indices = nullptr;
  37. m_cachedPrimitive.m_numIndices = 0;
  38. }
  39. ////////////////////////////////////////////////////////////////////////////////////////////////////
  40. UiMaskComponent::~UiMaskComponent()
  41. {
  42. // destroy render targets if we are using them
  43. DestroyRenderTarget();
  44. // We only deallocate the vertices on destruction rather than every time we recreate the render
  45. // target. Changing the size of the element requires recreating render target but doesn't change
  46. // the number of vertices. Note this may be nullptr which is fine for delete.
  47. delete [] m_cachedPrimitive.m_vertices;
  48. }
  49. ////////////////////////////////////////////////////////////////////////////////////////////////////
  50. void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
  51. UiRenderInterface* renderInterface, int numChildren, bool isInGame)
  52. {
  53. // Check if this mask is a stencil mask being used while rendering to stencil for a parent mask, if so ignore it
  54. if (!ValidateMaskConfiguration(renderGraph))
  55. {
  56. return;
  57. }
  58. // Get the element interface of the child mask element (if any is setup and it is render enabled)
  59. // If the child mask element can't be used because it is not a descendant or is used by multiple masks
  60. // then this sets it to null (and reports warnings)
  61. UiElementInterface* childMaskElementInterface = GetValidatedChildMaskElement();
  62. if (m_enableMasking)
  63. {
  64. if (GetUseRenderToTexture())
  65. {
  66. // First create the render targets if they are not already created or resize them if needed.
  67. // We delay first creation of the render targets until render time since size is not known in Activate.
  68. {
  69. // render targets are always snapped to pixels in viewport space, compute the snapped element bounds
  70. // and, from that, the size of the render target
  71. AZ::Vector2 pixelAlignedTopLeft, pixelAlignedBottomRight;
  72. ComputePixelAlignedBounds(pixelAlignedTopLeft, pixelAlignedBottomRight);
  73. AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft;
  74. bool needsResize = static_cast<int>(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast<int>(renderTargetSize.GetY()) != m_renderTargetHeight;
  75. if (m_contentAttachmentImageId.IsEmpty() || needsResize)
  76. {
  77. // Need to create or resize the render target
  78. CreateOrResizeRenderTarget(pixelAlignedTopLeft, pixelAlignedBottomRight);
  79. }
  80. // if the render targets failed to be created (zero size for example) we don't render anything
  81. // in theory the child mask element could still be non-zero size and could reveal things. But the way gradient masks
  82. // currently work is that the size of the render target is defined by the size of this element, therefore nothing would
  83. // be revealed by the mask if it is zero sized.
  84. if (m_contentAttachmentImageId.IsEmpty())
  85. {
  86. return;
  87. }
  88. }
  89. // Do gradient mask render
  90. RenderUsingGradientMask(renderGraph, elementInterface, renderInterface, childMaskElementInterface, numChildren, isInGame);
  91. }
  92. else
  93. {
  94. // using stencil mask, not going to use render targets, destroy previous render target, if exists
  95. if (!m_contentAttachmentImageId.IsEmpty())
  96. {
  97. DestroyRenderTarget();
  98. }
  99. // Do stencil mask render
  100. RenderUsingStencilMask(renderGraph, elementInterface, renderInterface, childMaskElementInterface, numChildren, isInGame);
  101. }
  102. }
  103. else
  104. {
  105. // masking disabled, not going to use render targets, destroy previous render target, if exists
  106. if (!m_contentAttachmentImageId.IsEmpty())
  107. {
  108. DestroyRenderTarget();
  109. }
  110. // draw behind and draw in front are only options when not using gradient masks. If they are both set then we need
  111. // to use a mask render node in the render graph, so handle that specially
  112. if (!GetUseRenderToTexture() && m_drawMaskVisualBehindChildren && m_drawMaskVisualInFrontOfChildren)
  113. {
  114. RenderDisabledMaskWithDoubleRender(renderGraph, elementInterface, renderInterface, childMaskElementInterface, numChildren, isInGame);
  115. }
  116. else
  117. {
  118. RenderDisabledMask(renderGraph, elementInterface, renderInterface, childMaskElementInterface, numChildren, isInGame);
  119. }
  120. }
  121. // re-enable the rendering of the child mask (if it was enabled before we changed it)
  122. // This allows the game code to turn the child mask element on and off if so desired.
  123. if (childMaskElementInterface)
  124. {
  125. childMaskElementInterface->SetIsRenderEnabled(true);
  126. }
  127. }
  128. ////////////////////////////////////////////////////////////////////////////////////////////////////
  129. bool UiMaskComponent::GetIsMaskingEnabled()
  130. {
  131. return m_enableMasking;
  132. }
  133. ////////////////////////////////////////////////////////////////////////////////////////////////////
  134. void UiMaskComponent::SetIsMaskingEnabled(bool enableMasking)
  135. {
  136. if (m_enableMasking != enableMasking)
  137. {
  138. // tell the canvas to invalidate the render graph
  139. MarkRenderGraphDirty();
  140. m_enableMasking = enableMasking;
  141. }
  142. }
  143. ////////////////////////////////////////////////////////////////////////////////////////////////////
  144. bool UiMaskComponent::GetIsInteractionMaskingEnabled()
  145. {
  146. return m_maskInteraction;
  147. }
  148. ////////////////////////////////////////////////////////////////////////////////////////////////////
  149. void UiMaskComponent::SetIsInteractionMaskingEnabled(bool enableInteractionMasking)
  150. {
  151. m_maskInteraction = enableInteractionMasking;
  152. }
  153. ////////////////////////////////////////////////////////////////////////////////////////////////////
  154. bool UiMaskComponent::GetDrawBehind()
  155. {
  156. return m_drawMaskVisualBehindChildren;
  157. }
  158. ////////////////////////////////////////////////////////////////////////////////////////////////////
  159. void UiMaskComponent::SetDrawBehind(bool drawMaskVisualBehindChildren)
  160. {
  161. if (m_drawMaskVisualBehindChildren != drawMaskVisualBehindChildren)
  162. {
  163. // tell the canvas to invalidate the render graph
  164. MarkRenderGraphDirty();
  165. m_drawMaskVisualBehindChildren = drawMaskVisualBehindChildren;
  166. }
  167. }
  168. ////////////////////////////////////////////////////////////////////////////////////////////////////
  169. bool UiMaskComponent::GetDrawInFront()
  170. {
  171. return m_drawMaskVisualInFrontOfChildren;
  172. }
  173. ////////////////////////////////////////////////////////////////////////////////////////////////////
  174. void UiMaskComponent::SetDrawInFront(bool drawMaskVisualInFrontOfChildren)
  175. {
  176. if (m_drawMaskVisualInFrontOfChildren != drawMaskVisualInFrontOfChildren)
  177. {
  178. // tell the canvas to invalidate the render graph
  179. MarkRenderGraphDirty();
  180. m_drawMaskVisualInFrontOfChildren = drawMaskVisualInFrontOfChildren;
  181. }
  182. }
  183. ////////////////////////////////////////////////////////////////////////////////////////////////////
  184. bool UiMaskComponent::GetUseAlphaTest()
  185. {
  186. return m_useAlphaTest;
  187. }
  188. ////////////////////////////////////////////////////////////////////////////////////////////////////
  189. void UiMaskComponent::SetUseAlphaTest(bool useAlphaTest)
  190. {
  191. if (m_useAlphaTest != useAlphaTest)
  192. {
  193. // tell the canvas to invalidate the render graph
  194. MarkRenderGraphDirty();
  195. m_useAlphaTest = useAlphaTest;
  196. }
  197. }
  198. ////////////////////////////////////////////////////////////////////////////////////////////////////
  199. bool UiMaskComponent::GetUseRenderToTexture()
  200. {
  201. return m_useRenderToTexture;
  202. }
  203. ////////////////////////////////////////////////////////////////////////////////////////////////////
  204. void UiMaskComponent::SetUseRenderToTexture(bool useRenderToTexture)
  205. {
  206. if (GetUseRenderToTexture() != useRenderToTexture)
  207. {
  208. m_useRenderToTexture = useRenderToTexture;
  209. OnRenderTargetChange();
  210. }
  211. }
  212. ////////////////////////////////////////////////////////////////////////////////////////////////////
  213. bool UiMaskComponent::IsPointMasked(AZ::Vector2 point)
  214. {
  215. bool isMasked = false;
  216. // it is never masked if the flag to mask interactions is not checked
  217. if (m_maskInteraction)
  218. {
  219. // Initially consider it outside all of the mask visuals. If it is inside any we return false.
  220. isMasked = true;
  221. // Right now we only do a check for the rectangle. If the point is outside of the rectangle
  222. // then it is masked.
  223. // In the future we will add the option to check the alpha of the mask texture for interaction masking
  224. // first check this element if there is a visual component
  225. if (UiVisualBus::FindFirstHandler(GetEntityId()))
  226. {
  227. bool isInRect = false;
  228. UiTransformBus::EventResult(isInRect, GetEntityId(), &UiTransformBus::Events::IsPointInRect, point);
  229. if (isInRect)
  230. {
  231. return false;
  232. }
  233. }
  234. // If there is a child mask element
  235. if (m_childMaskElement.IsValid())
  236. {
  237. // if it has a Visual component check if the point is in its rect
  238. if (UiVisualBus::FindFirstHandler(m_childMaskElement))
  239. {
  240. bool isInRect = false;
  241. UiTransformBus::EventResult(isInRect, m_childMaskElement, &UiTransformBus::Events::IsPointInRect, point);
  242. if (isInRect)
  243. {
  244. return false;
  245. }
  246. }
  247. // get any descendants of the child mask element that have visual components
  248. LyShine::EntityArray childMaskElements;
  249. UiElementBus::Event(
  250. m_childMaskElement,
  251. &UiElementBus::Events::FindDescendantElements,
  252. [](const AZ::Entity* descendant)
  253. {
  254. return UiVisualBus::FindFirstHandler(descendant->GetId()) != nullptr;
  255. },
  256. childMaskElements);
  257. // if the point is in any of their rects then it is not masked out
  258. for (auto child : childMaskElements)
  259. {
  260. bool isInRect = false;
  261. UiTransformBus::EventResult(isInRect, child->GetId(), &UiTransformBus::Events::IsPointInRect, point);
  262. if (isInRect)
  263. {
  264. return false;
  265. }
  266. }
  267. }
  268. }
  269. return isMasked;
  270. }
  271. ////////////////////////////////////////////////////////////////////////////////////////////////////
  272. void UiMaskComponent::OnCanvasSpaceRectChanged(AZ::EntityId /*entityId*/, const UiTransformInterface::Rect& /*oldRect*/, const UiTransformInterface::Rect& /*newRect*/)
  273. {
  274. // we only listen for this if using render target, if rect changed potentially recreate render target
  275. OnRenderTargetChange();
  276. }
  277. ////////////////////////////////////////////////////////////////////////////////////////////////////
  278. void UiMaskComponent::OnTransformToViewportChanged()
  279. {
  280. // we only listen for this if using render target, if transform changed potentially recreate render target
  281. OnRenderTargetChange();
  282. }
  283. ////////////////////////////////////////////////////////////////////////////////////////////////////
  284. // PUBLIC STATIC MEMBER FUNCTIONS
  285. ////////////////////////////////////////////////////////////////////////////////////////////////////
  286. void UiMaskComponent::Reflect(AZ::ReflectContext* context)
  287. {
  288. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  289. if (serializeContext)
  290. {
  291. serializeContext->Class<UiMaskComponent, AZ::Component>()
  292. ->Version(1)
  293. ->Field("EnableMasking", &UiMaskComponent::m_enableMasking)
  294. ->Field("MaskInteraction", &UiMaskComponent::m_maskInteraction)
  295. ->Field("ChildMaskElement", &UiMaskComponent::m_childMaskElement)
  296. ->Field("UseRenderToTexture", &UiMaskComponent::m_useRenderToTexture)
  297. ->Field("DrawBehind", &UiMaskComponent::m_drawMaskVisualBehindChildren)
  298. ->Field("DrawInFront", &UiMaskComponent::m_drawMaskVisualInFrontOfChildren)
  299. ->Field("UseAlphaTest", &UiMaskComponent::m_useAlphaTest)
  300. ;
  301. AZ::EditContext* ec = serializeContext->GetEditContext();
  302. if (ec)
  303. {
  304. auto editInfo = ec->Class<UiMaskComponent>("Mask", "A component that masks child elements using its visual component");
  305. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  306. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  307. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiMask.png")
  308. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiMask.png")
  309. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
  310. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  311. editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiMaskComponent::m_enableMasking, "Enable masking",
  312. "When checked, only the parts of child elements that are revealed by the mask will be seen.")
  313. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiMaskComponent::OnEditorRenderSettingChange);
  314. editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiMaskComponent::m_maskInteraction, "Mask interaction",
  315. "Check this box to prevent children hidden by the mask from getting input events.");
  316. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiMaskComponent::m_childMaskElement, "Child mask element",
  317. "A child element that is rendered as part of the mask.")
  318. ->Attribute(AZ::Edit::Attributes::EnumValues, &UiMaskComponent::PopulateChildEntityList)
  319. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiMaskComponent::OnEditorRenderSettingChange);
  320. editInfo->DataElement(0, &UiMaskComponent::m_useRenderToTexture, "Use alpha gradient",
  321. "If true, this element's content and the mask are rendered to separate render targets\n"
  322. "and then rendered to the screen using the mask render target as an alpha gradient mask.\n"
  323. "This allows soft-edged masking. The effect is limited to the rect of this element.")
  324. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c))
  325. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiMaskComponent::OnRenderTargetChange);
  326. editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiMaskComponent::m_drawMaskVisualBehindChildren, "Draw behind",
  327. "Check this box to draw the mask visual behind the child elements.")
  328. ->Attribute(AZ::Edit::Attributes::Visibility, &UiMaskComponent::IsStencilMask)
  329. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiMaskComponent::OnEditorRenderSettingChange);
  330. editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiMaskComponent::m_drawMaskVisualInFrontOfChildren, "Draw in front",
  331. "Check this box to draw the mask in front of the child elements.")
  332. ->Attribute(AZ::Edit::Attributes::Visibility, &UiMaskComponent::IsStencilMask)
  333. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiMaskComponent::OnEditorRenderSettingChange);
  334. editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiMaskComponent::m_useAlphaTest, "Use alpha test",
  335. "Check this box to use the alpha channel in the mask visual's texture to define the mask.")
  336. ->Attribute(AZ::Edit::Attributes::Visibility, &UiMaskComponent::IsStencilMask)
  337. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiMaskComponent::OnEditorRenderSettingChange);
  338. }
  339. }
  340. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  341. if (behaviorContext)
  342. {
  343. behaviorContext->EBus<UiMaskBus>("UiMaskBus")
  344. ->Event("GetIsMaskingEnabled", &UiMaskBus::Events::GetIsMaskingEnabled)
  345. ->Event("SetIsMaskingEnabled", &UiMaskBus::Events::SetIsMaskingEnabled)
  346. ->Event("GetIsInteractionMaskingEnabled", &UiMaskBus::Events::GetIsInteractionMaskingEnabled)
  347. ->Event("SetIsInteractionMaskingEnabled", &UiMaskBus::Events::SetIsInteractionMaskingEnabled)
  348. ->Event("GetDrawBehind", &UiMaskBus::Events::GetDrawBehind)
  349. ->Event("SetDrawBehind", &UiMaskBus::Events::SetDrawBehind)
  350. ->Event("GetDrawInFront", &UiMaskBus::Events::GetDrawInFront)
  351. ->Event("SetDrawInFront", &UiMaskBus::Events::SetDrawInFront)
  352. ->Event("GetUseAlphaTest", &UiMaskBus::Events::GetUseAlphaTest)
  353. ->Event("SetUseAlphaTest", &UiMaskBus::Events::SetUseAlphaTest)
  354. ->Event("GetUseRenderToTexture", &UiMaskBus::Events::GetUseRenderToTexture)
  355. ->Event("SetUseRenderToTexture", &UiMaskBus::Events::SetUseRenderToTexture);
  356. }
  357. }
  358. ////////////////////////////////////////////////////////////////////////////////////////////////////
  359. // PROTECTED MEMBER FUNCTIONS
  360. ////////////////////////////////////////////////////////////////////////////////////////////////////
  361. ////////////////////////////////////////////////////////////////////////////////////////////////////
  362. void UiMaskComponent::Activate()
  363. {
  364. UiRenderControlBus::Handler::BusConnect(m_entity->GetId());
  365. UiMaskBus::Handler::BusConnect(m_entity->GetId());
  366. UiInteractionMaskBus::Handler::BusConnect(m_entity->GetId());
  367. if (GetUseRenderToTexture())
  368. {
  369. UiTransformChangeNotificationBus::Handler::BusConnect(m_entity->GetId());
  370. }
  371. MarkRenderGraphDirty();
  372. // set the render target name to an automatically generated name based on entity Id
  373. AZStd::string idString = GetEntityId().ToString();
  374. m_renderTargetName = "ContentTarget_";
  375. m_renderTargetName += idString;
  376. m_maskRenderTargetName = "MaskTarget_";
  377. m_maskRenderTargetName += idString;
  378. }
  379. ////////////////////////////////////////////////////////////////////////////////////////////////////
  380. void UiMaskComponent::Deactivate()
  381. {
  382. UiRenderControlBus::Handler::BusDisconnect();
  383. UiMaskBus::Handler::BusDisconnect();
  384. UiInteractionMaskBus::Handler::BusDisconnect();
  385. if (UiTransformChangeNotificationBus::Handler::BusIsConnected())
  386. {
  387. UiTransformChangeNotificationBus::Handler::BusDisconnect();
  388. }
  389. MarkRenderGraphDirty();
  390. }
  391. ////////////////////////////////////////////////////////////////////////////////////////////////////
  392. // PRIVATE MEMBER FUNCTIONS
  393. ////////////////////////////////////////////////////////////////////////////////////////////////////
  394. ////////////////////////////////////////////////////////////////////////////////////////////////////
  395. UiMaskComponent::EntityComboBoxVec UiMaskComponent::PopulateChildEntityList()
  396. {
  397. EntityComboBoxVec result;
  398. // add a first entry for "None"
  399. result.push_back(AZStd::make_pair(AZ::EntityId(AZ::EntityId()), "<None>"));
  400. // Get a list of all child elements
  401. LyShine::EntityArray matchingElements;
  402. UiElementBus::Event(
  403. GetEntityId(),
  404. &UiElementBus::Events::FindDescendantElements,
  405. []([[maybe_unused]] const AZ::Entity* entity)
  406. {
  407. return true;
  408. },
  409. matchingElements);
  410. // add their names to the StringList and their IDs to the id list
  411. // also note whether the current value of m_childMaskElement is in the list
  412. bool isCurrentValueInList = (m_childMaskElement == AZ::EntityId());
  413. for (auto childEntity : matchingElements)
  414. {
  415. result.push_back(AZStd::make_pair(AZ::EntityId(childEntity->GetId()), childEntity->GetName()));
  416. if (!isCurrentValueInList && m_childMaskElement == childEntity->GetId())
  417. {
  418. isCurrentValueInList = true;
  419. }
  420. }
  421. if (!isCurrentValueInList)
  422. {
  423. // The current value is not in the list. It is invalid for the child mask element to not be a decscendant element
  424. // But that can be the case if the child is reparented. In this case a warning will be output during render.
  425. // However, if we don't add the current value into the list then the collapsed combo box will say <None> even though
  426. // it is set - making it confusing (and hard to change if there are no children).
  427. // So we add the current value to the list even though it is not a descendant.
  428. AZ::Entity* childMaskEntity = nullptr;
  429. AZ::ComponentApplicationBus::BroadcastResult(childMaskEntity, &AZ::ComponentApplicationBus::Events::FindEntity, m_childMaskElement);
  430. if (childMaskEntity)
  431. {
  432. result.push_back(AZStd::make_pair(AZ::EntityId(m_childMaskElement), childMaskEntity->GetName()));
  433. }
  434. }
  435. return result;
  436. }
  437. ////////////////////////////////////////////////////////////////////////////////////////////////////
  438. void UiMaskComponent::MarkRenderGraphDirty()
  439. {
  440. // tell the canvas to invalidate the render graph
  441. AZ::EntityId canvasEntityId;
  442. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  443. UiCanvasComponentImplementationBus::Event(canvasEntityId, &UiCanvasComponentImplementationBus::Events::MarkRenderGraphDirty);
  444. }
  445. ////////////////////////////////////////////////////////////////////////////////////////////////////
  446. void UiMaskComponent::OnEditorRenderSettingChange()
  447. {
  448. // something changed in the properties that requires re-rendering
  449. MarkRenderGraphDirty();
  450. }
  451. ////////////////////////////////////////////////////////////////////////////////////////////////////
  452. void UiMaskComponent::OnRenderTargetChange()
  453. {
  454. // mark render graph dirty so next render will recreate render targets if necessary
  455. MarkRenderGraphDirty();
  456. // update cached primitive to reflect new transforms
  457. AZ::Vector2 pixelAlignedTopLeft, pixelAlignedBottomRight;
  458. ComputePixelAlignedBounds(pixelAlignedTopLeft, pixelAlignedBottomRight);
  459. UpdateCachedPrimitive(pixelAlignedTopLeft, pixelAlignedBottomRight);
  460. // if using a render target we need to know if the element size or position changes since that
  461. // affects the render target and the viewport
  462. if (GetUseRenderToTexture())
  463. {
  464. if (!UiTransformChangeNotificationBus::Handler::BusIsConnected())
  465. {
  466. UiTransformChangeNotificationBus::Handler::BusConnect(m_entity->GetId());
  467. }
  468. }
  469. else
  470. {
  471. if (UiTransformChangeNotificationBus::Handler::BusIsConnected())
  472. {
  473. UiTransformChangeNotificationBus::Handler::BusDisconnect();
  474. }
  475. }
  476. }
  477. ////////////////////////////////////////////////////////////////////////////////////////////////////
  478. void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAlignedTopLeft, const AZ::Vector2& pixelAlignedBottomRight)
  479. {
  480. // The render target size is the pixel aligned element size.
  481. AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft;
  482. if (renderTargetSize.GetX() <= 0 || renderTargetSize.GetY() <= 0)
  483. {
  484. // if render targets exist then destroy them (just to be in a consistent state)
  485. DestroyRenderTarget();
  486. return;
  487. }
  488. m_viewportTopLeft = pixelAlignedTopLeft;
  489. m_viewportSize = renderTargetSize;
  490. // [LYSHINE_ATOM_TODO][GHI #6271] Optimize by reusing existing render targets
  491. DestroyRenderTarget();
  492. // Create a render target that this element and its children will be rendered to
  493. AZ::EntityId canvasEntityId;
  494. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  495. AZ::RHI::Size imageSize(static_cast<uint32_t>(renderTargetSize.GetX()), static_cast<uint32_t>(renderTargetSize.GetY()), 1);
  496. LyShine::RenderToTextureRequestBus::EventResult(
  497. m_contentAttachmentImageId,
  498. canvasEntityId,
  499. &LyShine::RenderToTextureRequestBus::Events::UseRenderTarget,
  500. AZ::Name(m_renderTargetName.c_str()),
  501. imageSize);
  502. if (m_contentAttachmentImageId.IsEmpty())
  503. {
  504. AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent");
  505. }
  506. // Create separate render target for the mask texture
  507. LyShine::RenderToTextureRequestBus::EventResult(
  508. m_maskAttachmentImageId,
  509. canvasEntityId,
  510. &LyShine::RenderToTextureRequestBus::Events::UseRenderTarget,
  511. AZ::Name(m_maskRenderTargetName.c_str()),
  512. imageSize);
  513. if (m_maskAttachmentImageId.IsEmpty())
  514. {
  515. AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent");
  516. DestroyRenderTarget();
  517. }
  518. // at this point either all render targets and depth surfaces are created or none are.
  519. // If all succeeded then update the render target size
  520. if (!m_contentAttachmentImageId.IsEmpty())
  521. {
  522. m_renderTargetWidth = static_cast<int>(renderTargetSize.GetX());
  523. m_renderTargetHeight = static_cast<int>(renderTargetSize.GetY());
  524. }
  525. UpdateCachedPrimitive(pixelAlignedTopLeft, pixelAlignedBottomRight);
  526. }
  527. ////////////////////////////////////////////////////////////////////////////////////////////////////
  528. void UiMaskComponent::DestroyRenderTarget()
  529. {
  530. if (!m_contentAttachmentImageId.IsEmpty())
  531. {
  532. AZ::EntityId canvasEntityId;
  533. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  534. LyShine::RenderToTextureRequestBus::Event(
  535. canvasEntityId, &LyShine::RenderToTextureRequestBus::Events::ReleaseRenderTarget, m_contentAttachmentImageId);
  536. m_contentAttachmentImageId = AZ::RHI::AttachmentId{};
  537. }
  538. if (!m_maskAttachmentImageId.IsEmpty())
  539. {
  540. AZ::EntityId canvasEntityId;
  541. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  542. LyShine::RenderToTextureRequestBus::Event(
  543. canvasEntityId, &LyShine::RenderToTextureRequestBus::Events::ReleaseRenderTarget, m_maskAttachmentImageId);
  544. m_maskAttachmentImageId = AZ::RHI::AttachmentId{};
  545. }
  546. }
  547. ////////////////////////////////////////////////////////////////////////////////////////////////////
  548. void UiMaskComponent::UpdateCachedPrimitive(const AZ::Vector2& pixelAlignedTopLeft, const AZ::Vector2& pixelAlignedBottomRight)
  549. {
  550. // update viewport position
  551. m_viewportTopLeft = pixelAlignedTopLeft;
  552. // now create the verts to render the texture to the screen, we cache these in m_cachedPrimitive
  553. const int numVertices = 4;
  554. if (!m_cachedPrimitive.m_vertices)
  555. {
  556. // verts not yet allocated, allocate them now
  557. const int numIndices = 6;
  558. m_cachedPrimitive.m_vertices = new LyShine::UiPrimitiveVertex[numVertices];
  559. m_cachedPrimitive.m_numVertices = numVertices;
  560. static uint16 indices[numIndices] = { 0, 1, 2, 2, 3, 0 };
  561. m_cachedPrimitive.m_indices = indices;
  562. m_cachedPrimitive.m_numIndices = numIndices;
  563. }
  564. float left = pixelAlignedTopLeft.GetX();
  565. float right = pixelAlignedBottomRight.GetX();
  566. float top = pixelAlignedTopLeft.GetY();
  567. float bottom = pixelAlignedBottomRight.GetY();
  568. Vec2 positions[numVertices] = { Vec2(left, top), Vec2(right, top), Vec2(right, bottom), Vec2(left, bottom) };
  569. static const Vec2 uvs[numVertices] = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
  570. for (int i = 0; i < numVertices; ++i)
  571. {
  572. m_cachedPrimitive.m_vertices[i].xy = positions[i];
  573. m_cachedPrimitive.m_vertices[i].color.dcolor = 0xFFFFFFFF;
  574. m_cachedPrimitive.m_vertices[i].st = uvs[i];
  575. m_cachedPrimitive.m_vertices[i].texIndex = 0; // this will be set later by render graph
  576. m_cachedPrimitive.m_vertices[i].texHasColorChannel = 1;
  577. m_cachedPrimitive.m_vertices[i].texIndex2 = 0;
  578. m_cachedPrimitive.m_vertices[i].pad = 0;
  579. }
  580. }
  581. ////////////////////////////////////////////////////////////////////////////////////////////////////
  582. void UiMaskComponent::ComputePixelAlignedBounds(AZ::Vector2& pixelAlignedTopLeft, AZ::Vector2& pixelAlignedBottomRight)
  583. {
  584. // The viewport has to be axis aligned so we get the axis-aligned top-left and bottom-right of the element
  585. // in main viewport space. We then snap them to the nearest pixel since the render target has to be an exact number
  586. // of pixels.
  587. UiTransformInterface::RectPoints points;
  588. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::GetViewportSpacePoints, points);
  589. pixelAlignedTopLeft = Draw2dHelper::RoundXY(points.GetAxisAlignedTopLeft(), IDraw2d::Rounding::Nearest);
  590. pixelAlignedBottomRight = Draw2dHelper::RoundXY(points.GetAxisAlignedBottomRight(), IDraw2d::Rounding::Nearest);
  591. }
  592. ////////////////////////////////////////////////////////////////////////////////////////////////////
  593. bool UiMaskComponent::IsStencilMask()
  594. {
  595. return !GetUseRenderToTexture();
  596. }
  597. ////////////////////////////////////////////////////////////////////////////////////////////////////
  598. void UiMaskComponent::RenderUsingStencilMask(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
  599. UiRenderInterface* renderInterface, UiElementInterface* childMaskElementInterface, int numChildren, bool isInGame)
  600. {
  601. // begin the mask render node
  602. renderGraph->BeginMask(m_enableMasking, m_useAlphaTest, m_drawMaskVisualBehindChildren, m_drawMaskVisualInFrontOfChildren);
  603. // We never want to apply the fade value when rendering the mask visual to stencil buffer
  604. renderGraph->PushOverrideAlphaFade(1.0f);
  605. // Render the visual component for this element (if there is one) plus the child mask element (if there is one)
  606. renderGraph->SetIsRenderingToMask(true);
  607. RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame);
  608. renderGraph->SetIsRenderingToMask(false);
  609. // Pop off the temporary fade value we pushed while rendering the mask to the stencil buffer
  610. renderGraph->PopAlphaFade();
  611. // tell render graph we have finished rendering the mask primitives and are starting the content primitives
  612. renderGraph->StartChildrenForMask();
  613. // Render the "content" - the child elements excluding the child mask element (if any)
  614. RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame);
  615. // end the mask render node
  616. renderGraph->EndMask();
  617. }
  618. ////////////////////////////////////////////////////////////////////////////////////////////////////
  619. void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
  620. UiRenderInterface* renderInterface, UiElementInterface* childMaskElementInterface, int numChildren, bool isInGame)
  621. {
  622. // we always clear to transparent black - the accumulation of alpha in the render target requires it
  623. AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f);
  624. // Get the render targets
  625. AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage;
  626. AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage;
  627. AZ::EntityId canvasEntityId;
  628. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  629. LyShine::RenderToTextureRequestBus::EventResult(
  630. contentAttachmentImage, canvasEntityId, &LyShine::RenderToTextureRequestBus::Events::GetRenderTarget, m_contentAttachmentImageId);
  631. LyShine::RenderToTextureRequestBus::EventResult(
  632. maskAttachmentImage, canvasEntityId, &LyShine::RenderToTextureRequestBus::Events::GetRenderTarget, m_maskAttachmentImageId);
  633. // We don't want parent faders to affect what is rendered to the render target since we will
  634. // apply those fades when we render from the render target.
  635. // Note that this means that, if there are parent (no render to texture) faders, we get a "free"
  636. // improved fade for the children of the mask. We could avoid this but it seems desirable.
  637. renderGraph->PushOverrideAlphaFade(1.0f);
  638. // mask render target
  639. {
  640. // Start building the render to texture node in the render graph
  641. renderGraph->BeginRenderToTexture(maskAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
  642. // Render the visual component for this element (if there is one) plus the child mask element (if there is one)
  643. RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame);
  644. // finish building the render to texture node for the mask render target in the render graph
  645. renderGraph->EndRenderToTexture();
  646. }
  647. // content render target
  648. {
  649. // Start building the render to texture node for the content render target in the render graph
  650. renderGraph->BeginRenderToTexture(contentAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
  651. // Render the "content" - the child elements excluding the child mask element (if any)
  652. RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame);
  653. // finish building the render to texture node in the render graph
  654. renderGraph->EndRenderToTexture();
  655. }
  656. // pop off the override alpha fade
  657. renderGraph->PopAlphaFade();
  658. // render from the render target to the screen (or a parent render target) with the any fade value
  659. // from ancestor faders
  660. {
  661. // set the alpha in the verts
  662. {
  663. float desiredAlpha = renderGraph->GetAlphaFade();
  664. uint32 desiredPackedAlpha = static_cast<uint8>(desiredAlpha * 255.0f);
  665. // If the fade value has changed we need to update the alpha values in the vertex colors but we do
  666. // not want to touch or recompute the RGB values
  667. if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha)
  668. {
  669. // go through all the cached vertices and update the alpha values
  670. LyShine::UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color;
  671. desiredPackedColor.a = static_cast<uint8>(desiredPackedAlpha);
  672. for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i)
  673. {
  674. m_cachedPrimitive.m_vertices[i].color = desiredPackedColor;
  675. }
  676. }
  677. }
  678. // Add a primitive to do the alpha mask
  679. {
  680. // Set the texture and other render state required
  681. AZ::Data::Instance<AZ::RPI::Image> contentImage = contentAttachmentImage;
  682. AZ::Data::Instance<AZ::RPI::Image> maskImage = maskAttachmentImage;
  683. bool isClampTextureMode = true;
  684. bool isTextureSRGB = true;
  685. bool isTexturePremultipliedAlpha = false;
  686. LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
  687. // add a render node to render using the two render targets, one as an alpha mask of the other
  688. renderGraph->AddAlphaMaskPrimitive(&m_cachedPrimitive,
  689. contentAttachmentImage,
  690. maskAttachmentImage,
  691. isClampTextureMode,
  692. isTextureSRGB,
  693. isTexturePremultipliedAlpha,
  694. blendMode);
  695. }
  696. }
  697. }
  698. ////////////////////////////////////////////////////////////////////////////////////////////////////
  699. void UiMaskComponent::RenderDisabledMask(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
  700. UiRenderInterface* renderInterface, UiElementInterface* childMaskElementInterface, int numChildren, bool isInGame)
  701. {
  702. // The calling function ensures this method is never called when we want to render both behind and in front.
  703. // That would not work because the same primitive cannot be added to the render graph twice due to the use of
  704. // intrusive lists.
  705. // Note that (currently at least) we never draw behind or in front when render to texture is enabled, so the value of
  706. // m_drawMaskVisualBehindChildren and m_drawMaskVisualInFrontOfChildren is irrelevant in that case.
  707. AZ_Assert(!(!GetUseRenderToTexture() && m_drawMaskVisualBehindChildren && m_drawMaskVisualInFrontOfChildren),
  708. "Cannot use RenderDisabledMask when we are drawing behind and in front");
  709. if (!GetUseRenderToTexture() && m_drawMaskVisualBehindChildren)
  710. {
  711. // Render the visual component for this element (if there is one) plus the child mask element (if there is one)
  712. RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame);
  713. }
  714. // Render the "content" - the child elements excluding the child mask element (if any)
  715. RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame);
  716. if (!GetUseRenderToTexture() && m_drawMaskVisualInFrontOfChildren)
  717. {
  718. // Render the visual component for this element (if there is one) plus the child mask element (if there is one)
  719. RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame);
  720. }
  721. }
  722. ////////////////////////////////////////////////////////////////////////////////////////////////////
  723. void UiMaskComponent::RenderDisabledMaskWithDoubleRender(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
  724. [[maybe_unused]] UiRenderInterface* renderInterface, UiElementInterface* childMaskElementInterface, int numChildren, bool isInGame)
  725. {
  726. // we still need to use a mask render node in order to render the behind and in-front mask visual twice
  727. renderGraph->BeginMask(m_enableMasking, m_useAlphaTest, m_drawMaskVisualBehindChildren, m_drawMaskVisualInFrontOfChildren);
  728. // No need to render the mask primitives since masking is disabled
  729. // tell render graph we have finished rendering the mask primitives and are starting the content primitives
  730. renderGraph->StartChildrenForMask();
  731. // Render the "content" - the child elements excluding the child mask element (if any)
  732. RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame);
  733. // end the mask render node
  734. renderGraph->EndMask();
  735. }
  736. ////////////////////////////////////////////////////////////////////////////////////////////////////
  737. void UiMaskComponent::RenderMaskPrimitives(LyShine::IRenderGraph* renderGraph,
  738. UiRenderInterface* renderInterface, UiElementInterface* childMaskElementInterface, bool isInGame)
  739. {
  740. // Render the visual component for this element (if there is one)
  741. if (renderInterface)
  742. {
  743. renderInterface->Render(renderGraph);
  744. }
  745. // If there is a child mask element, that was render enabled at start of render,
  746. // then render that (and any children it has) also
  747. if (childMaskElementInterface)
  748. {
  749. // enable the rendering of the child mask element
  750. childMaskElementInterface->SetIsRenderEnabled(true);
  751. // Render the child mask element, this can render a whole hierarchy into the stencil buffer or mask render target
  752. // as part of the mask.
  753. childMaskElementInterface->RenderElement(renderGraph, isInGame);
  754. }
  755. }
  756. ////////////////////////////////////////////////////////////////////////////////////////////////////
  757. void UiMaskComponent::RenderContentPrimitives(LyShine::IRenderGraph* renderGraph,
  758. UiElementInterface* elementInterface,
  759. UiElementInterface* childMaskElementInterface,
  760. int numChildren, bool isInGame)
  761. {
  762. if (childMaskElementInterface)
  763. {
  764. // disable the rendering of the child mask with the other children
  765. childMaskElementInterface->SetIsRenderEnabled(false);
  766. }
  767. // Render the child elements
  768. for (int childIndex = 0; childIndex < numChildren; ++childIndex)
  769. {
  770. UiElementInterface* childElementInterface = elementInterface->GetChildElementInterface(childIndex);
  771. // childElementInterface should never be nullptr but check just to be safe
  772. if (childElementInterface)
  773. {
  774. childElementInterface->RenderElement(renderGraph, isInGame);
  775. }
  776. }
  777. }
  778. ////////////////////////////////////////////////////////////////////////////////////////////////////
  779. bool UiMaskComponent::ValidateMaskConfiguration(const LyShine::IRenderGraph* renderGraph)
  780. {
  781. bool isValidConfiguration = true;
  782. // Check if this mask is a stencil mask being used while rendering to stencil for a parent mask, if so ignore it
  783. if (renderGraph->IsRenderingToMask() && m_enableMasking && !GetUseRenderToTexture())
  784. {
  785. // We are in the process of rendering a child element hierarchy into the stencil buffer for another stencil mask component.
  786. // Additional stencil masking while rendering a stencil mask is not supported.
  787. #ifndef _RELEASE
  788. // If this situation is new since last frame then output a warning message
  789. if (!m_reportedNestedStencilWarning)
  790. {
  791. const char* elementName = GetEntity()->GetName().c_str();
  792. AZ_Warning("UI", false,
  793. "Element \"%s\" with a stencil mask component is being used as a Child Mask Element for another stencil mask component, it will not be rendered.",
  794. elementName);
  795. m_reportedNestedStencilWarning = true;
  796. }
  797. #endif
  798. // this means we render nothing for this element or its children
  799. isValidConfiguration = false;
  800. }
  801. else
  802. {
  803. #ifndef _RELEASE
  804. // This allows us to report a warning if the situation is fixed but reintroduced
  805. m_reportedNestedStencilWarning = false;
  806. #endif
  807. }
  808. return isValidConfiguration;
  809. }
  810. ////////////////////////////////////////////////////////////////////////////////////////////////////
  811. UiElementInterface* UiMaskComponent::GetValidatedChildMaskElement()
  812. {
  813. // If there is a child mask element we need to know if it is render enabled. If not then we can ignore it.
  814. // If it is then we will enable it for the mask render and disable it for the content render.
  815. UiElementInterface* childMaskElementInterface = nullptr;
  816. if (m_childMaskElement.IsValid())
  817. {
  818. // There is a child mask element, get the UiElementInterface for the element
  819. childMaskElementInterface = UiElementBus::FindFirstHandler(m_childMaskElement);
  820. if (childMaskElementInterface)
  821. {
  822. if (!childMaskElementInterface->IsRenderEnabled())
  823. {
  824. // not render enabled, we can just ignore it
  825. childMaskElementInterface = nullptr;
  826. }
  827. else
  828. {
  829. // check if this is a valid descendant and not being used by a descendant mask
  830. bool isValidConfiguration = false;
  831. AZ::Entity* parent = childMaskElementInterface->GetParent();
  832. while (parent)
  833. {
  834. if (parent == GetEntity())
  835. {
  836. // we found this element as a parent (ancestor) of the child mask element, without finding any
  837. // other descendant mask that uses it so this is valid
  838. isValidConfiguration = true;
  839. break;
  840. }
  841. // Check if this parent (ancestor) of the child mask element has a Mask component that is using the
  842. // same child mask element
  843. UiMaskComponent* otherMask = parent->FindComponent<UiMaskComponent>();
  844. if (otherMask && otherMask->m_childMaskElement == m_childMaskElement)
  845. {
  846. // this other mask is using the same child mask element
  847. break;
  848. }
  849. // move up the parent chain
  850. UiElementInterface* parentElementInterface = UiElementBus::FindFirstHandler(parent->GetId());
  851. parent = parentElementInterface->GetParent();
  852. }
  853. if (!isValidConfiguration)
  854. {
  855. // we will return a null ptr becuase we want to ignore the child mask element in the case of an invalid configuration
  856. childMaskElementInterface = nullptr;
  857. #ifndef _RELEASE
  858. // If this situation is new since last frame then output a warning message
  859. if (!m_reportedInvalidChildMaskElementWarning)
  860. {
  861. const char* elementName = GetEntity()->GetName().c_str();
  862. const char* childMaskElementName = "";
  863. AZ::Entity* childMaskEntity = nullptr;
  864. AZ::ComponentApplicationBus::BroadcastResult(
  865. childMaskEntity, &AZ::ComponentApplicationBus::Events::FindEntity, m_childMaskElement);
  866. if (childMaskEntity)
  867. {
  868. childMaskElementName = childMaskEntity->GetName().c_str();
  869. }
  870. if (!parent)
  871. {
  872. // we never found this mask component's entity as a parent of the child mask element
  873. AZ_Warning("UI", false,
  874. "Element \"%s\" with a mask component references a child mask element \"%s\" which is not a descendant, the child mask element will be ignored.",
  875. elementName, childMaskElementName);
  876. }
  877. else
  878. {
  879. // only other error condition is that another mask was using the same child mask element
  880. // parent will be pointing at the other mask element
  881. const char* otherMaskElementName = parent->GetName().c_str();
  882. AZ_Warning("UI", false,
  883. "Element \"%s\" with a mask component references a child mask element \"%s\" which is also used as a child mask element by another mask \"%s\", the child mask element will be ignored.",
  884. elementName, childMaskElementName, otherMaskElementName);
  885. }
  886. m_reportedInvalidChildMaskElementWarning = true;
  887. }
  888. #endif
  889. }
  890. else
  891. {
  892. #ifndef _RELEASE
  893. // This allows us to report a warning if the situation is fixed but reintroduced
  894. m_reportedInvalidChildMaskElementWarning = false;
  895. #endif
  896. }
  897. }
  898. }
  899. }
  900. return childMaskElementInterface;
  901. }