Files
o3de/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp
T
Qing Tao 9e0756f3c1 ATOM-16656 PassTree tool: ParentPass image attachment preview doesn't work (#5032)
Move imageAttachmentCopy instance from RenderPass to Pass so it can support preview image for all passes but not only for RenderPass.
Fixed an issue with image attachment preview when switching render pipeline with attachment preview on.

Signed-off-by: Qing Tao <qingtao@amazon.com>
2021-10-27 11:28:21 -07:00

495 lines
20 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/RHI/CommandList.h>
#include <Atom/RHI/FrameScheduler.h>
#include <Atom/RHI.Reflect/RenderAttachmentLayoutBuilder.h>
#include <Atom/RPI.Public/Buffer/BufferSystemInterface.h>
#include <Atom/RPI.Public/Buffer/Buffer.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Pass/AttachmentReadback.h>
#include <Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h>
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
namespace AZ
{
namespace RPI
{
void ImageAttachmentCopy::SetImageAttachment(RHI::AttachmentId srcAttachmentId, RHI::AttachmentId destAttachmentId)
{
m_srcAttachmentId = srcAttachmentId;
m_destAttachmentId = destAttachmentId;
// Use the unique destination attachment id as scope id
SetScopeId(m_destAttachmentId);
// Clear the previous attachment and copy item
m_copyItem = {};
m_destImage = nullptr;
}
void ImageAttachmentCopy::Reset()
{
m_srcAttachmentId = RHI::AttachmentId{};
m_destAttachmentId = RHI::AttachmentId{};
m_copyItem = {};
m_destImage = nullptr;
}
void ImageAttachmentCopy::InvalidateDestImage()
{
m_destImage = nullptr;
}
void ImageAttachmentCopy::FrameBegin(Pass::FramePrepareParams params)
{
if (m_srcAttachmentId.IsEmpty())
{
return;
}
// Return if the source attachment is not imported
RHI::FrameGraphAttachmentInterface attachmentDatabase = params.m_frameGraphBuilder->GetAttachmentDatabase();
if (!attachmentDatabase.IsAttachmentValid(m_srcAttachmentId))
{
Reset();
return;
}
if (!m_destImage)
{
Data::Instance<AttachmentImagePool> pool = ImageSystemInterface::Get()->GetSystemAttachmentPool();
RHI::ImageDescriptor imageDesc = attachmentDatabase.GetImageDescriptor(m_srcAttachmentId);
// add read flag since the image will always be read by ImageAttachmentPreviewPass
imageDesc.m_bindFlags |= RHI::ImageBindFlags::ShaderRead;
m_destImage = AttachmentImage::Create(*pool.get(), imageDesc, m_srcAttachmentId, nullptr, nullptr);
}
if (!m_destImage)
{
AZ_Warning("ImageAttachmentCopy", false, "Failed to create a copy to preview attachment [%s]", m_srcAttachmentId.GetCStr());
Reset();
return;
}
// Import this scope producer
params.m_frameGraphBuilder->ImportScopeProducer(*this);
attachmentDatabase.ImportImage(m_destAttachmentId, m_destImage->GetRHIImage());
}
void ImageAttachmentCopy::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph)
{
RHI::ImageScopeAttachmentDescriptor srcDescriptor{ m_srcAttachmentId };
frameGraph.UseCopyAttachment(srcDescriptor, RHI::ScopeAttachmentAccess::Read);
RHI::ImageScopeAttachmentDescriptor destDescriptor{ m_destAttachmentId };
frameGraph.UseCopyAttachment(destDescriptor, RHI::ScopeAttachmentAccess::Write);
frameGraph.SetEstimatedItemCount(1);
}
void ImageAttachmentCopy::CompileResources(const RHI::FrameGraphCompileContext& context)
{
// copy descriptor for copying image
RHI::CopyImageDescriptor copyImage;
const AZ::RHI::Image* image = context.GetImage(m_srcAttachmentId);
copyImage.m_sourceImage = image;
copyImage.m_sourceSize = image->GetDescriptor().m_size;
copyImage.m_destinationImage = context.GetImage(m_destAttachmentId);
m_copyItem = copyImage;
}
void ImageAttachmentCopy::BuildCommandList(const RHI::FrameGraphExecuteContext& context)
{
context.GetCommandList()->Submit(m_copyItem);
}
Ptr<ImageAttachmentPreviewPass> ImageAttachmentPreviewPass::Create(const PassDescriptor& descriptor)
{
Ptr<ImageAttachmentPreviewPass> imageAttachmentPreviewPass = aznew ImageAttachmentPreviewPass(descriptor);
return imageAttachmentPreviewPass;
}
ImageAttachmentPreviewPass::ImageAttachmentPreviewPass(const PassDescriptor& descriptor)
: Pass(descriptor)
{
SetScopeId(RHI::ScopeId(GetPathName()));
}
ImageAttachmentPreviewPass::~ImageAttachmentPreviewPass()
{
Data::AssetBus::Handler::BusDisconnect();
}
void ImageAttachmentPreviewPass::PreviewImageAttachmentForPass(Pass* pass, const PassAttachment* passAttachment)
{
if (passAttachment->GetAttachmentType() != RHI::AttachmentType::Image)
{
return;
}
ClearPreviewAttachment();
// find the attachment in pass's attachment binding
uint32_t bindingIndex = 0;
for (auto& binding : pass->GetAttachmentBindings())
{
if (passAttachment == binding.m_attachment)
{
RHI::AttachmentId attachmentId = binding.m_attachment->GetAttachmentId();
// Append slot index and pass name so the read back's name won't be same as the attachment used in other passes.
AZStd::string readbackName = AZStd::string::format("%s_%d_%s", attachmentId.GetCStr(),
bindingIndex, GetName().GetCStr());
m_attachmentCopy = AZStd::make_shared<AZ::RPI::ImageAttachmentCopy>();
m_attachmentCopy->SetImageAttachment(attachmentId, AZ::Name(readbackName));
pass->m_attachmentCopy = m_attachmentCopy;
break;
}
bindingIndex++;
}
if (bindingIndex == pass->GetAttachmentBindings().size())
{
AZ_Warning("RPI", false, "failed to find the attachment %s", passAttachment->GetAttachmentId().GetCStr());
return;
}
m_updateDrawData = true;
m_imageAttachmentId = m_attachmentCopy->m_destAttachmentId;
// Set the output of this pass to write to the pipeline output
if (!m_outputColorAttachment)
{
RenderPipeline* pipeline = pass->GetRenderPipeline();
if (pipeline)
{
for (const auto& binding : pipeline->GetRootPass()->GetAttachmentBindings())
{
if (binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::RenderTarget
&& binding.m_slotType == PassSlotType::Output)
{
SetOutputColorAttachment(binding.m_attachment);
}
}
}
}
}
void ImageAttachmentPreviewPass::ClearPreviewAttachment()
{
ClearDrawData();
// Allocate and release the copy scope only when there is an attachment to preview.
// So we only need a weak ptr in the RenderPass and don't need to worry about releasing.
m_attachmentCopy = nullptr;
m_imageAttachmentId = RHI::AttachmentId{};
m_updateDrawData = true;
m_outputColorAttachment = nullptr;
}
void ImageAttachmentPreviewPass::SetPreviewLocation(AZ::Vector2 position, AZ::Vector2 size, bool keepAspectRatio)
{
m_position = position;
m_size = size;
m_keepAspectRatio = keepAspectRatio;
}
void ImageAttachmentPreviewPass::ClearDrawData()
{
if (m_needsShaderLoad)
{
return;
}
// update pass srg
for (auto& previewInfo : m_imageTypePreviewInfo)
{
// unbind previous binded image views
m_passSrg->SetImageView(previewInfo.m_imageInput, nullptr, 0);
previewInfo.m_item.m_pipelineState = nullptr;
previewInfo.m_imageCount = 0;
}
m_passSrgChanged = true;
}
void ImageAttachmentPreviewPass::SetOutputColorAttachment(RHI::Ptr<PassAttachment> outputImageAttachment)
{
m_outputColorAttachment = outputImageAttachment;
m_updateDrawData = true;
}
void ImageAttachmentPreviewPass::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
{
Data::Asset<ShaderAsset> shaderAsset = { asset.GetAs<ShaderAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
if (shaderAsset)
{
m_needsShaderLoad = true;
m_updateDrawData = true;
}
}
void ImageAttachmentPreviewPass::LoadShader()
{
m_needsShaderLoad = false;
// Load Shader
const char* ShaderPath = "shader/imagepreview.azshader";
Data::Asset<ShaderAsset> shaderAsset = RPI::FindShaderAsset(ShaderPath);
m_shader = Shader::FindOrCreate(shaderAsset);
if (m_shader == nullptr)
{
AZ_Error("PassSystem", false, "[ImageAttachmentsPreviewPass]: Failed to load shader '%s'!", ShaderPath);
return;
}
// Load SRG
const auto srgLayout = m_shader->FindShaderResourceGroupLayout(SrgBindingSlot::Pass);
if (srgLayout)
{
m_passSrg = ShaderResourceGroup::Create(shaderAsset, m_shader->GetSupervariantIndex(), srgLayout->GetName());
if (!m_passSrg)
{
AZ_Error("PassSystem", false, "Failed to create SRG from shader asset '%s'", ShaderPath);
return;
}
}
// Find srg input indexes
m_imageTypePreviewInfo[static_cast<uint32_t>(ImageType::Image2d)].m_imageInput = m_passSrg->FindShaderInputImageIndex(Name("m_image"));
m_imageTypePreviewInfo[static_cast<uint32_t>(ImageType::Image2dMs)].m_imageInput = m_passSrg->FindShaderInputImageIndex(Name("m_msImage"));
// Setup initial data for pipeline state descriptors. The rest of the data will be set when the draw data is updated
// option names from the azsl file
const char* optionValues[] =
{
"ImageType::Image2d",
"ImageType::Image2dMs"
};
const char* optionName = "o_imageType";
ShaderOptionGroup shaderOption = m_shader->CreateShaderOptionGroup();
RHI::InputStreamLayout inputStreamLayout;
inputStreamLayout.SetTopology(RHI::PrimitiveTopology::TriangleStrip);
inputStreamLayout.Finalize();
RHI::RenderAttachmentLayout attachmentsLayout;
RHI::RenderAttachmentLayoutBuilder attachmentsLayoutBuilder;
attachmentsLayoutBuilder.AddSubpass()
->RenderTargetAttachment(RHI::Format::R8G8B8A8_UNORM); // Set any format to avoid errors when building the layout.
attachmentsLayoutBuilder.End(attachmentsLayout);
for (uint32_t index = 0; index < static_cast<uint32_t>(ImageType::ImageTypeCount); index++)
{
ImageTypePreviewInfo& previewInfo = m_imageTypePreviewInfo[index];
auto& pipelineDesc = previewInfo.m_pipelineStateDescriptor;
shaderOption.SetValue(AZ::Name(optionName), AZ::Name(optionValues[index]));
m_shader->GetVariant(shaderOption.GetShaderVariantId()).ConfigurePipelineState(pipelineDesc);
pipelineDesc.m_renderAttachmentConfiguration.m_renderAttachmentLayout = attachmentsLayout;
pipelineDesc.m_inputStreamLayout = inputStreamLayout;
previewInfo.m_shaderVariantKeyFallback = shaderOption.GetShaderVariantKeyFallbackValue();
}
Data::AssetBus::Handler::BusDisconnect();
Data::AssetBus::Handler::BusConnect(shaderAsset.GetId());
}
void ImageAttachmentPreviewPass::FrameBeginInternal(FramePrepareParams params)
{
bool scopeImported = false;
if (!m_imageAttachmentId.IsEmpty() && m_outputColorAttachment)
{
// Only import the scope if the attachment is valid
if (params.m_frameGraphBuilder->GetAttachmentDatabase().IsAttachmentValid(m_imageAttachmentId))
{
if (m_needsShaderLoad)
{
LoadShader();
}
params.m_frameGraphBuilder->ImportScopeProducer(*this);
scopeImported = true;
}
}
// If the scope is not imported, we need compile the updated pass srg here
if (m_passSrgChanged && !scopeImported)
{
m_passSrg->Compile();
m_passSrgChanged = false;
}
}
bool ImageAttachmentPreviewPass::ReadbackOutput(AZStd::shared_ptr<AttachmentReadback> readback)
{
if (m_outputColorAttachment)
{
m_readbackOption = PassAttachmentReadbackOption::Output;
m_attachmentReadback = readback;
AZStd::string readbackName = AZStd::string::format("%s_%s", m_outputColorAttachment->GetAttachmentId().GetCStr(), GetName().GetCStr());
return m_attachmentReadback->ReadPassAttachment(m_outputColorAttachment.get(), AZ::Name(readbackName));
}
return false;
}
void ImageAttachmentPreviewPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph)
{
// add attachments to the scope
// input attachment
frameGraph.UseAttachment(RHI::ImageScopeAttachmentDescriptor{ m_imageAttachmentId }, RHI::ScopeAttachmentAccess::Read, RHI::ScopeAttachmentUsage::Shader);
// output attachment
frameGraph.UseColorAttachment(RHI::ImageScopeAttachmentDescriptor{ m_outputColorAttachment->GetAttachmentId() });
frameGraph.SetEstimatedItemCount(1);
}
void ImageAttachmentPreviewPass::CompileResources(const RHI::FrameGraphCompileContext& context)
{
// setup srg data and draw item
if (m_updateDrawData)
{
m_updateDrawData = false;
// clear some old data
ClearDrawData();
ImageType imageType = ImageType::Unsupported;
float aspectRatio = 1;
// Find image type
const RHI::ImageView* inputImageView = context.GetImageView(m_imageAttachmentId);
if (!inputImageView)
{
AZ_Warning("RPI", false, "Image attachment [%s] doesn't have image view in current context", m_imageAttachmentId.GetCStr());
}
else
{
const RHI::ImageDescriptor& desc = inputImageView->GetImage().GetDescriptor();
aspectRatio = desc.m_size.m_width / static_cast<float>(desc.m_size.m_height);
if (desc.m_dimension == RHI::ImageDimension::Image2D)
{
if (desc.m_multisampleState.m_samples == 1)
{
imageType = ImageType::Image2d;
}
else if (desc.m_multisampleState.m_samples > 1)
{
imageType = ImageType::Image2dMs;
}
}
if (imageType != ImageType::Unsupported)
{
const uint32_t typeIndex = static_cast<uint32_t>(imageType);
auto& previewInfo = m_imageTypePreviewInfo[typeIndex];
m_passSrg->SetShaderVariantKeyFallbackValue(previewInfo.m_shaderVariantKeyFallback);
m_passSrg->SetImageView(previewInfo.m_imageInput, inputImageView, 0);
m_passSrgChanged = true;
previewInfo.m_imageCount = 1;
}
else
{
AZ_Warning("RPI", false, "Image attachment [%s] with format %d is not supported", m_imageAttachmentId.GetCStr(),
static_cast<uint32_t>(desc.m_format));
}
}
// config pipeline states related to output attachment
const RHI::ImageView* outputImageView = context.GetImageView(m_outputColorAttachment->GetAttachmentId());
RHI::Format outputFormat = outputImageView->GetDescriptor().m_overrideFormat;
if (outputFormat == RHI::Format::Unknown)
{
outputFormat = outputImageView->GetImage().GetDescriptor().m_format;
}
// Base viewport and scissor off of output attachment
RHI::Size targetImageSize = outputImageView->GetImage().GetDescriptor().m_size;
float width = m_size.GetX() * targetImageSize.m_width;
float height = m_size.GetY() * targetImageSize.m_height;
if (m_keepAspectRatio)
{
if (width/height > aspectRatio)
{
width = height * aspectRatio;
}
else
{
height = width / aspectRatio;
}
}
float xMin = m_position.GetX() * targetImageSize.m_width;
float yMin = m_position.GetY() * targetImageSize.m_height;
m_viewport = RHI::Viewport(xMin, xMin + width, yMin, yMin + height);
m_scissor = RHI::Scissor(0, 0, targetImageSize.m_width, targetImageSize.m_height);
// compile
if (m_passSrgChanged)
{
m_passSrg->Compile();
m_passSrgChanged = false;
}
// rebuild draw item
for (auto& previewInfo : m_imageTypePreviewInfo)
{
if (previewInfo.m_imageCount == 0)
{
continue;
}
previewInfo.m_pipelineStateDescriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_attachmentFormats[0] = outputFormat;
previewInfo.m_pipelineStateDescriptor.m_renderStates.m_multisampleState = outputImageView->GetImage().GetDescriptor().m_multisampleState;
// draw each image by using instancing
RHI::DrawLinear drawLinear;
drawLinear.m_vertexCount = 4;
drawLinear.m_instanceCount = previewInfo.m_imageCount;
previewInfo.m_item.m_arguments = RHI::DrawArguments(drawLinear);
previewInfo.m_item.m_pipelineState = m_shader->AcquirePipelineState(previewInfo.m_pipelineStateDescriptor);
}
}
}
void ImageAttachmentPreviewPass::BuildCommandList(const RHI::FrameGraphExecuteContext& context)
{
RHI::CommandList* commandList = context.GetCommandList();
commandList->SetViewport(m_viewport);
commandList->SetScissor(m_scissor);
// submit srg
commandList->SetShaderResourceGroupForDraw(*m_passSrg->GetRHIShaderResourceGroup());
// submit draw call
for (const auto& previewInfo : m_imageTypePreviewInfo)
{
if (previewInfo.m_imageCount > 0)
{
commandList->Submit(previewInfo.m_item);
}
}
}
}
}