TaaPass.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/RHI.Reflect/Format.h>
  9. #include <PostProcessing/TaaPass.h>
  10. #include <AzCore/Math/Random.h>
  11. #include <Atom/RPI.Public/Image/AttachmentImagePool.h>
  12. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  13. #include <Atom/RPI.Public/Pass/PassUtils.h>
  14. #include <Atom/RPI.Public/RenderPipeline.h>
  15. #include <Atom/RPI.Public/View.h>
  16. #include <Atom/RPI.Reflect/Pass/PassName.h>
  17. namespace AZ::Render
  18. {
  19. RPI::Ptr<TaaPass> TaaPass::Create(const RPI::PassDescriptor& descriptor)
  20. {
  21. RPI::Ptr<TaaPass> pass = aznew TaaPass(descriptor);
  22. return pass;
  23. }
  24. TaaPass::TaaPass(const RPI::PassDescriptor& descriptor)
  25. : Base(descriptor)
  26. {
  27. uint32_t numJitterPositions = 8;
  28. const TaaPassData* taaPassData = RPI::PassUtils::GetPassData<TaaPassData>(descriptor);
  29. if (taaPassData)
  30. {
  31. numJitterPositions = taaPassData->m_numJitterPositions;
  32. }
  33. // The coprimes 2, 3 are commonly used for halton sequences because they have an even distribution even for
  34. // few samples. With larger primes you need to offset by some amount between each prime to have the same
  35. // effect. We could allow this to be configurable in the future.
  36. SetupSubPixelOffsets(2, 3, numJitterPositions);
  37. }
  38. void TaaPass::CompileResources(const RHI::FrameGraphCompileContext& context)
  39. {
  40. struct TaaConstants
  41. {
  42. AZStd::array<uint32_t, 2> m_size = { 1, 1 };
  43. AZStd::array<float, 2> m_rcpSize = { 0.0, 0.0 };
  44. AZStd::array<float, 4> m_weights1 = { 0.0 };
  45. AZStd::array<float, 4> m_weights2 = { 0.0 };
  46. AZStd::array<float, 4> m_weights3 = { 0.0 };
  47. };
  48. TaaConstants cb;
  49. RHI::Size inputSize = m_lastFrameAccumulationBinding->GetAttachment()->m_descriptor.m_image.m_size;
  50. cb.m_size[0] = inputSize.m_width;
  51. cb.m_size[1] = inputSize.m_height;
  52. cb.m_rcpSize[0] = 1.0f / inputSize.m_width;
  53. cb.m_rcpSize[1] = 1.0f / inputSize.m_height;
  54. Offset jitterOffset = m_subPixelOffsets.at(m_offsetIndex);
  55. GenerateFilterWeights(Vector2(jitterOffset.m_xOffset, jitterOffset.m_yOffset));
  56. cb.m_weights1 = { m_filterWeights[0], m_filterWeights[1], m_filterWeights[2], m_filterWeights[3] };
  57. cb.m_weights2 = { m_filterWeights[4], m_filterWeights[5], m_filterWeights[6], m_filterWeights[7] };
  58. cb.m_weights3 = { m_filterWeights[8], 0.0f, 0.0f, 0.0f };
  59. m_shaderResourceGroup->SetConstant(m_constantDataIndex, cb);
  60. Base::CompileResources(context);
  61. }
  62. void TaaPass::FrameBeginInternal(FramePrepareParams params)
  63. {
  64. RHI::Size inputSize = m_inputColorBinding->GetAttachment()->m_descriptor.m_image.m_size;
  65. Vector2 rcpInputSize = Vector2(1.0f / inputSize.m_width, 1.0f / inputSize.m_height);
  66. RPI::ViewPtr view = GetRenderPipeline()->GetFirstView(GetPipelineViewTag());
  67. if (view)
  68. {
  69. m_offsetIndex = (m_offsetIndex + 1) % m_subPixelOffsets.size();
  70. Offset offset = m_subPixelOffsets.at(m_offsetIndex);
  71. view->SetClipSpaceOffset(offset.m_xOffset * rcpInputSize.GetX(), offset.m_yOffset * rcpInputSize.GetY());
  72. }
  73. if (!ShouldCopyHistoryBuffer)
  74. {
  75. m_lastFrameAccumulationBinding->SetAttachment(m_accumulationAttachments[m_accumulationOuptutIndex]);
  76. m_accumulationOuptutIndex ^= 1; // swap which attachment is the output and last frame
  77. UpdateAttachmentImage(m_accumulationOuptutIndex);
  78. m_outputColorBinding->SetAttachment(m_accumulationAttachments[m_accumulationOuptutIndex]);
  79. }
  80. Base::FrameBeginInternal(params);
  81. }
  82. void TaaPass::ResetInternal()
  83. {
  84. m_accumulationAttachments[0].reset();
  85. m_accumulationAttachments[1].reset();
  86. m_inputColorBinding = nullptr;
  87. m_lastFrameAccumulationBinding = nullptr;
  88. m_outputColorBinding = nullptr;
  89. Base::ResetInternal();
  90. }
  91. void TaaPass::BuildInternal()
  92. {
  93. m_accumulationAttachments[0] = FindAttachment(Name("Accumulation1"));
  94. m_accumulationAttachments[1] = FindAttachment(Name("Accumulation2"));
  95. bool attachmentsValid = true;
  96. // Make sure the attachments have images when the pass first loads.
  97. for (auto i : { 0, 1 })
  98. {
  99. if (m_accumulationAttachments[i])
  100. {
  101. attachmentsValid = UpdateAttachmentImage(i);
  102. if (!attachmentsValid)
  103. {
  104. break;
  105. }
  106. }
  107. else
  108. {
  109. attachmentsValid = false;
  110. break;
  111. }
  112. }
  113. if (!attachmentsValid)
  114. {
  115. this->SetEnabled(false);
  116. AZ_Error(
  117. "TaaPass", attachmentsValid, "TaaPass disabled because the ImageAttachments Accumulation1 and Accumulation2 are invalid.");
  118. return;
  119. }
  120. m_inputColorBinding = FindAttachmentBinding(Name("InputColor"));
  121. AZ_Error("TaaPass", m_inputColorBinding, "TaaPass requires a slot for InputColor.");
  122. m_lastFrameAccumulationBinding = FindAttachmentBinding(Name("LastFrameAccumulation"));
  123. AZ_Error("TaaPass", m_lastFrameAccumulationBinding, "TaaPass requires a slot for LastFrameAccumulation.");
  124. m_outputColorBinding = FindAttachmentBinding(Name("OutputColor"));
  125. AZ_Error("TaaPass", m_outputColorBinding, "TaaPass requires a slot for OutputColor.");
  126. // Set up the attachment for last frame accumulation and output color if it's never been done to
  127. // ensure SRG indices are set up correctly by the pass system.
  128. if (m_lastFrameAccumulationBinding->GetAttachment() == nullptr)
  129. {
  130. m_lastFrameAccumulationBinding->SetAttachment(m_accumulationAttachments[0]);
  131. m_outputColorBinding->SetAttachment(m_accumulationAttachments[1]);
  132. }
  133. Base::BuildInternal();
  134. }
  135. bool TaaPass::UpdateAttachmentImage(uint32_t attachmentIndex)
  136. {
  137. RPI::Ptr<RPI::PassAttachment>& attachment = m_accumulationAttachments[attachmentIndex];
  138. if (!attachment)
  139. {
  140. return false;
  141. }
  142. // update the image attachment descriptor to sync up size and format
  143. attachment->Update(true);
  144. RHI::ImageDescriptor& imageDesc = attachment->m_descriptor.m_image;
  145. // The Format Source had no valid attachment
  146. if (imageDesc.m_format == RHI::Format::Unknown)
  147. {
  148. return false;
  149. }
  150. RPI::AttachmentImage* currentImage = azrtti_cast<RPI::AttachmentImage*>(attachment->m_importedResource.get());
  151. if (attachment->m_importedResource && imageDesc.m_size == currentImage->GetDescriptor().m_size)
  152. {
  153. // If there's a resource already and the size didn't change, just keep using the old AttachmentImage.
  154. return true;
  155. }
  156. Data::Instance<RPI::AttachmentImagePool> pool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
  157. // set the bind flags
  158. imageDesc.m_bindFlags |= RHI::ImageBindFlags::Color | RHI::ImageBindFlags::ShaderReadWrite;
  159. // The ImageViewDescriptor must be specified to make sure the frame graph compiler doesn't treat this as a transient image.
  160. RHI::ImageViewDescriptor viewDesc = RHI::ImageViewDescriptor::Create(imageDesc.m_format, 0, 0);
  161. viewDesc.m_aspectFlags = RHI::ImageAspectFlags::Color;
  162. // The full path name is needed for the attachment image so it's not deduplicated from accumulation images in different pipelines.
  163. AZStd::string imageName = RPI::ConcatPassString(GetPathName(), attachment->m_path);
  164. auto attachmentImage = RPI::AttachmentImage::Create(*pool.get(), imageDesc, Name(imageName), nullptr, &viewDesc);
  165. if (attachmentImage)
  166. {
  167. attachment->m_path = attachmentImage->GetAttachmentId();
  168. attachment->m_importedResource = attachmentImage;
  169. m_attachmentImages[attachmentIndex] = attachmentImage;
  170. return true;
  171. }
  172. return false;
  173. }
  174. void TaaPass::SetupSubPixelOffsets(uint32_t haltonX, uint32_t haltonY, uint32_t length)
  175. {
  176. m_subPixelOffsets.resize(length);
  177. HaltonSequence<2> sequence = HaltonSequence<2>({haltonX, haltonY});
  178. sequence.FillHaltonSequence(m_subPixelOffsets.begin(), m_subPixelOffsets.end());
  179. // Adjust to the -1.0 to 1.0 range. This is done because the view needs offsets in clip
  180. // space and is one less calculation that would need to be done in FrameBeginInternal()
  181. AZStd::for_each(m_subPixelOffsets.begin(), m_subPixelOffsets.end(),
  182. [](Offset& offset)
  183. {
  184. offset.m_xOffset = 2.0f * offset.m_xOffset - 1.0f;
  185. offset.m_yOffset = 2.0f * offset.m_yOffset - 1.0f;
  186. }
  187. );
  188. }
  189. // Approximation of a Blackman Harris window function of width 3.3.
  190. // https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window
  191. static float BlackmanHarris(AZ::Vector2 uv)
  192. {
  193. return expf(-2.29f * (uv.GetX() * uv.GetX() + uv.GetY() * uv.GetY()));
  194. }
  195. // Generates filter weights for the 3x3 neighborhood of a pixel. Since jitter positions are the
  196. // same for every pixel we can calculate this once here and upload to the SRG.
  197. // Jitter weights are based on a window function centered at the pixel center (we use Blackman-Harris).
  198. // As the jitter position moves around, some neighborhood locations decrease in weight, and others
  199. // increase in weight based on their distance from the center of the pixel.
  200. void TaaPass::GenerateFilterWeights(AZ::Vector2 jitterOffset)
  201. {
  202. static const AZStd::array<Vector2, 9> pixelOffsets =
  203. {
  204. // Center
  205. Vector2(0.0f, 0.0f),
  206. // Cross
  207. Vector2( 1.0f, 0.0f),
  208. Vector2( 0.0f, 1.0f),
  209. Vector2(-1.0f, 0.0f),
  210. Vector2( 0.0f, -1.0f),
  211. // Diagonals
  212. Vector2( 1.0f, 1.0f),
  213. Vector2( 1.0f, -1.0f),
  214. Vector2(-1.0f, 1.0f),
  215. Vector2(-1.0f, -1.0f),
  216. };
  217. float sum = 0.0f;
  218. for (uint32_t i = 0; i < 9; ++i)
  219. {
  220. m_filterWeights[i] = BlackmanHarris(pixelOffsets[i] + jitterOffset);
  221. sum += m_filterWeights[i];
  222. }
  223. // Normalize the weight so the sum of all weights is 1.0.
  224. float normalization = 1.0f / sum;
  225. for (uint32_t i = 0; i < 9; ++i)
  226. {
  227. m_filterWeights[i] *= normalization;
  228. }
  229. }
  230. } // namespace AZ::Render