Restore Editor viewport icon rendering (#879)

This introduces an EditorViewportIconDisplayInterface that will eventually be used to outright remove CIconManager, it provides a simple interface for loading 2D image assets and rendering them on-screen. It also introduces AtomBridge::PerViewportDynamicDraw for getting a dynamic draw instance on a per-viewport basis
main
Nicholas Van Sickle 5 years ago committed by GitHub
parent dc0f1ff0b1
commit d1863c6c5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,80 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Color.h>
#include <AzFramework/Viewport/ViewportId.h>
namespace AzToolsFramework
{
//! An interface for loading simple icon assets and rendering them to screen on a per-viewport basis.
class EditorViewportIconDisplayInterface
{
public:
AZ_RTTI(EditorViewportIconDisplayInterface, "{D5190B58-2561-4F3F-B793-F1E7D454CDF2}");
using IconId = AZ::s32;
static constexpr IconId InvalidIconId = -1;
enum class CoordinateSpace : AZ::u8
{
ScreenSpace,
WorldSpace
};
//! These draw parameters control rendering for a single icon to a single viewport.
struct DrawParameters
{
//! The ViewportId to render to.
AzFramework::ViewportId m_viewport = AzFramework::InvalidViewportId;
//! The icon ID, retrieved from GetOrLoadIconForPath, to render to screen.
IconId m_icon = InvalidIconId;
//! The color, including opacity, to render the icon with. White will render the icon as opaque in its original color.
AZ::Color m_color = AZ::Colors::White;
//! The position to render the icon to, in world or screen space depending on m_positionSpace.
AZ::Vector3 m_position;
//! The coordinate system to use for m_position.
//! ScreenSpace will accept m_position in the form of [X, Y, Depth], where X & Y are screen coordinates in
//! pixels and Depth is a z-ordering depth value from 0.0f to 1.0f.
//! WorldSpace will accept a 3D vector in world space coordinates that will be translated back into screen
//! space when the icon is rendered.
CoordinateSpace m_positionSpace = CoordinateSpace::ScreenSpace;
//! The size to render the icon as, in pixels.
AZ::Vector2 m_size;
};
//! The current load status of an icon retrieved by GetOrLoadIconForPath.
enum class IconLoadStatus : AZ::u8
{
Unloaded,
Loading,
Loaded,
Error
};
//! Draws an icon to a viewport given a set of draw parameters.
//! Requires an IconId retrieved from GetOrLoadIconForPath.
virtual void DrawIcon(const DrawParameters& drawParameters) = 0;
//! Retrieves a reusable IconId for an icon at a given path.
//! This will load the icon, if it has not already been loaded.
//! @param path should be a relative asset path to an icon image asset.
//! png and svg icons are currently supported.
virtual IconId GetOrLoadIconForPath(AZStd::string_view path) = 0;
//! Gets the current load status of an icon retrieved via GetOrLoadIconForPath.
virtual IconLoadStatus GetIconLoadStatus(IconId icon) = 0;
};
using EditorViewportIconDisplay = AZ::Interface<EditorViewportIconDisplayInterface>;
} //namespace AzToolsFramework

@ -17,6 +17,7 @@
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EditorViewportIconDisplayInterface.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
@ -313,8 +314,7 @@ namespace AzToolsFramework
// if we do not yet have a valid texture id, request it using the entity icon path
if (m_entityIconTextureId == 0)
{
EditorRequestBus::BroadcastResult(
m_entityIconTextureId, &EditorRequests::GetIconTextureIdFromEntityIconPath, m_entityIconPath);
m_entityIconTextureId = EditorViewportIconDisplay::Get()->GetOrLoadIconForPath(m_entityIconPath);
}
return m_entityIconTextureId;

@ -21,6 +21,7 @@
#include <AzToolsFramework/Viewport/ViewportTypes.h>
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AzToolsFramework/API/EditorViewportIconDisplayInterface.h>
AZ_CVAR(
bool, ed_visibility_showAggregateEntitySelectionBounds, false, nullptr, AZ::ConsoleFunctorFlags::Null,
@ -232,10 +233,14 @@ namespace AzToolsFramework
return AZ::Color(1.0f, 1.0f, 1.0f, 1.0f);
}();
debugDisplay.SetColor(iconHighlight);
// debugDisplay.DrawTextureLabel(
// iconTextureId, entityPosition, iconSize, iconSize,
// /*DisplayContext::ETextureIconFlags::TEXICON_ON_TOP=*/ 0x0008);
EditorViewportIconDisplay::Get()->DrawIcon({
viewportInfo.m_viewportId,
iconTextureId,
iconHighlight,
entityPosition,
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace,
AZ::Vector2{iconSize, iconSize}
});
}
}
}

@ -46,6 +46,7 @@ set(FILES
API/EditorWindowRequestBus.h
API/EntityCompositionRequestBus.h
API/EntityCompositionNotificationBus.h
API/EditorViewportIconDisplayInterface.h
API/ViewPaneOptions.h
Application/Ticker.h
Application/Ticker.cpp

@ -87,6 +87,14 @@ namespace AZ
//! Finalize and validate initialization. Any initialization functions should be called before EndInit is called.
void EndInit();
//! Set up the DynamicDrawContext for the input Scene.
//! This should be called after the last frame is done and before any draw calls.
void SetScene(Scene* scene);
//! Set up the DynamicDrawContext for the input RenderPipeline.
//! This should be called after the last frame is done and before any draw calls.
void SetRenderPipeline(RenderPipeline* pipeline);
//! Return if this DynamicDrawContext is ready to add draw calls
bool IsReady();

@ -26,9 +26,9 @@ namespace AZ
class ViewportContextManager;
//! ViewportContext wraps a native window and represents a minimal viewport
//! in which a scene is rendered on-screen
//! in which a scene is rendered on-screen.
//! ViewportContexts are registered on creation to allow consumers to listen to notifications
//! and manage the view stack for a given viewport
//! and manage the view stack for a given viewport.
class ViewportContext
: public SceneNotificationBus::Handler
, public AzFramework::WindowNotificationBus::Handler
@ -61,11 +61,11 @@ namespace AZ
//! Gets the current name of this ViewportContext.
//! This name is used to tie this ViewportContext to its View stack, and ViewportContexts may be
//! renamed via AZ::Interface<ViewportContextRequestsInterface>::Get()->RenameViewportContext.
//! renamed via AZ::RPI::ViewportContextRequests::Get()->RenameViewportContext(...).
AZ::Name GetName() const;
//! Gets the default view associated with this ViewportContext.
//! Alternatively, use AZ::Interface<ViewportContextRequestsInterface>::Get()->GetCurrentView.
//! Alternatively, use AZ::RPI::ViewportContextRequests::Get()->GetCurrentView().
ViewPtr GetDefaultView();
ConstViewPtr GetDefaultView() const;
@ -99,6 +99,18 @@ namespace AZ
//! Notifies consumers when the render scene has changed.
void ConnectSceneChangedHandler(SceneChangedEvent::Handler& handler);
using PipelineChangedEvent = AZ::Event<RenderPipelinePtr>;
//! Notifies consumers when the current pipeline associated with our window has changed.
void ConnectCurrentPipelineChangedHandler(PipelineChangedEvent::Handler& handler);
using ViewChangedEvent = AZ::Event<ViewPtr>;
//! Notifies consumers when the default view has changed.
void ConnectDefaultViewChangedHandler(ViewChangedEvent::Handler& handler);
using ViewportIdEvent = AZ::Event<AzFramework::ViewportId>;
//! Notifies consumers when this ViewportContext is about to be destroyed.
void ConnectAboutToBeDestroyedHandler(ViewportIdEvent::Handler& handler);
// ViewportRequestBus interface
//! Gets the current camera's view matrix.
const AZ::Matrix4x4& GetCameraViewMatrix() const override;
@ -123,12 +135,17 @@ namespace AZ
WindowContextSharedPtr m_windowContext;
ViewPtr m_defaultView;
AzFramework::WindowSize m_viewportSize;
SizeChangedEvent m_sizeChangedEvent;
MatrixChangedEvent m_viewMatrixChangedEvent;
MatrixChangedEvent::Handler m_onViewMatrixChangedHandler;
MatrixChangedEvent m_projectionMatrixChangedEvent;
MatrixChangedEvent::Handler m_onProjectionMatrixChangedHandler;
SceneChangedEvent m_sceneChangedEvent;
PipelineChangedEvent m_currentPipelineChangedEvent;
ViewChangedEvent m_defaultViewChangedEvent;
ViewportIdEvent m_aboutToBeDestroyedEvent;
ViewportContextManager* m_manager;
RenderPipelinePtr m_currentPipeline;
Name m_name;

@ -211,6 +211,42 @@ namespace AZ
m_rhiPipelineState = m_pipelineState->GetRHIPipelineState();
}
void DynamicDrawContext::SetScene(Scene* scene)
{
AZ_Assert(scene, "SetScene called with an invalid scene");
if (!scene || m_scene == scene)
{
return;
}
m_scene = scene;
m_drawFilter = RHI::DrawFilterMaskDefaultValue;
// Reinitialize if it was initialized
if (m_initialized)
{
// Report warning if there were some draw data
AZ_Warning(
"DynamicDrawContext", m_cachedDrawItems.size() == 0,
"DynamicDrawContext::SetForScene should be called"
" when there is no cached draw data");
// Clear some cached data
FrameEnd();
m_cachedRhiPipelineStates.clear();
// Reinitialize
EndInit();
}
}
void DynamicDrawContext::SetRenderPipeline(RenderPipeline* pipeline)
{
AZ_Assert(pipeline, "SetRenderPipeline called with an invalid pipeline");
if (!pipeline)
{
return;
}
SetScene(pipeline->GetScene());
m_drawFilter = pipeline->GetDrawFilterMask();
}
bool DynamicDrawContext::IsReady()
{
return m_initialized;

@ -51,6 +51,8 @@ namespace AZ
ViewportContext::~ViewportContext()
{
m_aboutToBeDestroyedEvent.Signal(m_id);
AzFramework::WindowNotificationBus::Handler::BusDisconnect();
AzFramework::ViewportRequestBus::Handler::BusDisconnect();
@ -171,6 +173,21 @@ namespace AZ
handler.Connect(m_sceneChangedEvent);
}
void ViewportContext::ConnectCurrentPipelineChangedHandler(PipelineChangedEvent::Handler& handler)
{
handler.Connect(m_currentPipelineChangedEvent);
}
void ViewportContext::ConnectDefaultViewChangedHandler(ViewChangedEvent::Handler& handler)
{
handler.Connect(m_defaultViewChangedEvent);
}
void ViewportContext::ConnectAboutToBeDestroyedHandler(ViewportIdEvent::Handler& handler)
{
handler.Connect(m_aboutToBeDestroyedEvent);
}
const AZ::Matrix4x4& ViewportContext::GetCameraViewMatrix() const
{
return GetDefaultView()->GetWorldToViewMatrix();
@ -214,6 +231,7 @@ namespace AZ
m_defaultView = view;
UpdatePipelineView();
m_defaultViewChangedEvent.Signal(view);
m_viewMatrixChangedEvent.Signal(view->GetWorldToViewMatrix());
m_projectionMatrixChangedEvent.Signal(view->GetViewToClipMatrix());
@ -232,6 +250,7 @@ namespace AZ
if (!m_currentPipeline)
{
m_currentPipeline = m_rootScene ? m_rootScene->FindRenderPipelineForWindow(m_windowContext->GetWindowHandle()) : nullptr;
m_currentPipelineChangedEvent.Signal(m_currentPipeline);
}
if (auto pipeline = GetCurrentPipeline())

@ -106,5 +106,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
Gem::AtomFont
Gem::AtomToolsFramework.Editor
Gem::AtomViewportDisplayInfo
Gem::AtomViewportDisplayIcons.Editor
)
endif()

@ -0,0 +1,44 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
#include <AzFramework/Viewport/ViewportId.h>
namespace AZ::AtomBridge
{
//! A simple interface for allocating a DynamicDrawContext on-demand for every viewport, based on
//! a registered initialization function.
class PerViewportDynamicDrawInterface
{
public:
AZ_RTTI(PerViewportDynamicDrawInterface, "{1FF054F5-55FF-4ADB-A86D-640B15FA0395}");
using DrawContextFactory = AZStd::function<void(RHI::Ptr<RPI::DynamicDrawContext>)>;
//! Register a named dynamic draw context that can be retrieved on a per-viewport basis.
//! GetNamedDynamicDraw context can be called on a registered context name to retrieve a
//! valid DynamicDrawContext for a given viewport.
virtual void RegisterDynamicDrawContext(AZ::Name name, DrawContextFactory contextInitializer) = 0;
//! Unregister a previously registered named per-viewport dynamic draw context.
//! This will dispose of all dynamic draw contexts currently associated with this name.
virtual void UnregisterDynamicDrawContext(AZ::Name name) = 0;
//! Get a dynamic draw context associated with the specified viewport based on a factory registered with
//! RegisterNamedDynamicDrawContext. This dynamic draw context will be created if it does not already exist.
virtual RHI::Ptr<RPI::DynamicDrawContext> GetDynamicDrawContextForViewport(AZ::Name name, AzFramework::ViewportId viewportId) = 0;
};
using PerViewportDynamicDraw = AZ::Interface<PerViewportDynamicDrawInterface>;
} // namespace AZ::AtomBridge

@ -13,6 +13,7 @@
#include <AtomBridgeSystemComponent.h>
#include <AtomDebugDisplayViewportInterface.h>
#include <FlyCameraInputComponent.h>
#include <PerViewportDynamicDrawManager.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
@ -104,10 +105,12 @@ namespace AZ
AzFramework::GameEntityContextRequestBus::BroadcastResult(m_entityContextId, &AzFramework::GameEntityContextRequestBus::Events::GetGameEntityContextId);
AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect();
m_dynamicDrawManager = AZStd::make_unique<PerViewportDynamicDrawManager>();
}
void AtomBridgeSystemComponent::Deactivate()
{
m_dynamicDrawManager.reset();
AZ::RPI::ViewportContextManagerNotificationsBus::Handler::BusDisconnect();
RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get();
// Check if scene is emptry since scene might be released already when running AtomSampleViewer

@ -33,6 +33,7 @@ namespace AZ
{
// forward declares
class AtomDebugDisplayViewportInterface;
class PerViewportDynamicDrawManager;
class AtomBridgeSystemComponent
: public Component
@ -82,6 +83,7 @@ namespace AZ
RPI::ViewPtr m_view = nullptr;
AZStd::unordered_map<AzFramework::ViewportId, AZStd::shared_ptr<AtomDebugDisplayViewportInterface> > m_activeViewportsList;
AZStd::unique_ptr<PerViewportDynamicDrawManager> m_dynamicDrawManager;
};
}
} // namespace AZ

@ -0,0 +1,119 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "PerViewportDynamicDrawManager.h"
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Public/ViewportContext.h>
namespace AZ::AtomBridge
{
PerViewportDynamicDrawManager::PerViewportDynamicDrawManager()
{
PerViewportDynamicDraw::Register(this);
}
PerViewportDynamicDrawManager::~PerViewportDynamicDrawManager()
{
PerViewportDynamicDraw::Unregister(this);
}
void PerViewportDynamicDrawManager::RegisterDynamicDrawContext(AZ::Name name, DrawContextFactory contextInitializer)
{
AZStd::lock_guard lock(m_mutexDrawContexts);
const bool alreadyRegistered = m_registeredDrawContexts.find(name) != m_registeredDrawContexts.end();
AZ_Error("AtomBridge", !alreadyRegistered, "Attempted to call RegisterDynamicDrawContext for already registered name: \"%s\"", name.GetCStr());
if (alreadyRegistered)
{
return;
}
m_registeredDrawContexts[name] = contextInitializer;
}
void PerViewportDynamicDrawManager::UnregisterDynamicDrawContext(AZ::Name name)
{
AZStd::lock_guard lock(m_mutexDrawContexts);
auto drawContextFactoryIt = m_registeredDrawContexts.find(name);
const bool registered = drawContextFactoryIt != m_registeredDrawContexts.end();
AZ_Error("AtomBridge", registered, "Attempted to call UnregisterDynamicDrawContext for unregistered name: \"%s\"", name.GetCStr());
if (!registered)
{
return;
}
m_registeredDrawContexts.erase(drawContextFactoryIt);
for (auto& viewportData : m_viewportData)
{
viewportData.second.m_dynamicDrawContexts.erase(name);
}
}
RHI::Ptr<RPI::DynamicDrawContext> PerViewportDynamicDrawManager::GetDynamicDrawContextForViewport(
AZ::Name name, AzFramework::ViewportId viewportId)
{
AZStd::lock_guard lock(m_mutexDrawContexts);
auto contextFactoryIt = m_registeredDrawContexts.find(name);
if (contextFactoryIt == m_registeredDrawContexts.end())
{
return nullptr;
}
auto viewportContextManager = RPI::ViewportContextRequests::Get();
RPI::ViewportContextPtr viewportContext = viewportContextManager->GetViewportContextById(viewportId);
if (viewportContext == nullptr)
{
return nullptr;
}
// Get or create a ViewportData if one doesn't already exist
ViewportData& viewportData = m_viewportData[viewportId];
if (!viewportData.m_initialized)
{
viewportData.m_pipelineChangedHandler = AZ::Event<RPI::RenderPipelinePtr>::Handler([this, viewportId](RPI::RenderPipelinePtr pipeline)
{
AZStd::lock_guard lock(m_mutexDrawContexts);
ViewportData& viewportData = m_viewportData[viewportId];
for (auto& context : viewportData.m_dynamicDrawContexts)
{
context.second->SetRenderPipeline(pipeline.get());
}
});
viewportData.m_viewportDestroyedHandler = AZ::Event<AzFramework::ViewportId>::Handler([this, viewportId](AzFramework::ViewportId id)
{
AZStd::lock_guard lock(m_mutexDrawContexts);
m_viewportData.erase(id);
});
viewportContext->ConnectCurrentPipelineChangedHandler(viewportData.m_pipelineChangedHandler);
viewportContext->ConnectAboutToBeDestroyedHandler(viewportData.m_viewportDestroyedHandler);
viewportData.m_initialized = true;
}
RHI::Ptr<RPI::DynamicDrawContext>& context = viewportData.m_dynamicDrawContexts[name];
if (context == nullptr)
{
auto pipeline = viewportContext->GetCurrentPipeline().get();
if (pipeline == nullptr)
{
return nullptr;
}
context = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(pipeline);
contextFactoryIt->second(context);
}
return context;
}
} //namespace AZ::AtomBridge

@ -0,0 +1,48 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AtomBridge/PerViewportDynamicDrawInterface.h>
namespace AZ::AtomBridge
{
class PerViewportDynamicDrawManager final : public PerViewportDynamicDrawInterface
{
public:
AZ_TYPE_INFO(PerViewportDynamicDrawManager, "{BED66185-00A7-43F7-BD28-C56BC8E4C535}");
PerViewportDynamicDrawManager();
~PerViewportDynamicDrawManager();
// PerViewportDynamicDrawInterface overrides...
void RegisterDynamicDrawContext(AZ::Name name, DrawContextFactory contextInitializer) override;
void UnregisterDynamicDrawContext(AZ::Name name) override;
RHI::Ptr<RPI::DynamicDrawContext> GetDynamicDrawContextForViewport(AZ::Name name, AzFramework::ViewportId viewportId) override;
private:
struct ViewportData
{
AZStd::unordered_map<AZ::Name, RHI::Ptr<RPI::DynamicDrawContext>> m_dynamicDrawContexts;
// Event handlers
AZ::Event<RPI::RenderPipelinePtr>::Handler m_pipelineChangedHandler;
AZ::Event<AzFramework::ViewportId>::Handler m_viewportDestroyedHandler;
// Cached state
bool m_initialized = false;
};
AZStd::map<AzFramework::ViewportId, ViewportData> m_viewportData;
AZStd::unordered_map<AZ::Name, DrawContextFactory> m_registeredDrawContexts;
AZStd::mutex m_mutexDrawContexts;
};
} //namespace AZ::AtomBridge

@ -12,10 +12,13 @@
set(FILES
Include/AtomBridge/AtomBridgeBus.h
Include/AtomBridge/FlyCameraInputBus.h
Include/AtomBridge/PerViewportDynamicDrawInterface.h
Source/AtomBridgeSystemComponent.cpp
Source/AtomBridgeSystemComponent.h
Source/FlyCameraInputComponent.cpp
Source/FlyCameraInputComponent.h
Source/AtomDebugDisplayViewportInterface.cpp
Source/AtomDebugDisplayViewportInterface.h
Source/FlyCameraInputComponent.cpp
Source/FlyCameraInputComponent.h
Source/PerViewportDynamicDrawManager.cpp
Source/PerViewportDynamicDrawManager.h
)

@ -0,0 +1,77 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/Features/SrgSemantics.azsli>
ShaderResourceGroup InstanceSrg : SRG_PerDraw
{
float2 m_viewportSize;
Texture2D m_texture;
Sampler m_sampler
{
MaxAnisotropy = 16;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
};
struct VSInput
{
float3 m_position : POSITION;
float4 m_color : COLOR0;
float2 m_uv : TEXCOORD0;
};
struct VSOutput
{
float4 m_position : SV_Position;
float4 m_color : COLOR0;
float2 m_uv : TEXCOORD0;
};
VSOutput MainVS(VSInput IN)
{
// Convert from screen space to clip space
float2 posXY = float2(IN.m_position.xy) / InstanceSrg::m_viewportSize * 2.0f - float2(1.0f, 1.0f);
posXY.y *= -1.0f;
float4 posPS = float4(posXY, IN.m_position.z, 1.0f);
VSOutput OUT;
OUT.m_position = posPS;
OUT.m_color = IN.m_color;
OUT.m_uv = IN.m_uv;
return OUT;
};
struct PSOutput
{
float4 m_color : SV_Target0;
};
PSOutput MainPS(VSOutput IN)
{
PSOutput OUT;
float4 tex;
tex = InstanceSrg::m_texture.Sample(InstanceSrg::m_sampler, IN.m_uv);
float opacity = IN.m_color.a * tex.a;
// We use pre-multiplied alpha here since it is more flexible. For example, it enables alpha-blended rendering to
// a render target and then alpha blending that render target into another render target
OUT.m_color.rgb = IN.m_color.rgb * tex.rgb * opacity;
OUT.m_color.a = opacity;
return OUT;
};

@ -0,0 +1,39 @@
{
"Source" : "TexturedIcon",
"DepthStencilState" : {
"Depth" : {
"Enable" : false,
"CompareFunc" : "Always"
}
},
"RasterState" : {
"DepthClipEnable" : false,
"CullMode" : "None"
},
"BlendState" : {
"Enable" : true,
"BlendSource" : "One",
"BlendDest" : "AlphaSourceInverse",
"BlendOp" : "Add"
},
"DrawList" : "2dpass",
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}

@ -0,0 +1,12 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
add_subdirectory(Code)

@ -0,0 +1,35 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME AtomViewportDisplayIcons.Editor ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
FILES_CMAKE
atomviewportdisplayicons_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
AZ::AtomCore
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Svg
Gem::Atom_RHI.Reflect
Gem::Atom_RPI.Public
Gem::Atom_Bootstrap.Headers
Gem::Atom_AtomBridge.Static
)
endif()

@ -0,0 +1,339 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AtomViewportDisplayIconsSystemComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/std/containers/array.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Public/ViewportContext.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
#include <Atom/RPI.Public/Image/StreamingImagePool.h>
#include <AtomBridge/PerViewportDynamicDrawInterface.h>
#include <QDir>
#include <QFileInfo>
#include <QImage>
#include <QPainter>
#include <QSvgRenderer>
namespace AZ::Render
{
void AtomViewportDisplayIconsSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<AtomViewportDisplayIconsSystemComponent, AZ::Component>()
->Version(0)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<AtomViewportDisplayIconsSystemComponent>("Viewport Display Icons", "Provides an interface for drawing simple icons to the Editor viewport")
->ClassElement(Edit::ClassElements::EditorData, "")
->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(Edit::Attributes::AutoExpand, true)
;
}
}
}
void AtomViewportDisplayIconsSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ViewportDisplayIconsService"));
}
void AtomViewportDisplayIconsSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ViewportDisplayIconsService"));
}
void AtomViewportDisplayIconsSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("RPISystem", 0xf2add773));
required.push_back(AZ_CRC("AtomBridgeService", 0xdb816a99));
}
void AtomViewportDisplayIconsSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
}
void AtomViewportDisplayIconsSystemComponent::Activate()
{
AzToolsFramework::EditorViewportIconDisplay::Register(this);
Bootstrap::NotificationBus::Handler::BusConnect();
}
void AtomViewportDisplayIconsSystemComponent::Deactivate()
{
Bootstrap::NotificationBus::Handler::BusDisconnect();
auto perViewportDynamicDrawInterface = AtomBridge::PerViewportDynamicDraw::Get();
if (!perViewportDynamicDrawInterface)
{
return;
}
if (perViewportDynamicDrawInterface)
{
perViewportDynamicDrawInterface->UnregisterDynamicDrawContext(m_drawContextName);
}
AzToolsFramework::EditorViewportIconDisplay::Unregister(this);
}
void AtomViewportDisplayIconsSystemComponent::DrawIcon(const DrawParameters& drawParameters)
{
// Ensure we have a valid viewport context & dynamic draw interface
auto viewportContext = RPI::ViewportContextRequests::Get()->GetViewportContextById(drawParameters.m_viewport);
if (viewportContext == nullptr)
{
return;
}
auto perViewportDynamicDrawInterface =
AtomBridge::PerViewportDynamicDraw::Get();
if (!perViewportDynamicDrawInterface)
{
return;
}
RHI::Ptr<RPI::DynamicDrawContext> dynamicDraw =
perViewportDynamicDrawInterface->GetDynamicDrawContextForViewport(m_drawContextName, drawParameters.m_viewport);
if (dynamicDraw == nullptr)
{
return;
}
// Find our icon, falling back on a grey placeholder if its image is unavailable
AZ::Data::Instance<AZ::RPI::Image> image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::Grey);
if (auto iconIt = m_iconData.find(drawParameters.m_icon); iconIt != m_iconData.end())
{
auto& iconData = iconIt->second;
if (iconData.m_image)
{
image = iconData.m_image;
}
}
else
{
return;
}
// Initialize our shader
auto viewportSize = viewportContext->GetViewportSize();
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
drawSrg->SetConstant(m_viewportSizeIndex, AZ::Vector2(aznumeric_cast<float>(viewportSize.m_width), aznumeric_cast<float>(viewportSize.m_height)));
drawSrg->SetImageView(m_textureParameterIndex, image->GetImageView());
drawSrg->Compile();
AZ::Vector3 screenPosition;
if (drawParameters.m_positionSpace == CoordinateSpace::ScreenSpace)
{
screenPosition = drawParameters.m_position;
}
else if (drawParameters.m_positionSpace == CoordinateSpace::WorldSpace)
{
using ViewportRequestBus = AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus;
AzFramework::ScreenPoint position;
ViewportRequestBus::EventResult(position, drawParameters.m_viewport, &ViewportRequestBus::Events::ViewportWorldToScreen, drawParameters.m_position);
screenPosition.SetX(aznumeric_cast<float>(position.m_x));
screenPosition.SetY(aznumeric_cast<float>(position.m_y));
}
struct Vertex
{
float m_position[3];
AZ::u32 m_color;
float m_uv[2];
};
using Indice = AZ::u16;
// Create a vertex offset from the position to draw from based on the icon size
// Vertex positions are in screen space coordinates
auto createVertex = [&](float offsetX, float offsetY, float u, float v) -> Vertex
{
Vertex vertex;
screenPosition.StoreToFloat3(vertex.m_position);
vertex.m_position[0] += offsetX * drawParameters.m_size.GetX();
vertex.m_position[1] += offsetY * drawParameters.m_size.GetY();
vertex.m_color = drawParameters.m_color.ToU32();
vertex.m_uv[0] = u;
vertex.m_uv[1] = v;
return vertex;
};
AZStd::array<Vertex, 4> vertices = {
createVertex(-0.5f, -0.5f, 0.f, 0.f),
createVertex(0.5f, -0.5f, 1.f, 0.f),
createVertex(0.5f, 0.5f, 1.f, 1.f),
createVertex(-0.5f, 0.5f, 0.f, 1.f)
};
AZStd::array<Indice, 6> indices = {0, 1, 2, 0, 2, 3};
dynamicDraw->DrawIndexed(&vertices, vertices.size(), &indices, indices.size(), RHI::IndexFormat::Uint16, drawSrg);
}
QString AtomViewportDisplayIconsSystemComponent::FindAssetPath(const QString& sourceRelativePath) const
{
bool found = false;
AZStd::vector<AZStd::string> scanFolders;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
found, &AzToolsFramework::AssetSystemRequestBus::Events::GetScanFolders, scanFolders);
if (!found)
{
AZ_Error("AtomViewportDisplayIconSystemComponent", false, "Failed to load asset scan folders");
return QString();
}
for (const auto& folder : scanFolders)
{
QDir dir(folder.data());
if (dir.exists(sourceRelativePath))
{
return dir.absoluteFilePath(sourceRelativePath);
}
}
return QString();
}
QImage AtomViewportDisplayIconsSystemComponent::RenderSvgToImage(const QString& svgPath) const
{
// Set up our SVG renderer
QSvgRenderer renderer(svgPath);
renderer.setAspectRatioMode(Qt::KeepAspectRatio);
// Set up our target image
QSize size = renderer.defaultSize().expandedTo(MinimumRenderedSvgSize);
QImage image(size, QtImageFormat);
image.fill(0x00000000);
// Render the SVG
QPainter painter(&image);
renderer.render(&painter);
return image;
}
AZ::Data::Instance<AZ::RPI::Image> AtomViewportDisplayIconsSystemComponent::ConvertToAtomImage(AZ::Uuid assetId, QImage image) const
{
// Ensure our image is in the correct pixel format so we can memcpy it to our renderer image
image.convertTo(QtImageFormat);
Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
return RPI::StreamingImage::CreateFromCpuData(
*streamingImagePool.get(),
RHI::ImageDimension::Image2D,
RHI::Size(image.width(), image.height(), 1),
RHI::Format::R8G8B8A8_UNORM_SRGB,
image.bits(),
image.sizeInBytes(),
assetId);
}
AzToolsFramework::EditorViewportIconDisplayInterface::IconId AtomViewportDisplayIconsSystemComponent::GetOrLoadIconForPath(
AZStd::string_view path)
{
AZ_Error(
"AtomViewportDisplayIconsSystemComponent", AzFramework::StringFunc::Path::IsRelative(path.data()),
"GetOrLoadIconForPath assumes that it will always be given a relative path, but got '%s'", path.data());
// Check our cache to see if the image is already loaded
auto existingEntryIt = AZStd::find_if(m_iconData.begin(), m_iconData.end(), [&path](const auto& iconData)
{
return iconData.second.m_path == path;
});
if (existingEntryIt != m_iconData.end())
{
return existingEntryIt->first;
}
AZ::Uuid assetId = AZ::Uuid::CreateName(path.data());
// Find the asset to load on disk
QString assetPath = FindAssetPath(path.data());
if (assetPath.isEmpty())
{
AZ_Error("AtomViewportDisplayIconSystemComponent", false, "Failed to locate icon on disk: \"%s\"", path.data());
return InvalidIconId;
}
QImage loadedImage;
AZStd::string extension;
AzFramework::StringFunc::Path::GetExtension(path.data(), extension, false);
// For SVGs, we need to actually rasterize to an image
if (extension == "svg")
{
loadedImage = RenderSvgToImage(assetPath);
}
// For everything else, we can just load it through QImage via its image plugins
else
{
const bool loaded = loadedImage.load(assetPath);
if (!loaded)
{
AZ_Error("AtomViewportDisplayIconSystemComponent", false, "Failed to load icon: \"%s\"", assetPath.toUtf8().constData());
return InvalidIconId;
}
}
// Cache our loaded icon
IconId id = m_currentId++;
IconData& iconData = m_iconData[id];
iconData.m_path = path;
iconData.m_image = ConvertToAtomImage(assetId, loadedImage);
return id;
}
AzToolsFramework::EditorViewportIconDisplayInterface::IconLoadStatus AtomViewportDisplayIconsSystemComponent::GetIconLoadStatus(
IconId icon)
{
auto iconIt = m_iconData.find(icon);
if (iconIt == m_iconData.end())
{
return IconLoadStatus::Unloaded;
}
if (iconIt->second.m_image)
{
return IconLoadStatus::Loaded;
}
return IconLoadStatus::Error;
}
void AtomViewportDisplayIconsSystemComponent::OnBootstrapSceneReady([[maybe_unused]]AZ::RPI::Scene* bootstrapScene)
{
AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext(m_drawContextName, [](RPI::Ptr<RPI::DynamicDrawContext> drawContext)
{
auto shader = RPI::LoadShader(DrawContextShaderPath);
drawContext->InitShader(shader);
drawContext->InitVertexFormat(
{{"POSITION", RHI::Format::R32G32B32_FLOAT},
{"COLOR", RHI::Format::R8G8B8A8_UNORM},
{"TEXCOORD", RHI::Format::R32G32_FLOAT}});
drawContext->EndInit();
});
}
} // namespace AZ::Render

@ -0,0 +1,82 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzToolsFramework/API/EditorViewportIconDisplayInterface.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
#include <Atom/Bootstrap/BootstrapNotificationBus.h>
#include <QImage>
#include <QSize>
#include <QString>
namespace AZ
{
class TickRequests;
namespace Render
{
class AtomViewportDisplayIconsSystemComponent
: public AZ::Component
, public AzToolsFramework::EditorViewportIconDisplayInterface
, public AZ::Render::Bootstrap::NotificationBus::Handler
{
public:
AZ_COMPONENT(AtomViewportDisplayIconsSystemComponent, "{AEC1D3E1-1D9A-437A-B4C6-CFAEE620C160}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
// AzToolsFramework::EditorViewportIconDisplayInterface overrides...
void DrawIcon(const DrawParameters& drawParameters) override;
IconId GetOrLoadIconForPath(AZStd::string_view path) override;
IconLoadStatus GetIconLoadStatus(IconId icon) override;
// AZ::Render::Bootstrap::NotificationBus::Handler overrides...
void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override;
private:
static constexpr const char* DrawContextShaderPath = "Shaders/TexturedIcon.azshader";
static constexpr QSize MinimumRenderedSvgSize = QSize(128, 128);
static constexpr QImage::Format QtImageFormat = QImage::Format_RGBA8888;
QString FindAssetPath(const QString& sourceRelativePath) const;
QImage RenderSvgToImage(const QString& svgPath) const;
AZ::Data::Instance<AZ::RPI::Image> ConvertToAtomImage(AZ::Uuid assetId, QImage image) const;
Name m_drawContextName = Name("ViewportIconDisplay");
bool m_shaderIndexesInitialized = false;
RHI::ShaderInputNameIndex m_textureParameterIndex = "m_texture";
RHI::ShaderInputNameIndex m_viewportSizeIndex = "m_viewportSize";
struct IconData
{
AZStd::string m_path;
AZ::Data::Instance<AZ::RPI::Image> m_image = nullptr;
};
AZStd::unordered_map<IconId, IconData> m_iconData;
IconId m_currentId = 0;
};
} // namespace Render
} // namespace AZ

@ -0,0 +1,51 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Module/Module.h>
#include "AtomViewportDisplayIconsSystemComponent.h"
namespace AZ
{
namespace Render
{
class AtomViewportDisplayInfoModule
: public AZ::Module
{
public:
AZ_RTTI(AtomViewportDisplayInfoModule, "{8D72F14E-958D-4225-B3BC-C5C87BDDD426}", AZ::Module);
AZ_CLASS_ALLOCATOR(AtomViewportDisplayInfoModule, AZ::SystemAllocator, 0);
AtomViewportDisplayInfoModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
AtomViewportDisplayIconsSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<AtomViewportDisplayIconsSystemComponent>(),
};
}
};
} // namespace Render
} // namespace AZ
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AtomViewportDisplayInfo, AZ::Render::AtomViewportDisplayInfoModule)

@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/AtomViewportDisplayIconsSystemComponent.cpp
Source/AtomViewportDisplayIconsSystemComponent.h
Source/Module.cpp
)

@ -1,12 +0,0 @@
{
"gem_name": "AtomLyIntegration_AtomViewportDisplayInfo",
"display_name": "Atom Viewport Display Info Overlay",
"summary": "Provides a diagnostic viewport overlay for the default O3DE Atom viewport.",
"canonical_tags": [
"Gem"
],
"user_tags": [
"AtomLyIntegration",
"AtomViewportDisplayInfo"
]
}

@ -17,3 +17,4 @@ add_subdirectory(AtomFont)
add_subdirectory(TechnicalArt)
add_subdirectory(AtomBridge)
add_subdirectory(AtomViewportDisplayInfo)
add_subdirectory(AtomViewportDisplayIcons)

Loading…
Cancel
Save