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 basismain
parent
dc0f1ff0b1
commit
d1863c6c5b
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue