| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- /*
- * 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 "AtomViewportDisplayIconsSystemComponent.h"
- #include <AzCore/Math/VectorConversions.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 <AzFramework/Viewport/ViewportScreen.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.Reflect/Asset/AssetUtils.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::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", 0x92d990b5));
- }
- void AtomViewportDisplayIconsSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
- {
- }
- void AtomViewportDisplayIconsSystemComponent::Activate()
- {
- m_drawContextRegistered = false;
- AzToolsFramework::EditorViewportIconDisplay::Register(this);
- Bootstrap::NotificationBus::Handler::BusConnect();
- }
- void AtomViewportDisplayIconsSystemComponent::Deactivate()
- {
- Data::AssetBus::Handler::BusDisconnect();
- Bootstrap::NotificationBus::Handler::BusDisconnect();
- auto perViewportDynamicDrawInterface = AtomBridge::PerViewportDynamicDraw::Get();
- if (!perViewportDynamicDrawInterface)
- {
- return;
- }
- if (perViewportDynamicDrawInterface && m_drawContextRegistered)
- {
- perViewportDynamicDrawInterface->UnregisterDynamicDrawContext(m_drawContextName);
- m_drawContextRegistered = false;
- }
- 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 gray 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;
- }
- const auto [viewportWidth, viewportHeight] = viewportContext->GetViewportSize();
- const auto viewportSize = AzFramework::ScreenSize(viewportWidth, viewportHeight);
- // Initialize our shader
- AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
- drawSrg->SetConstant(m_viewportSizeIndex, AzFramework::Vector2FromScreenSize(viewportSize));
- drawSrg->SetImageView(m_textureParameterIndex, image->GetImageView());
- drawSrg->Compile();
- // Scale icons by screen DPI
- float scalingFactor = 1.0f;
- {
- using ViewportRequestBus = AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus;
- ViewportRequestBus::EventResult(
- scalingFactor, drawParameters.m_viewport, &ViewportRequestBus::Events::DeviceScalingFactor);
- }
- AZ::Vector3 screenPosition;
- if (drawParameters.m_positionSpace == CoordinateSpace::ScreenSpace)
- {
- screenPosition = drawParameters.m_position;
- }
- else if (drawParameters.m_positionSpace == CoordinateSpace::WorldSpace)
- {
- // Calculate the ndc point (0.0-1.0 range) including depth
- const AZ::Vector3 ndcPoint = AzFramework::WorldToScreenNdc(
- drawParameters.m_position, viewportContext->GetCameraViewMatrixAsMatrix3x4(),
- viewportContext->GetCameraProjectionMatrix());
- // Calculate our screen space position using the viewport size
- // We want this instead of RenderViewportWidget::WorldToScreen which works in QWidget virtual coordinate space
- const AzFramework::ScreenPoint screenPoint = AzFramework::ScreenPointFromNdc(AZ::Vector3ToVector2(ndcPoint), viewportSize);
- screenPosition = AzFramework::Vector3FromScreenPoint(screenPoint, ndcPoint.GetZ());
- }
- 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() * scalingFactor;
- vertex.m_position[1] += offsetY * drawParameters.m_size.GetY() * scalingFactor;
- 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->SetSortKey(
- aznumeric_cast<int64_t>(screenPosition.GetZ() * aznumeric_cast<float>(AZStd::numeric_limits<int64_t>::max())));
- dynamicDraw->DrawIndexed(
- &vertices, static_cast<uint32_t>(vertices.size()), &indices, static_cast<uint32_t>(indices.size()), RHI::IndexFormat::Uint16,
- drawSrg);
- }
- QString AtomViewportDisplayIconsSystemComponent::FindAssetPath(const QString& path) const
- {
- // If we get an absolute path, just use it.
- QFileInfo pathInfo(path);
- if (pathInfo.isAbsolute())
- {
- return path;
- }
- 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(path))
- {
- return dir.absoluteFilePath(path);
- }
- }
- 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)
- {
- // 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)
- {
- // Queue a load for the draw context shader, and wait for it to load
- Data::Asset<RPI::ShaderAsset> shaderAsset = RPI::AssetUtils::GetAssetByProductPath<RPI::ShaderAsset>(DrawContextShaderPath, RPI::AssetUtils::TraceLevel::Assert);
- shaderAsset.QueueLoad();
- Data::AssetBus::Handler::BusConnect(shaderAsset.GetId());
- }
- void AtomViewportDisplayIconsSystemComponent::OnAssetReady(Data::Asset<Data::AssetData> asset)
- {
- // Once the shader is loaded, register it with the dynamic draw context
- Data::Asset<RPI::ShaderAsset> shaderAsset = asset;
- AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext(m_drawContextName, [shaderAsset](RPI::Ptr<RPI::DynamicDrawContext> dynamicDraw)
- {
- AZ_Assert(shaderAsset->IsReady(), "Attempting to register the AtomViewportDisplayIconsSystemComponent"
- " dynamic draw context before the shader asset is loaded. The shader should be loaded first"
- " to avoid a blocking asset load and potential deadlock, since the DynamicDrawContext lambda"
- " will be executed during scene processing and there may be multiple scenes executing in parallel.");
- Data::Instance<RPI::Shader> shader = RPI::Shader::FindOrCreate(shaderAsset);
- dynamicDraw->InitShader(shader);
- dynamicDraw->InitVertexFormat({ { "POSITION", RHI::Format::R32G32B32_FLOAT },
- { "COLOR", RHI::Format::R8G8B8A8_UNORM },
- { "TEXCOORD", RHI::Format::R32G32_FLOAT } });
- dynamicDraw->EndInit();
- });
- m_drawContextRegistered = true;
- Data::AssetBus::Handler::BusDisconnect();
- }
- } // namespace AZ::Render
|