From 2f3c4d37dfc77485041b79358c9683a3860574cd Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Wed, 9 Feb 2022 12:22:38 -0600 Subject: [PATCH] Improved image gradient GetValue(s) performance by using cached image data Signed-off-by: Chris Galvan --- .../Code/Include/Atom/RPI.Public/RPIUtils.h | 5 ++ .../RPI/Code/Source/RPI.Public/RPIUtils.cpp | 74 +++++++++++++------ .../Components/ImageGradientComponent.h | 3 + .../Code/Include/GradientSignal/ImageAsset.h | 2 +- .../Components/ImageGradientComponent.cpp | 49 +++++++++++- .../GradientSignal/Code/Source/ImageAsset.cpp | 7 +- 6 files changed, 109 insertions(+), 31 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h index 546089ab1e..6516407265 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPIUtils.h @@ -60,6 +60,11 @@ namespace AZ //! Same as above. Provided as a convenience when all arguments of the 'numthreads' attributes should be assigned to RHI::DispatchDirect::m_threadsPerGroup* variables. AZ::Outcome GetComputeShaderNumThreads(const Data::Asset& shaderAsset, RHI::DispatchDirect& dispatchDirect); + //! Get single image pixel value from raw image data + //! This assumes the imageData is not empty + template + T GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex = 0); + //! Get single image pixel value for specified mip and slice template T GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex = 0, uint32_t mip = 0, uint32_t slice = 0); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index 9ff26f0d3a..8a6cc80cfe 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -232,16 +232,12 @@ namespace AZ } } - template - T GetSubImagePixelValueInternal(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) + size_t GetImageDataIndex(const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) { - AZStd::array values{ aznumeric_cast(0) }; + auto width = imageDescriptor.m_size.m_width; + const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); - auto topLeft = AZStd::make_pair(x, y); - auto bottomRight = AZStd::make_pair(x + 1, y + 1); - GetSubImagePixelValues(imageAsset, topLeft, bottomRight, AZStd::span(values), componentIndex, mip, slice); - - return values[0]; + return (y * width + x) * numComponents + componentIndex; } } @@ -447,22 +443,60 @@ namespace AZ return GetComputeShaderNumThreads(shaderAsset, &dispatchDirect.m_threadsPerGroupX, &dispatchDirect.m_threadsPerGroupY, &dispatchDirect.m_threadsPerGroupZ); } + template<> + float GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) + { + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); + return Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); + } + + template<> + AZ::u32 GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) + { + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); + return Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); + } + + template<> + AZ::s32 GetImageDataPixelValue(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex) + { + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); + return Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); + } + + template + T GetSubImagePixelValueInternal(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) + { + if (!imageAsset.IsReady()) + { + return aznumeric_cast(0); + } + + auto imageData = imageAsset->GetSubImageData(mip, slice); + if (imageData.empty()) + { + return aznumeric_cast(0); + } + + return GetImageDataPixelValue(imageData, imageAsset->GetImageDescriptor(), x, y, componentIndex); + } + template<> float GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - return Internal::GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); + return GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); } template<> AZ::u32 GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - return Internal::GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); + return GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); } template<> AZ::s32 GetSubImagePixelValue(const AZ::Data::Asset& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - return Internal::GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); + return GetSubImagePixelValueInternal(imageAsset, x, y, componentIndex, mip, slice); } bool GetSubImagePixelValues(const AZ::Data::Asset& imageAsset, AZStd::pair topLeft, AZStd::pair bottomRight, AZStd::span outValues, uint32_t componentIndex, uint32_t mip, uint32_t slice) @@ -478,16 +512,14 @@ namespace AZ return false; } - const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); - auto width = imageDescriptor.m_size.m_width; - const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor(); size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -510,16 +542,14 @@ namespace AZ return false; } - const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); - auto width = imageDescriptor.m_size.m_width; - const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor(); size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -542,16 +572,14 @@ namespace AZ return false; } - const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); - auto width = imageDescriptor.m_size.m_width; - const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor(); size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; + size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex); auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h index 0cbd8bd4c7..7ec1c785e5 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h @@ -98,6 +98,8 @@ namespace GradientSignal void SetupDependencies(); + void GetSubImageData(); + // ImageGradientRequestBus overrides... AZStd::string GetImageAssetPath() const override; void SetImageAssetPath(const AZStd::string& assetPath) override; @@ -113,5 +115,6 @@ namespace GradientSignal LmbrCentral::DependencyMonitor m_dependencyMonitor; mutable AZStd::shared_mutex m_imageMutex; GradientTransform m_gradientTransform; + AZStd::span m_imageData; }; } diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h index 811d74082d..500251d924 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h @@ -62,6 +62,6 @@ namespace GradientSignal } }; - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); + float GetValueFromImageAsset(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); } // namespace GradientSignal diff --git a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp index 468b96e5ad..39a456a277 100644 --- a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp @@ -215,6 +215,16 @@ namespace GradientSignal m_dependencyMonitor.ConnectDependency(m_configuration.m_imageAsset.GetId()); } + void ImageGradientComponent::GetSubImageData() + { + if (!m_configuration.m_imageAsset || !m_configuration.m_imageAsset.IsReady()) + { + return; + } + + m_imageData = m_configuration.m_imageAsset->GetSubImageData(0, 0); + } + void ImageGradientComponent::Activate() { // This will immediately call OnGradientTransformChanged and initialize m_gradientTransform. @@ -226,8 +236,19 @@ namespace GradientSignal GradientRequestBus::Handler::BusConnect(GetEntityId()); AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId()); + // If the image asset is already ready (e.g. constructed in a unit test), + // then go ahead and retrieve the image data now AZStd::unique_lock imageLock(m_imageMutex); - m_configuration.m_imageAsset.QueueLoad(); + if (m_configuration.m_imageAsset.IsReady()) + { + GetSubImageData(); + } + // Otherwise for normal use-case, we queue the asset to be loaded now + else + { + m_imageData = AZStd::span(); + m_configuration.m_imageAsset.QueueLoad(); + } } void ImageGradientComponent::Deactivate() @@ -267,18 +288,24 @@ namespace GradientSignal { AZStd::unique_lock imageLock(m_imageMutex); m_configuration.m_imageAsset = asset; + + GetSubImageData(); } void ImageGradientComponent::OnAssetMoved(AZ::Data::Asset asset, [[maybe_unused]] void* oldDataPointer) { AZStd::unique_lock imageLock(m_imageMutex); m_configuration.m_imageAsset = asset; + + GetSubImageData(); } void ImageGradientComponent::OnAssetReloaded(AZ::Data::Asset asset) { AZStd::unique_lock imageLock(m_imageMutex); m_configuration.m_imageAsset = asset; + + GetSubImageData(); } void ImageGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform) @@ -292,6 +319,12 @@ namespace GradientSignal AZ::Vector3 uvw = sampleParams.m_position; bool wasPointRejected = false; + // Return immediately if our cached image data hasn't been retrieved yet + if (m_imageData.empty()) + { + return 0.0f; + } + { AZStd::shared_lock imageLock(m_imageMutex); @@ -300,7 +333,7 @@ namespace GradientSignal if (!wasPointRejected) { return GetValueFromImageAsset( - m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); + m_imageData, m_configuration.m_imageAsset->GetImageDescriptor(), uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); } } @@ -315,6 +348,12 @@ namespace GradientSignal return; } + // Return immediately if our cached image data hasn't been retrieved yet + if (m_imageData.empty()) + { + return; + } + AZ::Vector3 uvw; bool wasPointRejected = false; @@ -327,7 +366,7 @@ namespace GradientSignal if (!wasPointRejected) { outValues[index] = GetValueFromImageAsset( - m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); + m_imageData, m_configuration.m_imageAsset->GetImageDescriptor(), uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f); } else { @@ -353,6 +392,10 @@ namespace GradientSignal { AZStd::unique_lock imageLock(m_imageMutex); + + // Clear our cached image data + m_imageData = AZStd::span(); + m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid(), m_configuration.m_imageAsset.GetAutoLoadBehavior()); } diff --git a/Gems/GradientSignal/Code/Source/ImageAsset.cpp b/Gems/GradientSignal/Code/Source/ImageAsset.cpp index 0f91a3ea08..05ad13d193 100644 --- a/Gems/GradientSignal/Code/Source/ImageAsset.cpp +++ b/Gems/GradientSignal/Code/Source/ImageAsset.cpp @@ -78,11 +78,10 @@ namespace GradientSignal return true; } - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) + float GetValueFromImageAsset(AZStd::span imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) { - if (imageAsset.IsReady()) + if (!imageData.empty()) { - auto imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; auto height = imageDescriptor.m_size.m_height; @@ -125,7 +124,7 @@ namespace GradientSignal // Flip the y because images are stored in reverse of our world axes y = (height - 1) - y; - return AZ::RPI::GetSubImagePixelValue(imageAsset, x, y); + return AZ::RPI::GetImageDataPixelValue(imageData, imageDescriptor, x, y); } }