You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/Thumbnails/MaterialThumbnailRenderer.cpp

360 lines
17 KiB
C++

/*
* 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 <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzCore/Math/MatrixUtils.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Pass/Specific/SwapChainPass.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h>
#include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
#include <Mesh/MeshComponent.h>
#include <Material/MaterialComponent.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Source/Material/Thumbnails/MaterialThumbnailRenderer.h>
#include "Atom/Feature/Utils/FrameCaptureBus.h"
#include "Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h"
namespace AZ
{
namespace LyIntegration
{
using namespace AzToolsFramework::Thumbnailer;
static constexpr const char* ModelPath = "materialeditor/viewportmodels/quadsphere.azmodel";
static constexpr const char* LightingPresetPath = "lightingpresets/default.lightingpreset.azasset";
static constexpr float AspectRatio = 1.0f;
static constexpr float NearDist = 0.1f;
static constexpr float FarDist = 100.0f;
MaterialThumbnailRenderer::MaterialThumbnailRenderer()
{
m_entityContext = AZStd::make_unique<AzFramework::EntityContext>();
m_entityContext->InitContext();
Data::AssetType materialAssetType = AzTypeInfo<RPI::MaterialAsset>::Uuid();
ThumbnailerRendererRequestBus::Handler::BusConnect(materialAssetType);
SystemTickBus::Handler::BusConnect();
m_shouldPullNextAsset = true;
}
MaterialThumbnailRenderer::~MaterialThumbnailRenderer()
{
ThumbnailerRendererRequestBus::Handler::BusDisconnect();
SystemTickBus::Handler::BusDisconnect();
AZ::Data::AssetBus::Handler::BusDisconnect();
Render::MaterialComponentNotificationBus::Handler::BusDisconnect();
m_materialAssetToRender.Release();
if (m_initialized)
{
Render::FrameCaptureNotificationBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
if (m_modelEntity)
{
AzFramework::EntityContextRequestBus::Event(m_entityContext->GetContextId(),
&AzFramework::EntityContextRequestBus::Events::DestroyEntity, m_modelEntity);
m_modelEntity = nullptr;
}
m_frameworkScene->UnsetSubsystem<RPI::Scene>();
m_scene->Deactivate();
m_scene->RemoveRenderPipeline(m_renderPipeline->GetId());
RPI::RPISystemInterface::Get()->UnregisterScene(m_scene);
bool sceneRemovedSuccessfully = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(sceneRemovedSuccessfully, &AzFramework::SceneSystemRequests::RemoveScene, m_sceneName);
m_scene = nullptr;
m_renderPipeline = nullptr;
}
}
bool MaterialThumbnailRenderer::Installed() const
{
return true;
}
void MaterialThumbnailRenderer::OnSystemTick()
{
ThumbnailerRendererRequestBus::ExecuteQueuedEvents();
}
void MaterialThumbnailRenderer::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
m_deltaTime = deltaTime;
m_simulateTime = time.GetSeconds();
if (m_shouldPullNextAsset && !m_assetIdQueue.empty())
{
m_shouldPullNextAsset = false;
const auto assetId = m_assetIdQueue.front();
m_assetIdQueue.pop();
m_materialAssetToRender.Release();
AZ::Data::AssetBus::Handler::BusDisconnect();
if (assetId.IsValid())
{
m_materialAssetToRender.Create(assetId);
m_materialAssetToRender.QueueLoad();
AZ::Data::AssetBus::Handler::BusConnect(assetId);
}
}
else if (m_readyToCapture)
{
m_renderPipeline->AddToRenderTickOnce();
RPI::AttachmentReadback::CallbackFunction readbackCallback = [&](const RPI::AttachmentReadback::ReadbackResult& result)
{
uchar* data = result.m_dataBuffer.get()->data();
QImage image(data, result.m_imageDescriptor.m_size.m_width, result.m_imageDescriptor.m_size.m_height, QImage::Format_RGBA8888);
QPixmap pixmap;
pixmap.convertFromImage(image);
ThumbnailerRendererNotificationBus::Event(m_materialAssetToRender.GetId(),
&ThumbnailerRendererNotifications::ThumbnailRendered, pixmap);
};
Render::FrameCaptureNotificationBus::Handler::BusConnect();
bool startedCapture = false;
Render::FrameCaptureRequestBus::BroadcastResult(
startedCapture,
&Render::FrameCaptureRequestBus::Events::CapturePassAttachmentWithCallback,
m_passHierarchy, AZStd::string("Output"), readbackCallback);
// Reset the capture flag the capture requst was successful. Otherwise try capture it again next tick.
if (startedCapture)
{
m_readyToCapture = false;
}
}
}
void MaterialThumbnailRenderer::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
m_materialAssetToRender = asset;
AZ::Data::AssetBus::Handler::BusDisconnect();
Render::MaterialComponentRequestBus::Event(m_modelEntity->GetId(),
&Render::MaterialComponentRequestBus::Events::SetDefaultMaterialOverride,
m_materialAssetToRender.GetId());
// listen to material override finished notification.
Render::MaterialComponentNotificationBus::Handler::BusConnect(m_modelEntity->GetId());
}
void MaterialThumbnailRenderer::OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetCanceled(asset.GetId());
}
void MaterialThumbnailRenderer::OnAssetCanceled([[maybe_unused]] AZ::Data::AssetId assetId)
{
m_readyToCapture = false;
m_shouldPullNextAsset = true;
m_materialAssetToRender.Release();
AZ::Data::AssetBus::Handler::BusDisconnect();
}
void MaterialThumbnailRenderer::OnCaptureFinished([[maybe_unused]] Render::FrameCaptureResult result, [[maybe_unused]] const AZStd::string& info)
{
m_shouldPullNextAsset = true;
m_renderPipeline->RemoveFromRenderTick();
Render::FrameCaptureNotificationBus::Handler::BusDisconnect();
if (m_assetIdQueue.empty())
{
TickBus::Handler::BusDisconnect();
}
}
void MaterialThumbnailRenderer::Init()
{
using namespace Data;
// Create and register a scene with minimum required feature processors
RPI::SceneDescriptor sceneDesc;
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::TransformServiceFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::MeshFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PointLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SpotLightFeatureProcessor");
// There is currently a bug where having multiple DirectionalLightFeatureProcessors active can result in shadow flickering [ATOM-13568]
// as well as continually rebuilding MeshDrawPackets [ATOM-13633]. Lets just disable the directional light FP for now.
// Possibly re-enable with [GFX TODO][ATOM-13639]
// sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DirectionalLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DiskLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::CapsuleLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::QuadLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DecalTextureArrayFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::ImageBasedLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PostProcessFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SkyBoxFeatureProcessor");
m_scene = RPI::Scene::CreateScene(sceneDesc);
// Setup scene srg modification callback (to push per-frame values to the shaders)
RPI::ShaderResourceGroupCallback callback = [this](RPI::ShaderResourceGroup* srg)
{
if (srg == nullptr)
{
return;
}
bool needCompile = false;
RHI::ShaderInputConstantIndex timeIndex = srg->FindShaderInputConstantIndex(Name{ "m_time" });
if (timeIndex.IsValid())
{
srg->SetConstant(timeIndex, (float)m_simulateTime);
needCompile = true;
}
RHI::ShaderInputConstantIndex deltaTimeIndex = srg->FindShaderInputConstantIndex(Name{ "m_deltaTime" });
if (deltaTimeIndex.IsValid())
{
srg->SetConstant(deltaTimeIndex, m_deltaTime);
needCompile = true;
}
if (needCompile)
{
srg->Compile();
}
};
m_scene->SetShaderResourceGroupCallback(callback);
// Bind m_defaultScene to the GameEntityContext's AzFramework::Scene
Outcome<AzFramework::Scene*, AZStd::string> createSceneOutcome;
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequests::CreateScene,
m_sceneName);
AZ_Assert(createSceneOutcome, createSceneOutcome.GetError().c_str()); // This should never happen unless scene creation has changed.
createSceneOutcome.GetValue()->SetSubsystem(m_scene.get());
m_frameworkScene = createSceneOutcome.GetValue();
m_frameworkScene->SetSubsystem(m_scene.get());
m_entityContext->InitContext();
bool success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequests::SetSceneForEntityContextId,
m_entityContext->GetContextId(), m_frameworkScene);
AZ_Assert(success, "Unable to set entity context on AzFramework::Scene: %s", m_sceneName.c_str());
// Create a render pipeline from the specified asset for the window context and add the pipeline to the scene
RPI::RenderPipelineDescriptor pipelineDesc;
pipelineDesc.m_mainViewTagName = "MainCamera";
pipelineDesc.m_name = m_pipelineName;
pipelineDesc.m_rootPassTemplate = "MainPipelineRenderToTexture";
// We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue
// [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost
pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
m_renderPipeline = RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc);
m_scene->AddRenderPipeline(m_renderPipeline);
m_scene->Activate();
RPI::RPISystemInterface::Get()->RegisterScene(m_scene);
m_passHierarchy.push_back(m_pipelineName);
m_passHierarchy.push_back("CopyToSwapChain");
// Connect camera to pipeline's default view after camera entity activated
Name viewName = Name("MainCamera");
m_view = RPI::View::CreateView(viewName, RPI::View::UsageCamera);
m_transform = Transform::CreateFromQuaternionAndTranslation(Quaternion::CreateIdentity(), Vector3::CreateZero());
m_view->SetCameraTransform(Matrix3x4::CreateFromTransform(m_transform));
Matrix4x4 viewToClipMatrix;
MakePerspectiveFovMatrixRH(viewToClipMatrix,
Constants::HalfPi,
AspectRatio,
NearDist,
FarDist, true);
m_view->SetViewToClipMatrix(viewToClipMatrix);
m_renderPipeline->SetDefaultView(m_view);
// Create lighting preset
m_lightingPresetAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::AnyAsset>(LightingPresetPath);
if (m_lightingPresetAsset.IsReady())
{
auto preset = m_lightingPresetAsset->GetDataAs<AZ::Render::LightingPreset>();
if (preset)
{
auto iblFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::ImageBasedLightFeatureProcessorInterface>();
auto postProcessFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::PostProcessFeatureProcessorInterface>();
auto exposureControlSettingInterface = postProcessFeatureProcessor->GetOrCreateSettingsInterface(AZ::EntityId())->GetOrCreateExposureControlSettingsInterface();
auto directionalLightFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DirectionalLightFeatureProcessorInterface>();
auto skyboxFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
skyboxFeatureProcessor->Enable(true);
skyboxFeatureProcessor->SetSkyboxMode(AZ::Render::SkyBoxMode::Cubemap);
Camera::Configuration cameraConfig;
cameraConfig.m_fovRadians = Constants::HalfPi;
cameraConfig.m_nearClipDistance = NearDist;
cameraConfig.m_farClipDistance = FarDist;
cameraConfig.m_frustumWidth = 100.0f;
cameraConfig.m_frustumHeight = 100.0f;
AZStd::vector<AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle> lightHandles;
preset->ApplyLightingPreset(
iblFeatureProcessor,
skyboxFeatureProcessor,
exposureControlSettingInterface,
directionalLightFeatureProcessor,
cameraConfig,
lightHandles);
}
}
// Create preview model
AzFramework::EntityContextRequestBus::EventResult(m_modelEntity, m_entityContext->GetContextId(),
&AzFramework::EntityContextRequestBus::Events::CreateEntity, "PreviewModel");
m_modelEntity->CreateComponent(Render::MeshComponentTypeId);
m_modelEntity->CreateComponent(Render::MaterialComponentTypeId);
m_modelEntity->CreateComponent(azrtti_typeid<AzFramework::TransformComponent>());
m_modelEntity->Init();
m_modelEntity->Activate();
TransformBus::Event(m_modelEntity->GetId(), &TransformBus::Events::SetLocalTM,
Transform::CreateTranslation(Vector3(0, 0.8f, -0.5f)));
Render::MeshComponentRequestBus::Event(m_modelEntity->GetId(), &Render::MeshComponentRequestBus::Events::SetModelAssetPath, ModelPath);
}
void MaterialThumbnailRenderer::RenderThumbnail(Data::AssetId assetId, [[maybe_unused]] int thumbnailSize)
{
if (!m_initialized)
{
Init();
m_initialized = true;
}
if (m_assetIdQueue.empty())
{
m_shouldPullNextAsset = true;
TickBus::Handler::BusConnect();
}
m_assetIdQueue.emplace(assetId);
}
void MaterialThumbnailRenderer::OnMaterialsUpdated([[maybe_unused]] const Render::MaterialAssignmentMap& materials)
{
m_readyToCapture = true;
Render::MaterialComponentNotificationBus::Handler::BusDisconnect();
}
} // namespace LyIntegration
} // namespace AZ