Reenable support for UI Elements that use Render Targets (#2352)

* Re-add support for UI Elements that use Render Targets
* Move LyShine pass request from Atom's MainPipeline.pass to project's
* Make all dynamic draw contexts in LyShine draw to pass directly without the need of draw list tags
* Remove local RPI changes that are no longer needed
* Prevent crash if LyShine gem is enabled but its custom pass hasn't been added to the main render pipeline
* Revert to default UI pass if the LyShine pass has not been added to project's main render pipeline

Signed-off-by: abrmich <abrmich@amazon.com>
monroegm-disable-blank-issue-2
michabr 4 years ago committed by GitHub
parent 03722789cf
commit 3a689aa319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,483 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "MainPipeline",
"PassClass": "ParentPass",
"Slots": [
{
"Name": "SwapChainOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
}
],
"PassRequests": [
{
"Name": "MorphTargetPass",
"TemplateName": "MorphTargetPassTemplate"
},
{
"Name": "SkinningPass",
"TemplateName": "SkinningPassTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshOutputStream",
"AttachmentRef": {
"Pass": "MorphTargetPass",
"Attachment": "MorphTargetDeltaOutput"
}
}
]
},
{
"Name": "RayTracingAccelerationStructurePass",
"TemplateName": "RayTracingAccelerationStructurePassTemplate"
},
{
"Name": "DiffuseProbeGridUpdatePass",
"TemplateName": "DiffuseProbeGridUpdatePassTemplate",
"ExecuteAfter": [
"RayTracingAccelerationStructurePass"
]
},
{
"Name": "DepthPrePass",
"TemplateName": "DepthMSAAParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "MotionVectorPass",
"TemplateName": "MotionVectorParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "Depth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "LightCullingPass",
"TemplateName": "LightCullingParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "DepthMSAA",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthMSAA"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "ShadowPass",
"TemplateName": "ShadowParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "OpaquePass",
"TemplateName": "OpaqueParentTemplate",
"Connections": [
{
"LocalSlot": "DirectionalShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalShadowmap"
}
},
{
"LocalSlot": "DirectionalESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalESM"
}
},
{
"LocalSlot": "ProjectedShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedShadowmap"
}
},
{
"LocalSlot": "ProjectedESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedESM"
}
},
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "LightListRemapped",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "LightListRemapped"
}
},
{
"LocalSlot": "DepthLinear",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthLinear"
}
},
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthMSAA"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "TransparentPass",
"TemplateName": "TransparentParentTemplate",
"Connections": [
{
"LocalSlot": "DirectionalShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalShadowmap"
}
},
{
"LocalSlot": "DirectionalESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalESM"
}
},
{
"LocalSlot": "ProjectedShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedShadowmap"
}
},
{
"LocalSlot": "ProjectedESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedESM"
}
},
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "LightListRemapped",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "LightListRemapped"
}
},
{
"LocalSlot": "InputLinearDepth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthLinear"
}
},
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "OpaquePass",
"Attachment": "Output"
}
}
]
},
{
"Name": "DeferredFogPass",
"TemplateName": "DeferredFogPassTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "InputLinearDepth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthLinear"
}
},
{
"LocalSlot": "InputDepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": {
"Pass": "TransparentPass",
"Attachment": "InputOutput"
}
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/ScreenSpace/DeferredFog.shader"
},
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "ReflectionCopyFrameBufferPass",
"TemplateName": "ReflectionCopyFrameBufferPassTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "DeferredFogPass",
"Attachment": "RenderTargetInputOutput"
}
}
]
},
{
"Name": "PostProcessPass",
"TemplateName": "PostProcessParentTemplate",
"Connections": [
{
"LocalSlot": "LightingInput",
"AttachmentRef": {
"Pass": "DeferredFogPass",
"Attachment": "RenderTargetInputOutput"
}
},
{
"LocalSlot": "Depth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "MotionVectors",
"AttachmentRef": {
"Pass": "MotionVectorPass",
"Attachment": "MotionVectorOutput"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "AuxGeomPass",
"TemplateName": "AuxGeomPassTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "PostProcessPass",
"Attachment": "Output"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "auxgeom",
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "DebugOverlayPass",
"TemplateName": "DebugOverlayParentTemplate",
"Connections": [
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "RawLightingInput",
"AttachmentRef": {
"Pass": "PostProcessPass",
"Attachment": "RawLightingOutput"
}
},
{
"LocalSlot": "LuminanceMipChainInput",
"AttachmentRef": {
"Pass": "PostProcessPass",
"Attachment": "LuminanceMipChainOutput"
}
},
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "AuxGeomPass",
"Attachment": "ColorInputOutput"
}
}
]
},
{
"Name": "LyShinePass",
"TemplateName": "LyShineParentTemplate",
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "DebugOverlayPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
]
},
{
"Name": "UIPass",
"TemplateName": "UIParentTemplate",
"Connections": [
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "LyShinePass",
"Attachment": "ColorInputOutput"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
]
},
{
"Name": "CopyToSwapChain",
"TemplateName": "FullscreenCopyTemplate",
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "UIPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "Output",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
}
]
}
}
}

@ -13,13 +13,7 @@
"ScopeAttachmentUsage": "DepthStencil",
"LoadStoreAction": {
"ClearValue": {
"Type": "DepthStencil",
"Value": [
0.0,
0.0,
0.0,
0.0
]
"Type": "DepthStencil"
},
"LoadActionStencil": "Clear"
}

@ -172,7 +172,7 @@ namespace AZ
}
m_pipelineState = m_shader->AcquirePipelineState(descriptor);
}
}
m_dirty = false;
}
return m_pipelineState;

@ -10,9 +10,6 @@
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
// Indicates whether to use pre-multiplied alpha
option bool o_preMultiplyAlpha;
// If true pixels with an alpha value of less than 0.5 are clipped
option bool o_alphaTest;
@ -86,9 +83,9 @@ struct PSOutput
float4 m_color : SV_Target0;
};
float4 SampleTriangleTexture(int texIndex, float2 uv)
float4 SampleTriangleTexture(uint texIndex, float2 uv)
{
if ((InstanceSrg::m_isClamp & (1 << texIndex)) != 0)
if ((InstanceSrg::m_isClamp & (1U << texIndex)) != 0)
{
return InstanceSrg::m_texture[texIndex].Sample(InstanceSrg::m_clampSampler, uv);
}
@ -120,14 +117,6 @@ PSOutput MainPS(VSOutput IN)
resColor.xyz = LinearToSRGB(resColor.xyz);
}
// Check for flag to premultiply alpha
if (o_preMultiplyAlpha)
{
// premultiply the color by the alpha. This would not be required if we had full access to the separate alpha blend mode
float preMult = resColor.w;
resColor.xyz *= preMult;
}
// If the o_modulate option is not None it means that the verts have two texture indicies. The second texture is used to
// mask the first. This is used for gradient masks.
if (o_modulate == Modulate::Alpha)

@ -4,7 +4,6 @@
{
"StableId": 1,
"Options": {
"o_preMultiplyAlpha": "false",
"o_alphaTest": "false",
"o_srgbWrite": "true",
"o_modulate": "Modulate::None"
@ -13,11 +12,26 @@
{
"StableId": 2,
"Options": {
"o_preMultiplyAlpha": "false",
"o_alphaTest": "false",
"o_srgbWrite": "false",
"o_modulate": "Modulate::None"
}
},
{
"StableId": 3,
"Options": {
"o_alphaTest": "true",
"o_srgbWrite": "true",
"o_srgbWrite": "false",
"o_modulate": "Modulate::None"
}
},
{
"StableId": 4,
"Options": {
"o_alphaTest": "false",
"o_srgbWrite": "false",
"o_modulate": "Modulate::Alpha"
}
}
]
}

@ -0,0 +1,22 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LyShineParentTemplate",
"PassClass": "LyShinePass",
"Slots": [
{
"Name": "ColorInputOutput",
"SlotType": "InputOutput"
},
{
"Name": "DepthInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
}
]
}
}
}

@ -0,0 +1,13 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "AssetAliasesSourceData",
"ClassData": {
"AssetPaths": [
{
"Name": "LyShineParentTemplate",
"Path": "Passes/LyShineParent.pass"
}
]
}
}

@ -193,6 +193,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
FILES_CMAKE
lyshine_common_module_files.cmake
lyshine_tests_files.cmake
COMPILE_DEFINITIONS
PRIVATE
LYSHINE_TESTS
INCLUDE_DIRECTORIES
PRIVATE
Tests

@ -1547,6 +1547,20 @@ AssetTreeEntry* EditorWindow::GetSliceLibraryTree()
return m_sliceLibraryTree;
}
AZ::EntityId EditorWindow::GetCanvasForCurrentEditorMode()
{
AZ::EntityId canvasEntityId;
if (GetEditorMode() == UiEditorMode::Edit)
{
canvasEntityId = GetCanvas();
}
else
{
canvasEntityId = GetPreviewModeCanvas();
}
return canvasEntityId;
}
void EditorWindow::ToggleEditorMode()
{
m_editorMode = (m_editorMode == UiEditorMode::Edit) ? UiEditorMode::Preview : UiEditorMode::Edit;

@ -143,6 +143,9 @@ public: // member functions
//! Returns the current mode of the editor (Edit or Preview)
UiEditorMode GetEditorMode() { return m_editorMode; }
//! Returns the UI canvas for the current mode (Edit or Preview)
AZ::EntityId GetCanvasForCurrentEditorMode();
//! Toggle the editor mode between Edit and Preview
void ToggleEditorMode();

@ -7,6 +7,8 @@
*/
#include "EditorCommon.h"
#include "UiCanvasComponent.h"
#include "EditorDefs.h"
#include "Settings.h"
#include <AzCore/std/containers/map.h>
@ -245,6 +247,7 @@ ViewportWidget::ViewportWidget(EditorWindow* parent)
FontNotificationBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(GetCurrentContextName());
}
ViewportWidget::~ViewportWidget()
@ -252,6 +255,8 @@ ViewportWidget::~ViewportWidget()
AzToolsFramework::EditorPickModeNotificationBus::Handler::BusDisconnect();
FontNotificationBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
LyShinePassDataRequestBus::Handler::BusDisconnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
m_uiRenderer.reset();
@ -272,6 +277,8 @@ void ViewportWidget::InitUiRenderer()
lyShine->SetUiRendererForEditor(m_uiRenderer);
m_draw2d = AZStd::make_shared<CDraw2d>(GetViewportContext());
LyShinePassDataRequestBus::Handler::BusConnect(GetViewportContext()->GetRenderScene()->GetId());
}
ViewportInteraction* ViewportWidget::GetViewportInteraction()
@ -487,30 +494,44 @@ void ViewportWidget::EnableCanvasRender()
}
void ViewportWidget::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
// Update
UiEditorMode editorMode = m_editorWindow->GetEditorMode();
if (editorMode == UiEditorMode::Edit)
{
UpdateEditMode(deltaTime);
}
else // if (editorMode == UiEditorMode::Preview)
{
UpdatePreviewMode(deltaTime);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int ViewportWidget::GetTickOrder()
{
return AZ::TICK_PRE_RENDER;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void ViewportWidget::OnRenderTick()
{
if (!m_uiRenderer->IsReady() || !m_canvasRenderIsEnabled)
{
return;
}
#ifdef LYSHINE_ATOM_TODO
gEnv->pRenderer->SetSrgbWrite(true);
#endif
const float dpiScale = QtHelpers::GetHighDpiScaleFactor(*this);
ViewportIcon::SetDpiScaleFactor(dpiScale);
// Set up to render a frame to this viewport's window
GetViewportContext()->RenderTick();
UiEditorMode editorMode = m_editorWindow->GetEditorMode();
if (editorMode == UiEditorMode::Edit)
{
RenderEditMode(deltaTime);
RenderEditMode();
}
else // if (editorMode == UiEditorMode::Preview)
{
RenderPreviewMode(deltaTime);
RenderPreviewMode();
}
}
@ -884,17 +905,37 @@ void ViewportWidget::OnFontTextureUpdated([[maybe_unused]] IFFont* font)
m_fontTextureHasChanged = true;
}
LyShine::AttachmentImagesAndDependencies ViewportWidget::GetRenderTargets()
{
LyShine::AttachmentImagesAndDependencies canvasTargets;
AZ::EntityId canvasEntityId = m_editorWindow->GetCanvasForCurrentEditorMode();
if (canvasEntityId.IsValid())
{
AZ::Entity* canvasEntity = nullptr;
EBUS_EVENT_RESULT(canvasEntity, AZ::ComponentApplicationBus, FindEntity, canvasEntityId);
AZ_Assert(canvasEntity, "Canvas entity not found by ID");
if (canvasEntity)
{
UiCanvasComponent* canvasComponent = canvasEntity->FindComponent<UiCanvasComponent>();
AZ_Assert(canvasComponent, "Canvas entity has no canvas component");
if (canvasComponent)
{
canvasComponent->GetRenderTargets(canvasTargets);
}
}
}
return canvasTargets;
}
QPointF ViewportWidget::WidgetToViewport(const QPointF & point) const
{
return point * WidgetToViewportFactor();
}
void ViewportWidget::RenderEditMode(float deltaTime)
void ViewportWidget::UpdateEditMode(float deltaTime)
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
static const int64_t topLayerKey = 0x1000000;
if (m_fontTextureHasChanged)
{
// A font texture has changed since we last rendered. Force a render graph update for each loaded canvas
@ -908,6 +949,28 @@ void ViewportWidget::RenderEditMode(float deltaTime)
return; // this can happen if a render happens during a restart
}
AZ::Vector2 canvasSize;
EBUS_EVENT_ID_RESULT(canvasSize, canvasEntityId, UiCanvasBus, GetCanvasSize);
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, false, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, false);
}
void ViewportWidget::RenderEditMode()
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
static const int64_t topLayerKey = 0x1000000;
AZ::EntityId canvasEntityId = m_editorWindow->GetCanvas();
if (!canvasEntityId.IsValid())
{
return; // this can happen if a render happens during a restart
}
Draw2dHelper draw2d(m_draw2d.get()); // sets and resets 2D draw mode in constructor/destructor
QTreeWidgetItemRawPtrQList selection = m_editorWindow->GetHierarchy()->selectedItems();
@ -936,9 +999,6 @@ void ViewportWidget::RenderEditMode(float deltaTime)
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, false, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, false);
// Render this canvas
QSize scaledViewportSize = QtHelpers::GetDpiScaledViewportSize(*this);
AZ::Vector2 viewportSize(scaledViewportSize.width(), scaledViewportSize.height());
@ -1037,11 +1097,8 @@ void ViewportWidget::RenderEditMode(float deltaTime)
}
}
void ViewportWidget::RenderPreviewMode(float deltaTime)
void ViewportWidget::UpdatePreviewMode(float deltaTime)
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas();
if (m_fontTextureHasChanged)
@ -1051,6 +1108,37 @@ void ViewportWidget::RenderPreviewMode(float deltaTime)
m_fontTextureHasChanged = false;
}
if (canvasEntityId.IsValid())
{
QSize scaledViewportSize = QtHelpers::GetDpiScaledViewportSize(*this);
AZ::Vector2 viewportSize(scaledViewportSize.width(), scaledViewportSize.height());
// Get the canvas size
AZ::Vector2 canvasSize = m_editorWindow->GetPreviewCanvasSize();
if (canvasSize.GetX() == 0.0f && canvasSize.GetY() == 0.0f)
{
// special value of (0,0) means use the viewport size
canvasSize = viewportSize;
}
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, true, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, true);
// Execute events that have been queued during the canvas update
gEnv->pLyShine->ExecuteQueuedEvents();
}
}
void ViewportWidget::RenderPreviewMode()
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas();
// Rather than scaling to exactly fit we try to draw at one of these preset scale factors
// to make it it bit more obvious that the canvas size is changing
float zoomScales[] = {
@ -1096,15 +1184,6 @@ void ViewportWidget::RenderPreviewMode(float deltaTime)
}
}
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, true, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, true);
// Execute events that have been queued during the canvas update
gEnv->pLyShine->ExecuteQueuedEvents();
// match scale to one of the predefined scales. If the scale is so small
// that it is less than the smallest scale then leave it as it is
for (int i = 0; i < AZ_ARRAY_SIZE(zoomScales); ++i)
@ -1131,14 +1210,6 @@ void ViewportWidget::RenderPreviewMode(float deltaTime)
canvasToViewportMatrix.SetTranslation(translation);
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetCanvasToViewportMatrix, canvasToViewportMatrix);
#ifdef LYSHINE_ATOM_TODO // mask support with Atom
// clear the stencil buffer before rendering each canvas - required for masking
// NOTE: the FRT_CLEAR_IMMEDIATE is required since we will not be setting the render target
// We also clear the color to a mid grey so that we can see the bounds of the canvas
ColorF viewportBackgroundColor(0.5f, 0.5f, 0.5f, 0); // if clearing color we want to set alpha to zero also
gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR, viewportBackgroundColor);
#endif
m_draw2d->SetSortKey(backgroundKey);
RenderViewportBackground();

@ -9,9 +9,11 @@
#if !defined(Q_MOC_RUN)
#include "EditorCommon.h"
#include "LyShinePassDataBus.h"
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <IFont.h>
@ -27,6 +29,8 @@ class ViewportWidget
: public AtomToolsFramework::RenderViewportWidget
, private AzToolsFramework::EditorPickModeNotificationBus::Handler
, private FontNotificationBus::Handler
, private LyShinePassDataRequestBus::Handler
, public AZ::RPI::ViewportContextNotificationBus::Handler
{
Q_OBJECT
@ -138,15 +142,29 @@ private: // member functions
void OnFontTextureUpdated(IFFont* font) override;
// ~FontNotifications
// LyShinePassDataRequestBus
LyShine::AttachmentImagesAndDependencies GetRenderTargets() override;
// ~LyShinePassDataRequestBus
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
// ~AZ::TickBus::Handler
// AZ::RPI::ViewportContextNotificationBus::Handler overrides...
void OnRenderTick() override;
//! Update UI canvases when in edit mode
void UpdateEditMode(float deltaTime);
//! Render the viewport when in edit mode
void RenderEditMode(float deltaTime);
void RenderEditMode();
//! Update UI canvases when in preview mode
void UpdatePreviewMode(float deltaTime);
//! Render the viewport when in preview mode
void RenderPreviewMode(float deltaTime);
void RenderPreviewMode();
//! Fill the entire viewport area with a background color
void RenderViewportBackground();

@ -9,6 +9,7 @@
#include <IRenderer.h> // for SVF_P3F_C4B_T2F which will be removed in a coming PR
#include <LyShine/Draw2d.h>
#include "LyShinePassDataBus.h"
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/MatrixUtils.h>
@ -95,6 +96,12 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet.");
// Create and initialize a DynamicDrawContext for 2d drawing
// Get the pass for the dynamic draw context to render to
AZ::RPI::RasterPass* uiCanvasPass = nullptr;
AZ::RPI::SceneId sceneId = scene->GetId();
LyShinePassRequestBus::EventResult(uiCanvasPass, sceneId, &LyShinePassRequestBus::Events::GetUiCanvasPass);
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
AZ::RPI::ShaderOptionList shaderOptions;
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("true")));
@ -106,7 +113,15 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
{"TEXCOORD0", AZ::RHI::Format::R32G32_FLOAT} });
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::PrimitiveType
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
m_dynamicDraw->SetOutputScope(scene.get());
if (uiCanvasPass)
{
m_dynamicDraw->SetOutputScope(uiCanvasPass);
}
else
{
// Render target support is disabled
m_dynamicDraw->SetOutputScope(scene.get());
}
m_dynamicDraw->EndInit();
AZ::RHI::TargetBlendState targetBlendState;
@ -491,6 +506,7 @@ bool CDraw2d::GetDeferPrimitives()
return m_deferCalls;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CDraw2d::SetSortKey(int64_t key)
{
m_dynamicDraw->SetSortKey(key);

@ -163,6 +163,8 @@ CLyShine::CLyShine(ISystem* system)
AzFramework::InputTextEventListener::Connect();
UiCursorBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(
AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName());
AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect();
// These are internal Amazon components, so register them so that we can send back their names to our metrics collection
@ -240,9 +242,11 @@ CLyShine::~CLyShine()
{
UiCursorBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
AzFramework::InputTextEventListener::Disconnect();
AzFramework::InputChannelEventListener::Disconnect();
AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect();
LyShinePassDataRequestBus::Handler::BusDisconnect();
UiCanvasComponent::Shutdown();
@ -642,15 +646,19 @@ void CLyShine::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time
{
// Update the loaded UI canvases
Update(deltaTime);
// Recreate dirty render graphs and send primitive data to the dynamic draw context
Render();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int CLyShine::GetTickOrder()
{
return AZ::TICK_UI;
return AZ::TICK_PRE_RENDER;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CLyShine::OnRenderTick()
{
// Recreate dirty render graphs and send primitive data to the dynamic draw context
Render();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -658,6 +666,16 @@ void CLyShine::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapS
{
// Load cursor if its path was set before RPI was initialized
LoadUiCursor();
LyShinePassDataRequestBus::Handler::BusConnect(AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->GetId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
LyShine::AttachmentImagesAndDependencies CLyShine::GetRenderTargets()
{
LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies;
m_uiCanvasManager->GetRenderTargets(attachmentImagesAndDependencies);
return attachmentImagesAndDependencies;
}
////////////////////////////////////////////////////////////////////////////////////////////////////

@ -16,8 +16,11 @@
#include <AzFramework/Input/Events/InputTextEventListener.h>
#include <Atom/Bootstrap/BootstrapNotificationBus.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include "LyShinePassDataBus.h"
#if !defined(_RELEASE)
#define LYSHINE_INTERNAL_UNIT_TEST
#endif
@ -40,7 +43,9 @@ class CLyShine
, public AzFramework::InputChannelEventListener
, public AzFramework::InputTextEventListener
, public AZ::TickBus::Handler
, public AZ::RPI::ViewportContextNotificationBus::Handler
, protected AZ::Render::Bootstrap::NotificationBus::Handler
, protected LyShinePassDataRequestBus::Handler
{
public:
@ -111,10 +116,17 @@ public:
int GetTickOrder() override;
// ~TickEvents
// AZ::RPI::ViewportContextNotificationBus::Handler overrides...
void OnRenderTick() override;
// AZ::Render::Bootstrap::NotificationBus
void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override;
// ~AZ::Render::Bootstrap::NotificationBus
// LyShinePassDataRequestBus
LyShine::AttachmentImagesAndDependencies GetRenderTargets() override;
// ~LyShinePassDataRequestBus
// Get the UIRenderer for the game (which is owned by CLyShine). This is not exposed outside the gem.
UiRenderer* GetUiRenderer();

@ -0,0 +1,274 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/Trace.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Pass/PassAttachment.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
#include <AzCore/std/iterator.h>
#include "LyShinePass.h"
namespace LyShine
{
AZ::RPI::Ptr<LyShinePass> LyShinePass::Create(const AZ::RPI::PassDescriptor& descriptor)
{
return aznew LyShinePass(descriptor);
}
LyShinePass::LyShinePass(const AZ::RPI::PassDescriptor& descriptor)
: Base(descriptor)
{
}
LyShinePass::~LyShinePass()
{
LyShinePassRequestBus::Handler::BusDisconnect();
}
void LyShinePass::ResetInternal()
{
LyShinePassRequestBus::Handler::BusDisconnect();
Base::ResetInternal();
}
void LyShinePass::BuildInternal()
{
AZ::RPI::Scene* scene = GetScene();
if (scene)
{
// Listen for rebuild requests
LyShinePassRequestBus::Handler::BusConnect(scene->GetId());
RemoveChildren();
// Get the current list of render targets being used across all loaded UI Canvases
LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies;
LyShinePassDataRequestBus::EventResult(
attachmentImagesAndDependencies,
scene->GetId(),
&LyShinePassDataRequestBus::Events::GetRenderTargets
);
AddRttChildPasses(attachmentImagesAndDependencies);
AddUiCanvasChildPass(attachmentImagesAndDependencies);
}
Base::BuildInternal();
}
void LyShinePass::RebuildRttChildren()
{
QueueForBuildAndInitialization();
}
AZ::RPI::RasterPass* LyShinePass::GetRttPass(const AZStd::string& name)
{
for (auto child:m_children)
{
if (child->GetName() == AZ::Name(name))
{
return azrtti_cast<AZ::RPI::RasterPass*>(child.get());
}
}
return nullptr;
}
AZ::RPI::RasterPass* LyShinePass::GetUiCanvasPass()
{
return m_uiCanvasChildPass.get();
}
void LyShinePass::AddRttChildPasses(LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies)
{
for (const auto& attachmentImageAndDependencies : attachmentImagesAndDependencies)
{
AddRttChildPass(attachmentImageAndDependencies.first, attachmentImageAndDependencies.second);
}
}
void LyShinePass::AddRttChildPass(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage, AttachmentImages attachmentImageDependencies)
{
// Add a pass that renders to the specified texture
// Create a pass template
auto passTemplate = AZStd::make_shared<AZ::RPI::PassTemplate>();
passTemplate->m_name = "RttChildPass";
passTemplate->m_passClass = AZ::Name("RttChildPass");
// Slots
passTemplate->m_slots.resize(2);
AZ::RPI::PassSlot& depthInOutSlot = passTemplate->m_slots[0];
depthInOutSlot.m_name = "DepthInputOutput";
depthInOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput;
depthInOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::DepthStencil;
depthInOutSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(0.0f, 0);
depthInOutSlot.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::Clear;
AZ::RPI::PassSlot& outSlot = passTemplate->m_slots[1];
outSlot.m_name = AZ::Name("RenderTargetOutput");
outSlot.m_slotType = AZ::RPI::PassSlotType::Output;
outSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::RenderTarget;
outSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateVector4Float(0.0f, 0.0f, 0.0f, 0.0f);
outSlot.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Clear;
// Connections
passTemplate->m_connections.resize(1);
AZ::RPI::PassConnection& depthInOutConnection = passTemplate->m_connections[0];
depthInOutConnection.m_localSlot = "DepthInputOutput";
depthInOutConnection.m_attachmentRef.m_pass = "Parent";
depthInOutConnection.m_attachmentRef.m_attachment = "DepthInputOutput";
// Pass data
AZStd::shared_ptr<AZ::RPI::RasterPassData> passData = AZStd::make_shared<AZ::RPI::RasterPassData>();
passData->m_drawListTag = AZ::Name("uicanvas");
passData->m_pipelineViewTag = AZ::Name("MainCamera");
auto size = attachmentImage->GetRHIImage()->GetDescriptor().m_size;
passData->m_overrideScissor = AZ::RHI::Scissor(0, 0, size.m_width, size.m_height);
passData->m_overrideViewport = AZ::RHI::Viewport(0, size.m_width, 0, size.m_height);
passTemplate->m_passData = AZStd::move(passData);
// Create a pass descriptor for the new child pass
AZ::RPI::PassDescriptor childDesc;
childDesc.m_passTemplate = passTemplate;
childDesc.m_passName = attachmentImage->GetAttachmentId();
AZ::RPI::PassSystemInterface* passSystem = AZ::RPI::PassSystemInterface::Get();
AZ::RPI::Ptr<RttChildPass> rttChildPass = passSystem->CreatePass<RttChildPass>(childDesc);
AZ_Assert(rttChildPass, "[LyShinePass] Unable to create %s.", passTemplate->m_name.GetCStr());
// Store the info needed to attach to slots and set up frame graph dependencies
rttChildPass->m_attachmentImage = attachmentImage;
rttChildPass->m_attachmentImageDependencies = attachmentImageDependencies;
AddChild(rttChildPass);
}
void LyShinePass::AddUiCanvasChildPass(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies)
{
if (!m_uiCanvasChildPass)
{
// Create a pass template
auto passTemplate = AZStd::make_shared<AZ::RPI::PassTemplate>();
passTemplate->m_name = AZ::Name("LyShineChildPass");
passTemplate->m_passClass = AZ::Name("LyShineChildPass");
// Slots
passTemplate->m_slots.resize(2);
AZ::RPI::PassSlot& depthInOutSlot = passTemplate->m_slots[0];
depthInOutSlot.m_name = "DepthInputOutput";
depthInOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput;
depthInOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::DepthStencil;
depthInOutSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(0.0f, 0);
depthInOutSlot.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::Clear;
AZ::RPI::PassSlot& inOutSlot = passTemplate->m_slots[1];
inOutSlot.m_name = "ColorInputOutput";
inOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput;
inOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::RenderTarget;
// Connections
passTemplate->m_connections.resize(2);
AZ::RPI::PassConnection& depthInOutConnection = passTemplate->m_connections[0];
depthInOutConnection.m_localSlot = "DepthInputOutput";
depthInOutConnection.m_attachmentRef.m_pass = "Parent";
depthInOutConnection.m_attachmentRef.m_attachment = "DepthInputOutput";
AZ::RPI::PassConnection& inOutConnection = passTemplate->m_connections[1];
inOutConnection.m_localSlot = "ColorInputOutput";
inOutConnection.m_attachmentRef.m_pass = "Parent";
inOutConnection.m_attachmentRef.m_attachment = "ColorInputOutput";
// Pass data
AZStd::shared_ptr<AZ::RPI::RasterPassData> passData = AZStd::make_shared<AZ::RPI::RasterPassData>();
passData->m_drawListTag = AZ::Name("uicanvas");
passData->m_pipelineViewTag = AZ::Name("MainCamera");
passTemplate->m_passData = AZStd::move(passData);
// Create a pass descriptor for the new child pass
AZ::RPI::PassDescriptor childDesc;
childDesc.m_passTemplate = passTemplate;
childDesc.m_passName = AZ::Name("LyShineChildPass");
AZ::RPI::PassSystemInterface* passSystem = AZ::RPI::PassSystemInterface::Get();
m_uiCanvasChildPass = passSystem->CreatePass<LyShineChildPass>(childDesc);
AZ_Assert(m_uiCanvasChildPass, "[LyShinePass] Unable to create %s.", passTemplate->m_name.GetCStr());
}
// Store the info needed to set up frame graph dependencies
m_uiCanvasChildPass->m_attachmentImageDependencies.clear();
for (const auto& attachmentImageAndDescendents : AttachmentImagesAndDependencies)
{
m_uiCanvasChildPass->m_attachmentImageDependencies.emplace_back(attachmentImageAndDescendents.first);
}
AddChild(m_uiCanvasChildPass);
}
AZ::RPI::Ptr<LyShineChildPass> LyShineChildPass::Create(const AZ::RPI::PassDescriptor& descriptor)
{
return aznew LyShineChildPass(descriptor);
}
LyShineChildPass::LyShineChildPass(const AZ::RPI::PassDescriptor& descriptor)
: RasterPass(descriptor)
{
}
LyShineChildPass::~LyShineChildPass()
{
}
void LyShineChildPass::SetupFrameGraphDependencies(AZ::RHI::FrameGraphInterface frameGraph)
{
AZ::RPI::RasterPass::SetupFrameGraphDependencies(frameGraph);
for (auto attachmentImage : m_attachmentImageDependencies)
{
// Ensure that the image is imported into the attachment database.
// The image may not be imported if the owning pass has been disabled.
auto attachmentImageId = attachmentImage->GetAttachmentId();
if (!frameGraph.GetAttachmentDatabase().IsAttachmentValid(attachmentImageId))
{
frameGraph.GetAttachmentDatabase().ImportImage(attachmentImageId, attachmentImage->GetRHIImage());
}
AZ::RHI::ImageScopeAttachmentDescriptor desc;
desc.m_attachmentId = attachmentImageId;
desc.m_imageViewDescriptor = attachmentImage->GetImageView()->GetDescriptor();
desc.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Load;
frameGraph.UseShaderAttachment(desc, AZ::RHI::ScopeAttachmentAccess::Read);
}
}
AZ::RPI::Ptr<RttChildPass> RttChildPass::Create(const AZ::RPI::PassDescriptor& descriptor)
{
return aznew RttChildPass(descriptor);
}
RttChildPass::RttChildPass(const AZ::RPI::PassDescriptor& descriptor)
: LyShineChildPass(descriptor)
{
}
RttChildPass::~RttChildPass()
{
}
void RttChildPass::BuildInternal()
{
AttachImageToSlot(AZ::Name("RenderTargetOutput"), m_attachmentImage);
}
} // namespace LyShine

@ -0,0 +1,110 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <AtomCore/std/containers/array_view.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include "LyShinePassDataBus.h"
namespace LyShine
{
class LyShineChildPass;
//! Manages child passes at runtime that render to render targets
class LyShinePass final
: public AZ::RPI::ParentPass
, protected LyShinePassRequestBus::Handler
{
AZ_RPI_PASS(LyShinePass);
using Base = AZ::RPI::ParentPass;
public:
AZ_CLASS_ALLOCATOR(LyShinePass, AZ::SystemAllocator, 0);
AZ_RTTI(LyShinePass, "C3B812ED-3771-42F4-A96F-EBD94B4D54CA", Base);
virtual ~LyShinePass();
static AZ::RPI::Ptr<LyShinePass> Create(const AZ::RPI::PassDescriptor& descriptor);
protected:
// Pass behavior overrides
void ResetInternal() override;
void BuildInternal() override;
// LyShinePassRequestBus overrides
void RebuildRttChildren() override;
AZ::RPI::RasterPass* GetRttPass(const AZStd::string& name) override;
AZ::RPI::RasterPass* GetUiCanvasPass() override;
private:
LyShinePass() = delete;
explicit LyShinePass(const AZ::RPI::PassDescriptor& descriptor);
// Build the render to texture child passes
void AddRttChildPasses(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies);
// Add a render to texture child pass
void AddRttChildPass(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage, AttachmentImages dependentAttachmentImages);
// Append the final pass to render UI Canvas elements to the screen
void AddUiCanvasChildPass(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies);
// Pass that renders the UI Canvas elements to the screen
AZ::RPI::Ptr<LyShineChildPass> m_uiCanvasChildPass;
};
// Child pass with potential attachment dependencies
class LyShineChildPass
: public AZ::RPI::RasterPass
{
AZ_RPI_PASS(LyShineChildPass);
friend class LyShinePass;
public:
AZ_RTTI(LyShineChildPass, "{41D525F9-09EB-4004-97DC-082078FF8DD2}", RasterPass);
AZ_CLASS_ALLOCATOR(LyShineChildPass, AZ::SystemAllocator, 0);
virtual ~LyShineChildPass();
//! Creates a LyShineChildPass
static AZ::RPI::Ptr<LyShineChildPass> Create(const AZ::RPI::PassDescriptor& descriptor);
protected:
LyShineChildPass(const AZ::RPI::PassDescriptor& descriptor);
// Scope producer Overrides...
void SetupFrameGraphDependencies(AZ::RHI::FrameGraphInterface frameGraph) override;
AttachmentImages m_attachmentImageDependencies;
};
// Child pass that renders UI elements to a render target
class RttChildPass
: public LyShineChildPass
{
AZ_RPI_PASS(RttChildPass);
friend class LyShinePass;
public:
AZ_RTTI(RttChildPass, "{54B0574D-2EB3-4054-9E1D-0E0D9C8CB09A}", LyShineChildPass);
AZ_CLASS_ALLOCATOR(RttChildPass, AZ::SystemAllocator, 0);
virtual ~RttChildPass();
//! Creates a RttChildPass
static AZ::RPI::Ptr<RttChildPass> Create(const AZ::RPI::PassDescriptor& descriptor);
protected:
RttChildPass(const AZ::RPI::PassDescriptor& descriptor);
// Pass behavior overrides
void BuildInternal() override;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_attachmentImage;
};
} // namespace LyShine

@ -0,0 +1,61 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AtomCore/Instance/Instance.h>
#include <Atom/RPI.Public/Base.h>
namespace AZ
{
namespace RPI
{
class AttachmentImage;
class RasterPass;
}
}
namespace LyShine
{
using AttachmentImages = AZStd::vector<AZ::Data::Instance<AZ::RPI::AttachmentImage>>;
using AttachmentImageAndDependentsPair = AZStd::pair<AZ::Data::Instance<AZ::RPI::AttachmentImage>, AttachmentImages>;
using AttachmentImagesAndDependencies = AZStd::vector<AttachmentImageAndDependentsPair>;
}
class LyShinePassRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::RPI::SceneId;
//! Called when the number of render targets has changed and the LyShine pass needs to rebuild
virtual void RebuildRttChildren() = 0;
//! Returns a render to texture pass based on render target name
virtual AZ::RPI::RasterPass* GetRttPass(const AZStd::string& name) = 0;
//! Returns the final pass that renders the UI canvas contents
virtual AZ::RPI::RasterPass* GetUiCanvasPass() = 0;
};
using LyShinePassRequestBus = AZ::EBus<LyShinePassRequests>;
class LyShinePassDataRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::RPI::SceneId;
//! Get a list of render targets that require a render to texture pass, and any
//! other render targets that are drawn on them
virtual LyShine::AttachmentImagesAndDependencies GetRenderTargets() = 0;
};
using LyShinePassDataRequestBus = AZ::EBus<LyShinePassDataRequests>;

@ -49,6 +49,7 @@
#include "UiDynamicLayoutComponent.h"
#include "UiDynamicScrollBoxComponent.h"
#include "UiNavigationSettings.h"
#include "LyShinePass.h"
namespace LyShine
{
@ -113,9 +114,11 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void LyShineSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
void LyShineSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
(void)required;
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
required.push_back(AZ_CRC("RPISystem", 0xf2add773));
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -186,6 +189,17 @@ namespace LyShine
RegisterComponentTypeForMenuOrdering(UiDynamicScrollBoxComponent::RTTI_Type());
RegisterComponentTypeForMenuOrdering(UiParticleEmitterComponent::RTTI_Type());
RegisterComponentTypeForMenuOrdering(UiFlipbookAnimationComponent::RTTI_Type());
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
// Add LyShine pass
auto* passSystem = AZ::RPI::PassSystemInterface::Get();
AZ_Assert(passSystem, "Cannot get the pass system.");
passSystem->AddPassCreator(AZ::Name("LyShinePass"), &LyShine::LyShinePass::Create);
// Setup handler for load pass template mappings
m_loadTemplatesHandler = AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); });
AZ::RPI::PassSystemInterface::Get()->ConnectEvent(m_loadTemplatesHandler);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -386,4 +400,13 @@ namespace LyShine
{
UiCursorBus::Broadcast(&UiCursorInterface::SetUiCursor, m_cursorImagePathname.GetAssetPath().c_str());
}
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
////////////////////////////////////////////////////////////////////////////////////////////////////
void LyShineSystemComponent::LoadPassTemplateMappings()
{
const char* passTemplatesFile = "Passes/LyShinePassTemplates.azasset";
AZ::RPI::PassSystemInterface::Get()->LoadPassTemplateMappings(passTemplatesFile);
}
#endif
}

@ -20,6 +20,10 @@
#include <LyShine/UiComponentTypes.h>
#include "LyShine.h"
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#endif
namespace LyShine
{
// LyShine depends on the LegacyAllocator and CryStringAllocator. This will be managed
@ -90,6 +94,11 @@ namespace LyShine
void BroadcastCursorImagePathname();
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
// Load pass template mappings for this gem
void LoadPassTemplateMappings();
#endif
protected: // data
CLyShine* m_pLyShine = nullptr;
@ -102,5 +111,9 @@ namespace LyShine
// We only store this in order to generate metrics on LyShine specific components
static const AZStd::list<AZ::ComponentDescriptor*>* m_componentDescriptors;
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler m_loadTemplatesHandler;
#endif
};
}

@ -10,6 +10,9 @@
#include "UiRenderer.h"
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <AzCore/Math/MatrixUtils.h>
#ifndef _RELEASE
#include <AzCore/Asset/AssetManagerBus.h>
@ -78,57 +81,28 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void PrimitiveListRenderNode::Render(UiRenderer* uiRenderer)
void PrimitiveListRenderNode::Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw)
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (masks/render targets)
for (int i = 0; i < m_numTextures; ++i)
if (!uiRenderer->IsReady())
{
uiRenderer->SetTexture(m_textures[i].m_texture, i, m_textures[i].m_isClampTextureMode);
return;
}
int blendModeState = m_blendModeState;
IRenderer* renderer = gEnv->pRenderer;
renderer->SetState(blendModeState | uiRenderer->GetBaseState());
UiRenderer::BaseState curBaseState = uiRenderer->GetBaseState();
UiRenderer::BaseState prevBaseState = curBaseState;
if (m_isTextureSRGB)
{
renderer->SetSrgbWrite(false);
curBaseState.m_srgbWrite = false;
}
// We are using SetColorOp as a way to set flags for the ui.cfx shader by reusing flags
// that the FixedPipelineEmu.cfx shader uses. So the names colorOp and alphaOp are used
// just because this are the inputs to SetColorOp.
uint8 colorOp = m_preMultiplyAlpha ? ColorOp_PreMultiplyAlpha : ColorOp_Normal;
uint8 alphaOp = AlphaOp_Normal;
switch (m_alphaMaskType)
{
case AlphaMaskType::None:
alphaOp = AlphaOp_Normal;
break;
case AlphaMaskType::ModulateAlpha:
alphaOp = AlphaOp_ModulateAlpha;
break;
case AlphaMaskType::ModulateAlphaAndColor:
alphaOp = AlphaOp_ModulateAlphaAndColor;
break;
}
renderer->SetColorOp(colorOp, alphaOp, DEF_TEXARG0, DEF_TEXARG0);
renderer->DrawDynUiPrimitiveList(m_primitives, m_totalNumVertices, m_totalNumIndices);
if (m_isTextureSRGB)
if (m_alphaMaskType == AlphaMaskType::ModulateAlpha)
{
renderer->SetSrgbWrite(true);
}
#endif
if (!uiRenderer->IsReady())
{
return;
curBaseState.m_modulateAlpha = true;
}
uiRenderer->SetBaseState(curBaseState);
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
const UiRenderer::UiShaderData& uiShaderData = uiRenderer->GetUiShaderData();
// Set render state
@ -167,7 +141,7 @@ namespace LyShine
drawSrg->SetConstant(uiShaderData.m_isClampInputIndex, isClampTextureMode);
// Set projection matrix
drawSrg->SetConstant(uiShaderData.m_viewProjInputIndex, uiRenderer->GetModelViewProjectionMatrix());
drawSrg->SetConstant(uiShaderData.m_viewProjInputIndex, modelViewProjMat);
drawSrg->Compile();
@ -180,6 +154,8 @@ namespace LyShine
{
dynamicDraw->DrawIndexed(primitive.m_vertices, primitive.m_numVertices, primitive.m_indices, primitive.m_numIndices, AZ::RHI::IndexFormat::Uint16, drawSrg);
}
uiRenderer->SetBaseState(prevBaseState);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -303,33 +279,35 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void MaskRenderNode::Render(UiRenderer* uiRenderer)
void MaskRenderNode::Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw)
{
UiRenderer::BaseState priorBaseState = uiRenderer->GetBaseState();
if (m_isMaskingEnabled || m_drawBehind)
{
SetupBeforeRenderingMask(uiRenderer, true, priorBaseState);
SetupBeforeRenderingMask(uiRenderer, dynamicDraw, true, priorBaseState);
for (RenderNode* renderNode : m_maskRenderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw);
}
SetupAfterRenderingMask(uiRenderer, true, priorBaseState);
SetupAfterRenderingMask(uiRenderer, dynamicDraw, true, priorBaseState);
}
for (RenderNode* renderNode : m_contentRenderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw);
}
if (m_isMaskingEnabled || m_drawInFront)
{
SetupBeforeRenderingMask(uiRenderer, false, priorBaseState);
SetupBeforeRenderingMask(uiRenderer, dynamicDraw, false, priorBaseState);
for (RenderNode* renderNode : m_maskRenderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw);
}
SetupAfterRenderingMask(uiRenderer, false, priorBaseState);
SetupAfterRenderingMask(uiRenderer, dynamicDraw, false, priorBaseState);
}
}
@ -367,7 +345,9 @@ namespace LyShine
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
void MaskRenderNode::SetupBeforeRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState)
void MaskRenderNode::SetupBeforeRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState)
{
UiRenderer::BaseState curBaseState = priorBaseState;
@ -406,7 +386,6 @@ namespace LyShine
curBaseState.m_stencilState.m_backFace = stencilOpState;
// set up for stencil write
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
dynamicDraw->SetStencilReference(uiRenderer->GetStencilRef());
curBaseState.m_stencilState.m_enable = true;
curBaseState.m_stencilState.m_writeMask = 0xFF;
@ -421,7 +400,9 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void MaskRenderNode::SetupAfterRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState)
void MaskRenderNode::SetupAfterRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState)
{
if (m_isMaskingEnabled)
{
@ -439,7 +420,6 @@ namespace LyShine
uiRenderer->DecrementStencilRef();
}
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
dynamicDraw->SetStencilReference(uiRenderer->GetStencilRef());
if (firstPass)
@ -474,16 +454,14 @@ namespace LyShine
////////////////////////////////////////////////////////////////////////////////////////////////////
RenderTargetRenderNode::RenderTargetRenderNode(
RenderTargetRenderNode* parentRenderTarget,
int renderTargetHandle,
SDepthTexture* renderTargetDepthSurface,
AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft,
const AZ::Vector2& viewportSize,
const AZ::Color& clearColor,
int nestLevel)
: RenderNode(RenderNodeType::RenderTarget)
, m_parentRenderTarget(parentRenderTarget)
, m_renderTargetHandle(renderTargetHandle)
, m_renderTargetDepthSurface(renderTargetDepthSurface)
, m_attachmentImage(attachmentImage)
, m_viewportX(viewportTopLeft.GetX())
, m_viewportY(viewportTopLeft.GetY())
, m_viewportWidth(viewportSize.GetX())
@ -491,6 +469,13 @@ namespace LyShine
, m_clearColor(clearColor)
, m_nestLevel(nestLevel)
{
AZ::MakeOrthographicMatrixRH(m_modelViewProjMat,
m_viewportX,
m_viewportX + m_viewportWidth,
m_viewportY + m_viewportHeight,
m_viewportY,
0.0f,
1.0f);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -505,9 +490,11 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderTargetRenderNode::Render(UiRenderer* uiRenderer)
void RenderTargetRenderNode::Render(UiRenderer* uiRenderer
, [[maybe_unused]] const AZ::Matrix4x4& modelViewProjMat
, [[maybe_unused]] AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw)
{
if (m_renderTargetHandle <= 0)
if (!m_attachmentImage)
{
return;
}
@ -515,39 +502,52 @@ namespace LyShine
ISystem* system = gEnv->pSystem;
if (system && !gEnv->IsDedicated())
{
TransformationMatrices backupMatrices;
gEnv->pRenderer->Set2DModeNonZeroTopLeft(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight, backupMatrices);
// this will change the viewport
gEnv->pRenderer->SetRenderTarget(m_renderTargetHandle, m_renderTargetDepthSurface);
// clear the render target before rendering to it
// NOTE: the FRT_CLEAR_IMMEDIATE is required since we will have already set the render target
// In theory we could call this before setting the render target without the immediate flag
// but that doesn't work. Perhaps because FX_Commit is not called.
ColorF viewportBackgroundColor(m_clearColor.GetR(), m_clearColor.GetG(), m_clearColor.GetB(), m_clearColor.GetA());
gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR, viewportBackgroundColor);
// we could use SetSrgbWrite to write to a linear texture here. But that gets complicated with
// having to affect all decsendant element renders. So we just let it write srgb to the render target and
// allow for that when we render using the render target as a source texture.
for (RenderNode* renderNode : m_childRenderNodes)
// Use a dedicated dynamic draw context for rendering to the texture since it can only have one draw list tag
if (!m_dynamicDraw)
{
renderNode->Render(uiRenderer);
m_dynamicDraw = uiRenderer->CreateDynamicDrawContextForRTT(GetRenderTargetName());
}
gEnv->pRenderer->SetRenderTarget(0); // restore render target
if (m_dynamicDraw)
{
UiRenderer::BaseState priorBaseState = uiRenderer->GetBaseState();
UiRenderer::BaseState curBaseState = priorBaseState;
curBaseState.m_blendState.m_blendAlphaSource = AZ::RHI::BlendFactor::One;
curBaseState.m_blendState.m_blendAlphaDest = AZ::RHI::BlendFactor::AlphaSource1Inverse;
uiRenderer->SetBaseState(curBaseState);
for (RenderNode* renderNode : m_childRenderNodes)
{
renderNode->Render(uiRenderer, m_modelViewProjMat, m_dynamicDraw);
}
gEnv->pRenderer->Unset2DMode(backupMatrices);
uiRenderer->SetBaseState(priorBaseState);
}
else
{
AZ_WarningOnce("UI", false, "Failed to create a Dynamic Draw Context for UI Element's render target. "\
"Please ensure that the custom LyShinePass has been added to the project's main render pipeline.");
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const char* RenderTargetRenderNode::GetRenderTargetName() const
{
ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_renderTargetHandle);
return texture->GetName();
return m_attachmentImage->GetRHIImage()->GetName().GetCStr();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int RenderTargetRenderNode::GetNestLevel() const
{
return m_nestLevel;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const AZ::Data::Instance<AZ::RPI::AttachmentImage> RenderTargetRenderNode::GetRenderTarget() const
{
return m_attachmentImage;
}
#ifndef _RELEASE
@ -671,31 +671,29 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::BeginRenderToTexture(int renderTargetHandle, SDepthTexture* renderTargetDepthSurface,
void RenderGraph::BeginRenderToTexture([[maybe_unused]] int renderTargetHandle, [[maybe_unused]] SDepthTexture* renderTargetDepthSurface,
[[maybe_unused]] const AZ::Vector2& viewportTopLeft, [[maybe_unused]] const AZ::Vector2& viewportSize, [[maybe_unused]] const AZ::Color& clearColor)
{
// LYSHINE_ATOM_TODO - this function will be removed when all IRenderer references are gone from UI components
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::BeginRenderToTexture(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor)
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets)
// this uses pool allocator
RenderTargetRenderNode* renderTargetRenderNode = new RenderTargetRenderNode(
m_currentRenderTarget, renderTargetHandle, renderTargetDepthSurface,
m_currentRenderTarget, attachmentImage,
viewportTopLeft, viewportSize, clearColor, m_renderTargetNestLevel);
m_currentRenderTarget = renderTargetRenderNode;
m_renderNodeListStack.push(&m_currentRenderTarget->GetChildRenderNodeList());
m_renderTargetNestLevel++;
#else
AZ_UNUSED(clearColor);
AZ_UNUSED(viewportSize);
AZ_UNUSED(viewportTopLeft);
AZ_UNUSED(renderTargetDepthSurface);
AZ_UNUSED(renderTargetHandle);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::EndRenderToTexture()
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets)
AZ_Assert(m_currentRenderTarget, "Calling EndRenderToTexture while not defining a render target node");
if (m_currentRenderTarget)
{
@ -709,7 +707,6 @@ namespace LyShine
m_renderNodeListStack.pop();
m_renderTargetNestLevel--;
}
#endif
}
void RenderGraph::AddPrimitive(
@ -803,11 +800,22 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::AddAlphaMaskPrimitive(IRenderer::DynUiPrimitive* primitive,
ITexture* texture, ITexture* maskTexture,
bool isClampTextureMode, bool isTextureSRGB, bool isTexturePremultipliedAlpha, BlendMode blendMode)
void RenderGraph::AddAlphaMaskPrimitive([[maybe_unused]] IRenderer::DynUiPrimitive* primitive,
[[maybe_unused]] ITexture* texture, [[maybe_unused]] ITexture* maskTexture,
[[maybe_unused]] bool isClampTextureMode, [[maybe_unused]] bool isTextureSRGB, [[maybe_unused]] bool isTexturePremultipliedAlpha, [[maybe_unused]] BlendMode blendMode)
{
// LYSHINE_ATOM_TODO - this function will be removed when all IRenderer references are gone from UI components
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::AddAlphaMaskPrimitiveAtom(IRenderer::DynUiPrimitive* primitive,
AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage,
AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage,
bool isClampTextureMode,
bool isTextureSRGB,
bool isTexturePremultipliedAlpha,
BlendMode blendMode)
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets)
AZStd::vector<RenderNode*>* renderNodeList = m_renderNodeListStack.top();
int texUnit0 = -1;
@ -842,8 +850,8 @@ namespace LyShine
{
// render state is the same - we can add the primitive to this list if the texture is in
// the list or there is space for another texture
texUnit0 = primListRenderNode->GetOrAddTexture(texture, true);
texUnit1 = primListRenderNode->GetOrAddTexture(maskTexture, true);
texUnit0 = primListRenderNode->GetOrAddTexture(contentAttachmentImage, true);
texUnit1 = primListRenderNode->GetOrAddTexture(maskAttachmentImage, true);
if (texUnit0 != -1 && texUnit1 != -1)
{
@ -857,7 +865,7 @@ namespace LyShine
{
// We can't add this primitive to the existing render node, we need to create a new render node
// this uses a pool allocator for fast allocation
renderNodeToAddTo = new PrimitiveListRenderNode(texture, maskTexture,
renderNodeToAddTo = new PrimitiveListRenderNode(contentAttachmentImage, maskAttachmentImage,
isClampTextureMode, isTextureSRGB, isPreMultiplyAlpha, alphaMaskType, blendModeState);
renderNodeList->push_back(renderNodeToAddTo);
@ -881,15 +889,6 @@ namespace LyShine
// add this primitive to the render node
renderNodeToAddTo->AddPrimitive(primitive);
}
#else
AZ_UNUSED(primitive);
AZ_UNUSED(texture);
AZ_UNUSED(maskTexture);
AZ_UNUSED(isClampTextureMode);
AZ_UNUSED(isTextureSRGB);
AZ_UNUSED(isTexturePremultipliedAlpha);
AZ_UNUSED(blendMode);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -972,11 +971,8 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::Render(UiRenderer* uiRenderer, const AZ::Vector2& viewportSize)
void RenderGraph::Render(UiRenderer* uiRenderer, [[maybe_unused]] const AZ::Vector2& viewportSize)
{
// LYSHINE_ATOM_TODO - will probably need to support this when converting UI Editor to use Atom
AZ_UNUSED(viewportSize);
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
// Disable stencil and enable blend/color write
@ -984,57 +980,35 @@ namespace LyShine
dynamicDraw->SetTarget0BlendState(uiRenderer->GetBaseState().m_blendState);
// First render the render targets, they are sorted so that more deeply nested ones are rendered first.
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (render targets)
// They only need to be rendered the first time that a render graph is rendered after it has been built.
// Though there is a special case, if this is the first time a shader variant has been used it can miss
// the first render. So to be safe we only stop rendering to render targets after we have rendered to
// them twice with no shader compiles initiated.
if (m_renderToRenderTargetCount < 2)
if (m_renderToRenderTargetCount == 0)
{
for (RenderNode* renderNode : m_renderTargetRenderNodes)
{
renderNode->Render(uiRenderer);
}
// Enable the Rtt passes to draw onto the render targets
SetRttPassesEnabled(uiRenderer, true);
}
// if the render targets render OK we don't need to render them every frame. But if a new shader
// variant needed to be compiled then they will not have rendered OK. So we check is there are
// any shaders still in the process of compiling. Because they are compiled on the render
// thread, we may not know until the next frame that a shader needed to be compiled. So we need
// the counter.
SShaderCacheStatistics stats;
gEnv->pRenderer->EF_Query(EFQ_GetShaderCacheInfo, stats);
bool waitingOnShadersToCompile = stats.m_nNumShaderAsyncCompiles > 0 ? true : false;
if (!waitingOnShadersToCompile)
{
m_renderToRenderTargetCount++;
}
else
// LYSHINE_ATOM_TODO - It is currently necessary to render to the targets twice. Needs investigation
constexpr int timesToRenderToRenderTargets = 2;
if (m_renderToRenderTargetCount < timesToRenderToRenderTargets)
{
for (RenderNode* renderNode : m_renderTargetRenderNodes)
{
m_renderToRenderTargetCount = 0;
renderNode->Render(uiRenderer, uiRenderer->GetModelViewProjectionMatrix(), dynamicDraw);
}
m_renderToRenderTargetCount++;
}
#else
for (RenderNode* renderNode : m_renderTargetRenderNodes)
else if (m_renderToRenderTargetCount < timesToRenderToRenderTargets + 1)
{
renderNode->Render(uiRenderer);
// Disable the rtt render passes since they don't need to be rendered to until the graph becomes invalidated again.
// This is also necessary to prevent the render targets' contents getting cleared on load by the pass.
SetRttPassesEnabled(uiRenderer, false);
m_renderToRenderTargetCount++;
}
#endif
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (UI Editor)
// Set2DMode defines the viewport so we set it to canvas viewport here (the render target render nodes
// above will have set the viewport as they needed).
TransformationMatrices backupMatrices;
gEnv->pRenderer->Set2DMode(static_cast<uint32>(viewportSize.GetX()), static_cast<uint32>(viewportSize.GetY()), backupMatrices);
#endif
for (RenderNode* renderNode : m_renderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, uiRenderer->GetModelViewProjectionMatrix(), dynamicDraw);
}
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (UI Editor)
// end the 2D mode
gEnv->pRenderer->Unset2DMode(backupMatrices);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -1072,6 +1046,31 @@ namespace LyShine
return m_renderNodes.empty();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::GetRenderTargetsAndDependencies(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies)
{
for (RenderNode* renderNode : m_renderTargetRenderNodes)
{
const RenderTargetRenderNode* renderTargetRenderNode = static_cast<const RenderTargetRenderNode*>(renderNode);
if (renderTargetRenderNode->GetNestLevel() == 0)
{
LyShine::AttachmentImages attachmentImages;
const AZStd::vector<RenderNode*>& childNodeList = renderTargetRenderNode->GetChildRenderNodeList();
for (auto& childNode : childNodeList)
{
if (childNode->GetType() == RenderNodeType::RenderTarget)
{
const RenderTargetRenderNode* childRenderTargetRenderNode = static_cast<const RenderTargetRenderNode*>(childNode);
attachmentImages.emplace_back(childRenderTargetRenderNode->GetRenderTarget());
}
}
attachmentImagesAndDependencies.emplace_back(AttachmentImageAndDependentsPair(renderTargetRenderNode->GetRenderTarget(), attachmentImages));
}
}
}
#ifndef _RELEASE
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::ValidateGraph()
@ -1540,4 +1539,19 @@ namespace LyShine
return flags;
}
void RenderGraph::SetRttPassesEnabled(UiRenderer* uiRenderer, bool enabled)
{
// Enable or disable the rtt render passes
AZ::RPI::SceneId sceneId = uiRenderer->GetViewportContext()->GetRenderScene()->GetId();
for (RenderTargetRenderNode* renderTargetRenderNode : m_renderTargetRenderNodes)
{
// Find the rtt pass to disable
AZ::RPI::RasterPass* rttPass = nullptr;
LyShinePassRequestBus::EventResult(rttPass, sceneId, &LyShinePassRequestBus::Events::GetRttPass, renderTargetRenderNode->GetRenderTargetName());
if (rttPass)
{
rttPass->SetEnabled(enabled);
}
}
}
}

@ -15,10 +15,13 @@
#include <AzCore/std/containers/set.h>
#include <AzCore/Math/Color.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
#include <AtomCore/Instance/Instance.h>
#include "UiRenderer.h"
#include "LyShinePass.h"
#ifndef _RELEASE
#include "LyShineDebug.h"
#endif
@ -46,7 +49,9 @@ namespace LyShine
RenderNode(RenderNodeType type) : m_type(type) {}
virtual ~RenderNode() {};
virtual void Render(UiRenderer* uiRenderer) = 0;
virtual void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) = 0;
RenderNodeType GetType() const { return m_type; }
@ -70,7 +75,9 @@ namespace LyShine
PrimitiveListRenderNode(const AZ::Data::Instance<AZ::RPI::Image>& texture, const AZ::Data::Instance<AZ::RPI::Image>& maskTexture,
bool isClampTextureMode, bool isTextureSRGB, bool preMultiplyAlpha, AlphaMaskType alphaMaskType, int blendModeState);
~PrimitiveListRenderNode() override;
void Render(UiRenderer* uiRenderer) override;
void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) override;
void AddPrimitive(IRenderer::DynUiPrimitive* primitive);
IRenderer::DynUiPrimitiveList& GetPrimitives() const;
@ -128,7 +135,9 @@ namespace LyShine
MaskRenderNode(MaskRenderNode* parentMask, bool isMaskingEnabled, bool useAlphaTest, bool drawBehind, bool drawInFront);
~MaskRenderNode() override;
void Render(UiRenderer* uiRenderer) override;
void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) override;
AZStd::vector<RenderNode*>& GetMaskRenderNodeList() { return m_maskRenderNodes; }
const AZStd::vector<RenderNode*>& GetMaskRenderNodeList() const { return m_maskRenderNodes; }
@ -152,8 +161,12 @@ namespace LyShine
#endif
private: // functions
void SetupBeforeRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState);
void SetupAfterRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState);
void SetupBeforeRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState);
void SetupAfterRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState);
private: // data
AZStd::vector<RenderNode*> m_maskRenderNodes; //!< The render nodes used to render the mask shape
@ -175,15 +188,17 @@ namespace LyShine
// We use a pool allocator to keep these allocations fast.
AZ_CLASS_ALLOCATOR(RenderTargetRenderNode, AZ::PoolAllocator, 0);
RenderTargetRenderNode(RenderTargetRenderNode* parentRenderTarget, int renderTargetHandle,
SDepthTexture* renderTargetDepthSurface,
RenderTargetRenderNode(RenderTargetRenderNode* parentRenderTarget,
AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft,
const AZ::Vector2& viewportSize,
const AZ::Color& clearColor,
int nestLevel);
~RenderTargetRenderNode() override;
void Render(UiRenderer* uiRenderer) override;
void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) override;
AZStd::vector<RenderNode*>& GetChildRenderNodeList() { return m_childRenderNodes; }
const AZStd::vector<RenderNode*>& GetChildRenderNodeList() const { return m_childRenderNodes; }
@ -197,6 +212,9 @@ namespace LyShine
AZ::Color GetClearColor() const { return m_clearColor; }
const char* GetRenderTargetName() const;
int GetNestLevel() const;
const AZ::Data::Instance<AZ::RPI::AttachmentImage> GetRenderTarget() const;
#ifndef _RELEASE
// A debug-only function useful for debugging
@ -213,13 +231,16 @@ namespace LyShine
RenderTargetRenderNode* m_parentRenderTarget = nullptr; //! Used while building the render graph.
int m_renderTargetHandle = -1;
SDepthTexture* m_renderTargetDepthSurface = nullptr;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_attachmentImage;
// Each render target requires a unique dynamic draw context to draw to the raster pass associated with the target
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> m_dynamicDraw;
float m_viewportX = 0;
float m_viewportY = 0;
float m_viewportWidth = 0;
float m_viewportHeight = 0;
AZ::Matrix4x4 m_modelViewProjMat;
AZ::Color m_clearColor;
int m_nestLevel = 0;
};
@ -241,9 +262,10 @@ namespace LyShine
void StartChildrenForMask() override;
void EndMask() override;
//! Begin rendering to a texture
void BeginRenderToTexture(int renderTargetHandle, SDepthTexture* renderTargetDepthSurface,
const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize,
const AZ::Color& clearColor) override;
const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor) override;
void EndRenderToTexture() override;
void AddPrimitive(IRenderer::DynUiPrimitive* primitive, ITexture* texture,
@ -268,6 +290,20 @@ namespace LyShine
void AddPrimitiveAtom(IRenderer::DynUiPrimitive* primitive, const AZ::Data::Instance<AZ::RPI::Image>& texture,
bool isClampTextureMode, bool isTextureSRGB, bool isTexturePremultipliedAlpha, BlendMode blendMode);
//! Add an indexed triangle list primitive to the render graph which will use maskTexture as an alpha (gradient) mask
void AddAlphaMaskPrimitiveAtom(IRenderer::DynUiPrimitive* primitive,
AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage,
AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage,
bool isClampTextureMode,
bool isTextureSRGB,
bool isTexturePremultipliedAlpha,
BlendMode blendMode);
void BeginRenderToTexture(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft,
const AZ::Vector2& viewportSize,
const AZ::Color& clearColor);
//! Render the display graph
void Render(UiRenderer* uiRenderer, const AZ::Vector2& viewportSize);
@ -283,6 +319,8 @@ namespace LyShine
//! Test whether the render graph contains any render nodes
bool IsEmpty();
void GetRenderTargetsAndDependencies(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies);
#ifndef _RELEASE
// A debug-only function useful for debugging, not called but calls can be added during debugging
void ValidateGraph();
@ -311,6 +349,8 @@ namespace LyShine
//! Given a blend mode and whether the shader will be outputing premultiplied alpha, return state flags
int GetBlendModeState(LyShine::BlendMode blendMode, bool isShaderOutputPremultAlpha) const;
void SetRttPassesEnabled(UiRenderer* uiRenderer, bool enabled);
protected: // data
AZStd::vector<RenderNode*> m_renderNodes;

@ -0,0 +1,22 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
namespace LyShine
{
//! Ebus to handle render target requests
class RenderToTextureRequests
: public AZ::ComponentBus
{
public:
virtual AZ::RHI::AttachmentId UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) = 0;
virtual void ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) = 0;
virtual AZ::Data::Instance<AZ::RPI::AttachmentImage> GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) = 0;
};
using RenderToTextureRequestBus = AZ::EBus<RenderToTextureRequests>;
}

@ -49,6 +49,8 @@
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include "Animation/UiAnimationSystem.h"
@ -64,6 +66,8 @@
#include <LyShine/Bus/UiFaderBus.h>
#endif
#include "LyShinePassDataBus.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
//! UiCanvasNotificationBus Behavior context handler class
class UiCanvasNotificationBusBehaviorHandler
@ -251,14 +255,22 @@ namespace
UiRenderer* GetUiRendererForGame()
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine ? lyShine->GetUiRenderer() : nullptr;
if (gEnv && gEnv->pLyShine)
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine->GetUiRenderer();
}
return nullptr;
}
UiRenderer* GetUiRendererForEditor()
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine ? lyShine->GetUiRendererForEditor() : nullptr;
if (gEnv && gEnv->pLyShine)
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine->GetUiRendererForEditor();
}
return nullptr;
}
bool IsValidInteractable(const AZ::EntityId& entityId)
@ -1829,6 +1841,46 @@ void UiCanvasComponent::MarkRenderGraphDirty()
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::RHI::AttachmentId UiCanvasComponent::UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size)
{
// Create a render target that UI elements will render to
AZ::RHI::ImageDescriptor imageDesc;
imageDesc.m_bindFlags = AZ::RHI::ImageBindFlags::Color | AZ::RHI::ImageBindFlags::ShaderReadWrite;
imageDesc.m_size = size;
imageDesc.m_format = AZ::RHI::Format::R8G8B8A8_UNORM;
AZ::Data::Instance<AZ::RPI::AttachmentImagePool> pool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
auto attachmentImage = AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, renderTargetName);
if (!attachmentImage)
{
AZ_Warning("UI", false, "Failed to create render target");
return AZ::RHI::AttachmentId();
}
m_attachmentImageMap[attachmentImage->GetAttachmentId()] = attachmentImage;
// Notify LyShine render pass that it needs to rebuild
QueueRttPassRebuild();
return attachmentImage->GetAttachmentId();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId)
{
m_attachmentImageMap.erase(attachmentId);
// Notify LyShine render pass that it needs to rebuild
QueueRttPassRebuild();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Data::Instance<AZ::RPI::AttachmentImage> UiCanvasComponent::GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId)
{
return m_attachmentImageMap[attachmentId];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::UpdateCanvas(float deltaTime, bool isInGame)
{
@ -1864,6 +1916,8 @@ void UiCanvasComponent::RenderCanvas(bool isInGame, AZ::Vector2 viewportSize, Ui
return;
}
m_renderInEditor = uiRenderer ? true : false;
if (!uiRenderer)
{
uiRenderer = GetUiRendererForGame();
@ -1948,6 +2002,12 @@ void UiCanvasComponent::ScheduleElementDestroy(AZ::EntityId entityId)
m_elementsScheduledForDestroy.push_back(entityId);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies)
{
m_renderGraph.GetRenderTargetsAndDependencies(attachmentImagesAndDependencies);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::DestroyScheduledElements()
{
@ -1959,6 +2019,17 @@ void UiCanvasComponent::DestroyScheduledElements()
m_elementsScheduledForDestroy.clear();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::QueueRttPassRebuild()
{
UiRenderer* uiRenderer = m_renderInEditor ? GetUiRendererForEditor() : GetUiRendererForGame();
if (uiRenderer && uiRenderer->GetViewportContext()) // can be null in automated testing
{
AZ::RPI::SceneId sceneId = uiRenderer->GetViewportContext()->GetRenderScene()->GetId();
EBUS_EVENT_ID(sceneId, LyShinePassRequestBus, RebuildRttChildren);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef _RELEASE
void UiCanvasComponent::GetDebugInfoInteractables(AZ::EntityId& activeInteractable, AZ::EntityId& hoverInteractable) const
@ -2350,6 +2421,7 @@ void UiCanvasComponent::Activate()
UiCanvasComponentImplementationBus::Handler::BusConnect(m_entity->GetId());
UiEditorCanvasBus::Handler::BusConnect(m_entity->GetId());
UiAnimationBus::Handler::BusConnect(m_entity->GetId());
LyShine::RenderToTextureRequestBus::Handler::BusConnect(m_entity->GetId());
// Reconnect to buses that we connect to intermittently
// This will only happen if we have been deactivated and reactivated at runtime
@ -2382,6 +2454,7 @@ void UiCanvasComponent::Deactivate()
UiCanvasComponentImplementationBus::Handler::BusDisconnect();
UiEditorCanvasBus::Handler::BusDisconnect();
UiAnimationBus::Handler::BusDisconnect();
LyShine::RenderToTextureRequestBus::Handler::BusDisconnect();
// disconnect from any other buses we could be connected to
if (m_hoverInteractable.IsValid() && AZ::EntityBus::Handler::BusIsConnectedId(m_hoverInteractable))
@ -2400,6 +2473,12 @@ void UiCanvasComponent::Deactivate()
DestroyRenderTarget();
}
// Destroy owned render targets
m_attachmentImageMap.clear();
//! Notify LyShine pass that it needs to rebuild
QueueRttPassRebuild();
delete m_layoutManager;
m_layoutManager = nullptr;

@ -32,6 +32,8 @@
#include "TextureAtlas/TextureAtlasBus.h"
#include "TextureAtlas/TextureAtlasNotificationBus.h"
#include "RenderToTextureBus.h"
namespace AZ
{
class SerializeContext;
@ -51,6 +53,7 @@ class UiCanvasComponent
, public IUiAnimationListener
, public UiEditorCanvasBus::Handler
, public UiCanvasComponentImplementationBus::Handler
, public LyShine::RenderToTextureRequestBus::Handler
{
public: // constants
static const AZ::Vector2 s_defaultCanvasSize;
@ -232,6 +235,12 @@ public: // member functions
void MarkRenderGraphDirty() override;
// ~UiCanvasComponentImplementationInterface
// RenderToTextureRequests
AZ::RHI::AttachmentId UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) override;
void ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) override;
AZ::Data::Instance<AZ::RPI::AttachmentImage> GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) override;
// ~RenderToTextureRequests
void UpdateCanvas(float deltaTime, bool isInGame);
void RenderCanvas(bool isInGame, AZ::Vector2 viewportSize, UiRenderer* uiRenderer = nullptr);
@ -257,6 +266,10 @@ public: // member functions
//! Queue an element to be destroyed at end of frame
void ScheduleElementDestroy(AZ::EntityId entityId);
bool IsRenderGraphDirty() { return m_renderGraph.GetDirtyFlag(); }
void GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies);
#ifndef _RELEASE
struct DebugInfoNumElements
{
@ -427,6 +440,9 @@ private: // member functions
void DestroyScheduledElements();
//! Notify LyShine pass that it needs to rebuild its Rtt child passes
void QueueRttPassRebuild();
private: // static member functions
static AZ::u64 CreateUniqueId();
@ -597,4 +613,8 @@ private: // static data
LyShine::RenderGraph m_renderGraph; //!< the render graph for rendering the canvas, can be cached between frames
bool m_isRendering = false;
bool m_renderInEditor = false; //!< indicates whether this canvas will render in the Editor viewport or the Game viewport
//! Map of attachments used by this canvas's elements
AZStd::unordered_map<AZ::RHI::AttachmentId, AZ::Data::Instance<AZ::RPI::AttachmentImage>> m_attachmentImageMap;
};

@ -301,6 +301,17 @@ void UiCanvasManager::OnFontTextureUpdated([[maybe_unused]] IFFont* font)
m_fontTextureHasChanged = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasManager::GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies)
{
for (auto canvas : m_loadedCanvases)
{
LyShine::AttachmentImagesAndDependencies canvasTargets;
canvas->GetRenderTargets(canvasTargets);
attachmentImagesAndDependencies.insert(attachmentImagesAndDependencies.end(), canvasTargets.begin(), canvasTargets.end());
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasManager::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
@ -606,13 +617,6 @@ void UiCanvasManager::RenderLoadedCanvases()
m_fontTextureHasChanged = false;
}
#ifdef LYSHINE_ATOM_TODO // render target conversion to Atom
// clear the stencil buffer before rendering the loaded canvases - required for masking
// NOTE: We want to use ClearTargetsImmediately instead of ClearTargetsLater since we will not be setting the render target
ColorF viewportBackgroundColor(0, 0, 0, 0); // if clearing color we want to set alpha to zero also
gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR_STENCIL, viewportBackgroundColor);
#endif
for (auto canvas : m_loadedCanvases)
{
if (!canvas->GetIsRenderToTexture())

@ -11,6 +11,7 @@
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/UiEntityContext.h>
#include "LyShinePassDataBus.h"
#include <IFont.h>
class UiCanvasComponent;
@ -92,6 +93,9 @@ public: // member functions
bool HandleInputEventForLoadedCanvases(const AzFramework::InputChannel& inputChannel);
bool HandleTextEventForLoadedCanvases(const AZStd::string& textUTF8);
// Get the render targets used by all currently loaded UI Canvases
void GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies);
#ifndef _RELEASE
void DebugDisplayCanvasData(int setting) const;
void DebugDisplayDrawCallData() const;

@ -6,6 +6,7 @@
*
*/
#include "UiFaderComponent.h"
#include "RenderGraph.h"
#include <LyShine/Draw2d.h>
#include <AzCore/Math/Crc.h>
@ -14,6 +15,9 @@
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <AtomCore/Instance/Instance.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiRenderBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
@ -22,6 +26,7 @@
#include <ITimer.h>
#include "UiSerialize.h"
#include "RenderToTextureBus.h"
// BehaviorContext UiFaderNotificationBus forwarder
class BehaviorUiFaderNotificationBusHandler
@ -120,7 +125,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter
AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft;
bool needsResize = static_cast<int>(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast<int>(renderTargetSize.GetY()) != m_renderTargetHeight;
if (m_renderTargetHandle == -1 || needsResize)
if (m_attachmentImageId.IsEmpty() || needsResize)
{
// We delay first creation of the render target until render time since size is not known in Activate
// We also call this if the size has changed
@ -128,7 +133,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter
}
// if the render target failed to be created (zero size for example) we don't render the element at all
if (m_renderTargetHandle == -1)
if (m_attachmentImageId.IsEmpty())
{
return;
}
@ -139,7 +144,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter
else
{
// destroy previous render target, if exists
if (m_renderTargetHandle != -1)
if (!m_attachmentImageId.IsEmpty())
{
DestroyRenderTarget();
}
@ -452,54 +457,22 @@ void UiFaderComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligne
m_viewportTopLeft = pixelAlignedTopLeft;
m_viewportSize = renderTargetSize;
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Check if the render target already exists
if (m_renderTargetHandle != -1)
{
// Render target exists, resize it to the given size
if (!gEnv->pRenderer->ResizeRenderTarget(m_renderTargetHandle, static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY())))
{
AZ_Warning("UI", false, "Failed to resize render target for UiFaderComponent");
DestroyRenderTarget();
}
}
else
{
// Create a render target that this element and its children will be rendered to.
m_renderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_renderTargetName.c_str(),
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8);
if (m_renderTargetHandle == -1)
{
AZ_Warning("UI", false, "Failed to create render target for UiFaderComponent");
}
}
// if depth surface already exists then destroy it
if (m_renderTargetDepthSurface)
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
}
// LYSHINE_ATOM_TODO: optimize by reusing/resizing targets
DestroyRenderTarget();
if (m_renderTargetHandle != -1)
// Create a render target that this element and its children will be rendered to
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
AZ::RHI::Size imageSize(renderTargetSize.GetX(), renderTargetSize.GetY(), 1);
EBUS_EVENT_ID_RESULT(m_attachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_renderTargetName.c_str()), imageSize);
if (m_attachmentImageId.IsEmpty())
{
// Also create a depth surface to render the canvas to, we need depth for masking
// since that uses the stencil buffer. We support any combination of nesting faders and masks
m_renderTargetDepthSurface = gEnv->pRenderer->CreateDepthSurface(
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()));
if (!m_renderTargetDepthSurface)
{
AZ_Warning("UI", false, "Failed to create depth surface for UiFaderComponent");
DestroyRenderTarget();
}
AZ_Warning("UI", false, "Failed to create render target for UiFaderComponent");
}
#endif
// at this point either all render targets and depth surfaces are created or none are.
// If all succeeded then update the render target size
if (m_renderTargetHandle != -1)
if (!m_attachmentImageId.IsEmpty())
{
m_renderTargetWidth = static_cast<int>(renderTargetSize.GetX());
m_renderTargetHeight = static_cast<int>(renderTargetSize.GetY());
@ -511,16 +484,12 @@ void UiFaderComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligne
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiFaderComponent::DestroyRenderTarget()
{
if (m_renderTargetHandle != -1)
{
gEnv->pRenderer->DestroyRenderTarget(m_renderTargetHandle);
m_renderTargetHandle = -1;
}
if (m_renderTargetDepthSurface)
if (!m_attachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_attachmentImageId);
m_attachmentImageId = AZ::RHI::AttachmentId{};
}
}
@ -594,14 +563,20 @@ void UiFaderComponent::RenderStandardFader(LyShine::IRenderGraph* renderGraph, U
void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
UiRenderInterface* renderInterface, int numChildren, bool isInGame)
{
// Get the render target
AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID_RESULT(attachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_attachmentImageId);
// Render the element and its children to a render target
{
// we always clear to transparent black - the accumulation of alpha in the render target requires it
AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Start building the render to texture node in the render graph
renderGraph->BeginRenderToTexture(m_renderTargetHandle, m_renderTargetDepthSurface,
m_viewportTopLeft, m_viewportSize, clearColor);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
lyRenderGraph->BeginRenderToTexture(attachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
// We don't want this fader or parent faders to affect what is rendered to the render target since we will
// apply those fades when we render from the render target.
@ -624,14 +599,13 @@ void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElem
float desiredAlpha = renderGraph->GetAlphaFade() * m_fade;
uint8 desiredPackedAlpha = static_cast<uint8>(desiredAlpha * 255.0f);
UCol desiredPackedColor;
// This is a special case. We have an input texture that already has premultiplied alpha.
// So we tell the shader not to premultiply the output colors and we premultiply the alpha
// into the vertex colors so that they are premultiplied too.
desiredPackedColor.r = desiredPackedColor.g = desiredPackedColor.b = desiredPackedColor.a = desiredPackedAlpha;
if (m_cachedPrimitive.m_vertices[0].color.dcolor != desiredPackedColor.dcolor)
// If the fade value has changed we need to update the alpha values in the vertex colors but we do
// not want to touch or recompute the RGB values
if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha)
{
// go through the cached vertices and update the color values
// go through all the cached vertices and update the alpha values
UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color;
desiredPackedColor.a = desiredPackedAlpha;
for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i)
{
m_cachedPrimitive.m_vertices[i].color = desiredPackedColor;
@ -639,21 +613,20 @@ void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElem
}
}
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Add a primitive to render a quad using the render target we have created
{
// Set the texture and other render state required
ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_renderTargetHandle);
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = true;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
// add a render node to render from the render target texture to the current target
renderGraph->AddPrimitive(&m_cachedPrimitive, texture,
isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
if (lyRenderGraph)
{
// Set the texture and other render state required
AZ::Data::Instance<AZ::RPI::Image> image = attachmentImage;
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = true;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
lyRenderGraph->AddPrimitiveAtom(&m_cachedPrimitive, image, isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
}
}
#endif
}
}

@ -18,6 +18,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Math/Color.h>
#include <Atom/RHI.Reflect/AttachmentId.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
class UiFaderComponent
@ -156,11 +157,8 @@ private: // data
//! This is generated from the entity ID and cached
AZStd::string m_renderTargetName;
//! When rendering to a texture this is the texture ID of the render target
int m_renderTargetHandle = -1;
//! When rendering to a texture this is our depth surface
SDepthTexture* m_renderTargetDepthSurface = nullptr;
//! When rendering to a texture this is the attachment image for the render target
AZ::RHI::AttachmentId m_attachmentImageId;
//! The positions used for the render to texture viewport and to render the render target to the screen
AZ::Vector2 m_viewportTopLeft;

@ -14,12 +14,17 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include "IRenderer.h"
#include "RenderToTextureBus.h"
#include "RenderGraph.h"
#include <LyShine/Bus/UiTransformBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiRenderBus.h>
#include <LyShine/Bus/UiVisualBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <AtomCore/Instance/Instance.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -79,7 +84,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft;
bool needsResize = static_cast<int>(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast<int>(renderTargetSize.GetY()) != m_renderTargetHeight;
if (m_contentRenderTargetHandle == -1 || needsResize)
if (m_contentAttachmentImageId.IsEmpty() || needsResize)
{
// Need to create or resize the render target
CreateOrResizeRenderTarget(pixelAlignedTopLeft, pixelAlignedBottomRight);
@ -89,7 +94,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
// in theory the child mask element could still be non-zero size and could reveal things. But the way gradient masks
// currently work is that the size of the render target is defined by the size of this element, therefore nothing would
// be revealed by the mask if it is zero sized.
if (m_contentRenderTargetHandle == -1)
if (m_contentAttachmentImageId.IsEmpty())
{
return;
}
@ -101,7 +106,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
else
{
// using stencil mask, not going to use render targets, destroy previous render target, if exists
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
DestroyRenderTarget();
}
@ -113,7 +118,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
else
{
// masking disabled, not going to use render targets, destroy previous render target, if exists
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
DestroyRenderTarget();
}
@ -553,77 +558,30 @@ void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligned
m_viewportTopLeft = pixelAlignedTopLeft;
m_viewportSize = renderTargetSize;
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Check if the render target already exists
if (m_contentRenderTargetHandle != -1)
{
// Render target exists, resize it to the given size
if (!gEnv->pRenderer->ResizeRenderTarget(m_contentRenderTargetHandle, static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY())))
{
AZ_Warning("UI", false, "Failed to resize content render target for UiMaskComponent");
DestroyRenderTarget();
}
}
else
{
// Create a render target that this element and its children will be rendered to.
m_contentRenderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_renderTargetName.c_str(),
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8);
if (m_contentRenderTargetHandle == -1)
{
AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent");
}
}
// if depth surface already exists then destroy it
if (m_renderTargetDepthSurface)
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
}
// LYSHINE_ATOM_TODO: optimize by reusing/resizing targets
DestroyRenderTarget();
if (m_contentRenderTargetHandle != -1)
// Create a render target that this element and its children will be rendered to
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
AZ::RHI::Size imageSize(renderTargetSize.GetX(), renderTargetSize.GetY(), 1);
EBUS_EVENT_ID_RESULT(m_contentAttachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_renderTargetName.c_str()), imageSize);
if (m_contentAttachmentImageId.IsEmpty())
{
// Also create a depth surface to render the canvas to, we need depth for masking
// since that uses the stencil buffer. We support any combination of nesting faders and masks
m_renderTargetDepthSurface = gEnv->pRenderer->CreateDepthSurface(
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()));
if (!m_renderTargetDepthSurface)
{
AZ_Warning("UI", false, "Failed to create depth surface for UiMaskComponent");
DestroyRenderTarget();
}
AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent");
}
// Check if the mask render target already exists
if (m_maskRenderTargetHandle != -1)
// Create separate render target for the mask texture
EBUS_EVENT_ID_RESULT(m_maskAttachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_maskRenderTargetName.c_str()), imageSize);
if (m_maskAttachmentImageId.IsEmpty())
{
// Render target exists, resize it to the given size
if (!gEnv->pRenderer->ResizeRenderTarget(m_maskRenderTargetHandle, static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY())))
{
AZ_Warning("UI", false, "Failed to resize mask render target for UiMaskComponent");
DestroyRenderTarget();
}
AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent");
DestroyRenderTarget();
}
else
{
// create separate render target for the mask texture
m_maskRenderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_maskRenderTargetName.c_str(),
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8);
if (m_maskRenderTargetHandle == -1)
{
AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent");
DestroyRenderTarget();
}
}
#endif
// at this point either all render targets and depth surfaces are created or none are.
// If all succeeded then update the render target size
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
m_renderTargetWidth = static_cast<int>(renderTargetSize.GetX());
m_renderTargetHeight = static_cast<int>(renderTargetSize.GetY());
@ -635,22 +593,22 @@ void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligned
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiMaskComponent::DestroyRenderTarget()
{
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyRenderTarget(m_contentRenderTargetHandle);
m_contentRenderTargetHandle = -1;
}
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_contentAttachmentImageId);
if (m_renderTargetDepthSurface)
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
m_contentAttachmentImageId = AZ::RHI::AttachmentId{};
}
if (m_maskRenderTargetHandle != -1)
if (!m_maskAttachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyRenderTarget(m_maskRenderTargetHandle);
m_maskRenderTargetHandle = -1;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_maskAttachmentImageId);
m_maskAttachmentImageId = AZ::RHI::AttachmentId{};
}
}
@ -747,6 +705,14 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
// we always clear to transparent black - the accumulation of alpha in the render target requires it
AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Get the render targets
AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage;
AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID_RESULT(contentAttachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_contentAttachmentImageId);
EBUS_EVENT_ID_RESULT(maskAttachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_maskAttachmentImageId);
// We don't want parent faders to affect what is rendered to the render target since we will
// apply those fades when we render from the render target.
// Note that this means that, if there are parent (no render to texture) faders, we get a "free"
@ -756,8 +722,8 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
// mask render target
{
// Start building the render to texture node in the render graph
renderGraph->BeginRenderToTexture(m_maskRenderTargetHandle, m_renderTargetDepthSurface,
m_viewportTopLeft, m_viewportSize, clearColor);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
lyRenderGraph->BeginRenderToTexture(maskAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
// Render the visual component for this element (if there is one) plus the child mask element (if there is one)
RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame);
@ -769,8 +735,8 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
// content render target
{
// Start building the render to texture node for the content render target in the render graph
renderGraph->BeginRenderToTexture(m_contentRenderTargetHandle, m_renderTargetDepthSurface,
m_viewportTopLeft, m_viewportSize, clearColor);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
lyRenderGraph->BeginRenderToTexture(contentAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
// Render the "content" - the child elements excluding the child mask element (if any)
RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame);
@ -790,14 +756,13 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
float desiredAlpha = renderGraph->GetAlphaFade();
uint32 desiredPackedAlpha = static_cast<uint8>(desiredAlpha * 255.0f);
UCol desiredPackedColor;
// This is a special case. We have an input texture that already has premultiplied alpha.
// So we tell the shader not to premultiply the output colors and we premultiply the alpha
// into the vertex colors so that they are premultiplied too.
desiredPackedColor.r = desiredPackedColor.g = desiredPackedColor.b = desiredPackedColor.a = desiredPackedAlpha;
if (m_cachedPrimitive.m_vertices[0].color.dcolor != desiredPackedColor.dcolor)
// If the fade value has changed we need to update the alpha values in the vertex colors but we do
// not want to touch or recompute the RGB values
if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha)
{
// go through the cached vertices and update the color values
// go through all the cached vertices and update the alpha values
UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color;
desiredPackedColor.a = desiredPackedAlpha;
for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i)
{
m_cachedPrimitive.m_vertices[i].color = desiredPackedColor;
@ -805,22 +770,29 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
}
}
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Add a primitive to do the alpha mask
{
// Set the texture and other render state required
ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_contentRenderTargetHandle);
ITexture* maskTexture = gEnv->pRenderer->EF_GetTextureByID(m_maskRenderTargetHandle);
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = true;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
// add a render node to render using the two render targets, one as an alpha mask of the other
renderGraph->AddAlphaMaskPrimitive(&m_cachedPrimitive, texture, maskTexture,
isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
if (lyRenderGraph)
{
// Set the texture and other render state required
AZ::Data::Instance<AZ::RPI::Image> contentImage = contentAttachmentImage;
AZ::Data::Instance<AZ::RPI::Image> maskImage = maskAttachmentImage;
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = false;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
// add a render node to render using the two render targets, one as an alpha mask of the other
lyRenderGraph->AddAlphaMaskPrimitiveAtom(&m_cachedPrimitive,
contentAttachmentImage,
maskAttachmentImage,
isClampTextureMode,
isTextureSRGB,
isTexturePremultipliedAlpha,
blendMode);
}
}
#endif
}
}

@ -15,6 +15,7 @@
#include <LyShine/IRenderGraph.h>
#include <AzCore/Component/Component.h>
#include <Atom/RHI.Reflect/AttachmentId.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
class UiMaskComponent
@ -184,15 +185,16 @@ private: // data
//! This is generated from the entity ID and cached
AZStd::string m_maskRenderTargetName;
//! When rendering to a texture this is the texture ID of the render target
int m_contentRenderTargetHandle = -1;
//! When rendering to a texture this is the attachment image for the render target
AZ::RHI::AttachmentId m_contentAttachmentImageId;
//! When rendering to a texture this is our depth surface, we use the same one for rendering the mask elements
//! and the content elements - it is cleared in between.
SDepthTexture* m_renderTargetDepthSurface = nullptr;
//! When rendering to a texture this is the texture ID of the render target
int m_maskRenderTargetHandle = -1;
//! When rendering to a texture this is the attachment image for the render target
AZ::RHI::AttachmentId m_maskAttachmentImageId;
//! The positions used for the render to texture viewport and to render the render target to the screen
AZ::Vector2 m_viewportTopLeft = AZ::Vector2::CreateZero();

@ -6,6 +6,7 @@
*
*/
#include "UiRenderer.h"
#include "LyShinePassDataBus.h"
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
@ -60,25 +61,32 @@ void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstra
AZ::Data::Instance<AZ::RPI::Shader> uiShader = AZ::RPI::LoadShader(uiShaderFilepath);
// Create scene to be used by the dynamic draw context
AZ::RPI::ScenePtr scene;
if (m_viewportContext)
{
// Create a new scene based on the user specified viewport context
scene = CreateScene(m_viewportContext);
m_scene = CreateScene(m_viewportContext);
}
else
{
// No viewport context specified, use default scene
scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
m_scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
}
// Create a dynamic draw context for UI Canvas drawing for the scene
CreateDynamicDrawContext(scene, uiShader);
m_dynamicDraw = CreateDynamicDrawContext(m_scene, uiShader);
// Cache shader data such as input indices for later use
CacheShaderData(m_dynamicDraw);
if (m_dynamicDraw)
{
// Cache shader data such as input indices for later use
CacheShaderData(m_dynamicDraw);
m_isRPIReady = true;
m_isRPIReady = true;
}
else
{
AZ_Error(LogName, false, "Failed to create a dynamic draw context for LyShine. \
This can happen if the LyShine pass hasn't been added to the main render pipeline.");
}
}
AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportContext> viewportContext)
@ -107,22 +115,40 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportCon
return atomScene;
}
void UiRenderer::CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Instance<AZ::RPI::Shader> uiShader)
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::CreateDynamicDrawContext(
AZ::RPI::ScenePtr scene,
AZ::Data::Instance<AZ::RPI::Shader> uiShader)
{
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Find the pass that renders the UI canvases after the rtt passes
AZ::RPI::RasterPass* uiCanvasPass = nullptr;
AZ::RPI::SceneId sceneId = m_scene->GetId();
LyShinePassRequestBus::EventResult(uiCanvasPass, sceneId, &LyShinePassRequestBus::Events::GetUiCanvasPass);
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Initialize the dynamic draw context
m_dynamicDraw->InitShader(uiShader);
m_dynamicDraw->InitVertexFormat(
dynamicDraw->InitShader(uiShader);
dynamicDraw->InitVertexFormat(
{ { "POSITION", AZ::RHI::Format::R32G32_FLOAT },
{ "COLOR", AZ::RHI::Format::B8G8R8A8_UNORM },
{ "TEXCOORD", AZ::RHI::Format::R32G32_FLOAT },
{ "BLENDINDICES", AZ::RHI::Format::R16G16_UINT } }
);
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
m_dynamicDraw->SetOutputScope(scene.get());
m_dynamicDraw->EndInit();
if (uiCanvasPass)
{
dynamicDraw->SetOutputScope(uiCanvasPass);
}
else
{
// Render target support is disabled
dynamicDraw->SetOutputScope(m_scene.get());
}
dynamicDraw->EndInit();
return dynamicDraw;
}
AZStd::shared_ptr<AZ::RPI::ViewportContext> UiRenderer::GetViewportContext()
@ -158,19 +184,26 @@ void UiRenderer::CacheShaderData(const AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext>
isClampIndexName);
// Cache shader variants that will be used
// LYSHINE_ATOM_TODO - more variants will be used in future phase (masks/render target support)
AZ::RPI::ShaderOptionList shaderOptionsDefault;
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_preMultiplyAlpha"), AZ::Name("false")));
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true")));
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantDefault = dynamicDraw->UseShaderVariant(shaderOptionsDefault);
AZ::RPI::ShaderOptionList shaderOptionsAlphaTest;
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_preMultiplyAlpha"), AZ::Name("false")));
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("true")));
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true")));
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantAlphaTest = dynamicDraw->UseShaderVariant(shaderOptionsAlphaTest);
AZ::RPI::ShaderOptionList shaderOptionsTextureLinear;
shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true")));
shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantTextureLinear = dynamicDraw->UseShaderVariant(shaderOptionsTextureLinear);
AZ::RPI::ShaderOptionList shaderOptionsTextureSrgb;
shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false")));
shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantTextureSrgb = dynamicDraw->UseShaderVariant(shaderOptionsTextureSrgb);
AZ::RPI::ShaderOptionList shaderVariantAlphaTestMask;
shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("true")));
shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false")));
shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantAlphaTestMask = dynamicDraw->UseShaderVariant(shaderVariantAlphaTestMask);
AZ::RPI::ShaderOptionList shaderVariantGradientMask;
shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false")));
shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::Alpha")));
m_uiShaderData.m_shaderVariantGradientMask = dynamicDraw->UseShaderVariant(shaderVariantGradientMask);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -215,6 +248,38 @@ AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::GetDynamicDrawContext()
return m_dynamicDraw;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::CreateDynamicDrawContextForRTT(const AZStd::string& rttName)
{
// find the rtt pass with the specified name
AZ::RPI::RasterPass* rttPass = nullptr;
AZ::RPI::SceneId sceneId = m_scene->GetId();
LyShinePassRequestBus::EventResult(rttPass, sceneId, &LyShinePassRequestBus::Events::GetRttPass, rttName);
if (!rttPass)
{
return nullptr;
}
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Initialize the dynamic draw context
dynamicDraw->InitShader(m_dynamicDraw->GetShader());
dynamicDraw->InitVertexFormat(
{ { "POSITION", AZ::RHI::Format::R32G32_FLOAT },
{ "COLOR", AZ::RHI::Format::B8G8R8A8_UNORM },
{ "TEXCOORD", AZ::RHI::Format::R32G32_FLOAT },
{ "BLENDINDICES", AZ::RHI::Format::R16G16_UINT } }
);
dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
dynamicDraw->SetOutputScope(rttPass);
dynamicDraw->EndInit();
return dynamicDraw;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const UiRenderer::UiShaderData& UiRenderer::GetUiShaderData()
{
@ -270,11 +335,27 @@ void UiRenderer::SetBaseState(BaseState state)
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::RPI::ShaderVariantId UiRenderer::GetCurrentShaderVariant()
{
AZ::RPI::ShaderVariantId variantId = m_uiShaderData.m_shaderVariantDefault;
AZ::RPI::ShaderVariantId variantId = m_uiShaderData.m_shaderVariantTextureLinear;
if (m_baseState.m_useAlphaTest)
{
variantId = m_uiShaderData.m_shaderVariantAlphaTest;
variantId = m_uiShaderData.m_shaderVariantAlphaTestMask;
}
else if (m_baseState.m_modulateAlpha)
{
variantId = m_uiShaderData.m_shaderVariantGradientMask;
}
else if (!m_baseState.m_useAlphaTest && m_baseState.m_srgbWrite)
{
variantId = m_uiShaderData.m_shaderVariantTextureLinear;
}
else if (!m_baseState.m_useAlphaTest && !m_baseState.m_srgbWrite)
{
variantId = m_uiShaderData.m_shaderVariantTextureSrgb;
}
else
{
AZ_Error(LogName, 0, "Unsupported shader variant.");
}
return variantId;

@ -36,8 +36,10 @@ public: // types
AZ::RHI::ShaderInputConstantIndex m_viewProjInputIndex;
AZ::RHI::ShaderInputConstantIndex m_isClampInputIndex;
AZ::RPI::ShaderVariantId m_shaderVariantDefault;
AZ::RPI::ShaderVariantId m_shaderVariantAlphaTest;
AZ::RPI::ShaderVariantId m_shaderVariantTextureLinear;
AZ::RPI::ShaderVariantId m_shaderVariantTextureSrgb;
AZ::RPI::ShaderVariantId m_shaderVariantAlphaTestMask;
AZ::RPI::ShaderVariantId m_shaderVariantGradientMask;
};
// Base state
@ -56,17 +58,23 @@ public: // types
m_blendState.m_blendSource = AZ::RHI::BlendFactor::AlphaSource;
m_blendState.m_blendDest = AZ::RHI::BlendFactor::AlphaSourceInverse;
m_blendState.m_blendOp = AZ::RHI::BlendOp::Add;
m_blendState.m_blendAlphaSource = AZ::RHI::BlendFactor::One;
m_blendState.m_blendAlphaDest = AZ::RHI::BlendFactor::Zero;
m_blendState.m_blendAlphaOp = AZ::RHI::BlendOp::Add;
// Disable stencil
m_stencilState = AZ::RHI::StencilState();
m_stencilState.m_enable = 0;
m_useAlphaTest = false;
m_modulateAlpha = false;
}
AZ::RHI::TargetBlendState m_blendState;
AZ::RHI::StencilState m_stencilState;
bool m_useAlphaTest = false;
bool m_modulateAlpha = false;
bool m_srgbWrite = true;
};
public: // member functions
@ -93,6 +101,8 @@ public: // member functions
//! Return the dynamic draw context associated with this UI renderer
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> GetDynamicDrawContext();
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> CreateDynamicDrawContextForRTT(const AZStd::string& rttName);
//! Return the shader data for the ui shader
const UiShaderData& GetUiShaderData();
@ -123,6 +133,9 @@ public: // member functions
//! Decrement the current stencil reference value
void DecrementStencilRef();
//! Return the viewport context set by the user, or the default if not set
AZStd::shared_ptr<AZ::RPI::ViewportContext> GetViewportContext();
#ifndef _RELEASE
//! Setup to record debug texture data before rendering
void DebugSetRecordingOptionForTextureData(int recordingOption);
@ -143,10 +156,9 @@ private: // member functions
AZ::RPI::ScenePtr CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportContext> viewportContext);
//! Create a dynamic draw context for this renderer
void CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Instance<AZ::RPI::Shader>);
//! Return the viewport context set by the user, or the default if not set
AZStd::shared_ptr<AZ::RPI::ViewportContext> GetViewportContext();
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> CreateDynamicDrawContext(
AZ::RPI::ScenePtr scene,
AZ::Data::Instance<AZ::RPI::Shader> uiShader);
//! Bind the global white texture for all the texture units we use
void BindNullTexture();
@ -168,6 +180,8 @@ protected: // attributes
// Set by user when viewport context is not the main/default viewport
AZStd::shared_ptr<AZ::RPI::ViewportContext> m_viewportContext;
AZ::RPI::ScenePtr m_scene;
#ifndef _RELEASE
int m_debugTextureDataRecordLevel = 0;
AZStd::unordered_set<ITexture*> m_texturesUsedInFrame; // LYSHINE_ATOM_TODO - convert to RPI::Image

@ -163,11 +163,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnHover);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@ -193,11 +191,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnHover);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@ -221,11 +217,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnPress);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@ -249,11 +243,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnPress);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@ -277,11 +269,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnClick);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();

@ -11,8 +11,11 @@ set(FILES
Include/LyShine/Draw2d.h
Source/LyShine.cpp
Source/LyShine.h
Source/LyShinePassDataBus.h
Source/LyShineDebug.cpp
Source/LyShineDebug.h
Source/LyShinePass.cpp
Source/LyShinePass.h
Source/StringUtfUtils.h
Source/UiImageComponent.cpp
Source/UiImageComponent.h
@ -28,6 +31,7 @@ set(FILES
Source/LyShineLoadScreen.h
Source/RenderGraph.cpp
Source/RenderGraph.h
Source/RenderToTextureBus.h
Source/TextMarkup.cpp
Source/TextMarkup.h
Source/UiButtonComponent.cpp

@ -0,0 +1,20 @@
{
"Name": "LyShinePass",
"TemplateName": "LyShineParentTemplate",
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "DebugOverlayPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
]
}

@ -0,0 +1,71 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import sys
# Parse arguments
if len(sys.argv) != 3:
print('Incorrect number of args')
exit()
engine_path = sys.argv[1]
if not os.path.exists(engine_path):
print(f'Given path {engine_path} does not exist')
exit()
project_path = sys.argv[2]
if not os.path.exists(project_path):
print(f'Given path {project_path} does not exist')
exit()
sys.path.insert(0, os.path.join(engine_path, 'Gems/Atom/RPI/Tools/'))
from atom_rpi_tools.pass_data import PassTemplate
import atom_rpi_tools.utils as utils
# Folder of this py file
dir_name = os.path.dirname(os.path.realpath(__file__))
# Patch render pipeline to insert a custom LyShine parent pass
# Gem::Atom_Feature_Common gem's path since default render pipeline is comming from this gem
gem_assets_path = os.path.join(engine_path,'Gems/Atom/feature/Common/Assets/')
pipeline_relatvie_path = 'Passes/MainPipeline.pass'
srcRenderPipeline = os.path.join(gem_assets_path, pipeline_relatvie_path)
destRenderPipeline = os.path.join(project_path, pipeline_relatvie_path)
# If the project doesn't have a customized main pipeline
# copy the default render pipeline from Atom_Common_Feature gem to same path in project folder
utils.find_or_copy_file(destRenderPipeline, srcRenderPipeline)
# Load project render pipeline
renderPipeline = PassTemplate(destRenderPipeline)
# Skip if LyShinePass already exist
newPassName = 'LyShinePass'
if renderPipeline.find_pass(newPassName)>-1:
print('Skip merging. LyShinePass already exists')
exit()
# Insert LyShinePass between DebugOverlayPass and UIPass
refPass = 'DebugOverlayPass'
# The data file for new pass request is in the same folder of the py file
newPassRequestFilePath = os.path.join(dir_name, 'LyShinePass.data')
newPassRequestData = utils.load_json_file(newPassRequestFilePath)
insertIndex = renderPipeline.find_pass(refPass) + 1
if insertIndex>-1:
renderPipeline.insert_pass_request(insertIndex, newPassRequestData)
else:
print('Failed to find ', refPass)
exit()
# Update attachment references for the passes following LyShinePass
renderPipeline.replace_references_after(newPassName, 'DebugOverlayPass', 'InputOutput', 'LyShinePass', 'ColorInputOutput')
# Save the updated render pipeline
renderPipeline.save()
Loading…
Cancel
Save