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.
514 lines
21 KiB
C++
514 lines
21 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
|
|
InitScope(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)
|
|
{
|
|
RHI::FrameGraphAttachmentInterface attachmentDatabase = params.m_frameGraphBuilder->GetAttachmentDatabase();
|
|
|
|
if (m_srcAttachmentId.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Return if the source attachment is not imported
|
|
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)
|
|
{
|
|
InitScope(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 = Data::static_pointer_cast<ShaderAsset>(asset);
|
|
if (shaderAsset)
|
|
{
|
|
m_needsShaderLoad = true;
|
|
m_updateDrawData = true;
|
|
}
|
|
}
|
|
|
|
void ImageAttachmentPreviewPass::LoadShader()
|
|
{
|
|
m_needsShaderLoad = false;
|
|
|
|
// Load Shader
|
|
const char* ShaderPath = "shaders/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::BuildInternal()
|
|
{
|
|
m_updateDrawData = true;
|
|
}
|
|
|
|
void ImageAttachmentPreviewPass::FrameBeginInternal(FramePrepareParams params)
|
|
{
|
|
bool scopeImported = false;
|
|
if (!m_imageAttachmentId.IsEmpty() && m_outputColorAttachment)
|
|
{
|
|
// Only import the scope if the attachment is valid
|
|
auto attachmentDatabase = params.m_frameGraphBuilder->GetAttachmentDatabase();
|
|
bool isAttachmentValid = attachmentDatabase.IsAttachmentValid(m_imageAttachmentId);
|
|
if (!isAttachmentValid)
|
|
{
|
|
// Import the cached copy dest image if it exists (copied)
|
|
// So the attachment can be still previewed when the pass is disabled.
|
|
if (m_attachmentCopy && m_attachmentCopy->m_destImage)
|
|
{
|
|
attachmentDatabase.ImportImage(m_attachmentCopy->m_destAttachmentId, m_attachmentCopy->m_destImage->GetRHIImage());
|
|
isAttachmentValid = true;
|
|
}
|
|
}
|
|
|
|
if (isAttachmentValid)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|