You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1513 lines
63 KiB
C++
1513 lines
63 KiB
C++
/*
|
|
* 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 "UiCanvasManager.h"
|
|
#include <LyShine/IDraw2d.h>
|
|
|
|
#include "UiCanvasFileObject.h"
|
|
#include "UiCanvasComponent.h"
|
|
#include "UiGameEntityContext.h"
|
|
|
|
#include <CryCommon/StlUtils.h>
|
|
#include <LyShine/UiSerializeHelpers.h>
|
|
|
|
#include <AzCore/Memory/Memory.h>
|
|
#include <AzCore/Component/ComponentApplicationBus.h>
|
|
#include <AzCore/Component/TickBus.h>
|
|
#include <AzCore/Asset/AssetManagerBus.h>
|
|
#include <AzFramework/API/ApplicationAPI.h>
|
|
#include <AzFramework/Input/Channels/InputChannel.h>
|
|
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
|
|
#include <LyShine/Bus/UiCursorBus.h>
|
|
#include <LyShine/Bus/World/UiCanvasOnMeshBus.h>
|
|
#include <LyShine/Bus/World/UiCanvasRefBus.h>
|
|
|
|
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
|
|
|
#ifndef _RELEASE
|
|
#include <AzFramework/IO/LocalFileIO.h>
|
|
#endif
|
|
|
|
#include <AzFramework/Entity/GameEntityContextBus.h>
|
|
#include <AzFramework/Render/Intersector.h>
|
|
#include <MathConversion.h>
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Anonymous namespace
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
namespace
|
|
{
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Compare function used for sorting
|
|
bool CompareCanvasDrawOrder(UiCanvasComponent* a, UiCanvasComponent* b)
|
|
{
|
|
return a->GetDrawOrder() < b->GetDrawOrder();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Transform the pathname so that a) it works for opening a file that could be in a Gem or in
|
|
// a pak file, and b) so that it is in a consistent form that can be used for string comparison
|
|
AZStd::string GetAssePathFromUserDefinedPath(const AZStd::string& userPath)
|
|
{
|
|
if (userPath.empty())
|
|
{
|
|
AZ_Warning("UI", false, "Given UI canvas path is empty.")
|
|
return userPath;
|
|
}
|
|
|
|
AZStd::string assetPath = userPath;
|
|
|
|
// Check if extension needs to be fixed up
|
|
const AZStd::string canvasExtension("uicanvas");
|
|
bool validExtension = AzFramework::StringFunc::Path::IsExtension(assetPath.c_str(), canvasExtension.c_str(), true);
|
|
if (!validExtension)
|
|
{
|
|
// Fix extension
|
|
AZ_Warning("UI", !AzFramework::StringFunc::Path::HasExtension(assetPath.c_str()), "Given UI canvas path \"%s\" has an invalid extension. Replacing extension with \"%s\".",
|
|
userPath.c_str(), canvasExtension.c_str());
|
|
AzFramework::StringFunc::Path::ReplaceExtension(assetPath, canvasExtension.c_str());
|
|
}
|
|
|
|
// Normalize path
|
|
EBUS_EVENT(AzFramework::ApplicationRequests::Bus, NormalizePathKeepCase, assetPath);
|
|
|
|
// Check for any leading slashes as the specified path should be a relative path to the @products@ alias.
|
|
// This eliminates inconsistencies between lower level file opens on different platforms
|
|
int numCharsToErase = 0;
|
|
for (; numCharsToErase < assetPath.length(); ++numCharsToErase)
|
|
{
|
|
if (assetPath[numCharsToErase] != '/')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (numCharsToErase > 0)
|
|
{
|
|
// Remove leading slashes
|
|
AZ_Warning("UI", false, "Given UI canvas path \"%s\" has invalid leading slashes that make the path not relative. "
|
|
"Removing the invalid leading slashes.", userPath.c_str());
|
|
assetPath.erase(0, numCharsToErase);
|
|
}
|
|
|
|
return assetPath;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiCanvasManager::UiCanvasManager()
|
|
: m_latestViewportSize(UiCanvasComponent::s_defaultCanvasSize)
|
|
, m_localUserIdInputFilter(AzFramework::LocalUserIdAny)
|
|
{
|
|
UiCanvasManagerBus::Handler::BusConnect();
|
|
UiCanvasOrderNotificationBus::Handler::BusConnect();
|
|
UiCanvasEnabledStateNotificationBus::Handler::BusConnect();
|
|
FontNotificationBus::Handler::BusConnect();
|
|
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiCanvasManager::~UiCanvasManager()
|
|
{
|
|
UiCanvasManagerBus::Handler::BusDisconnect();
|
|
UiCanvasOrderNotificationBus::Handler::BusDisconnect();
|
|
UiCanvasEnabledStateNotificationBus::Handler::BusDisconnect();
|
|
FontNotificationBus::Handler::BusDisconnect();
|
|
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
|
|
|
|
// destroy ALL the loaded canvases, whether loaded in game or in Editor
|
|
for (auto canvas : (m_loadedCanvases))
|
|
{
|
|
delete canvas->GetEntity();
|
|
}
|
|
|
|
for (auto canvas : (m_loadedCanvasesInEditor))
|
|
{
|
|
delete canvas->GetEntity();
|
|
}
|
|
|
|
AZ_Assert(m_recursionGuardCount == 0, "Destroying the UiCanvasManager while it is processing canvases");
|
|
DeleteCanvasesQueuedForDeletion();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::CreateCanvas()
|
|
{
|
|
// Prevent in-game canvas from being created when we are in the editor in a simulation mode
|
|
// but not in game mode (ex. AI/Physics mode or Preview mode)
|
|
if (gEnv && gEnv->IsEditor() && gEnv->IsEditing())
|
|
{
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
UiGameEntityContext* entityContext = new UiGameEntityContext();
|
|
|
|
UiCanvasComponent* canvasComponent = UiCanvasComponent::CreateCanvasInternal(entityContext, false);
|
|
|
|
m_loadedCanvases.push_back(canvasComponent);
|
|
SortCanvasesByDrawOrder();
|
|
|
|
// The game entity context needs to know its corresponding canvas entity for instantiating dynamic slices
|
|
entityContext->SetCanvasEntity(canvasComponent->GetEntityId());
|
|
|
|
// When we create a canvas in game we want it to have the correct viewport size from the first frame rather
|
|
// than having to wait a frame to have it updated
|
|
canvasComponent->SetTargetCanvasSize(true, m_latestViewportSize);
|
|
|
|
return canvasComponent->GetEntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::LoadCanvas(const AZStd::string& assetIdPathname)
|
|
{
|
|
// Prevent canvas from being loaded when we are in the editor in a simulation mode
|
|
// but not in game mode (ex. AI/Physics mode or Preview mode).
|
|
// NOTE: Normal Preview mode load does not come through here since we clone the canvas rather than load it
|
|
if (gEnv && gEnv->IsEditor() && gEnv->IsEditing())
|
|
{
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
UiGameEntityContext* entityContext = new UiGameEntityContext();
|
|
|
|
AZ::EntityId canvasEntityId = LoadCanvasInternal(assetIdPathname, false, "", entityContext);
|
|
|
|
if (!canvasEntityId.IsValid())
|
|
{
|
|
delete entityContext;
|
|
}
|
|
else
|
|
{
|
|
// The game entity context needs to know its corresponding canvas entity for instantiating dynamic slices
|
|
entityContext->SetCanvasEntity(canvasEntityId);
|
|
|
|
EBUS_EVENT(UiCanvasManagerNotificationBus, OnCanvasLoaded, canvasEntityId);
|
|
}
|
|
|
|
return canvasEntityId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::UnloadCanvas(AZ::EntityId canvasEntityId)
|
|
{
|
|
ReleaseCanvasDeferred(canvasEntityId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::FindLoadedCanvasByPathName(const AZStd::string& assetIdPathName, bool loadIfNotFound)
|
|
{
|
|
// this is only used for finding canvases loaded in game
|
|
UiCanvasComponent* canvasComponent = FindCanvasComponentByPathname(assetIdPathName.c_str());
|
|
AZ::EntityId canvasId = canvasComponent ? canvasComponent->GetEntityId() : AZ::EntityId();
|
|
if (!canvasId.IsValid() && loadIfNotFound)
|
|
{
|
|
canvasId = LoadCanvas(assetIdPathName);
|
|
}
|
|
return canvasId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiCanvasManager::CanvasEntityList UiCanvasManager::GetLoadedCanvases()
|
|
{
|
|
CanvasEntityList list;
|
|
for (auto canvasComponent : m_loadedCanvases)
|
|
{
|
|
list.push_back(canvasComponent->GetEntityId());
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::SetLocalUserIdInputFilterForAllCanvases(AzFramework::LocalUserId localUserId)
|
|
{
|
|
m_localUserIdInputFilter = localUserId;
|
|
for (auto canvasComponent : m_loadedCanvases)
|
|
{
|
|
canvasComponent->SetLocalUserIdInputFilter(m_localUserIdInputFilter);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::OnCanvasDrawOrderChanged([[maybe_unused]] AZ::EntityId canvasEntityId)
|
|
{
|
|
SortCanvasesByDrawOrder();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::OnCanvasEnabledStateChanged(AZ::EntityId canvasEntityId, bool enabled)
|
|
{
|
|
if (enabled)
|
|
{
|
|
bool isConsumingAllInputEvents = false;
|
|
EBUS_EVENT_ID_RESULT(isConsumingAllInputEvents, canvasEntityId, UiCanvasBus, GetIsConsumingAllInputEvents);
|
|
if (isConsumingAllInputEvents)
|
|
{
|
|
AzFramework::InputChannelRequestBus::Broadcast(&AzFramework::InputChannelRequests::ResetState);
|
|
EBUS_EVENT(UiCanvasBus, ClearAllInteractables);
|
|
}
|
|
}
|
|
|
|
// Update hover state for loaded canvases
|
|
m_generateMousePositionInputEvent = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::GenerateMousePositionInputEvent()
|
|
{
|
|
const AzFramework::InputDevice* mouseDevice = AzFramework::InputDeviceRequests::FindInputDevice(AzFramework::InputDeviceMouse::Id);
|
|
if (mouseDevice && mouseDevice->IsConnected())
|
|
{
|
|
// Create a game input event for the system cursor position
|
|
const AzFramework::InputChannel::Snapshot inputSnapshot(AzFramework::InputDeviceMouse::SystemCursorPosition,
|
|
AzFramework::InputDeviceMouse::Id,
|
|
AzFramework::InputChannel::State::Updated);
|
|
|
|
// Get the current system cursor viewport position
|
|
AZ::Vector2 systemCursorPositionNormalized(0.0f, 0.0f);
|
|
AzFramework::InputSystemCursorRequestBus::EventResult(systemCursorPositionNormalized,
|
|
AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::GetSystemCursorPositionNormalized);
|
|
AZ::Vector2 cursorViewportPos(systemCursorPositionNormalized.GetX() * m_latestViewportSize.GetX(),
|
|
systemCursorPositionNormalized.GetY() * m_latestViewportSize.GetY());
|
|
|
|
// Handle the input event
|
|
HandleInputEventForLoadedCanvases(inputSnapshot, cursorViewportPos, AzFramework::ModifierKeyMask::None, true);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::OnFontsReloaded()
|
|
{
|
|
m_fontTextureHasChanged = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
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)
|
|
{
|
|
// get AssetInfo from asset id
|
|
AZ::Data::AssetInfo assetInfo;
|
|
EBUS_EVENT_RESULT(assetInfo, AZ::Data::AssetCatalogRequestBus, GetAssetInfoById, assetId);
|
|
if (assetInfo.m_assetType != LyShine::CanvasAsset::TYPEINFO_Uuid())
|
|
{
|
|
// this is not a UI canvas asset
|
|
return;
|
|
}
|
|
|
|
// get pathname from asset id
|
|
AZStd::string assetPath;
|
|
EBUS_EVENT_RESULT(assetPath, AZ::Data::AssetCatalogRequestBus, GetAssetPathById, assetId);
|
|
|
|
CanvasList reloadedCanvases; // keep track of the reloaded canvases and add them to m_loadedCanvases after the loop
|
|
AZStd::vector<AZ::EntityId> unloadedCanvases; // also keep track of any canvases that fail to reload and are unloaded
|
|
|
|
// loop over all canvases loaded in game and reload any canvases loaded from this canvas asset
|
|
// NOTE: this could be improved by using AssetId for the comparison rather than pathnames
|
|
auto iter = m_loadedCanvases.begin();
|
|
while (iter != m_loadedCanvases.end())
|
|
{
|
|
UiCanvasComponent* canvasComponent = *iter;
|
|
if (assetPath == canvasComponent->GetPathname())
|
|
{
|
|
AZ::EntityId existingCanvasEntityId = canvasComponent->GetEntityId();
|
|
|
|
//Before unloading the existing canvas, make a copy of its mapping table
|
|
AZ::SliceComponent::EntityIdToEntityIdMap existingRemapTable = canvasComponent->GetEditorToGameEntityIdMap();
|
|
|
|
// unload the canvas, just deleting the canvas entity does this
|
|
AZ::Entity* existingCanvasEntity = canvasComponent->GetEntity();
|
|
delete existingCanvasEntity;
|
|
|
|
// Remove the deleted canvas entry in the m_loadedCanvases vector and move the iterator to the next
|
|
iter = m_loadedCanvases.erase(iter);
|
|
|
|
// reload canvas with the same entity IDs (except for new entities, deleted entities etc)
|
|
UiGameEntityContext* entityContext = new UiGameEntityContext();
|
|
AZStd::string pathname(assetPath.c_str());
|
|
UiCanvasComponent* newCanvasComponent = UiCanvasComponent::LoadCanvasInternal(pathname, false, "", entityContext, &existingRemapTable, existingCanvasEntityId);
|
|
|
|
if (!newCanvasComponent)
|
|
{
|
|
delete entityContext;
|
|
unloadedCanvases.push_back(existingCanvasEntityId);
|
|
}
|
|
else
|
|
{
|
|
// The game entity context needs to know its corresponding canvas entity for instantiating dynamic slices
|
|
entityContext->SetCanvasEntity(newCanvasComponent->GetEntityId());
|
|
reloadedCanvases.push_back(newCanvasComponent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++iter; // we did not delete this canvas - move to next in list
|
|
}
|
|
}
|
|
|
|
// add the successfully reloaded canvases at the end
|
|
m_loadedCanvases.insert(m_loadedCanvases.end(), reloadedCanvases.begin(), reloadedCanvases.end());
|
|
|
|
// In case any draw orders changed resort
|
|
SortCanvasesByDrawOrder();
|
|
|
|
// notify any listeners of any UI canvases that were reloaded
|
|
for (auto reloadedCanvasComponent : reloadedCanvases)
|
|
{
|
|
EBUS_EVENT(UiCanvasManagerNotificationBus, OnCanvasReloaded, reloadedCanvasComponent->GetEntityId());
|
|
}
|
|
|
|
// notify any listeners of any UI canvases that were unloaded
|
|
for (auto unloadedCanvas : unloadedCanvases)
|
|
{
|
|
EBUS_EVENT(UiCanvasManagerNotificationBus, OnCanvasUnloaded, unloadedCanvas);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::CreateCanvasInEditor(UiEntityContext* entityContext)
|
|
{
|
|
UiCanvasComponent* canvasComponent = UiCanvasComponent::CreateCanvasInternal(entityContext, true);
|
|
|
|
m_loadedCanvasesInEditor.push_back(canvasComponent);
|
|
|
|
return canvasComponent->GetEntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::LoadCanvasInEditor(const AZStd::string& assetIdPathname, const AZStd::string& sourceAssetPathname, UiEntityContext* entityContext)
|
|
{
|
|
return LoadCanvasInternal(assetIdPathname.c_str(), true, sourceAssetPathname.c_str(), entityContext);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::ReloadCanvasFromXml(const AZStd::string& xmlString, UiEntityContext* entityContext)
|
|
{
|
|
// Load new canvas from xml
|
|
AZ::IO::MemoryStream memoryStream(xmlString.c_str(), xmlString.size());
|
|
AZ::Entity* rootSliceEntity = nullptr;
|
|
AZ::Entity* newCanvasEntity = UiCanvasFileObject::LoadCanvasEntitiesFromStream(memoryStream, rootSliceEntity);
|
|
if (newCanvasEntity && rootSliceEntity)
|
|
{
|
|
// Find the old canvas to replace
|
|
UiCanvasComponent* oldCanvasComponent = nullptr;
|
|
for (auto canvas : (m_loadedCanvasesInEditor))
|
|
{
|
|
if (canvas->GetEntityId() == newCanvasEntity->GetId())
|
|
{
|
|
oldCanvasComponent = canvas;
|
|
break;
|
|
}
|
|
}
|
|
|
|
AZ_Assert(oldCanvasComponent, "Canvas not found");
|
|
if (oldCanvasComponent)
|
|
{
|
|
LyShine::CanvasId oldCanvasId = oldCanvasComponent->GetCanvasId();
|
|
const AZStd::string oldPathname = oldCanvasComponent->GetPathname();
|
|
AZ::Matrix4x4 oldCanvasToViewportMatrix = oldCanvasComponent->GetCanvasToViewportMatrix();
|
|
|
|
// Delete the old canvas. We assume this is for editor
|
|
ReleaseCanvas(oldCanvasComponent->GetEntityId(), true);
|
|
|
|
// Complete initialization of new canvas. We assume this is for editor
|
|
UiCanvasComponent* newCanvasComponent = UiCanvasComponent::FixupReloadedCanvasForEditorInternal(
|
|
newCanvasEntity, rootSliceEntity, entityContext, oldCanvasId, oldPathname);
|
|
|
|
newCanvasComponent->SetCanvasToViewportMatrix(oldCanvasToViewportMatrix);
|
|
|
|
// Add new canvas to the list of loaded canvases
|
|
m_loadedCanvasesInEditor.push_back(newCanvasComponent);
|
|
|
|
return newCanvasComponent->GetEntityId();
|
|
}
|
|
else
|
|
{
|
|
delete newCanvasEntity;
|
|
}
|
|
}
|
|
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::ReleaseCanvas(AZ::EntityId canvasEntityId, bool forEditor)
|
|
{
|
|
if(!canvasEntityId.IsValid())
|
|
{
|
|
AZ_Warning("UI", false, "%s has been invoked with an Invalid Canvas Entity ID. No Canvas can be released", AZ_FUNCTION_SIGNATURE);
|
|
return;
|
|
}
|
|
|
|
// if we are currently processing canvases for input handling or update then defer the deletion of the canvas
|
|
if (!forEditor && m_recursionGuardCount > 0)
|
|
{
|
|
ReleaseCanvasDeferred(canvasEntityId);
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (forEditor)
|
|
{
|
|
stl::find_and_erase(m_loadedCanvasesInEditor, canvasComponent);
|
|
delete canvasEntity;
|
|
}
|
|
else
|
|
{
|
|
stl::find_and_erase(m_loadedCanvases, canvasComponent);
|
|
delete canvasEntity;
|
|
|
|
EBUS_EVENT(UiCanvasManagerNotificationBus, OnCanvasUnloaded, canvasEntityId);
|
|
|
|
// Update hover state for loaded canvases
|
|
m_generateMousePositionInputEvent = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::ReleaseCanvasDeferred(AZ::EntityId canvasEntityId)
|
|
{
|
|
if (!canvasEntityId.IsValid())
|
|
{
|
|
AZ_Warning("UI", false, "%s has been invoked with an Invalid Canvas Entity ID. No Canvas can be released", AZ_FUNCTION_SIGNATURE);
|
|
return;
|
|
}
|
|
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)
|
|
{
|
|
// Remove canvas component from list of loaded canvases
|
|
stl::find_and_erase(m_loadedCanvases, canvasComponent);
|
|
|
|
// Deactivate elements of the canvas
|
|
canvasComponent->DeactivateElements();
|
|
|
|
// Deactivate the canvas element
|
|
canvasEntity->Deactivate();
|
|
|
|
EBUS_EVENT(UiCanvasManagerNotificationBus, OnCanvasUnloaded, canvasEntityId);
|
|
|
|
// Queue UI canvas deletion. This is because this function could have been triggered in input processing of
|
|
// a component within the canvas. i.e. there could be a member function of the canvas or one of it's child entities
|
|
// on the callstack. Unfortunately, just delaying until the next tick is not enough - pressing a button could cause
|
|
// unloading of an entire level which could flush the tick bus. So we have to use our own queue.
|
|
QueueCanvasForDeletion(canvasEntityId);
|
|
|
|
// Update hover state for loaded canvases
|
|
m_generateMousePositionInputEvent = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::FindCanvasById(LyShine::CanvasId id)
|
|
{
|
|
// this is only used for finding canvases loaded in game
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
if (canvas->GetCanvasId() == id)
|
|
{
|
|
return canvas->GetEntityId();
|
|
}
|
|
}
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::SetTargetSizeForLoadedCanvases(AZ::Vector2 viewportSize)
|
|
{
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
canvas->SetTargetCanvasSize(true, viewportSize);
|
|
}
|
|
|
|
m_latestViewportSize = viewportSize;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::UpdateLoadedCanvases(float deltaTime)
|
|
{
|
|
// make a temporary copy of the list in case the update code ends up releasing or loading canvases during iterating over the list
|
|
auto loadedCanvases = m_loadedCanvases;
|
|
|
|
// Update all the canvases loaded in game.
|
|
// It is unlikely this will call out to client code that could remove a canvas but we have no
|
|
// control over what custom components do so we increment the count that will defer all canvas deletion
|
|
m_recursionGuardCount++;
|
|
if (m_generateMousePositionInputEvent)
|
|
{
|
|
// Update hover state for loaded canvases
|
|
m_generateMousePositionInputEvent = false;
|
|
GenerateMousePositionInputEvent();
|
|
}
|
|
for (auto canvas : loadedCanvases)
|
|
{
|
|
canvas->UpdateCanvas(deltaTime, true);
|
|
}
|
|
m_recursionGuardCount--;
|
|
|
|
// If not being called recursively from other canvas processing then immediately do any deferred canvas deletes
|
|
DeleteCanvasesQueuedForDeletion();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::RenderLoadedCanvases()
|
|
{
|
|
// Render all the canvases loaded in game
|
|
// canvases loaded in editor are rendered by the viewport window
|
|
|
|
// If any font texture has changed we force all canvases to rebuild the render graph. Individual text components
|
|
// that use this font will also have got the notification and will have set a flag in their render cache
|
|
// to indicate that the font texture has changed. This allows them to regenerate the quads with no reallocation.
|
|
if (m_fontTextureHasChanged)
|
|
{
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
canvas->MarkRenderGraphDirty();
|
|
}
|
|
|
|
m_fontTextureHasChanged = false;
|
|
}
|
|
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
if (!canvas->GetIsRenderToTexture())
|
|
{
|
|
// Rendering in game full screen so the viewport size and target canvas size are the same
|
|
AZ::Vector2 viewportSize = canvas->GetTargetCanvasSize();
|
|
|
|
canvas->RenderCanvas(true, viewportSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::DestroyLoadedCanvases(bool keepCrossLevelCanvases)
|
|
{
|
|
// Find all the canvases loaded in game (but not loaded in editor) that need destroying
|
|
AZStd::vector<AZ::EntityId> canvasesToUnload;
|
|
canvasesToUnload.reserve(m_loadedCanvases.size());
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
if (!(keepCrossLevelCanvases && canvas->GetKeepLoadedOnLevelUnload()))
|
|
{
|
|
canvasesToUnload.push_back(canvas->GetEntityId());
|
|
}
|
|
}
|
|
|
|
// Unload the canvases. This will also send the OnCanvasUnloaded notification which
|
|
// ensures that components such as UiCanvasAsserRefComponent can clean up properly
|
|
for (auto canvasEntityId : canvasesToUnload)
|
|
{
|
|
UnloadCanvas(canvasEntityId);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::OnLoadScreenUnloaded()
|
|
{
|
|
// Mark all render graphs dirty in case the loaded canvases were already rendered before their textures were
|
|
// done loading. This happens when a load screen is being rendered during a level load. When other canvases
|
|
// associated with the level are loaded, they also get rendered by the UiLoadScreenComponent, but their texture
|
|
// loading is delayed until further down the level load process. Once a canvas is rendered, its render graph's
|
|
// dirty flag is cleared, so the render graph needs to be marked dirty again after the textures are loaded
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiCanvasManager::HandleInputEventForLoadedCanvases(const AzFramework::InputChannel& inputChannel)
|
|
{
|
|
// Take a snapshot of the input channel instead of just passing through the channel itself.
|
|
// This is necessary because UI input is currently simulated in the editor's UI Preview mode
|
|
// by constructing 'fake' input events, which we can do with snapshots but not input channels.
|
|
// Long term we should look to update the AzFramework input system while in UI Editor Preview
|
|
// mode so that it works exactly the same as in-game input, but this is a larger task for later.
|
|
const AzFramework::InputChannel::Snapshot inputSnapshot(inputChannel);
|
|
|
|
// De-normalize the position (if any) of the input event, as the UI system expects it relative
|
|
// to the viewport from here on.
|
|
AZ::Vector2 viewportPos(0.0f, 0.0f);
|
|
const AzFramework::InputChannel::PositionData2D* positionData2D = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
|
|
if (positionData2D)
|
|
{
|
|
viewportPos.SetX(positionData2D->m_normalizedPosition.GetX() * m_latestViewportSize.GetX());
|
|
viewportPos.SetY(positionData2D->m_normalizedPosition.GetY() * m_latestViewportSize.GetY());
|
|
}
|
|
|
|
// Get the active modifier keys (if any) of the input event. Will only exist for keyboard keys.
|
|
const AzFramework::ModifierKeyStates* modifierKeyStates = inputChannel.GetCustomData<AzFramework::ModifierKeyStates>();
|
|
const AzFramework::ModifierKeyMask activeModifierKeys = modifierKeyStates ?
|
|
modifierKeyStates->GetActiveModifierKeys() :
|
|
AzFramework::ModifierKeyMask::None;
|
|
|
|
bool handled = HandleInputEventForLoadedCanvases(inputSnapshot, viewportPos, activeModifierKeys, positionData2D ? true : false);
|
|
return handled;
|
|
}
|
|
|
|
bool UiCanvasManager::HandleInputEventForLoadedCanvases(const AzFramework::InputChannel::Snapshot& inputSnapshot,
|
|
const AZ::Vector2& viewportPos,
|
|
AzFramework::ModifierKeyMask activeModifierKeys,
|
|
bool isPositional)
|
|
{
|
|
bool handled = false;
|
|
|
|
if (isPositional)
|
|
{
|
|
m_generateMousePositionInputEvent = false;
|
|
}
|
|
|
|
// make a temporary copy of the list in case the input handling ends up releasing or loading canvases during iterating over the list
|
|
auto loadedCanvases = m_loadedCanvases;
|
|
|
|
// reverse iterate over the loaded canvases so that the front most canvas gets first chance to
|
|
// handle the event
|
|
bool areAnyInWorldInputCanvasesLoaded = false;
|
|
|
|
// HandleInputEvent is likely to call user code and scripts that could potentially cause a canvas to be released.
|
|
// Setting this flag will cause any canvas deletions to be deferred. Due to the weird behavior when switching levels this function
|
|
// (HandleInputEventForLoadedCanvases) can actually be called recursively because it can flush the input events.
|
|
m_recursionGuardCount++;
|
|
for (auto iter = loadedCanvases.rbegin(); iter != loadedCanvases.rend(); ++iter)
|
|
{
|
|
UiCanvasComponent* canvas = *iter;
|
|
|
|
if (canvas->GetIsRenderToTexture() && canvas->GetIsPositionalInputSupported())
|
|
{
|
|
// keep track of whether any canvases are rendering to texture. Positional events for these
|
|
// are ignored in HandleInputEvent and handled later in this function by HandleInputEventForInWorldCanvases
|
|
areAnyInWorldInputCanvasesLoaded = true;
|
|
}
|
|
|
|
if (canvas->HandleInputEvent(inputSnapshot, &viewportPos, activeModifierKeys))
|
|
{
|
|
handled = true;
|
|
break;
|
|
}
|
|
}
|
|
m_recursionGuardCount--;
|
|
|
|
// If not being called recursively from other canvas processing then immediately do any deferred canvas deletes
|
|
DeleteCanvasesQueuedForDeletion();
|
|
|
|
// if there are any canvases loaded that are rendering to texture we handle them seperately after the screen canvases
|
|
// only do this for input events that are actually associated with a position
|
|
if (!handled && areAnyInWorldInputCanvasesLoaded && isPositional)
|
|
{
|
|
if (HandleInputEventForInWorldCanvases(inputSnapshot, viewportPos))
|
|
{
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiCanvasManager::HandleTextEventForLoadedCanvases(const AZStd::string& textUTF8)
|
|
{
|
|
// reverse iterate over the loaded canvases so that the front most canvas gets first chance to
|
|
// handle the event
|
|
for (auto iter = m_loadedCanvases.rbegin(); iter != m_loadedCanvases.rend(); ++iter)
|
|
{
|
|
if ((*iter)->HandleTextEvent(textUTF8))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::SortCanvasesByDrawOrder()
|
|
{
|
|
std::stable_sort(m_loadedCanvases.begin(), m_loadedCanvases.end(), CompareCanvasDrawOrder);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiCanvasComponent* UiCanvasManager::FindCanvasComponentByPathname(const AZStd::string& name)
|
|
{
|
|
AZStd::string adjustedSearchName = GetAssePathFromUserDefinedPath(name);
|
|
for (auto canvas : (m_loadedCanvases))
|
|
{
|
|
if (adjustedSearchName == canvas->GetPathname())
|
|
{
|
|
return canvas;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiCanvasComponent* UiCanvasManager::FindEditorCanvasComponentByPathname(const AZStd::string& name)
|
|
{
|
|
for (auto canvas : (m_loadedCanvasesInEditor))
|
|
{
|
|
if (name == canvas->GetPathname())
|
|
{
|
|
return canvas;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiCanvasManager::HandleInputEventForInWorldCanvases([[maybe_unused]] const AzFramework::InputChannel::Snapshot& inputSnapshot, [[maybe_unused]] const AZ::Vector2& viewportPos)
|
|
{
|
|
// First we need to construct a ray from the either the center of the screen or the mouse position.
|
|
// This requires knowledge of the camera
|
|
// for initial testing we will just use a ray in the center of the viewport
|
|
|
|
// ToDo: Re-implement by getting the camera from Atom. LYN-3680
|
|
return false;
|
|
//const CCamera cam;
|
|
//
|
|
//// construct a ray from the camera position in the view direction of the camera
|
|
//const float rayLength = 5000.0f;
|
|
//Vec3 rayOrigin(cam.GetPosition());
|
|
//Vec3 rayDirection = cam.GetViewdir() * rayLength;
|
|
//
|
|
//// If the mouse cursor is visible we will assume that the ray should be in the direction of the
|
|
//// mouse pointer. This is a temporary solution. A better solution is to be able to configure the
|
|
//// LyShine system to say how ray input should be handled.
|
|
//bool isCursorVisible = false;
|
|
//UiCursorBus::BroadcastResult(isCursorVisible, &UiCursorInterface::IsUiCursorVisible);
|
|
//if (isCursorVisible)
|
|
//{
|
|
// // for some reason Unproject seems to work when given the viewport pos with (0,0) at the
|
|
// // bottom left as opposed to the top left - even though that function specifically sets top left
|
|
// // to (0,0).
|
|
// const float viewportYInverted = cam.GetViewSurfaceZ() - viewportPos.GetY();
|
|
//
|
|
// // Unproject to get the screen position in world space, use arbitrary Z that is within the depth range
|
|
// Vec3 flippedViewportRayOrigin(viewportPos.GetX(), viewportYInverted, 0.f);
|
|
// Vec3 flippedViewportRayForward(viewportPos.GetX(), viewportYInverted, 1.f);
|
|
//
|
|
// cam.Unproject(flippedViewportRayOrigin, rayOrigin);
|
|
//
|
|
// Vec3 unprojectedPosForward;
|
|
// cam.Unproject(flippedViewportRayForward, unprojectedPosForward);
|
|
//
|
|
// // We want a vector relative to the camera origin
|
|
// Vec3 rayVec = unprojectedPosForward - rayOrigin;
|
|
//
|
|
// // we want to ensure that the ray is a certain length so normalize it and scale it
|
|
// rayVec.NormalizeSafe();
|
|
// rayDirection = rayVec * rayLength;
|
|
//}
|
|
//
|
|
//
|
|
//AzFramework::EntityContextId gameContextId;
|
|
//AzFramework::GameEntityContextRequestBus::BroadcastResult(gameContextId,
|
|
// &AzFramework::GameEntityContextRequests::GetGameEntityContextId);
|
|
//
|
|
//AzFramework::RenderGeometry::RayRequest request;
|
|
//request.m_startWorldPosition = LYVec3ToAZVec3(rayOrigin);
|
|
//request.m_endWorldPosition = LYVec3ToAZVec3(rayOrigin + rayDirection);
|
|
//
|
|
//AzFramework::RenderGeometry::RayResult rayResult;
|
|
//AzFramework::RenderGeometry::IntersectorBus::EventResult(rayResult, gameContextId,
|
|
// &AzFramework::RenderGeometry::IntersectorInterface::RayIntersect, request);
|
|
//
|
|
//if (rayResult)
|
|
//{
|
|
// AZ::EntityId hitEntity = rayResult.m_entityAndComponent.GetEntityId();
|
|
// if (hitEntity.IsValid())
|
|
// {
|
|
// AZ::EntityId canvasEntityId;
|
|
// UiCanvasRefBus::EventResult(canvasEntityId, hitEntity, &UiCanvasRefInterface::GetCanvas);
|
|
// if (canvasEntityId.IsValid())
|
|
// {
|
|
// // Checkif the UI canvas referenced by the hit entity supports automatic input
|
|
// bool doesCanvasSupportInput = false;
|
|
// UiCanvasBus::EventResult(doesCanvasSupportInput, canvasEntityId, &UiCanvasInterface::GetIsPositionalInputSupported);
|
|
//
|
|
// if (doesCanvasSupportInput)
|
|
// {
|
|
// // set the hit details to the hit entity, it will convert into canvas coords and send to canvas
|
|
// bool handled = false;
|
|
// UiCanvasOnMeshBus::EventResult(handled, hitEntity,
|
|
// &UiCanvasOnMeshInterface::ProcessHitInputEvent, inputSnapshot, rayResult);
|
|
//
|
|
// if (handled)
|
|
// {
|
|
// return true;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
//
|
|
//
|
|
//return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiCanvasManager::LoadCanvasInternal(const AZStd::string& assetIdPathname, bool forEditor, const AZStd::string& fullSourceAssetPathname, UiEntityContext* entityContext,
|
|
const AZ::SliceComponent::EntityIdToEntityIdMap* previousRemapTable, AZ::EntityId previousCanvasId)
|
|
{
|
|
// Fix up user defined asset path for runtime. For editor, the asset path should already be valid
|
|
AZStd::string assetIdPath = forEditor ? assetIdPathname : GetAssePathFromUserDefinedPath(assetIdPathname);
|
|
|
|
// If loading from the editor we load the source asset path.
|
|
// If loading in game this could be a path that a developer typed into a script.
|
|
// However, it has to be a valid asset ID path. E.g. it can be resolved from the asset root
|
|
// since at runtime we cannot convert from an arbitrary dev asset path to an asset ID
|
|
const AZStd::string& pathToOpen = forEditor ? fullSourceAssetPathname : assetIdPath;
|
|
|
|
// if the canvas is already loaded in the editor and we are running in game then we clone the
|
|
// editor version so that the user can test their canvas without saving it
|
|
UiCanvasComponent* canvasComponent = FindEditorCanvasComponentByPathname(assetIdPath);
|
|
|
|
if (canvasComponent)
|
|
{
|
|
// this canvas is already loaded in the editor
|
|
if (forEditor)
|
|
{
|
|
// should never load a canvas in Editor if it is already loaded. The Editor should avoid loading the
|
|
// same canvas twice in Editor. If the game is running it is not possible to load a canvas
|
|
// from the editor.
|
|
gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,
|
|
pathToOpen.c_str(),
|
|
"UI canvas file: %s is already loaded",
|
|
pathToOpen.c_str());
|
|
return AZ::EntityId();
|
|
}
|
|
else
|
|
{
|
|
// we are loading from the game, the canvas is already open in the editor, so
|
|
// we clone the canvas that is open in the editor.
|
|
canvasComponent = canvasComponent->CloneAndInitializeCanvas(entityContext, assetIdPath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not already loaded in editor, attempt to load...
|
|
canvasComponent = UiCanvasComponent::LoadCanvasInternal(pathToOpen.c_str(), forEditor, assetIdPath.c_str(), entityContext, previousRemapTable, previousCanvasId);
|
|
}
|
|
|
|
if (canvasComponent)
|
|
{
|
|
// canvas loaded OK (or cloned from Editor canvas OK)
|
|
|
|
// add to the list of loaded canvases
|
|
if (forEditor)
|
|
{
|
|
m_loadedCanvasesInEditor.push_back(canvasComponent);
|
|
}
|
|
else
|
|
{
|
|
if (canvasComponent->GetEnabled() && canvasComponent->GetIsConsumingAllInputEvents())
|
|
{
|
|
AzFramework::InputChannelRequestBus::Broadcast(&AzFramework::InputChannelRequests::ResetState);
|
|
EBUS_EVENT(UiCanvasBus, ClearAllInteractables);
|
|
}
|
|
m_loadedCanvases.push_back(canvasComponent);
|
|
SortCanvasesByDrawOrder();
|
|
|
|
// Update hover state for loaded canvases
|
|
m_generateMousePositionInputEvent = true;
|
|
}
|
|
canvasComponent->SetLocalUserIdInputFilter(m_localUserIdInputFilter);
|
|
}
|
|
|
|
return (canvasComponent) ? canvasComponent->GetEntityId() : AZ::EntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::QueueCanvasForDeletion(AZ::EntityId canvasEntityId)
|
|
{
|
|
m_canvasesQueuedForDeletion.push_back(canvasEntityId);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::DeleteCanvasesQueuedForDeletion()
|
|
{
|
|
// In weird cases like level unload HandleInputEventForLoadedCanvases can get called recursively
|
|
// so do not delete any canvases until there is no recursion
|
|
if (m_recursionGuardCount == 0)
|
|
{
|
|
for (auto canvasEntityId : m_canvasesQueuedForDeletion)
|
|
{
|
|
AZ::Entity* canvasEntity = nullptr;
|
|
EBUS_EVENT_RESULT(canvasEntity, AZ::ComponentApplicationBus, FindEntity, canvasEntityId);
|
|
delete canvasEntity;
|
|
}
|
|
|
|
m_canvasesQueuedForDeletion.clear();
|
|
}
|
|
}
|
|
|
|
#ifndef _RELEASE
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::DebugDisplayCanvasData(int setting) const
|
|
{
|
|
bool onlyShowEnabledCanvases = (setting == 2) ? true : false;
|
|
|
|
IDraw2d* draw2d = Draw2dHelper::GetDefaultDraw2d();
|
|
|
|
float dpiScale = draw2d->GetViewportDpiScalingFactor();
|
|
float xOffset = 20.0f * dpiScale;
|
|
float yOffset = 20.0f * dpiScale;
|
|
|
|
const int elementNameFieldLength = 20;
|
|
|
|
auto blackTexture = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::Black);
|
|
float textOpacity = 1.0f;
|
|
float backgroundRectOpacity = 0.0f; // 0.75f; // [GHI #6515] Reenable background rect
|
|
|
|
const AZ::Vector3 white(1.0f, 1.0f, 1.0f);
|
|
const AZ::Vector3 grey(0.5f, 0.5f, 0.5f);
|
|
const AZ::Vector3 red(1.0f, 0.3f, 0.3f);
|
|
const AZ::Vector3 blue(0.3f, 0.3f, 1.0f);
|
|
|
|
const float fontSize = 8.0f;
|
|
const float lineSpacing = 20.0f * dpiScale;
|
|
|
|
// local function to write a line of text (with a background rect) and increment Y offset
|
|
AZStd::function<void(const char*, const AZ::Vector3&)> WriteLine = [&](const char* buffer, const AZ::Vector3& color)
|
|
{
|
|
IDraw2d::TextOptions textOptions = draw2d->GetDefaultTextOptions();
|
|
textOptions.color = color;
|
|
AZ::Vector2 textSize = draw2d->GetTextSize(buffer, fontSize, &textOptions);
|
|
AZ::Vector2 rectTopLeft = AZ::Vector2(xOffset - 2, yOffset);
|
|
AZ::Vector2 rectSize = AZ::Vector2(textSize.GetX() + 4, lineSpacing);
|
|
draw2d->DrawImage(blackTexture, rectTopLeft, rectSize, backgroundRectOpacity);
|
|
draw2d->DrawText(buffer, AZ::Vector2(xOffset, yOffset), fontSize, textOpacity, &textOptions);
|
|
yOffset += lineSpacing;
|
|
};
|
|
|
|
char buffer[200];
|
|
|
|
sprintf_s(buffer, "There are %d loaded UI canvases", static_cast<int>(m_loadedCanvases.size()));
|
|
WriteLine(buffer, white);
|
|
|
|
sprintf_s(buffer, "NN: %20s %2s %2s %2s %11s %5s %5s %5s %5s %5s %5s %5s %5s %5s %5s %20s %20s",
|
|
"Name", "En", "Po", "Na", "DrawOrder",
|
|
"nElem", "nEnab", "nRend", "nRCtl", "nImg", "nText", "nMask", "nFadr", "nIntr", "nUpdt", "ActiveInt", "HoverInt");
|
|
WriteLine(buffer, white);
|
|
|
|
int totalEnabled = 0;
|
|
int totalPositionalInputs = 0;
|
|
int totalNavigable = 0;
|
|
int totalElements = 0;
|
|
int totalEnabledElements = 0;
|
|
int totalEnabledRenderables = 0;
|
|
int totalEnabledRCtls = 0;
|
|
int totalEnabledImages = 0;
|
|
int totalEnabledTexts = 0;
|
|
int totalEnabledMasks = 0;
|
|
int totalEnabledFaders = 0;
|
|
int totalEnabledIntrs = 0;
|
|
int totalEnabledUpdates = 0;
|
|
|
|
int i = 0;
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
// Name
|
|
const AZStd::string pathname = canvas->GetPathname().c_str();
|
|
size_t lastDot = pathname.find_last_of(".");
|
|
size_t lastSlash = pathname.find_last_of("/");
|
|
AZStd::string leafName = pathname;
|
|
if (lastDot > lastSlash)
|
|
{
|
|
leafName = pathname.substr(lastSlash+1, lastDot-lastSlash-1);
|
|
}
|
|
leafName = leafName.substr(0, 20);
|
|
|
|
// Enabled
|
|
bool isCanvasEnabled = canvas->GetEnabled();
|
|
if (onlyShowEnabledCanvases && !isCanvasEnabled)
|
|
{
|
|
continue;
|
|
}
|
|
const char* enabledString = isCanvasEnabled ? "Y" : "N";
|
|
totalEnabled += isCanvasEnabled ? 1 : 0;
|
|
|
|
bool posEnabled = canvas->GetIsPositionalInputSupported();
|
|
const char* posEnabledString = posEnabled ? "Y" : "N";
|
|
totalPositionalInputs += posEnabled ? 1 : 0;
|
|
|
|
bool navEnabled = canvas->GetIsNavigationSupported();
|
|
const char* navEnabledString = navEnabled ? "Y" : "N";
|
|
totalNavigable += navEnabled ? 1 : 0;
|
|
|
|
// Draw order
|
|
int drawOrder = canvas->GetDrawOrder();
|
|
|
|
// Active and hover
|
|
AZ::EntityId activeInteractableId;
|
|
AZ::EntityId hoverInteractableId;
|
|
canvas->GetDebugInfoInteractables(activeInteractableId, hoverInteractableId);
|
|
|
|
AZStd::string activeInteractableName = "None";
|
|
AZStd::string hoverInteractableName = "None";
|
|
if (activeInteractableId.IsValid())
|
|
{
|
|
activeInteractableName = DebugGetElementName(activeInteractableId, elementNameFieldLength);
|
|
}
|
|
if (hoverInteractableId.IsValid())
|
|
{
|
|
hoverInteractableName = DebugGetElementName(hoverInteractableId, elementNameFieldLength);
|
|
}
|
|
|
|
// Num elements
|
|
UiCanvasComponent::DebugInfoNumElements info;
|
|
canvas->GetDebugInfoNumElements(info);
|
|
|
|
sprintf_s(buffer, "%2d: %20s %2s %2s %2s %11d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %20s %20s",
|
|
i, leafName.c_str(),
|
|
enabledString, posEnabledString, navEnabledString,
|
|
drawOrder,
|
|
info.m_numElements, info.m_numEnabledElements,
|
|
info.m_numRenderElements, info.m_numRenderControlElements,
|
|
info.m_numImageElements, info.m_numTextElements,
|
|
info.m_numMaskElements, info.m_numFaderElements,
|
|
info.m_numInteractableElements,info.m_numUpdateElements,
|
|
activeInteractableName.c_str(), hoverInteractableName.c_str());
|
|
|
|
const AZ::Vector3& color = isCanvasEnabled ? white : grey;
|
|
WriteLine(buffer, color);
|
|
|
|
++i;
|
|
|
|
totalElements += info.m_numElements;
|
|
totalEnabledElements += info.m_numEnabledElements;
|
|
totalEnabledRenderables += info.m_numRenderElements;
|
|
totalEnabledRCtls += info.m_numRenderControlElements;
|
|
totalEnabledImages += info.m_numImageElements;
|
|
totalEnabledTexts += info.m_numTextElements;
|
|
totalEnabledMasks += info.m_numMaskElements;
|
|
totalEnabledFaders += info.m_numFaderElements;
|
|
totalEnabledIntrs += info.m_numInteractableElements;
|
|
totalEnabledUpdates += info.m_numUpdateElements;
|
|
}
|
|
|
|
sprintf_s(buffer, "Totals: %16s %2d %2d %2d %11s %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
|
|
"",
|
|
totalEnabled, totalPositionalInputs, totalNavigable,
|
|
"",
|
|
totalElements, totalEnabledElements,
|
|
totalEnabledRenderables, totalEnabledRCtls,
|
|
totalEnabledImages, totalEnabledTexts,
|
|
totalEnabledMasks, totalEnabledFaders,
|
|
totalEnabledIntrs, totalEnabledUpdates);
|
|
|
|
WriteLine(buffer, red);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::DebugDisplayDrawCallData() const
|
|
{
|
|
IDraw2d* draw2d = Draw2dHelper::GetDefaultDraw2d();
|
|
|
|
float dpiScale = draw2d->GetViewportDpiScalingFactor();
|
|
float xOffset = 20.0f * dpiScale;
|
|
float yOffset = 20.0f * dpiScale;
|
|
|
|
auto blackTexture = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::Black);
|
|
float textOpacity = 1.0f;
|
|
float backgroundRectOpacity = 0.0f; // 0.75f; // [GHI #6515] Reenable background rect
|
|
|
|
const AZ::Vector3 white(1,1,1);
|
|
const AZ::Vector3 red(1,0.3f,0.3f);
|
|
const AZ::Vector3 blue(0.3f,0.3f,1);
|
|
const AZ::Vector3 green(0.3f,1,0.3f);
|
|
const AZ::Vector3 yellow(0.7f,0.7f,0.2f);
|
|
|
|
const float fontSize = 8.0f;
|
|
const float lineSpacing = 20.0f * dpiScale;
|
|
|
|
// local function to write a line of text (with a background rect) and increment Y offset
|
|
AZStd::function<void(const char*, const AZ::Vector3&)> WriteLine = [&](const char* buffer, const AZ::Vector3& color)
|
|
{
|
|
IDraw2d::TextOptions textOptions = draw2d->GetDefaultTextOptions();
|
|
textOptions.color = color;
|
|
AZ::Vector2 textSize = draw2d->GetTextSize(buffer, fontSize, &textOptions);
|
|
AZ::Vector2 rectTopLeft = AZ::Vector2(xOffset - 2, yOffset);
|
|
AZ::Vector2 rectSize = AZ::Vector2(textSize.GetX() + 4, lineSpacing);
|
|
draw2d->DrawImage(blackTexture, rectTopLeft, rectSize, backgroundRectOpacity);
|
|
draw2d->DrawText(buffer, AZ::Vector2(xOffset, yOffset), fontSize, textOpacity, &textOptions);
|
|
yOffset += lineSpacing;
|
|
};
|
|
|
|
char buffer[200];
|
|
|
|
sprintf_s(buffer, "NN: %20s %5s %5s %5s %5s %5s %5s %5s %5s %5s %5s %5s %5s",
|
|
"Canvas name", "nDraw", "nPrim", "nTris", "nMask", "nRTs", "nUTex", "XMask", "XRT", "XBlnd", "XSrgb", "XMaxV", "XTex");
|
|
WriteLine(buffer, blue);
|
|
|
|
int totalRenderNodes = 0;
|
|
int totalPrimitives = 0;
|
|
int totalTriangles = 0;
|
|
int totalMasks = 0;
|
|
int totalRTs = 0;
|
|
int totalDueToMask = 0;
|
|
int totalDueToRT = 0;
|
|
int totalDueToBlendMode = 0;
|
|
int totalDueToSrgb = 0;
|
|
int totalDueToMaxVerts = 0;
|
|
int totalDueToTextures = 0;
|
|
|
|
int i = 0;
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
// Name
|
|
const AZStd::string pathname = canvas->GetPathname().c_str();
|
|
size_t lastDot = pathname.find_last_of(".");
|
|
size_t lastSlash = pathname.find_last_of("/");
|
|
AZStd::string leafName = pathname;
|
|
if (lastDot > lastSlash)
|
|
{
|
|
leafName = pathname.substr(lastSlash+1, lastDot-lastSlash-1);
|
|
}
|
|
|
|
// Enabled
|
|
bool isCanvasEnabled = canvas->GetEnabled();
|
|
if (!isCanvasEnabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Num elements
|
|
LyShineDebug::DebugInfoRenderGraph info;
|
|
canvas->GetDebugInfoRenderGraph(info);
|
|
|
|
sprintf_s(buffer, "%2d: %20s %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
|
|
i, leafName.c_str(),
|
|
info.m_numRenderNodes,
|
|
info.m_numPrimitives, info.m_numTriangles,
|
|
info.m_numMasks, info.m_numRTs, info.m_numUniqueTextures,
|
|
info.m_numNodesDueToMask, info.m_numNodesDueToRT,
|
|
info.m_numNodesDueToBlendMode, info.m_numNodesDueToSrgb,
|
|
info.m_numNodesDueToMaxVerts, info.m_numNodesDueToTextures);
|
|
|
|
AZ::u64 timeSinceBuiltMs = AZStd::GetTimeUTCMilliSecond() - info.m_timeGraphLastBuiltMs;
|
|
if (timeSinceBuiltMs > 1000)
|
|
{
|
|
timeSinceBuiltMs = 1000;
|
|
}
|
|
float percentageOfSecSinceLastBuilt = static_cast<float>(timeSinceBuiltMs) / 1000.0f;
|
|
|
|
AZ::Vector3 color;
|
|
if (info.m_wasBuiltThisFrame)
|
|
{
|
|
color = white; // white used if the render graph was rebuilt this frame
|
|
}
|
|
else
|
|
{
|
|
if (info.m_isReusingRenderTargets)
|
|
{
|
|
color = yellow; // yellow used if the render graph was not rebuilt and render targets were reused
|
|
}
|
|
else
|
|
{
|
|
color = green; // green used if render graph not regenerated this frame and no render targets reused
|
|
}
|
|
|
|
// When the render graph switches to not being built each frame we take 1 second to interpolate from white to
|
|
// the desired color, otherwise it is not possible to see when the rendergraph gets rebuilt at high frame rates
|
|
color = white + (color - white) * percentageOfSecSinceLastBuilt;
|
|
}
|
|
|
|
WriteLine(buffer, color);
|
|
++i;
|
|
|
|
totalRenderNodes += info.m_numRenderNodes;
|
|
totalPrimitives += info.m_numPrimitives;
|
|
totalTriangles += info.m_numTriangles;
|
|
totalMasks += info.m_numMasks;
|
|
totalRTs += info.m_numRTs;
|
|
totalDueToMask += info.m_numNodesDueToMask;
|
|
totalDueToRT += info.m_numNodesDueToRT;
|
|
totalDueToBlendMode += info.m_numNodesDueToBlendMode;
|
|
totalDueToSrgb += info.m_numNodesDueToSrgb;
|
|
totalDueToMaxVerts += info.m_numNodesDueToMaxVerts;
|
|
totalDueToTextures += info.m_numNodesDueToTextures;
|
|
}
|
|
|
|
sprintf_s(buffer, "Totals: %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
|
|
totalRenderNodes,
|
|
totalPrimitives, totalTriangles, totalMasks, totalRTs,
|
|
totalDueToMask, totalDueToRT,
|
|
totalDueToBlendMode, totalDueToSrgb,
|
|
totalDueToMaxVerts, totalDueToTextures);
|
|
|
|
WriteLine(buffer, red);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZStd::string UiCanvasManager::DebugGetElementName(AZ::EntityId entityId, int maxLength) const
|
|
{
|
|
AZStd::string name = "None";
|
|
AZ::Entity* entity = nullptr;
|
|
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
|
|
if (entity)
|
|
{
|
|
name = entity->GetName();
|
|
if (name.length() < maxLength)
|
|
{
|
|
AZ::Entity* parent = nullptr;
|
|
EBUS_EVENT_ID_RESULT(parent, entityId, UiElementBus, GetParent);
|
|
if (parent)
|
|
{
|
|
name = parent->GetName() + "/" + name;
|
|
if (name.length() > maxLength)
|
|
{
|
|
AZStd::string maxString = name.substr(name.length() - maxLength);
|
|
name = maxString;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::DebugReportDrawCalls(const AZStd::string& name) const
|
|
{
|
|
AZStd::string logFolder = AZStd::string::format("@log@/LyShine");
|
|
AZ::IO::LocalFileIO::GetInstance()->CreatePath(logFolder.c_str());
|
|
|
|
AZStd::string logFileLeafName = "DrawCallReport";
|
|
if (!name.empty())
|
|
{
|
|
logFileLeafName += "_";
|
|
logFileLeafName += name;
|
|
}
|
|
AZStd::string logFile = AZStd::string::format("%s/%s.txt", logFolder.c_str(), logFileLeafName.c_str());
|
|
|
|
AZ::IO::HandleType logHandle;
|
|
AZ::IO::Result result = AZ::IO::LocalFileIO::GetInstance()->Open(logFile.c_str(), AZ::IO::OpenMode::ModeWrite, logHandle);
|
|
if (!result)
|
|
{
|
|
AZ_TracePrintf("UI", "Failed to open file for Draw Call Report at %s\n", logFile.c_str());
|
|
return;
|
|
}
|
|
|
|
AZStd::string logLine = AZStd::string::format("Draw call report for \'%s\'\r\n", name.c_str());
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
logLine = AZStd::string::format("Output by the ui_ReportDrawCalls console command\r\n\r\n");
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
int numEnabledCanvases = 0;
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
if (canvas->GetEnabled())
|
|
{
|
|
numEnabledCanvases++;
|
|
}
|
|
}
|
|
|
|
logLine = AZStd::string::format("There are %zu loaded UI canvases, %d of which are enabled.\r\nThe below report only includes the enabled canvases\r\n",
|
|
m_loadedCanvases.size(), numEnabledCanvases);
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
LyShineDebug::DebugInfoDrawCallReport reportInfo;
|
|
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
// Check enabled
|
|
bool isCanvasEnabled = canvas->GetEnabled();
|
|
if (!isCanvasEnabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Name of canvas
|
|
const AZStd::string pathname = canvas->GetPathname().c_str();
|
|
logLine = "\r\n=====================================================================================\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = AZStd::string::format("Canvas: %s\r\n", pathname.c_str());
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = "=====================================================================================\r\n\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
// Get canvas summary data
|
|
LyShineDebug::DebugInfoRenderGraph renderGraphInfo;
|
|
canvas->GetDebugInfoRenderGraph(renderGraphInfo);
|
|
|
|
// Output a summary report
|
|
if (renderGraphInfo.m_numRenderNodes > 0)
|
|
{
|
|
logLine = AZStd::string::format("Canvas has %d draw calls and %d primitives with a total of %d triangles\r\n",
|
|
renderGraphInfo.m_numRenderNodes, renderGraphInfo.m_numPrimitives, renderGraphInfo.m_numTriangles);
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = AZStd::string::format("There are %d unique textures used, %d mask render nodes and %d render target render nodes\r\n",
|
|
renderGraphInfo.m_numUniqueTextures, renderGraphInfo.m_numMasks, renderGraphInfo.m_numRTs);
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = AZStd::string::format(
|
|
"Extra draw calls caused by... Masks: %d, RenderTargets: %d, BlendModes: %d, Srgb: %d, MaxVerts: %d, MaxTextures: %d\r\n\r\n",
|
|
renderGraphInfo.m_numNodesDueToMask, renderGraphInfo.m_numNodesDueToRT,
|
|
renderGraphInfo.m_numNodesDueToBlendMode,
|
|
renderGraphInfo.m_numNodesDueToSrgb, renderGraphInfo.m_numNodesDueToMaxVerts, renderGraphInfo.m_numNodesDueToTextures);
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
}
|
|
|
|
// output the details on each draw call and gather info for all canvases
|
|
canvas->DebugReportDrawCalls(logHandle, reportInfo, canvas);
|
|
}
|
|
|
|
AZStd::string fontTexturePrefix = "$AutoFont";
|
|
|
|
logLine = "\r\n\r\n--------------------------------------------------------------------------------------------\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = AZStd::string::format("Textures used on multiple canvases that are causing extra draw calls\r\n");
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = "--------------------------------------------------------------------------------------------\r\n\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
for (LyShineDebug::DebugInfoTextureUsage& reportTextureUsage : reportInfo.m_textures)
|
|
{
|
|
if (reportTextureUsage.m_numCanvasesUsed > 1 &&
|
|
reportTextureUsage.m_numDrawCallsWhereExceedingMaxTextures)
|
|
{
|
|
AZStd::string textureName;
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(textureName, &AZ::Data::AssetCatalogRequests::GetAssetPathById, reportTextureUsage.m_texture->GetAssetId());
|
|
if (textureName.compare(0, fontTexturePrefix.length(), fontTexturePrefix) != 0)
|
|
{
|
|
logLine = AZStd::string::format("%s\r\n", textureName.c_str());
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
logLine = "\r\n\r\n--------------------------------------------------------------------------------------------\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = AZStd::string::format("Per canvas report of textures used on only that canvas that are causing extra draw calls\r\n");
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = "--------------------------------------------------------------------------------------------\r\n\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
// Check enabled
|
|
bool isCanvasEnabled = canvas->GetEnabled();
|
|
if (!isCanvasEnabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool loggedCanvasHeader = false;
|
|
for (LyShineDebug::DebugInfoTextureUsage& reportTextureUsage : reportInfo.m_textures)
|
|
{
|
|
if (reportTextureUsage.m_numCanvasesUsed == 1 &&
|
|
reportTextureUsage.m_lastContextUsed == canvas &&
|
|
reportTextureUsage.m_numDrawCallsWhereExceedingMaxTextures)
|
|
{
|
|
AZStd::string textureName;
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(textureName, &AZ::Data::AssetCatalogRequests::GetAssetPathById, reportTextureUsage.m_texture->GetAssetId());
|
|
|
|
// exclude font textures
|
|
if (textureName.compare(0, fontTexturePrefix.length(), fontTexturePrefix) != 0)
|
|
{
|
|
if (!loggedCanvasHeader)
|
|
{
|
|
const AZStd::string pathname = canvas->GetPathname().c_str();
|
|
logLine = AZStd::string::format("\r\nCanvas: %s\r\n\r\n", pathname.c_str());
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
loggedCanvasHeader = true;
|
|
}
|
|
|
|
logLine = AZStd::string::format("%s\r\n", textureName.c_str());
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
logLine = "\r\n--------------------------------------------------------------------------------------------\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = "End of report\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
logLine = "--------------------------------------------------------------------------------------------\r\n";
|
|
AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
|
|
|
|
AZ::IO::LocalFileIO::GetInstance()->Close(logHandle);
|
|
|
|
AZ_TracePrintf("UI", "Wrote Draw Call Report to %s\n", logFile.c_str());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiCanvasManager::DebugDisplayElemBounds(int canvasIndexFilter) const
|
|
{
|
|
IDraw2d* draw2d = Draw2dHelper::GetDefaultDraw2d();
|
|
|
|
int canvasIndex = 0;
|
|
for (auto canvas : m_loadedCanvases)
|
|
{
|
|
// Enabled
|
|
bool isCanvasEnabled = canvas->GetEnabled();
|
|
if (!isCanvasEnabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// filter canvas index
|
|
if (canvasIndexFilter == -1 || canvasIndexFilter == canvasIndex)
|
|
{
|
|
// Display the elem bounds
|
|
canvas->DebugDisplayElemBounds(draw2d);
|
|
}
|
|
|
|
++canvasIndex; // only increments for enabled canvases so index matches "ui_DisplayCanvasData 2"
|
|
}
|
|
}
|
|
|
|
#endif
|