Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi pipeline support #6

Open
wants to merge 4 commits into
base: mp/build_linux
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 107 additions & 58 deletions Gem/Code/Source/Renderer/CloudscapeFeatureProcessor.cpp
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
/*
* Copyright (c) Galib Arrieta (aka lumbermixalot@github, aka galibzon@github).
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
* Copyright (c) Galib Arrieta (aka lumbermixalot@github, aka galibzon@github).
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/

#include <AzCore/Name/Name.h>
#include <AzCore/Name/NameDictionary.h>

#include <Atom/RHI/DrawPacketBuilder.h>
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
#include <Atom/RHI/DrawPacketBuilder.h>

#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/RenderPipeline.h>

#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h> // FIXME: Try removing
// #include <Atom/RPI.Reflect/Asset/AssetUtils.h> // FIXME: Try removing

#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/ViewportContext.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>

#include <Renderer/Passes/CloudscapeComputePass.h>
#include <Renderer/Passes/CloudscapeRenderPass.h>
#include <sys/types.h>
// #include <Renderer/Passes/DepthBufferCopyPass.h>
#include "Atom/RPI.Public/Pass/PassAttachment.h"
#include "CloudscapeFeatureProcessor.h"

namespace VolumetricClouds
Expand All @@ -35,9 +38,7 @@ namespace VolumetricClouds
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->Class<CloudscapeFeatureProcessor, AZ::RPI::FeatureProcessor>()
->Version(1);
serializeContext->Class<CloudscapeFeatureProcessor, AZ::RPI::FeatureProcessor>()->Version(1);
}
}

Expand All @@ -50,127 +51,175 @@ namespace VolumetricClouds

void CloudscapeFeatureProcessor::Deactivate()
{
if (m_cloudscapeComputePass)
{
// This is necessary to avoid pesky error messages of invalid attachments when
// the feature processor is being destroyed.
m_cloudscapeComputePass->QueueForRemoval();
m_cloudscapeReprojectionPass->QueueForRemoval();
m_cloudscapeRenderPass->QueueForRemoval();
//m_depthBufferCopyPass->QueueForRemoval();
}

DisableSceneNotification();
m_viewportSize = { 0,0 };
m_viewportSize = { 0, 0 };
m_viewToIndexMap.clear();
m_renderPipelineToIndexMap.clear();
m_cloudscapeComputePasses.clear();
m_cloudscapeReprojectionPasses.clear();
m_cloudscapeRenderPasses.clear();
}

void CloudscapeFeatureProcessor::Simulate(const SimulatePacket&)
void CloudscapeFeatureProcessor::Render(const RenderPacket& renderPacket)
{
if (m_cloudscapeComputePass)
// Update the frame counter for the compute and render passes if required.
for (const auto& renderPipeline : m_renderPipelineToIndexMap)
{
m_cloudscapeComputePass->UpdateFrameCounter(m_frameCounter);

const auto& passSrg = m_cloudscapeReprojectionPass->GetShaderResourceGroup();
const uint32_t pixelIndex4x4 = m_frameCounter % 16;
passSrg->SetConstant(m_pixelIndex4x4Index, pixelIndex4x4);

m_cloudscapeRenderPass->UpdateFrameCounter(m_frameCounter);

m_frameCounter++;
const auto& [pipeline, index] = renderPipeline;
if (pipeline)
{
if (pipeline->NeedsRender())
{
if (m_cloudscapeComputePasses[index])
{
uint32_t pixelIndex = m_cloudscapeComputePasses[index]->GetPixelIndex();
pixelIndex++;
m_cloudscapeComputePasses[index]->UpdateFrameCounter(pixelIndex);

const auto& passSrg = m_cloudscapeReprojectionPasses[index]->GetShaderResourceGroup();
const uint32_t pixelIndex4x4 = pixelIndex % 16;
passSrg->SetConstant(m_pixelIndex4x4Index, pixelIndex4x4);

m_cloudscapeRenderPasses[index]->UpdateFrameCounter(pixelIndex);
}
}
}
}
}

void CloudscapeFeatureProcessor::AddRenderPasses([[maybe_unused]] AZ::RPI::RenderPipeline* renderPipeline)
{
// Extract the image size from the first attachment of the render pipeline.
auto renderRootPass = renderPipeline->GetRootPass();
if (!renderRootPass)
{
return;
}
AZ::RPI::PassAttachmentBinding rootBinding;
if (renderRootPass->GetInputOutputCount() != 0)
{
rootBinding = renderRootPass->GetInputOutputBinding(0);
}
else if (renderRootPass->GetOutputCount() != 0)
{
rootBinding = renderRootPass->GetOutputBinding(0);
}
else
{
return;
}

auto rootAttachment = rootBinding.GetAttachment();
if (!rootAttachment)
{
return;
}

if (rootAttachment->GetAttachmentType() != AZ::RHI::AttachmentType::Image)
{
return;
}
auto attachmentImageDesc = rootAttachment->GetTransientImageDescriptor();
[[maybe_unused]] auto imageSize = attachmentImageDesc.m_imageDescriptor.m_size;

uint32_t width = imageSize.m_width;
uint32_t height = imageSize.m_height;

// Add render pipeline to the map to check later if the frame counter needs to be updated.
m_renderPipelineToIndexMap[renderPipeline] = m_cloudscapeComputePasses.size();

// Create a pair of output textures used for the modified pipeline..
m_cloudOutputPairs.push_back({ CreateCloudscapeOutputAttachment(AZ::Name("CloudscapeOutput0"), { width, height }),
CreateCloudscapeOutputAttachment(AZ::Name("CloudscapeOutput1"), { width, height }) });

// Create a index value for the passes.
const uint32_t passIndex = m_cloudscapeComputePasses.size();

// Get the pass requests to create passes from the asset
AddPassRequestToRenderPipeline(renderPipeline, "Passes/CloudscapeComputePassRequest.azasset", "DepthPrePass", false /*before*/);
// Hold a reference to the compute pass
{
const auto passName = AZ::Name("CloudscapeComputePass");
AZ::RPI::PassFilter passFilter = AZ::RPI::PassFilter::CreateWithPassName(passName, renderPipeline);
AZ::RPI::Pass* existingPass = AZ::RPI::PassSystemInterface::Get()->FindFirstPass(passFilter);
m_cloudscapeComputePass = azrtti_cast<CloudscapeComputePass*>(existingPass);
if (!m_cloudscapeComputePass)
m_cloudscapeComputePasses.push_back(azrtti_cast<CloudscapeComputePass*>(existingPass));

if (!m_cloudscapeComputePasses.back())
{
AZ_Error(LogName, false, "%s Failed to find as RenderPass: %s", __FUNCTION__, passName.GetCStr());
return;
}

m_cloudscapeComputePasses.back()->SetPassIndex(passIndex);

if (m_shaderConstantData)
{
m_cloudscapeComputePass->UpdateShaderConstantData(*m_shaderConstantData);
m_cloudscapeComputePasses.back()->UpdateShaderConstantData(*m_shaderConstantData);
}
}

AddPassRequestToRenderPipeline(renderPipeline, "Passes/CloudscapeReprojectionComputePassRequest.azasset", "MotionVectorPass", false /*before*/);
AddPassRequestToRenderPipeline(
renderPipeline, "Passes/CloudscapeReprojectionComputePassRequest.azasset", "MotionVectorPass", false /*before*/);
// Hold a reference to the compute pass
{
const auto passName = AZ::Name("CloudscapeReprojectionComputePass");
AZ::RPI::PassFilter passFilter = AZ::RPI::PassFilter::CreateWithPassName(passName, renderPipeline);
AZ::RPI::Pass* existingPass = AZ::RPI::PassSystemInterface::Get()->FindFirstPass(passFilter);
m_cloudscapeReprojectionPass = azrtti_cast<AZ::RPI::ComputePass*>(existingPass);
if (!m_cloudscapeReprojectionPass)
m_cloudscapeReprojectionPasses.push_back(azrtti_cast<AZ::RPI::ComputePass*>(existingPass));
if (!m_cloudscapeReprojectionPasses.back())
{
AZ_Error(LogName, false, "%s Failed to find as RenderPass: %s", __FUNCTION__, passName.GetCStr());
return;
}
m_cloudscapeReprojectionPass->SetTargetThreadCounts(m_viewportSize.m_width, m_viewportSize.m_height, 1);
m_cloudscapeReprojectionPasses.back()->SetTargetThreadCounts(width, height, 1);
}


AddPassRequestToRenderPipeline(renderPipeline, "Passes/CloudscapeRenderPassRequest.azasset", "TransparentPass", true /*before*/);
// Hold a reference to the render pass
{
const auto passName = AZ::Name("CloudscapeRenderPass");
AZ::RPI::PassFilter passFilter = AZ::RPI::PassFilter::CreateWithPassName(passName, renderPipeline);
AZ::RPI::Pass* existingPass = AZ::RPI::PassSystemInterface::Get()->FindFirstPass(passFilter);
m_cloudscapeRenderPass = azrtti_cast<CloudscapeRenderPass*>(existingPass);
if (!m_cloudscapeRenderPass)
m_cloudscapeRenderPasses.push_back(azrtti_cast<CloudscapeRenderPass*>(existingPass));
if (!m_cloudscapeRenderPasses.back())
{
AZ_Error(LogName, false, "%s Failed to find as RenderPass: %s", __FUNCTION__, passName.GetCStr());
return;
}
}

}

//! AZ::RPI::FeatureProcessor overrides END ...
/////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////
//! Functions called by CloudscapeComponentController START
void CloudscapeFeatureProcessor::UpdateShaderConstantData(const CloudscapeShaderConstantData& shaderData)
{
m_shaderConstantData = &shaderData;
if (m_cloudscapeComputePass)
for (unsigned int i = 0; i < m_cloudscapeComputePasses.size(); i++)
{
m_cloudscapeComputePass->UpdateShaderConstantData(shaderData);
if (m_cloudscapeComputePasses[i])
{
m_cloudscapeComputePasses[i]->UpdateShaderConstantData(shaderData);
}
}
}

//! Functions called by CloudscapeComponentController END
/////////////////////////////////////////////////////////////////////


void CloudscapeFeatureProcessor::ActivateInternal()
{
auto viewportContextInterface = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
auto viewportContext = viewportContextInterface->GetViewportContextByScene(GetParentScene());
m_viewportSize = viewportContext->GetViewportSize();

m_cloudOutput0 = CreateCloudscapeOutputAttachment(AZ::Name("CloudscapeOutput0"), m_viewportSize);
AZ_Assert(!!m_cloudOutput0, "Failed to create CloudscapeOutput0");
m_cloudOutput1 = CreateCloudscapeOutputAttachment(AZ::Name("CloudscapeOutput1"), m_viewportSize);
AZ_Assert(!!m_cloudOutput1, "Failed to create CloudscapeOutput1");

DisableSceneNotification();
EnableSceneNotification();
}


AZ::Data::Instance<AZ::RPI::AttachmentImage> CloudscapeFeatureProcessor::CreateCloudscapeOutputAttachment(const AZ::Name& attachmentName
, const AzFramework::WindowSize attachmentSize) const
AZ::Data::Instance<AZ::RPI::AttachmentImage> CloudscapeFeatureProcessor::CreateCloudscapeOutputAttachment(
const AZ::Name& attachmentName, const AzFramework::WindowSize attachmentSize) const
{
AZ::RHI::ImageDescriptor imageDesc = AZ::RHI::ImageDescriptor::Create2D(
AZ::RHI::ImageBindFlags::ShaderReadWrite, attachmentSize.m_width, attachmentSize.m_height, AZ::RHI::Format::R8G8B8A8_UNORM);
Expand Down
36 changes: 20 additions & 16 deletions Gem/Code/Source/Renderer/CloudscapeFeatureProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@

#pragma once

#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/vector.h>

#include <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Public/Buffer/Buffer.h>
#include <Atom/RPI.Public/FeatureProcessor.h>
#include <Atom/RPI.Public/Pass/ComputePass.h>
#include <Atom/RPI.Public/PipelineState.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>

#include <Renderer/CloudTexturePresentationData.h>
#include <Renderer/CloudscapeShaderConstantData.h>
Expand Down Expand Up @@ -58,49 +61,50 @@ namespace VolumetricClouds
const AZ::Name& attachmentName, const AzFramework::WindowSize attachmentSize) const;

// Call by the passes owned by this feature processor.
AZ::Data::Instance<AZ::RPI::AttachmentImage> GetOutput0ImageAttachment()
AZ::Data::Instance<AZ::RPI::AttachmentImage> GetOutput0ImageAttachment(unsigned int index)
{
return m_cloudOutput0;
return m_cloudOutputPairs[index].first;
}
AZ::Data::Instance<AZ::RPI::AttachmentImage> GetOutput1ImageAttachment()
AZ::Data::Instance<AZ::RPI::AttachmentImage> GetOutput1ImageAttachment(unsigned int index)
{
return m_cloudOutput1;
return m_cloudOutputPairs[index].second;
}

//////////////////////////////////////////////////////////////////
//! AZ::RPI::FeatureProcessor overrides START...
void Activate() override;
void Deactivate() override;
void Simulate(const SimulatePacket&) override;
void Render(const RenderPacket& renderPacket) override;
void AddRenderPasses(AZ::RPI::RenderPipeline* renderPipeline) override;
//! AZ::RPI::FeatureProcessor overrides END ...
///////////////////////////////////////////////////////////////////

static constexpr const char* FeatureProcessorName = "CloudscapeFeatureProcessor";

// There are two fullscreen sized "render attachments" for the cloudscape.
// These attachments exist per render pipeline.
// They are owned by this feature processor.
// Each frame one of the attachments is the current attachment and the other
// represents the previous frame. This means that for all the passes involved
// in cloudscape rendering these attachments become "Imported" attachments.
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_cloudOutput0;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_cloudOutput1;

AZStd::vector<AZStd::pair<AZ::Data::Instance<AZ::RPI::AttachmentImage>, AZ::Data::Instance<AZ::RPI::AttachmentImage>>>
m_cloudOutputPairs;

// We need a copy of the previous frame depth buffer, because we reproject 15/16 pixels each frame.
// This causes visible artifacts at the borders of moving objects. The solution is that if
// in the current frame a pixel is one of those non-raymarched pixels, and it is visible now, but was not visible
// in the previous frame then we can choose to ray march it, or interpolate it.
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_previousFrameDepthBuffer;

// We keep track of the number of rendered frames so we can do the modulo 16 and pass
// the counter to the Cloudscape passes so they know who is the current frame and who is the
// previous frame.
uint32_t m_frameCounter = 0;

// The passes managed by this feature processor.
CloudscapeComputePass* m_cloudscapeComputePass = nullptr;
AZ::RPI::ComputePass* m_cloudscapeReprojectionPass = nullptr;
CloudscapeRenderPass* m_cloudscapeRenderPass = nullptr;
AZStd::vector<CloudscapeComputePass*> m_cloudscapeComputePasses;
AZStd::vector<AZ::RPI::ComputePass*> m_cloudscapeReprojectionPasses;
AZStd::vector<CloudscapeRenderPass*> m_cloudscapeRenderPasses;

// View to pass index map used when updating the pixel index of the passes.
AZStd::map<AZ::RPI::ViewPtr, uint32_t> m_viewToIndexMap;
AZStd::map<AZ::RPI::RenderPipeline*, uint32_t> m_renderPipelineToIndexMap;

// Shader constants for m_cloudscapeReprojectionPass
AZ::RHI::ShaderInputNameIndex m_pixelIndex4x4Index = "m_pixelIndex4x4";
Expand Down
Loading