diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp index 20e1b18e77..44539ad33c 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp @@ -242,6 +242,10 @@ namespace ImageProcessingAtom } } + // Should we actually put the GetSubImagePixelValue API in this Utils file instead, since it already has + // some conversion logic, and has the helper method for retrieving an entire image (LoadImageFromImageAsset) + // whereas this is a helper for retrieving a specific pixel from the image? + IImageObjectPtr LoadImageFromImageAsset(const AZ::Data::Asset& imageAsset) { if (!imageAsset.IsReady()) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAsset.h index 73af8370cb..1db2a63b1c 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAsset.h @@ -85,6 +85,10 @@ namespace AZ //! Get image data for specified mip and slice. It may return empty array if its mipchain assets are not loaded AZStd::array_view GetSubImageData(uint32_t mip, uint32_t slice); + //! Get image pixel value for specified mip and slice + template + T GetSubImagePixelValue(uint32_t mip, uint32_t slice, uint32_t x, uint32_t y, uint32_t componentIndex = 0); + //! Returns streaming image pool asset id of the pool that will be used to create the streaming image. const Data::AssetId& GetPoolAssetId() const; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAsset.cpp index 59f359e474..f1d9b1782b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAsset.cpp @@ -13,6 +13,95 @@ namespace AZ { + namespace Internal + { + template + float RetrieveFloatValue(const AZ::u8* mem, size_t index) + { + AZ_Assert(false, "Unsupported pixel format"); + } + + template + AZ::u32 RetrieveUintValue(const AZ::u8* mem, size_t index) + { + AZ_Assert(false, "Unsupported pixel format"); + } + + template <> + float RetrieveFloatValue([[maybe_unused]] const AZ::u8* mem, [[maybe_unused]] size_t index) + { + return 0.0f; + } + + template <> + AZ::u32 RetrieveUintValue([[maybe_unused]] const AZ::u8* mem, [[maybe_unused]] size_t index) + { + return 0; + } + + template <> + AZ::u32 RetrieveUintValue(const AZ::u8* mem, size_t index) + { + return mem[index] / static_cast(std::numeric_limits::max()); + } + + template <> + AZ::u32 RetrieveUintValue(const AZ::u8* mem, size_t index) + { + // 16 bits per channel + auto actualMem = reinterpret_cast(mem); + actualMem += index; + + return *actualMem / static_cast(std::numeric_limits::max()); + } + + template <> + AZ::u32 RetrieveUintValue(const AZ::u8* mem, size_t index) + { + // 32 bits per channel + auto actualMem = reinterpret_cast(mem); + actualMem += index; + + return *actualMem / static_cast(std::numeric_limits::max()); + } + + template <> + float RetrieveFloatValue(const AZ::u8* mem, size_t index) + { + // 32 bits per channel + auto actualMem = reinterpret_cast(mem); + actualMem += index; + + return *actualMem; + } + + float RetrieveFloatValue(const AZ::u8* mem, size_t index, AZ::RHI::Format format) + { + switch (format) + { + case AZ::RHI::Format::R32_FLOAT: + return RetrieveFloatValue(mem, index); + default: + return RetrieveFloatValue(mem, index); + } + } + + AZ::u32 RetrieveUintValue(const AZ::u8* mem, size_t index, AZ::RHI::Format format) + { + switch (format) + { + case AZ::RHI::Format::R8_UNORM: + return RetrieveUintValue(mem, index); + case AZ::RHI::Format::R16_UNORM: + return RetrieveUintValue(mem, index); + case AZ::RHI::Format::R32_UINT: + return RetrieveUintValue(mem, index); + default: + return RetrieveUintValue(mem, index); + } + } + } + namespace RPI { const char* StreamingImageAsset::DisplayName = "StreamingImage"; @@ -124,5 +213,45 @@ namespace AZ return mipChainAsset->GetSubImageData(mip - mipChain.m_mipOffset, slice); } + + template<> + float StreamingImageAsset::GetSubImagePixelValue(uint32_t mip, uint32_t slice, uint32_t x, uint32_t y, uint32_t componentIndex) + { + // TODO: Use the component index + (void)componentIndex; + + auto imageData = GetSubImageData(mip, slice); + + if (!imageData.empty()) + { + const AZ::RHI::ImageDescriptor imageDescriptor = GetImageDescriptor(); + auto width = imageDescriptor.m_size.m_width; + size_t index = (y * width) + x; + + return Internal::RetrieveFloatValue(imageData.data(), index, imageDescriptor.m_format); + } + + return 0.0f; + } + + template<> + AZ::u32 StreamingImageAsset::GetSubImagePixelValue(uint32_t mip, uint32_t slice, uint32_t x, uint32_t y, uint32_t componentIndex) + { + // TODO: Use the component index + (void)componentIndex; + + auto imageData = GetSubImageData(mip, slice); + + if (!imageData.empty()) + { + const AZ::RHI::ImageDescriptor imageDescriptor = GetImageDescriptor(); + auto width = imageDescriptor.m_size.m_width; + size_t index = (y * width) + x; + + return Internal::RetrieveUintValue(imageData.data(), index, imageDescriptor.m_format); + } + + return 0; + } } } diff --git a/Gems/GradientSignal/Code/CMakeLists.txt b/Gems/GradientSignal/Code/CMakeLists.txt index 657a88db47..fda04ceb82 100644 --- a/Gems/GradientSignal/Code/CMakeLists.txt +++ b/Gems/GradientSignal/Code/CMakeLists.txt @@ -21,6 +21,7 @@ ly_add_target( AZ::AzCore AZ::AtomCore AZ::AzFramework + Gem::Atom_RPI.Public Gem::SurfaceData Gem::ImageProcessingAtom.Headers Gem::LmbrCentral diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h index 8214436f83..bbb22a061d 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h @@ -10,6 +10,9 @@ #include #include + +#include + #include #include #include @@ -33,6 +36,7 @@ namespace GradientSignal AZ_RTTI(ImageGradientConfig, "{1BDB5DA4-A4A8-452B-BE6D-6BD451D4E7CD}", AZ::ComponentConfig); static void Reflect(AZ::ReflectContext* context); AZ::Data::Asset m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; + AZ::Data::Asset m_streamingImageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; float m_tilingX = 1.0f; float m_tilingY = 1.0f; }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h index 4ffab0b4b4..811d74082d 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace AZ { @@ -61,6 +62,6 @@ namespace GradientSignal } }; - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); + float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); } // namespace GradientSignal diff --git a/Gems/GradientSignal/Code/Source/ImageAsset.cpp b/Gems/GradientSignal/Code/Source/ImageAsset.cpp index 7e73dc32d6..afa233e5cb 100644 --- a/Gems/GradientSignal/Code/Source/ImageAsset.cpp +++ b/Gems/GradientSignal/Code/Source/ImageAsset.cpp @@ -20,6 +20,7 @@ namespace { + // Could (should) move these RetrieveValue helper methods over to where our new API lives template float RetrieveValue(const AZ::u8* mem, size_t index) { @@ -151,17 +152,17 @@ namespace GradientSignal return true; } - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) + float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) { if (imageAsset.IsReady()) { - const auto& image = imageAsset.Get(); - AZStd::size_t imageSize = image->m_imageWidth * image->m_imageHeight * - static_cast(image->m_bytesPerPixel); + const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); + auto width = imageDescriptor.m_size.m_width; + auto height = imageDescriptor.m_size.m_height; - if (image->m_imageWidth > 0 && - image->m_imageHeight > 0 && - image->m_imageData.size() == imageSize) + if (width > 0 + && height > 0 + ) { // When "rasterizing" from uvs, a range of 0-1 has slightly different meanings depending on the sampler state. // For repeating states (Unbounded/None, Repeat), a uv value of 1 should wrap around back to our 0th pixel. @@ -184,8 +185,8 @@ namespace GradientSignal // A 16x16 pixel image and tilingX = tilingY = 1 maps the uv range of 0-1 to 0-16 pixels. // A 16x16 pixel image and tilingX = tilingY = 1.5 maps the uv range of 0-1 to 0-24 pixels. - const AZ::Vector3 tiledDimensions((image->m_imageWidth * tilingX), - (image->m_imageHeight * tilingY), + const AZ::Vector3 tiledDimensions((width * tilingX), + (height * tilingY), 0.0f); // Convert from uv space back to pixel space @@ -194,13 +195,10 @@ namespace GradientSignal // UVs outside the 0-1 range are treated as infinitely tiling, so that we behave the same as the // other gradient generators. As mentioned above, if clamping is desired, we expect it to be applied // outside of this function. - size_t x = static_cast(pixelLookup.GetX()) % image->m_imageWidth; - size_t y = static_cast(pixelLookup.GetY()) % image->m_imageHeight; + uint32_t x = static_cast(pixelLookup.GetX()) % width; + uint32_t y = static_cast(pixelLookup.GetY()) % height; - // Flip the y because images are stored in reverse of our world axes - size_t index = ((image->m_imageHeight - 1) - y) * image->m_imageWidth + x; - - return RetrieveValue(image->m_imageData.data(), index, image->m_imageFormat); + return imageAsset->GetSubImagePixelValue(0, 0, x, y, 0); } }