From 1c1ded586916784045fe1ec52738d622a3d521f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 30 Jun 2025 19:16:20 +0200 Subject: [PATCH] Store block size properties in compressed images. Similarly like pixel size is stored in uncompressed images, block size makes it possible to perform size checks on passed data, slice the images and so on. It only took over a decade to get that done. The block properties coming from CompressedPixelStorage are currently expected to be either not set at all or exactly match what's stored in the image for given format. The PixelStorage will get eventually deprecated in favor of a simpler and more flexible representation, but that's another big chunk of work so it's first done like this. The GL library tests currently blow up on various assertions and it isn't yet updated to make use of the known format properties instead of querying them from GL. That'll be done in the following commits. --- src/Magnum/GL/AbstractTexture.cpp | 26 +- src/Magnum/GL/BufferImage.cpp | 33 +- src/Magnum/GL/BufferImage.h | 44 +- src/Magnum/GL/CubeMapTexture.cpp | 8 +- .../GL/Implementation/imageProperties.h | 17 +- src/Magnum/GL/Test/BufferImageGLTest.cpp | 291 ++++++++---- src/Magnum/GL/Test/BufferImageTest.cpp | 2 + src/Magnum/GL/Test/PixelStorageTest.cpp | 34 +- src/Magnum/Image.cpp | 43 +- src/Magnum/Image.h | 113 ++++- src/Magnum/ImageView.cpp | 48 +- src/Magnum/ImageView.h | 181 +++++++- src/Magnum/Implementation/ImageProperties.h | 62 ++- src/Magnum/PixelStorage.h | 31 +- src/Magnum/Test/ImageTest.cpp | 316 +++++++++---- src/Magnum/Test/ImageViewTest.cpp | 419 +++++++++++++---- src/Magnum/Test/PixelStorageTest.cpp | 85 ++-- src/Magnum/Trade/ImageData.cpp | 93 +++- src/Magnum/Trade/ImageData.h | 165 ++++++- src/Magnum/Trade/Test/ImageDataTest.cpp | 429 +++++++++++++----- 20 files changed, 1837 insertions(+), 603 deletions(-) diff --git a/src/Magnum/GL/AbstractTexture.cpp b/src/Magnum/GL/AbstractTexture.cpp index 630cfb2d2..3b2d5b79e 100644 --- a/src/Magnum/GL/AbstractTexture.cpp +++ b/src/Magnum/GL/AbstractTexture.cpp @@ -2174,7 +2174,7 @@ void AbstractTexture::DataHelper<1>::setCompressedImage(AbstractTexture& texture Buffer::unbindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); texture.bindInternal(); - glCompressedTexImage1D(texture._target, level, GLenum(compressedPixelFormat(image.format())), image.size()[0], 0, Implementation::occupiedCompressedImageDataSize(image, image.data().size()), image.data()); + glCompressedTexImage1D(texture._target, level, GLenum(compressedPixelFormat(image.format())), image.size()[0], 0, Implementation::occupiedCompressedImageDataSize(image), image.data()); } void AbstractTexture::DataHelper<1>::setImage(AbstractTexture& texture, const GLint level, const TextureFormat internalFormat, BufferImage1D& image) { @@ -2188,7 +2188,7 @@ void AbstractTexture::DataHelper<1>::setCompressedImage(AbstractTexture& texture image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); texture.bindInternal(); - glCompressedTexImage1D(texture._target, level, GLenum(image.format()), image.size()[0], 0, Implementation::occupiedCompressedImageDataSize(image, image.dataSize()), nullptr); + glCompressedTexImage1D(texture._target, level, GLenum(image.format()), image.size()[0], 0, Implementation::occupiedCompressedImageDataSize(image), nullptr); } void AbstractTexture::DataHelper<1>::setSubImage(AbstractTexture& texture, const GLint level, const Math::Vector<1, GLint>& offset, const ImageView1D& image) { @@ -2200,7 +2200,7 @@ void AbstractTexture::DataHelper<1>::setSubImage(AbstractTexture& texture, const void AbstractTexture::DataHelper<1>::setCompressedSubImage(AbstractTexture& texture, const GLint level, const Math::Vector<1, GLint>& offset, const CompressedImageView1D& image) { Buffer::unbindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.compressedSubImage1DImplementation(texture, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image, image.data().size())); + Context::current().state().texture.compressedSubImage1DImplementation(texture, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image)); } void AbstractTexture::DataHelper<1>::setSubImage(AbstractTexture& texture, const GLint level, const Math::Vector<1, GLint>& offset, BufferImage1D& image) { @@ -2212,7 +2212,7 @@ void AbstractTexture::DataHelper<1>::setSubImage(AbstractTexture& texture, const void AbstractTexture::DataHelper<1>::setCompressedSubImage(AbstractTexture& texture, const GLint level, const Math::Vector<1, GLint>& offset, CompressedBufferImage1D& image) { image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.compressedSubImage1DImplementation(texture, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image, image.dataSize())); + Context::current().state().texture.compressedSubImage1DImplementation(texture, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image)); } #endif @@ -2234,7 +2234,7 @@ void AbstractTexture::DataHelper<2>::setCompressedImage(AbstractTexture& texture #endif Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); texture.bindInternal(); - glCompressedTexImage2D(target, level, GLenum(compressedPixelFormat(image.format())), image.size().x(), image.size().y(), 0, Implementation::occupiedCompressedImageDataSize(image, image.data().size()), image.data()); + glCompressedTexImage2D(target, level, GLenum(compressedPixelFormat(image.format())), image.size().x(), image.size().y(), 0, Implementation::occupiedCompressedImageDataSize(image), image.data()); } #ifndef MAGNUM_TARGET_GLES2 @@ -2249,7 +2249,7 @@ void AbstractTexture::DataHelper<2>::setCompressedImage(AbstractTexture& texture image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); texture.bindInternal(); - glCompressedTexImage2D(target, level, GLenum(image.format()), image.size().x(), image.size().y(), 0, Implementation::occupiedCompressedImageDataSize(image, image.dataSize()), nullptr); + glCompressedTexImage2D(target, level, GLenum(image.format()), image.size().x(), image.size().y(), 0, Implementation::occupiedCompressedImageDataSize(image), nullptr); } #endif @@ -2270,7 +2270,7 @@ void AbstractTexture::DataHelper<2>::setCompressedSubImage(AbstractTexture& text Buffer::unbindInternal(Buffer::TargetHint::PixelUnpack); #endif Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.compressedSubImage2DImplementation(texture, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image, image.data().size())); + Context::current().state().texture.compressedSubImage2DImplementation(texture, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image)); } #ifndef MAGNUM_TARGET_GLES2 @@ -2283,7 +2283,7 @@ void AbstractTexture::DataHelper<2>::setSubImage(AbstractTexture& texture, const void AbstractTexture::DataHelper<2>::setCompressedSubImage(AbstractTexture& texture, const GLint level, const Vector2i& offset, CompressedBufferImage2D& image) { image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.compressedSubImage2DImplementation(texture, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image, image.dataSize())); + Context::current().state().texture.compressedSubImage2DImplementation(texture, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image)); } #endif @@ -2307,9 +2307,9 @@ void AbstractTexture::DataHelper<3>::setCompressedImage(AbstractTexture& texture Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); texture.bindInternal(); #ifndef MAGNUM_TARGET_GLES2 - glCompressedTexImage3D(texture._target, level, GLenum(compressedPixelFormat(image.format())), image.size().x(), image.size().y(), image.size().z(), 0, Implementation::occupiedCompressedImageDataSize(image, image.data().size()), image.data()); + glCompressedTexImage3D(texture._target, level, GLenum(compressedPixelFormat(image.format())), image.size().x(), image.size().y(), image.size().z(), 0, Implementation::occupiedCompressedImageDataSize(image), image.data()); #else - glCompressedTexImage3DOES(texture._target, level, GLenum(compressedPixelFormat(image.format())), image.size().x(), image.size().y(), image.size().z(), 0, Implementation::occupiedCompressedImageDataSize(image, image.data().size()), image.data()); + glCompressedTexImage3DOES(texture._target, level, GLenum(compressedPixelFormat(image.format())), image.size().x(), image.size().y(), image.size().z(), 0, Implementation::occupiedCompressedImageDataSize(image), image.data()); #endif } #endif @@ -2326,7 +2326,7 @@ void AbstractTexture::DataHelper<3>::setCompressedImage(AbstractTexture& texture image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); texture.bindInternal(); - glCompressedTexImage3D(texture._target, level, GLenum(image.format()), image.size().x(), image.size().y(), image.size().z(), 0, Implementation::occupiedCompressedImageDataSize(image, image.dataSize()), nullptr); + glCompressedTexImage3D(texture._target, level, GLenum(image.format()), image.size().x(), image.size().y(), image.size().z(), 0, Implementation::occupiedCompressedImageDataSize(image), nullptr); } #endif @@ -2348,7 +2348,7 @@ void AbstractTexture::DataHelper<3>::setCompressedSubImage(AbstractTexture& text Buffer::unbindInternal(Buffer::TargetHint::PixelUnpack); #endif Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.compressedSubImage3DImplementation(texture, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image, image.data().size())); + Context::current().state().texture.compressedSubImage3DImplementation(texture, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image)); } #endif @@ -2362,7 +2362,7 @@ void AbstractTexture::DataHelper<3>::setSubImage(AbstractTexture& texture, const void AbstractTexture::DataHelper<3>::setCompressedSubImage(AbstractTexture& texture, const GLint level, const Vector3i& offset, CompressedBufferImage3D& image) { image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.compressedSubImage3DImplementation(texture, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image, image.dataSize())); + Context::current().state().texture.compressedSubImage3DImplementation(texture, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image)); } #endif diff --git a/src/Magnum/GL/BufferImage.cpp b/src/Magnum/GL/BufferImage.cpp index 095ec09c9..e16acdfbf 100644 --- a/src/Magnum/GL/BufferImage.cpp +++ b/src/Magnum/GL/BufferImage.cpp @@ -95,23 +95,32 @@ template void BufferImage::setData(const Pix } template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data, const BufferUsage usage): CompressedBufferImage{storage, format, size, Buffer{Buffer::TargetHint::PixelPack}, data.size()} { + /* Size and block properties checks done in the delegated-to constructor + already */ _buffer.setData(data, usage); } template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage, const Magnum::CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data, const BufferUsage usage): CompressedBufferImage{storage, compressedPixelFormat(format), size, data, usage} {} -template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Buffer&& buffer, const std::size_t dataSize) noexcept: _storage{storage}, _format{format}, _size{size}, _buffer{Utility::move(buffer)}, _dataSize{dataSize} {} +template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Buffer&& buffer, const std::size_t dataSize) noexcept: _storage{storage}, _format{format}, _blockSize{Vector3ub(compressedPixelFormatBlockSize(format))}, _blockDataSize{UnsignedByte(compressedPixelFormatBlockDataSize(format))}, _size{size}, _buffer{Utility::move(buffer)}, _dataSize{dataSize} { + #ifndef CORRADE_NO_ASSERT + Magnum::Implementation::checkBlockPropertiesForStorage("GL::CompressedBufferImage:", Vector3i{_blockSize}, _blockDataSize, storage); + CORRADE_ASSERT(Magnum::Implementation::compressedImageDataSize(*this) <= dataSize, "GL::CompressedBufferImage: data too small, got" << dataSize << "but expected at least" << Magnum::Implementation::compressedImageDataSize(*this) << "bytes", ); + #endif +} template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage, const Magnum::CompressedPixelFormat format, const VectorTypeFor& size, Buffer&& buffer, const std::size_t dataSize) noexcept: CompressedBufferImage{storage, compressedPixelFormat(format), size, Utility::move(buffer), dataSize} {} -template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage): _storage{storage}, _format{}, _buffer{Buffer::TargetHint::PixelPack}, _dataSize{} { - /* Not delegating to the (buffer&&, dataSize) constructor to avoid a size - assertion that'd happen with certain storage parameters */ +template CompressedBufferImage::CompressedBufferImage(const CompressedPixelStorage storage): _storage{storage}, _format{}, _blockDataSize{}, _buffer{Buffer::TargetHint::PixelPack}, _dataSize{} { + CORRADE_ASSERT(storage.compressedBlockSize() == Vector3i{}, + "GL::CompressedBufferImage: expected pixel storage block size to not be set at all but got" << Debug::packed << storage.compressedBlockSize(), ); + CORRADE_ASSERT(!storage.compressedBlockDataSize(), + "GL::CompressedBufferImage: expected pixel storage block data size to not be set at all but got" << storage.compressedBlockDataSize(), ); } -template CompressedBufferImage::CompressedBufferImage(NoCreateT) noexcept: _format{}, _buffer{NoCreate}, _dataSize{} {} +template CompressedBufferImage::CompressedBufferImage(NoCreateT) noexcept: _format{}, _blockDataSize{}, _buffer{NoCreate}, _dataSize{} {} -template CompressedBufferImage::CompressedBufferImage(CompressedBufferImage&& other) noexcept: _storage{Utility::move(other._storage)}, _format{Utility::move(other._format)}, _size{Utility::move(other._size)}, _buffer{Utility::move(other._buffer)}, _dataSize{Utility::move(other._dataSize)} { +template CompressedBufferImage::CompressedBufferImage(CompressedBufferImage&& other) noexcept: _storage{other._storage}, _format{other._format}, _blockSize{other._blockSize}, _blockDataSize{other._blockDataSize}, _size{other._size}, _buffer{Utility::move(other._buffer)}, _dataSize{other._dataSize} { other._size = {}; other._dataSize = {}; } @@ -120,6 +129,8 @@ template CompressedBufferImage& CompressedBu using Utility::swap; swap(_storage, other._storage); swap(_format, other._format); + swap(_blockSize, other._blockSize); + swap(_blockDataSize, other._blockDataSize); swap(_size, other._size); swap(_buffer, other._buffer); swap(_dataSize, other._dataSize); @@ -133,10 +144,18 @@ template std::pair void CompressedBufferImage::setData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data, const BufferUsage usage) { _storage = storage; _format = format; + _blockSize = Vector3ub(compressedPixelFormatBlockSize(format)); + _blockDataSize = UnsignedByte(compressedPixelFormatBlockDataSize(format)); _size = size; + #ifndef CORRADE_NO_ASSERT + Magnum::Implementation::checkBlockPropertiesForStorage("GL::CompressedBufferImage::setData():", Vector3i{_blockSize}, _blockDataSize, storage); + #endif /* Keep the old storage if zero-sized nullptr buffer was passed */ - if(!(data.data() == nullptr && data.size() == 0)) { + if(data.data() == nullptr && data.size() == 0) { + CORRADE_ASSERT(Magnum::Implementation::compressedImageDataSize(*this) <= _dataSize, "GL::CompressedBufferImage::setData(): current storage too small, got" << _dataSize << "but expected at least" << Magnum::Implementation::compressedImageDataSize(*this) << "bytes", ); + } else { + CORRADE_ASSERT(Magnum::Implementation::compressedImageDataSize(*this) <= data.size(), "GL::CompressedBufferImage::setData(): data too small, got" << data.size() << "but expected at least" << Magnum::Implementation::compressedImageDataSize(*this) << "bytes", ); _buffer.setData(data, usage); _dataSize = data.size(); } diff --git a/src/Magnum/GL/BufferImage.h b/src/Magnum/GL/BufferImage.h index a0e23ecb7..992c4d4b9 100644 --- a/src/Magnum/GL/BufferImage.h +++ b/src/Magnum/GL/BufferImage.h @@ -454,6 +454,12 @@ template class CompressedBufferImage { * @param data Image data * @param usage Image buffer usage * + * The @p data array is expected to be of proper size for given + * parameters. @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * properties of given @p format. + * * @requires_gl42 Extension @gl_extension{ARB,compressed_texture_pixel_storage} * for non-default @ref CompressedPixelStorage * @requires_gl Non-default @ref CompressedPixelStorage is not @@ -507,6 +513,12 @@ template class CompressedBufferImage { * @param buffer Buffer * @param dataSize Buffer data size * + * The @p dataSize is expected to be of proper size for given + * parameters. @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * properties of given @p format. + * * If @p dataSize is @cpp 0 @ce, the buffer is unconditionally * reallocated on the first call to @ref setData(). * @requires_gl42 Extension @gl_extension{ARB,compressed_texture_pixel_storage} @@ -558,8 +570,12 @@ template class CompressedBufferImage { * @brief Construct an image placeholder * @param storage Storage of compressed pixel data * - * Format is undefined, size is zero and buffer is empty, call - * @ref setData() to fill the image with data. + * Format and block properties are undefined, size is zero and buffer + * is empty. Call @ref setData() to fill the image with data. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be both zero. * @requires_gl42 Extension @gl_extension{ARB,compressed_texture_pixel_storage} * for non-default @ref CompressedPixelStorage * @requires_gl Non-default @ref CompressedPixelStorage is not @@ -608,6 +624,25 @@ template class CompressedBufferImage { /** @brief Format of compressed pixel data */ CompressedPixelFormat format() const { return _format; } + /** + * @brief Size of a compressed block in pixels + * @m_since_latest + * + * Note that the blocks can be 3D even for 2D images and 2D or 3D even + * for 1D images, in which case only the first slice in the extra + * dimensions is used. + * @see @ref blockDataSize(), @ref compressedPixelFormatBlockSize() + */ + Vector3i blockSize() const { return Vector3i{_blockSize}; } + + /** + * @brief Size of a compressed block in bytes + * @m_since_latest + * + * @see @ref blockSize(), @ref compressedPixelFormatBlockDataSize() + */ + UnsignedInt blockDataSize() const { return _blockDataSize; } + /** @brief Image size */ VectorTypeFor size() const { return _size; } @@ -706,6 +741,11 @@ template class CompressedBufferImage { private: CompressedPixelStorage _storage; CompressedPixelFormat _format; + /* Largest blocks are 12x12 in ASTC and at most 32 bytes, so an 8-bit + type should be more than enough. As even 1D images can have 3D + blocks, the member isn't dependent on dimension count. */ + Vector3ub _blockSize; + UnsignedByte _blockDataSize; Math::Vector _size; Buffer _buffer; std::size_t _dataSize; diff --git a/src/Magnum/GL/CubeMapTexture.cpp b/src/Magnum/GL/CubeMapTexture.cpp index 2084e7097..e1041c8a3 100644 --- a/src/Magnum/GL/CubeMapTexture.cpp +++ b/src/Magnum/GL/CubeMapTexture.cpp @@ -561,7 +561,7 @@ CubeMapTexture& CubeMapTexture::setCompressedSubImage(const Int level, const Vec Buffer::unbindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - glCompressedTextureSubImage3D(_id, level, offset.x(), offset.y(), offset.z(), image.size().x(), image.size().y(), image.size().z(), GLenum(compressedPixelFormat(image.format())), Implementation::occupiedCompressedImageDataSize(image, image.data().size()), image.data()); + glCompressedTextureSubImage3D(_id, level, offset.x(), offset.y(), offset.z(), image.size().x(), image.size().y(), image.size().z(), GLenum(compressedPixelFormat(image.format())), Implementation::occupiedCompressedImageDataSize(image), image.data()); return *this; } @@ -572,7 +572,7 @@ CubeMapTexture& CubeMapTexture::setCompressedSubImage(const Int level, const Vec image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - glCompressedTextureSubImage3D(_id, level, offset.x(), offset.y(), offset.z(), image.size().x(), image.size().y(), image.size().z(), GLenum(image.format()), Implementation::occupiedCompressedImageDataSize(image, image.dataSize()), nullptr); + glCompressedTextureSubImage3D(_id, level, offset.x(), offset.y(), offset.z(), image.size().x(), image.size().y(), image.size().z(), GLenum(image.format()), Implementation::occupiedCompressedImageDataSize(image), nullptr); return *this; } #endif @@ -604,7 +604,7 @@ CubeMapTexture& CubeMapTexture::setCompressedSubImage(const CubeMapCoordinate co Buffer::unbindInternal(Buffer::TargetHint::PixelUnpack); #endif Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.cubeCompressedSubImageImplementation(*this, coordinate, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image, image.data().size())); + Context::current().state().texture.cubeCompressedSubImageImplementation(*this, coordinate, level, offset, image.size(), compressedPixelFormat(image.format()), image.data(), Implementation::occupiedCompressedImageDataSize(image)); return *this; } @@ -612,7 +612,7 @@ CubeMapTexture& CubeMapTexture::setCompressedSubImage(const CubeMapCoordinate co CubeMapTexture& CubeMapTexture::setCompressedSubImage(const CubeMapCoordinate coordinate, const Int level, const Vector2i& offset, CompressedBufferImage2D& image) { image.buffer().bindInternal(Buffer::TargetHint::PixelUnpack); Context::current().state().renderer.applyPixelStorageUnpack(image.storage()); - Context::current().state().texture.cubeCompressedSubImageImplementation(*this, coordinate, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image, image.dataSize())); + Context::current().state().texture.cubeCompressedSubImageImplementation(*this, coordinate, level, offset, image.size(), image.format(), nullptr, Implementation::occupiedCompressedImageDataSize(image)); return *this; } #endif diff --git a/src/Magnum/GL/Implementation/imageProperties.h b/src/Magnum/GL/Implementation/imageProperties.h index a02ae0fe8..820970b9a 100644 --- a/src/Magnum/GL/Implementation/imageProperties.h +++ b/src/Magnum/GL/Implementation/imageProperties.h @@ -26,7 +26,7 @@ DEALINGS IN THE SOFTWARE. */ -#include "Magnum/PixelStorage.h" +#include "Magnum/Math/Vector3.h" namespace Magnum { namespace GL { namespace Implementation { @@ -40,18 +40,11 @@ namespace Magnum { namespace GL { namespace Implementation { ARB_compressed_texture_pixel_storage (which makes skip, row length etc. possible for compressed formats) didn't bother thinking about what the existing parameter is for, just left it unchanged, and nobody else in the - commitee bothered either. + commitee bothered either. */ +template std::size_t occupiedCompressedImageDataSize(const T& image) { + const auto realBlockCount = Math::Vector3{(Vector3i::pad(image.size(), 1) + image.blockSize() - Vector3i{1})/image.blockSize()}; - In case the block size properties aren't set, the actual image data size is - used as a backup, which might still be correct in most cases. */ -template std::size_t occupiedCompressedImageDataSize(const T& image, std::size_t dataSize) { - if(image.storage().compressedBlockSize().product() && image.storage().compressedBlockDataSize()) { - const auto realBlockCount = Math::Vector3{(Vector3i::pad(image.size(), 1) + image.storage().compressedBlockSize() - Vector3i{1})/image.storage().compressedBlockSize()}; - - return realBlockCount.product()*image.storage().compressedBlockDataSize(); - } - - return dataSize; + return realBlockCount.product()*image.blockDataSize(); } }}} diff --git a/src/Magnum/GL/Test/BufferImageGLTest.cpp b/src/Magnum/GL/Test/BufferImageGLTest.cpp index 145c5e772..14336ac66 100644 --- a/src/Magnum/GL/Test/BufferImageGLTest.cpp +++ b/src/Magnum/GL/Test/BufferImageGLTest.cpp @@ -52,6 +52,7 @@ struct BufferImageGLTest: OpenGLTester { void constructBufferCompressedGeneric(); void constructInvalidSize(); + void constructCompressedInvalidBlockSize(); void constructCompressedInvalidSize(); void constructMove(); @@ -67,6 +68,7 @@ struct BufferImageGLTest: OpenGLTester { void setDataCompressedGeneric(); void setDataCompressedKeepStorage(); void setDataInvalidSize(); + void setDataCompressedInvalidBlockSize(); void setDataCompressedInvalidSize(); void release(); @@ -86,6 +88,7 @@ BufferImageGLTest::BufferImageGLTest() { &BufferImageGLTest::constructBufferCompressedGeneric, &BufferImageGLTest::constructInvalidSize, + &BufferImageGLTest::constructCompressedInvalidBlockSize, &BufferImageGLTest::constructCompressedInvalidSize, &BufferImageGLTest::constructMove, @@ -101,6 +104,7 @@ BufferImageGLTest::BufferImageGLTest() { &BufferImageGLTest::setDataCompressedGeneric, &BufferImageGLTest::setDataCompressedKeepStorage, &BufferImageGLTest::setDataInvalidSize, + &BufferImageGLTest::setDataCompressedInvalidBlockSize, &BufferImageGLTest::setDataCompressedInvalidSize, &BufferImageGLTest::release, @@ -188,13 +192,16 @@ void BufferImageGLTest::constructPlaceholder() { } void BufferImageGLTest::constructCompressed() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; + const char data[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; CompressedBufferImage2D a{ - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif + CompressedPixelStorage{}.setRowLength(16), CompressedPixelFormat::RGBAS3tcDxt1, - {4, 4}, data, BufferUsage::StaticDraw}; + {12, 8}, data, BufferUsage::StaticDraw}; #ifndef MAGNUM_TARGET_GLES const auto imageData = a.buffer().data(); @@ -202,12 +209,12 @@ void BufferImageGLTest::constructCompressed() { MAGNUM_VERIFY_NO_GL_ERROR(); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - #endif + CORRADE_COMPARE(a.storage().rowLength(), 16); CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt1); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.dataSize(), 8); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(a.dataSize(), 8*8); /** @todo How to verify the contents in ES? */ #ifndef MAGNUM_TARGET_GLES @@ -217,13 +224,16 @@ void BufferImageGLTest::constructCompressed() { } void BufferImageGLTest::constructCompressedGeneric() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; + const char data[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; CompressedBufferImage2D a{ - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif + CompressedPixelStorage{}.setRowLength(16), Magnum::CompressedPixelFormat::Bc1RGBAUnorm, - {4, 4}, data, BufferUsage::StaticDraw}; + {12, 8}, data, BufferUsage::StaticDraw}; #ifndef MAGNUM_TARGET_GLES const auto imageData = a.buffer().data(); @@ -231,12 +241,12 @@ void BufferImageGLTest::constructCompressedGeneric() { MAGNUM_VERIFY_NO_GL_ERROR(); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - #endif + CORRADE_COMPARE(a.storage().rowLength(), 16); CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt1); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.dataSize(), 8); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(a.dataSize(), 8*8); /** @todo How to verify the contents in ES? */ #ifndef MAGNUM_TARGET_GLES @@ -251,6 +261,8 @@ void BufferImageGLTest::constructCompressedPlaceholder() { CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), CompressedPixelFormat{}); + CORRADE_COMPARE(a.blockSize(), Vector3i{}); + CORRADE_COMPARE(a.blockDataSize(), 0); CORRADE_COMPARE(a.size(), Vector2i{}); CORRADE_COMPARE(a.dataSize(), 0); CORRADE_VERIFY(a.buffer().id()); @@ -265,6 +277,8 @@ void BufferImageGLTest::constructCompressedPlaceholder() { CORRADE_COMPARE(a.storage().skip(), (Vector3i{1, 0, 0})); CORRADE_COMPARE(a.storage().rowLength(), 12); CORRADE_COMPARE(a.format(), CompressedPixelFormat{}); + CORRADE_COMPARE(a.blockSize(), Vector3i{}); + CORRADE_COMPARE(a.blockDataSize(), 0); CORRADE_COMPARE(a.size(), Vector2i{}); CORRADE_COMPARE(a.dataSize(), 0); CORRADE_VERIFY(a.buffer().id()); @@ -334,17 +348,20 @@ void BufferImageGLTest::constructBufferGeneric() { } void BufferImageGLTest::constructBufferCompressed() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; + const char data[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; Buffer buffer; buffer.setData(data, BufferUsage::StaticDraw); const UnsignedInt id = buffer.id(); CompressedBufferImage2D a{ - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif + CompressedPixelStorage{}.setRowLength(16), CompressedPixelFormat::RGBAS3tcDxt1, - {4, 4}, Utility::move(buffer), sizeof(data)}; + {12, 8}, Utility::move(buffer), sizeof(data)}; #ifndef MAGNUM_TARGET_GLES const auto imageData = a.buffer().data(); @@ -352,14 +369,14 @@ void BufferImageGLTest::constructBufferCompressed() { MAGNUM_VERIFY_NO_GL_ERROR(); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - #endif + CORRADE_COMPARE(a.storage().rowLength(), 16); CORRADE_VERIFY(!buffer.id()); CORRADE_COMPARE(a.buffer().id(), id); CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt1); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.dataSize(), 8); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(a.dataSize(), 8*8); /** @todo How to verify the contents in ES? */ #ifndef MAGNUM_TARGET_GLES @@ -369,17 +386,20 @@ void BufferImageGLTest::constructBufferCompressed() { } void BufferImageGLTest::constructBufferCompressedGeneric() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; + const char data[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; Buffer buffer; buffer.setData(data, BufferUsage::StaticDraw); const UnsignedInt id = buffer.id(); CompressedBufferImage2D a{ - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif + CompressedPixelStorage{}.setRowLength(16), Magnum::CompressedPixelFormat::Bc1RGBAUnorm, - {4, 4}, Utility::move(buffer), sizeof(data)}; + {12, 8}, Utility::move(buffer), sizeof(data)}; #ifndef MAGNUM_TARGET_GLES const auto imageData = a.buffer().data(); @@ -387,14 +407,14 @@ void BufferImageGLTest::constructBufferCompressedGeneric() { MAGNUM_VERIFY_NO_GL_ERROR(); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - #endif + CORRADE_COMPARE(a.storage().rowLength(), 16); CORRADE_VERIFY(!buffer.id()); CORRADE_COMPARE(a.buffer().id(), id); CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt1); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.dataSize(), 8); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(a.dataSize(), 8*8); /** @todo How to verify the contents in ES? */ #ifndef MAGNUM_TARGET_GLES @@ -412,10 +432,43 @@ void BufferImageGLTest::constructInvalidSize() { CORRADE_COMPARE(out, "GL::BufferImage: data too small, got 11 but expected at least 12 bytes\n"); } -void BufferImageGLTest::constructCompressedInvalidSize() { +void BufferImageGLTest::constructCompressedInvalidBlockSize() { CORRADE_SKIP_IF_NO_ASSERT(); - CORRADE_EXPECT_FAIL("Size checking for compressed image data is not implemented yet."); + /* This is okay */ + const char data[8]{}; + CompressedBufferImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(8), + CompressedPixelFormat::SRGBS3tcDxt1, {1, 1}, data, BufferUsage::StaticDraw}; + + /* Tested mainly in ImageViewTest, here is just a subset to verify the same + helper is used internally and a proper prefix is printed. The block size + is picked up implicitly so it cannot be 0 or >= 256. */ + Containers::String out; + Error redirectError{&out}; + CompressedBufferImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 5}) + .setCompressedBlockDataSize(8), + CompressedPixelFormat::SRGBS3tcDxt1, {1, 1}, data, BufferUsage::StaticDraw}; + CompressedBufferImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(4), + CompressedPixelFormat::SRGBS3tcDxt1, {1, 1}, data, BufferUsage::StaticDraw}; + CompressedBufferImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({0, 1, 0})}; + CompressedBufferImage2D{CompressedPixelStorage{} + .setCompressedBlockDataSize(1)}; + CORRADE_COMPARE_AS(out, + "GL::CompressedBufferImage: expected pixel storage block size to be either not set at all or equal to {4, 4, 1} but got {5, 5, 5}\n" + "GL::CompressedBufferImage: expected pixel storage block data size to be either not set at all or equal to 8 but got 4\n" + "GL::CompressedBufferImage: expected pixel storage block size to not be set at all but got {0, 1, 0}\n" + "GL::CompressedBufferImage: expected pixel storage block data size to not be set at all but got 1\n", + TestSuite::Compare::String); +} + +void BufferImageGLTest::constructCompressedInvalidSize() { + CORRADE_SKIP_IF_NO_ASSERT(); /* Too small for given format */ { @@ -488,8 +541,16 @@ void BufferImageGLTest::constructMove() { } void BufferImageGLTest::constructMoveCompressed() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; - CompressedBufferImage2D a{CompressedPixelFormat::RGBAS3tcDxt1, {4, 4}, data, BufferUsage::StaticDraw}; + const char data[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; + CompressedBufferImage2D a{ + CompressedPixelStorage{}.setRowLength(16), + CompressedPixelFormat::RGBAS3tcDxt1, + {12, 8}, data, BufferUsage::StaticDraw}; const Int id = a.buffer().id(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -501,20 +562,20 @@ void BufferImageGLTest::constructMoveCompressed() { CORRADE_COMPARE(a.size(), Vector2i()); CORRADE_COMPARE(a.dataSize(), 0); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{0}); - #endif + CORRADE_COMPARE(b.storage().rowLength(), 16); CORRADE_COMPARE(b.format(), CompressedPixelFormat::RGBAS3tcDxt1); - CORRADE_COMPARE(b.size(), Vector2i(4, 4)); - CORRADE_COMPARE(b.dataSize(), 8); + CORRADE_COMPARE(b.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(b.blockDataSize(), 8); + CORRADE_COMPARE(b.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(b.dataSize(), 8*8); CORRADE_COMPARE(b.buffer().id(), id); - const unsigned char data2[] = { 'a', 0, 0, 0, 'b', 0, 0, 0, 'c', 0, 0, 0, 'd', 0, 0, 0 }; + const char data2[]{ + 'h', 0, 0, 0, 'i', 0, 0, 0, 'j', 0, 0, 0, 'k', 0, 0, 0 + }; CompressedBufferImage2D c{ - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif - CompressedPixelFormat::RGBAS3tcDxt1, {8, 4}, data2, BufferUsage::StaticDraw}; + CompressedPixelFormat::RGBAAstc5x5, {5, 5}, + data2, BufferUsage::StaticDraw}; const Int cId = c.buffer().id(); c = Utility::move(b); @@ -522,15 +583,17 @@ void BufferImageGLTest::constructMoveCompressed() { CORRADE_VERIFY(cId > 0); CORRADE_COMPARE(b.buffer().id(), cId); - CORRADE_COMPARE(b.size(), Vector2i(8, 4)); + CORRADE_COMPARE(b.size(), (Vector2i{5, 5})); CORRADE_COMPARE(b.dataSize(), 16); #ifndef MAGNUM_TARGET_GLES CORRADE_COMPARE(c.storage().compressedBlockSize(), Vector3i{0}); #endif CORRADE_COMPARE(c.format(), CompressedPixelFormat::RGBAS3tcDxt1); - CORRADE_COMPARE(c.size(), Vector2i(4, 4)); - CORRADE_COMPARE(c.dataSize(), 8); + CORRADE_COMPARE(c.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(c.blockDataSize(), 8); + CORRADE_COMPARE(c.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(c.dataSize(), 8*8); CORRADE_COMPARE(c.buffer().id(), id); CORRADE_VERIFY(std::is_nothrow_move_constructible::value); @@ -549,17 +612,16 @@ void BufferImageGLTest::dataProperties() { } void BufferImageGLTest::dataPropertiesCompressed() { - /* Yes, I know, this is totally bogus and doesn't match the BC1 format */ - const char data[1]{}; + const char data[336]{}; CompressedBufferImage3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({3, 4, 5}) - .setCompressedBlockDataSize(16) - .setImageHeight(12) - .setSkip({5, 8, 11}), - Magnum::CompressedPixelFormat::Bc1RGBAUnorm, {2, 8, 11}, data, BufferUsage::StaticDraw}; + .setRowLength(12) + .setImageHeight(8) + .setSkip({8, 4, 4}), + CompressedPixelFormat::RGBAS3tcDxt1, {2, 3, 3}, + data, BufferUsage::StaticDraw}; CORRADE_COMPARE(image.dataProperties(), - (std::pair, Math::Vector3>{{2*16, 2*16, 9*16}, {1, 3, 3}})); + (std::pair, Math::Vector3>{{16, 24, 192}, {3, 2, 3}})); } void BufferImageGLTest::setData() { @@ -654,15 +716,21 @@ void BufferImageGLTest::setDataKeepStorage() { } void BufferImageGLTest::setDataCompressed() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; - CompressedBufferImage2D a{CompressedPixelFormat::RGBAS3tcDxt1, {4, 4}, data, BufferUsage::StaticDraw}; + const char data[]{ + 'h', 0, 0, 0, 'i', 0, 0, 0, 'j', 0, 0, 0, 'k', 0, 0, 0 + }; + CompressedBufferImage2D a{CompressedPixelFormat::RGBAAstc5x5, {5, 5}, data, BufferUsage::StaticDraw}; - const char data2[] = { 'a', 0, 0, 0, 'b', 0, 0, 0, 'c', 0, 0, 0, 'd', 0, 0, 0 }; + const char data2[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; a.setData( - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif - CompressedPixelFormat::RGBAS3tcDxt3, {8, 4}, data2, BufferUsage::StaticDraw); + CompressedPixelStorage{}.setRowLength(16), + CompressedPixelFormat::RGBAS3tcDxt1, + {12, 8}, data2, BufferUsage::StaticDraw); #ifndef MAGNUM_TARGET_GLES const auto imageData = a.buffer().data(); @@ -670,12 +738,12 @@ void BufferImageGLTest::setDataCompressed() { MAGNUM_VERIFY_NO_GL_ERROR(); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - #endif - CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt3); - CORRADE_COMPARE(a.size(), Vector2i(8, 4)); - CORRADE_COMPARE(a.dataSize(), 16); + CORRADE_COMPARE(a.storage().rowLength(), 16); + CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt1); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(a.dataSize(), 8*8); /** @todo How to verify the contents in ES? */ #ifndef MAGNUM_TARGET_GLES @@ -685,15 +753,21 @@ void BufferImageGLTest::setDataCompressed() { } void BufferImageGLTest::setDataCompressedGeneric() { - const char data[] = { 'a', 0, 0, 0, 'b', 0, 0, 0 }; - CompressedBufferImage2D a{CompressedPixelFormat::RGBAS3tcDxt1, {4, 4}, data, BufferUsage::StaticDraw}; + const char data[]{ + 'h', 0, 0, 0, 'i', 0, 0, 0, 'j', 0, 0, 0, 'k', 0, 0, 0 + }; + CompressedBufferImage2D a{CompressedPixelFormat::RGBAAstc5x5, {5, 5}, data, BufferUsage::StaticDraw}; - const char data2[] = { 'a', 0, 0, 0, 'b', 0, 0, 0, 'c', 0, 0, 0, 'd', 0, 0, 0 }; + const char data2[]{ + 'a', 0, 0, 0, 0, 0, 0, 0, 'b', 0, 0, 0, 0, 0, 0, 0, + 'c', 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0, 0, 0, 0, + 'e', 0, 0, 0, 0, 0, 0, 0, 'f', 0, 0, 0, 0, 0, 0, 0, + 'g', 0, 0, 0, 0, 0, 0, 0, 'h', 0, 0, 0, 0, 0, 0, 0 + }; a.setData( - #ifndef MAGNUM_TARGET_GLES - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - #endif - Magnum::CompressedPixelFormat::Bc2RGBAUnorm, {8, 4}, data2, BufferUsage::StaticDraw); + CompressedPixelStorage{}.setRowLength(16), + Magnum::CompressedPixelFormat::Bc1RGBAUnorm, + {12, 8}, data2, BufferUsage::StaticDraw); #ifndef MAGNUM_TARGET_GLES const auto imageData = a.buffer().data(); @@ -701,12 +775,12 @@ void BufferImageGLTest::setDataCompressedGeneric() { MAGNUM_VERIFY_NO_GL_ERROR(); - #ifndef MAGNUM_TARGET_GLES - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - #endif - CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt3); - CORRADE_COMPARE(a.size(), Vector2i(8, 4)); - CORRADE_COMPARE(a.dataSize(), 16); + CORRADE_COMPARE(a.storage().rowLength(), 16); + CORRADE_COMPARE(a.format(), CompressedPixelFormat::RGBAS3tcDxt1); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); + CORRADE_COMPARE(a.dataSize(), 8*8); /** @todo How to verify the contents in ES? */ #ifndef MAGNUM_TARGET_GLES @@ -763,6 +837,39 @@ void BufferImageGLTest::setDataInvalidSize() { TestSuite::Compare::String); } +void BufferImageGLTest::setDataCompressedInvalidBlockSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + /* This is okay */ + const char data[8]{}; + CompressedBufferImage2D image; + image.setData( + CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(8), + CompressedPixelFormat::SRGBS3tcDxt1, {1, 1}, data, BufferUsage::StaticDraw); + + /* Tested mainly in ImageViewTest, here is just a subset to verify the same + helper is used internally and a proper prefix is printed. The block size + is picked up implicitly so it cannot be 0 or >= 256. */ + Containers::String out; + Error redirectError{&out}; + image.setData( + CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 5}) + .setCompressedBlockDataSize(8), + CompressedPixelFormat::SRGBS3tcDxt1, {1, 1}, data, BufferUsage::StaticDraw); + image.setData( + CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(4), + CompressedPixelFormat::SRGBS3tcDxt1, {1, 1}, data, BufferUsage::StaticDraw); + CORRADE_COMPARE_AS(out, + "GL::CompressedBufferImage::setData(): expected pixel storage block size to be either not set at all or equal to {4, 4, 1} but got {5, 5, 5}\n" + "GL::CompressedBufferImage::setData(): expected pixel storage block data size to be either not set at all or equal to 8 but got 4\n", + TestSuite::Compare::String); +} + void BufferImageGLTest::setDataCompressedInvalidSize() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -770,8 +877,6 @@ void BufferImageGLTest::setDataCompressedInvalidSize() { CompressedBufferImage2D a{CompressedPixelFormat::RGBAS3tcDxt1, {4, 4}, "helloheyhellhe", BufferUsage::StaticDraw}; CORRADE_COMPARE(a.dataSize(), 15); - CORRADE_EXPECT_FAIL("Size checking for compressed image data is not implemented yet."); - /* Too small for given format */ { Containers::String out; diff --git a/src/Magnum/GL/Test/BufferImageTest.cpp b/src/Magnum/GL/Test/BufferImageTest.cpp index f362e4462..abfd6d113 100644 --- a/src/Magnum/GL/Test/BufferImageTest.cpp +++ b/src/Magnum/GL/Test/BufferImageTest.cpp @@ -70,6 +70,8 @@ void BufferImageTest::constructNoCreateCompressed() { CORRADE_COMPARE(image.buffer().id(), 0); CORRADE_COMPARE(image.size(), Vector2i{}); CORRADE_COMPARE(image.format(), CompressedPixelFormat{}); + CORRADE_COMPARE(image.blockSize(), Vector3i{}); + CORRADE_COMPARE(image.blockDataSize(), 0); CORRADE_COMPARE(image.dataSize(), 0); } diff --git a/src/Magnum/GL/Test/PixelStorageTest.cpp b/src/Magnum/GL/Test/PixelStorageTest.cpp index 736bf20ad..edb30986c 100644 --- a/src/Magnum/GL/Test/PixelStorageTest.cpp +++ b/src/Magnum/GL/Test/PixelStorageTest.cpp @@ -48,51 +48,37 @@ void PixelStorageTest::occupiedCompressedImageDataSize() { being 55x28x12 */ const char data[(55/5)*(28/4)*(12/2)*16]{}; - /* If we have no block properties, the passed data size is taken, assuming - it's the best bet. Image size or row length and image height isn't taken - into account in any way. */ - { - CompressedImageView3D image{ - CompressedPixelStorage{} - .setRowLength(55) - .setImageHeight(28) - .setSkip({10, 8, 4}), - 42069, /* custom format */ - {35, 20, 6}, - data}; - CORRADE_COMPARE(Implementation::occupiedCompressedImageDataSize(image, 1337), 1337); - - /* If we have block properties, the size is calculated from those and the - *image* size, not the supplied row length / image height. This is what - GL wants, it has no relation to anything useful. For comparison see the PixelStorageTest::dataOffsetSizeCompressed() test in the core + /* The size is calculated from block properties and the *image* size, not + the supplied row length / image height. This is what GL wants, it has no + relation to anything useful. For comparison see the PixelStorageTest::dataOffsetSizeCompressed() test in the core library. */ - } { + { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(55) .setImageHeight(28) .setSkip({10, 8, 4}), 42069, /* custom format */ + {5, 4, 2}, + 16, {35, 20, 6}, data}; - CORRADE_COMPARE(Implementation::occupiedCompressedImageDataSize(image, 1337), + CORRADE_COMPARE(Implementation::occupiedCompressedImageDataSize(image), (35/5)*(20/4)*(6/2)*16); /* Same result if the size isn't whole blocks */ } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(55) .setImageHeight(28) .setSkip({10, 8, 4}), 42069, /* custom format */ + {5, 4, 2}, + 16, {31, 19, 5}, data}; - CORRADE_COMPARE(Implementation::occupiedCompressedImageDataSize(image, 1337), + CORRADE_COMPARE(Implementation::occupiedCompressedImageDataSize(image), (35/5)*(20/4)*(6/2)*16); } } diff --git a/src/Magnum/Image.cpp b/src/Magnum/Image.cpp index 99b0e477b..43326771b 100644 --- a/src/Magnum/Image.cpp +++ b/src/Magnum/Image.cpp @@ -96,18 +96,45 @@ template Containers::Array Image::rele return data; } -template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: _storage{storage}, _format{format}, _flags{flags}, _size{size}, _data{Utility::move(data)} { +template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: CompressedImage{storage, format, (CORRADE_CONSTEXPR_ASSERT(!isCompressedPixelFormatImplementationSpecific(format), "CompressedImage: can't determine block size of an implementation-specific pixel format" << Debug::hex << compressedPixelFormatUnwrap(format) << Debug::nospace << ", pass it explicitly"), compressedPixelFormatBlockSize(format)), ( + /* Have to do it like this to ensure the above assertion is printed before + the subsequent one from compressedPixelFormatBlockDataSize(), as + compilers are free to reorder these as they want */ #ifndef CORRADE_NO_ASSERT + isCompressedPixelFormatImplementationSpecific(format) ? 0 : + #endif + compressedPixelFormatBlockDataSize(format) +), size, Utility::move(data), flags} {} + +template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: _storage{storage}, _format{format}, _flags{flags}, _blockSize{Vector3ub{blockSize}}, _blockDataSize{UnsignedByte(blockDataSize)}, _size{size}, _data{Utility::move(data)} { + #ifndef CORRADE_NO_ASSERT + const bool passed = + Implementation::checkBlockProperties("CompressedImage:", blockSize, blockDataSize); + #ifdef CORRADE_GRACEFUL_ASSERT + /* If the above check fails on a build with graceful assertions, the data + size check below could then die on division by zero. Exit early in that + case. */ + /** @todo any better idea to handle this? ugh */ + if(!passed) return; + #else + static_cast(passed); + #endif + Implementation::checkBlockPropertiesForStorage("CompressedImage:", blockSize, blockDataSize, storage); + CORRADE_ASSERT(Implementation::compressedImageDataSize(*this) <= _data.size(), "CompressedImage: data too small, got" << _data.size() << "but expected at least" << Implementation::compressedImageDataSize(*this) << "bytes", ); Implementation::checkImageFlagsForSize("CompressedImage:", flags, size); #endif } -template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: CompressedImage{storage, compressedPixelFormatWrap(format), size, Utility::move(data), flags} {} +template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const UnsignedInt format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: CompressedImage{storage, compressedPixelFormatWrap(format), blockSize, blockDataSize, size, Utility::move(data), flags} {} -template CompressedImage::CompressedImage(const CompressedPixelStorage storage) noexcept: _storage{storage}, _format{} {} +template CompressedImage::CompressedImage(const CompressedPixelStorage storage) noexcept: _storage{storage}, _format{}, _blockDataSize{} { + CORRADE_ASSERT(storage.compressedBlockSize() == Vector3i{}, + "CompressedImage: expected pixel storage block size to not be set at all but got" << Debug::packed << storage.compressedBlockSize(), ); + CORRADE_ASSERT(!storage.compressedBlockDataSize(), + "CompressedImage: expected pixel storage block data size to not be set at all but got" << storage.compressedBlockDataSize(), ); +} -template CompressedImage::CompressedImage(CompressedImage&& other) noexcept: _storage{Utility::move(other._storage)}, _format{Utility::move(other._format)}, _flags{other._flags}, _size{Utility::move(other._size)}, _data{Utility::move(other._data)} -{ +template CompressedImage::CompressedImage(CompressedImage&& other) noexcept: _storage{other._storage}, _format{other._format}, _flags{other._flags}, _blockSize{other._blockSize}, _blockDataSize{other._blockDataSize}, _size{other._size}, _data{Utility::move(other._data)} { other._size = {}; } @@ -116,17 +143,19 @@ template CompressedImage& CompressedImage CompressedImage::operator BasicMutableCompressedImageView() { - return BasicMutableCompressedImageView{_storage, _format, _size, _data, _flags}; + return BasicMutableCompressedImageView{_storage, _format, Vector3i{_blockSize}, _blockDataSize, _size, _data, _flags}; } template CompressedImage::operator BasicCompressedImageView() const { - return BasicCompressedImageView{_storage, _format, _size, _data, _flags}; + return BasicCompressedImageView{_storage, _format, Vector3i{_blockSize}, _blockDataSize, _size, _data, _flags}; } template std::pair, VectorTypeFor> CompressedImage::dataProperties() const { diff --git a/src/Magnum/Image.h b/src/Magnum/Image.h index f362ed090..f43742786 100644 --- a/src/Magnum/Image.h +++ b/src/Magnum/Image.h @@ -573,8 +573,23 @@ template class CompressedImage { * @param data Image data * @param flags Image layout flags * - * For a 3D image, if @p flags contain @ref ImageFlag3D::CubeMap, the - * @p size is expected to match its restrictions. + * The @p data array is expected to be of proper size for given + * parameters. For a 3D image, if @p flags contain + * @ref ImageFlag3D::CubeMap, the @p size is expected to match its + * restrictions. + * + * The @p format is expected to not be implementation-specific, use the + * @ref CompressedImage(CompressedPixelStorage, CompressedPixelFormat, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::Array&&, ImageFlags) + * overload to explicitly pass pass an implementation-specific + * @ref CompressedPixelFormat along with its block properties, or the + * @ref CompressedImage(CompressedPixelStorage, T, const VectorTypeFor&, Containers::Array&&, ImageFlags) + * overload with the original implementation-specific enum type to have + * the pixel size determined implicitly. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * properties of given @p format. */ explicit CompressedImage(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}) noexcept; @@ -590,6 +605,51 @@ template class CompressedImage { */ explicit CompressedImage(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}) noexcept: CompressedImage{{}, format, size, Utility::move(data), flags} {} + /** + * @brief Construct a compressed image with an implementation-specific pixel format + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param blockSize Size of a compressed block in given format, + * in pixels + * @param blockDataSize Size of a compressed block in given format, + * in bytes + * @param size Image size, in pixels + * @param data Image data + * @param flags Image layout flags + * @m_since_latest + * + * Unlike with @ref CompressedImage(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, ImageFlags), + * where block size is determined automatically + * @ref compressedPixelFormatBlockSize() and + * @ref compressedPixelFormatBlockDataSize(), this allows you to + * specify an implementation-specific pixel format and block properties + * directly. Uses @ref compressedPixelFormatWrap() internally to wrap + * @p format in @ref CompressedPixelFormat. The @p blockSize and + * @p blockDataSize is expected to be greater than @cpp 0 @ce and less + * than @cpp 256 @ce. Note that the blocks can be 3D even for 2D images + * and 2D or 3D even for 1D images, in which case only the first slice + * in the extra dimensions is used. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * @p blockSize and @p blockDataSize. + * + * The @p data array is expected to be of proper size for given + * parameters. For a 3D image, if @p flags contain + * @ref ImageFlag3D::CubeMap, the @p size is expected to match its + * restrictions. + */ + explicit CompressedImage(CompressedPixelStorage storage, UnsignedInt format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref compressedPixelFormatWrap(). + */ + explicit CompressedImage(CompressedPixelStorage storage, CompressedPixelFormat format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}) noexcept; + /** * @brief Construct a compressed image with implementation-specific format * @param storage Storage of compressed pixel data @@ -598,11 +658,10 @@ template class CompressedImage { * @param data Image data * @param flags Image layout flags * - * Uses @ref compressedPixelFormatWrap() internally to convert - * @p format to @ref CompressedPixelFormat. - * - * For a 3D image, if @p flags contain @ref ImageFlag3D::CubeMap, the - * @p size is expected to match its restrictions. + * Uses ADL to find a corresponding @cpp compressedPixelFormatBlockSize(T) @ce + * and @cpp compressedPixelFormatBlockDataSize(T) @ce overloads, then + * calls @ref CompressedImage(CompressedPixelStorage, UnsignedInt, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::Array&&, ImageFlags) + * with determined block size properties. */ template explicit CompressedImage(CompressedPixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}) noexcept; @@ -622,9 +681,13 @@ template class CompressedImage { * @brief Construct an image placeholder * @param storage Storage of compressed pixel data * - * Format is undefined, size is zero, data is @cpp nullptr @ce and - * data layout flags are empty. Move over a non-empty instance to make - * it useful. + * Format and block properties are undefined, size is zero, data is + * @cpp nullptr @ce and data layout flags are empty. Move over a + * non-empty instance to make it useful. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be both zero. */ /* No ImageFlags parameter here as this constructor is mainly used to query GL textures, and there the flags are forcibly reset */ @@ -681,6 +744,25 @@ template class CompressedImage { */ CompressedPixelFormat format() const { return _format; } + /** + * @brief Size of a compressed block in pixels + * @m_since_latest + * + * Note that the blocks can be 3D even for 2D images and 2D or 3D even + * for 1D images, in which case only the first slice in the extra + * dimensions is used. + * @see @ref blockDataSize(), @ref compressedPixelFormatBlockSize() + */ + Vector3i blockSize() const { return Vector3i{_blockSize}; } + + /** + * @brief Size of a compressed block in bytes + * @m_since_latest + * + * @see @ref blockSize(), @ref compressedPixelFormatBlockDataSize() + */ + UnsignedInt blockDataSize() const { return _blockDataSize; } + /** @brief Image size in pixels */ /* Unlike other getters this one is a const& so it's possible to slice to the sizes when all images are in an array, for example for use @@ -732,13 +814,14 @@ template class CompressedImage { Containers::Array release(); private: - /* To be made public once block size and block data size are stored - together with the image */ - explicit CompressedImage(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags) noexcept; - CompressedPixelStorage _storage; CompressedPixelFormat _format; ImageFlags _flags; + /* Largest blocks are 12x12 in ASTC and at most 32 bytes, so an 8-bit + type should be more than enough. As even 1D images can have 3D + blocks, the member isn't dependent on dimension count. */ + Vector3ub _blockSize; + UnsignedByte _blockDataSize; VectorTypeFor _size; Containers::Array _data; }; @@ -772,7 +855,7 @@ template template inline Image::Ima "format types larger than 32bits are not supported"); } -template template inline CompressedImage::CompressedImage(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: CompressedImage{storage, UnsignedInt(format), size, Utility::move(data), flags} { +template template inline CompressedImage::CompressedImage(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags) noexcept: CompressedImage{storage, UnsignedInt(format), compressedPixelFormatBlockSize(format), compressedPixelFormatBlockDataSize(format), size, Utility::move(data), flags} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } diff --git a/src/Magnum/ImageView.cpp b/src/Magnum/ImageView.cpp index dd2b861d0..f1306713f 100644 --- a/src/Magnum/ImageView.cpp +++ b/src/Magnum/ImageView.cpp @@ -68,26 +68,66 @@ template auto ImageView::pixels( return Implementation::imagePixelView(*this, data()); } -template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: _storage{storage}, _format{format}, _flags{flags}, _size{size}, _data{reinterpret_cast(data.data()), data.size()} { +template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: CompressedImageView{storage, format, (CORRADE_CONSTEXPR_ASSERT(!isCompressedPixelFormatImplementationSpecific(format), "CompressedImageView: can't determine block size of an implementation-specific pixel format" << Debug::hex << compressedPixelFormatUnwrap(format) << Debug::nospace << ", pass it explicitly"), compressedPixelFormatBlockSize(format)), ( + /* Have to do it like this to ensure the above assertion is printed before + the subsequent one from compressedPixelFormatBlockDataSize(), as + compilers are free to reorder these as they want */ #ifndef CORRADE_NO_ASSERT + isCompressedPixelFormatImplementationSpecific(format) ? 0 : + #endif + compressedPixelFormatBlockDataSize(format) +), size, data, flags} {} + +template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: _storage{storage}, _format{format}, _flags{flags}, _blockSize{Vector3ub{blockSize}}, _blockDataSize{UnsignedByte(blockDataSize)}, _size{size}, _data{reinterpret_cast(data.data()), data.size()} { + #ifndef CORRADE_NO_ASSERT + const bool passed = + Implementation::checkBlockProperties("CompressedImageView:", blockSize, blockDataSize); + #ifdef CORRADE_GRACEFUL_ASSERT + /* If the above check fails on a build with graceful assertions, the data + size check below could then die on division by zero. Exit early in that + case. */ + /** @todo any better idea to handle this? ugh */ + if(!passed) return; + #else + static_cast(passed); + #endif + Implementation::checkBlockPropertiesForStorage("CompressedImageView:", blockSize, blockDataSize, storage); + CORRADE_ASSERT(Implementation::compressedImageDataSize(*this) <= data.size(), "CompressedImageView: data too small, got" << data.size() << "but expected at least" << Implementation::compressedImageDataSize(*this) << "bytes", ); Implementation::checkImageFlagsForSize("CompressedImageView:", flags, size); #endif } -template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const ImageFlags flags) noexcept: _storage{storage}, _format{format}, _flags{flags}, _size{size} { +template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const UnsignedInt format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: CompressedImageView{storage, compressedPixelFormatWrap(format), blockSize, blockDataSize, size, data, flags} {} + +template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, const ImageFlags flags) noexcept: _storage{storage}, _format{format}, _flags{flags}, _blockSize{Vector3ub{blockSize}}, _blockDataSize{UnsignedByte(blockDataSize)}, _size{size} { #ifndef CORRADE_NO_ASSERT + Implementation::checkBlockProperties("CompressedImageView:", blockSize, blockDataSize); + Implementation::checkBlockPropertiesForStorage("CompressedImageView:", blockSize, blockDataSize, storage); Implementation::checkImageFlagsForSize("CompressedImageView:", flags, size); #endif } -template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: CompressedImageView{storage, compressedPixelFormatWrap(format), size, data, flags} {} +template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const UnsignedInt format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, const ImageFlags flags) noexcept: CompressedImageView{storage, compressedPixelFormatWrap(format), blockSize, blockDataSize, size, flags} {} -template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, const ImageFlags flags) noexcept: CompressedImageView{storage, compressedPixelFormatWrap(format), size, flags} {} +template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const ImageFlags flags) noexcept: CompressedImageView{storage, format, (CORRADE_CONSTEXPR_ASSERT(!isCompressedPixelFormatImplementationSpecific(format), "CompressedImageView: can't determine block size of an implementation-specific pixel format" << Debug::hex << compressedPixelFormatUnwrap(format) << Debug::nospace << ", pass it explicitly"), compressedPixelFormatBlockSize(format)), ( + /* Have to do it like this to ensure the above assertion is printed before + the subsequent one from compressedPixelFormatBlockDataSize(), as + compilers are free to reorder these as they want */ + #ifndef CORRADE_NO_ASSERT + isCompressedPixelFormatImplementationSpecific(format) ? 0 : + #endif + compressedPixelFormatBlockDataSize(format) +), size, flags} {} template std::pair, VectorTypeFor> CompressedImageView::dataProperties() const { return Implementation::compressedImageDataProperties(*this); } +template void CompressedImageView::setData(const Containers::ArrayView data) { + CORRADE_ASSERT(Implementation::compressedImageDataSize(*this) <= data.size(), "CompressedImageView::setData(): data too small, got" << data.size() << "but expected at least" << Implementation::compressedImageDataSize(*this) << "bytes", ); + _data = {reinterpret_cast(data.data()), data.size()}; +} + template class MAGNUM_EXPORT ImageView<1, const char>; template class MAGNUM_EXPORT ImageView<2, const char>; template class MAGNUM_EXPORT ImageView<3, const char>; diff --git a/src/Magnum/ImageView.h b/src/Magnum/ImageView.h index 7df1b75a5..8a509152d 100644 --- a/src/Magnum/ImageView.h +++ b/src/Magnum/ImageView.h @@ -730,8 +730,23 @@ template class CompressedImageView { * @param data Image data * @param flags Image layout flags * - * For a 3D image, if @p flags contain @ref ImageFlag3D::CubeMap, the - * @p size is expected to match its restrictions. + * The @p data array is expected to be of proper size for given + * parameters. For a 3D image, if @p flags contain + * @ref ImageFlag3D::CubeMap, the @p size is expected to match its + * restrictions. + * + * The @p format is expected to not be implementation-specific, use the + * @ref CompressedImageView(CompressedPixelStorage, CompressedPixelFormat, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::ArrayView, ImageFlags) + * overload to explicitly pass pass an implementation-specific + * @ref CompressedPixelFormat along with its block properties, or the + * @ref CompressedImageView(CompressedPixelStorage, U, const VectorTypeFor&, Containers::ArrayView, ImageFlags) + * overload with the original implementation-specific enum type to have + * the pixel size determined implicitly. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * properties of given @p format. */ explicit CompressedImageView(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, Containers::ArrayView data, ImageFlags flags = {}) noexcept; @@ -758,6 +773,19 @@ template class CompressedImageView { * assign a memory view to the image. For a 3D image, if @p flags * contain @ref ImageFlag3D::CubeMap, the @p size is expected to match * its restrictions. + * + * The @p format is expected to not be implementation-specific, use the + * @ref CompressedImageView(CompressedPixelStorage, CompressedPixelFormat, const Vector3i&, UnsignedInt, const VectorTypeFor&, ImageFlags) + * overload to explicitly pass pass an implementation-specific + * @ref CompressedPixelFormat along with its block properties, or the + * @ref CompressedImageView(CompressedPixelStorage, U, const VectorTypeFor&, ImageFlags) + * overload with the original implementation-specific enum type to have + * the pixel size determined implicitly. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * properties of given @p format. */ explicit CompressedImageView(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, ImageFlags flags = {}) noexcept; @@ -772,6 +800,94 @@ template class CompressedImageView { */ explicit CompressedImageView(CompressedPixelFormat format, const VectorTypeFor& size, ImageFlags flags = {}) noexcept: CompressedImageView{{}, format, size, flags} {} + /** + * @brief Construct a view with an implementation-specific pixel format + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param blockSize Size of a compressed block in given format, + * in pixels + * @param blockDataSize Size of a compressed block in given format, + * in bytes + * @param size Image size, in pixels + * @param data Image data + * @param flags Image layout flags + * @m_since_latest + * + * Unlike with @ref CompressedImageView(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::ArrayView, ImageFlags), + * where block size is determined automatically + * @ref compressedPixelFormatBlockSize() and + * @ref compressedPixelFormatBlockDataSize(), this allows you to + * specify an implementation-specific pixel format and block properties + * directly. Uses @ref compressedPixelFormatWrap() internally to wrap + * @p format in @ref CompressedPixelFormat. The @p blockSize and + * @p blockDataSize is expected to be greater than @cpp 0 @ce and less + * than @cpp 256 @ce. Note that the blocks can be 3D even for 2D images + * and 2D or 3D even for 1D images, in which case only the first slice + * in the extra dimensions is used. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * @p blockSize and @p blockDataSize. + * + * The @p data array is expected to be of proper size for given + * parameters. For a 3D image, if @p flags contain + * @ref ImageFlag3D::CubeMap, the @p size is expected to match its + * restrictions. + */ + explicit CompressedImageView(CompressedPixelStorage storage, UnsignedInt format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::ArrayView data, ImageFlags flags = {}) noexcept; + + /** @overload + * + * Equivalent to the above for @p format already wrapped with + * @ref compressedPixelFormatWrap(). + */ + explicit CompressedImageView(CompressedPixelStorage storage, CompressedPixelFormat format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::ArrayView data, ImageFlags flags = {}) noexcept; + + /** + * @brief Construct an empty view with an implementation-specific pixel format + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param blockSize Size of a compressed block in given format, + * in pixels + * @param blockDataSize Size of a compressed block in given format, + * in bytes + * @param size Image size, in pixels + * @param flags Image layout flags + * @m_since_latest + * + * Unlike with @ref CompressedImageView(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, ImageFlags), + * where block size is determined automatically + * @ref compressedPixelFormatBlockSize() and + * @ref compressedPixelFormatBlockDataSize(), this allows you to + * specify an implementation-specific pixel format and block properties + * directly. Uses @ref compressedPixelFormatWrap() internally to wrap + * @p format in @ref CompressedPixelFormat. The @p blockSize and + * @p blockDataSize is expected to be greater than @cpp 0 @ce and less + * than @cpp 256 @ce. Note that the blocks can be 3D even for 2D images + * and 2D or 3D even for 1D images, in that case only the first slice + * in the extra dimensions is used. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * @p blockSize and @p blockDataSize. + * + * Data pointer is set to @cpp nullptr @ce, call @ref setData() to + * assign a memory view to the image. For a 3D image, if @p flags + * contain @ref ImageFlag3D::CubeMap, the @p size is expected to match + * its restrictions. + */ + explicit CompressedImageView(CompressedPixelStorage storage, UnsignedInt format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, ImageFlags flags = {}) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref compressedPixelFormatWrap(). + */ + explicit CompressedImageView(CompressedPixelStorage storage, CompressedPixelFormat format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, ImageFlags flags = {}) noexcept; + /** * @brief Construct an image view with implementation-specific format * @param storage Storage of compressed pixel data @@ -780,11 +896,10 @@ template class CompressedImageView { * @param data Image data * @param flags Image layout flags * - * Uses @ref compressedPixelFormatWrap() internally to convert - * @p format to @ref CompressedPixelFormat. - * - * For a 3D image, if @p flags contain @ref ImageFlag3D::CubeMap, the - * @p size is expected to match its restrictions. + * Uses ADL to find a corresponding @cpp compressedPixelFormatBlockSize(U) @ce + * and @cpp compressedPixelFormatBlockDataSize(U) @ce overloads, then + * calls @ref CompressedImageView(CompressedPixelStorage, UnsignedInt, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::ArrayView, ImageFlags) + * with determined block size properties. */ template explicit CompressedImageView(CompressedPixelStorage storage, U format, const VectorTypeFor& size, Containers::ArrayView data, ImageFlags flags = {}) noexcept; @@ -807,13 +922,10 @@ template class CompressedImageView { * @param size Image size * @param flags Image layout flags * - * Uses @ref compressedPixelFormatWrap() internally to convert - * @p format to @ref CompressedPixelFormat. - * - * Data pointer is set to @cpp nullptr @ce, call @ref setData() to - * assign a memory view to the image. For a 3D image, if @p flags - * contain @ref ImageFlag3D::CubeMap, the @p size is expected to match - * its restrictions. + * Uses ADL to find a corresponding @cpp compressedPixelFormatBlockSize(U) @ce + * and @cpp compressedPixelFormatBlockDataSize(U) @ce overloads, then + * calls @ref CompressedImageView(CompressedPixelStorage, UnsignedInt, const Vector3i&, UnsignedInt, const VectorTypeFor&, ImageFlags) + * with determined block size properties. */ template explicit CompressedImageView(CompressedPixelStorage storage, U format, const VectorTypeFor& size, ImageFlags flags = {}) noexcept; @@ -874,6 +986,25 @@ template class CompressedImageView { */ CompressedPixelFormat format() const { return _format; } + /** + * @brief Size of a compressed block in pixels + * @m_since_latest + * + * Note that the blocks can be 3D even for 2D images and 2D or 3D even + * for 1D images, in which case only the first slice in the extra + * dimensions is used. + * @see @ref blockDataSize(), @ref compressedPixelFormatBlockSize() + */ + Vector3i blockSize() const { return Vector3i{_blockSize}; } + + /** + * @brief Size of a compressed block in bytes + * @m_since_latest + * + * @see @ref blockSize(), @ref compressedPixelFormatBlockDataSize() + */ + UnsignedInt blockDataSize() const { return _blockDataSize; } + /** @brief Image size in pixels */ /* Unlike other getters this one is a const& so it's possible to slice to the sizes when all images are in an array, for example for use @@ -897,22 +1028,20 @@ template class CompressedImageView { * The data array is expected to be of proper size for parameters * specified in the constructor. */ - void setData(Containers::ArrayView data) { - _data = {reinterpret_cast(data.data()), data.size()}; - } + void setData(Containers::ArrayView data); private: /* Needed for mutable->const conversion */ template friend class CompressedImageView; - /* To be made public once block size and block data size are stored - together with the image */ - explicit CompressedImageView(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, Containers::ArrayView data, ImageFlags flags) noexcept; - explicit CompressedImageView(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, ImageFlags flags) noexcept; - CompressedPixelStorage _storage; CompressedPixelFormat _format; ImageFlags _flags; + /* Largest blocks are 12x12 in ASTC and at most 32 bytes, so an 8-bit + type should be more than enough. As even 1D images can have 3D + blocks, the member isn't dependent on dimension count. */ + Vector3ub _blockSize; + UnsignedByte _blockDataSize; VectorTypeFor _size; Containers::ArrayView _data; }; @@ -1024,12 +1153,12 @@ template template template::value && !std::is_const::value, int>::type> ImageView::ImageView(const ImageView& other) noexcept: _storage{other._storage}, _format{other._format}, _formatExtra{other._formatExtra}, _pixelSize{other._pixelSize}, _flags{other._flags}, _size{other._size}, _data{other._data} {} #endif -template template inline CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const U format, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: CompressedImageView{storage, UnsignedInt(format), size, data, flags} { +template template inline CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const U format, const VectorTypeFor& size, const Containers::ArrayView data, const ImageFlags flags) noexcept: CompressedImageView{storage, UnsignedInt(format), compressedPixelFormatBlockSize(format), compressedPixelFormatBlockDataSize(format), size, data, flags} { static_assert(sizeof(U) <= 4, "format types larger than 32bits are not supported"); } -template template inline CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const U format, const VectorTypeFor& size, const ImageFlags flags) noexcept: CompressedImageView{storage, UnsignedInt(format), size, flags} { +template template inline CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const U format, const VectorTypeFor& size, const ImageFlags flags) noexcept: CompressedImageView{storage, UnsignedInt(format), compressedPixelFormatBlockSize(format), compressedPixelFormatBlockDataSize(format), size, flags} { static_assert(sizeof(U) <= 4, "format types larger than 32bits are not supported"); } @@ -1040,10 +1169,12 @@ template template(UnsignedShort(other._flags)&~UnsignedShort(ImageFlag2D::Array))|flags}, + _blockSize{other._blockSize}, + _blockDataSize{other._blockDataSize}, _size{Math::Vector::pad(other._size, 1)}, _data{other._data} {} -template template::value && !std::is_const::value, int>::type> CompressedImageView::CompressedImageView(const CompressedImageView& other) noexcept: _storage{other._storage}, _format{other._format}, _flags{other._flags}, _size{other._size}, _data{other._data} {} +template template::value && !std::is_const::value, int>::type> CompressedImageView::CompressedImageView(const CompressedImageView& other) noexcept: _storage{other._storage}, _format{other._format}, _flags{other._flags}, _blockSize{other._blockSize}, _blockDataSize{other._blockDataSize}, _size{other._size}, _data{other._data} {} #endif } diff --git a/src/Magnum/Implementation/ImageProperties.h b/src/Magnum/Implementation/ImageProperties.h index 3a1fdc26c..0a3755bf3 100644 --- a/src/Magnum/Implementation/ImageProperties.h +++ b/src/Magnum/Implementation/ImageProperties.h @@ -49,6 +49,33 @@ inline void checkPixelSize(const char* prefix << "expected pixel size to be non-zero and less than 256 but got" << pixelSize, ); } +inline bool checkBlockProperties(const char* + #ifndef CORRADE_STANDARD_ASSERT + const prefix + #endif + , const Vector3i& blockSize, const UnsignedInt blockDataSize) +{ + CORRADE_ASSERT((blockSize > Vector3i{}).all() && + (blockSize < Vector3i{256}).all(), + prefix << "expected block size to be greater than zero and less than 256 but got" << Debug::packed << blockSize, {}); + CORRADE_ASSERT(blockDataSize && blockDataSize < 256, + prefix << "expected block data size to be non-zero and less than 256 but got" << blockDataSize, {}); + return true; +} +/* GL::BufferImage has block size statically defined for all known formats so + it doesn't need the above, only this */ +inline void checkBlockPropertiesForStorage(const char* + #ifndef CORRADE_STANDARD_ASSERT + const prefix + #endif + , const Vector3i& blockSize, const UnsignedInt blockDataSize, const CompressedPixelStorage& storage) +{ + CORRADE_ASSERT(storage.compressedBlockSize() == Vector3i{} || storage.compressedBlockSize() == blockSize, + prefix << "expected pixel storage block size to be either not set at all or equal to" << Debug::packed << blockSize << "but got" << Debug::packed << storage.compressedBlockSize(), ); + CORRADE_ASSERT(!storage.compressedBlockDataSize() || UnsignedInt(storage.compressedBlockDataSize()) == blockDataSize, + prefix << "expected pixel storage block data size to be either not set at all or equal to" << blockDataSize << "but got" << storage.compressedBlockDataSize(), ); +} + inline void checkImageFlagsForSize(const char*, const ImageFlags1D, const Math::Vector<1, Int>&) {} inline void checkImageFlagsForSize(const char*, const ImageFlags2D, const Vector2i&) {} inline void checkImageFlagsForSize(const char* @@ -73,7 +100,8 @@ template std::pair, Math::Vector3> compressedDataProperties(const CompressedPixelStorage& storage, const Vector3i& blockSize, const UnsignedInt blockDataSize, const Vector3i& size) { const Vector3i blockCount = (size + blockSize - Vector3i{1})/blockSize; const Math::Vector3 dataSize{ @@ -87,9 +115,17 @@ inline std::pair, Math::Vector3> compres return std::make_pair(offset, size.product() ? dataSize : Math::Vector3{}); } +template struct CompressedImageTraits; +template struct CompressedImageTraits().storage()), CompressedPixelStorage>::value>::type> { + static CompressedPixelStorage storage(const T& image) { return image.storage(); } +}; +template struct CompressedImageTraits().storage()), PixelStorage>::value>::type> { + static CompressedPixelStorage storage(const T& image) { return image.compressedStorage(); } +}; + /* Used in Compressed*Image::dataProperties() */ template std::pair, Math::Vector> compressedImageDataProperties(const T& image) { - std::pair, Math::Vector3> dataProperties = image.storage().dataProperties(Vector3i::pad(image.size(), 1)); + std::pair, Math::Vector3> dataProperties = compressedDataProperties(CompressedImageTraits::storage(image), image.blockSize(), image.blockDataSize(), Vector3i::pad(image.size(), 1)); return std::make_pair(Math::Vector::pad(dataProperties.first), Math::Vector::pad(dataProperties.second)); } @@ -126,10 +162,8 @@ template inline std::size_t imageDataSize(const T& image) { where the nv-cubemap-broken-full-compressed-image-query workaround needs to go slice by slice, taking offset and incrementing it by size divided by the Z dimension. */ -template std::pair compressedImageDataOffsetSizeFor(const T& image, const Math::Vector& size) { - CORRADE_INTERNAL_ASSERT(image.storage().compressedBlockSize().product() && image.storage().compressedBlockDataSize()); - - std::pair, Math::Vector3> dataProperties = image.storage().dataProperties(Vector3i::pad(size, 1)); +template std::pair compressedImageDataOffsetSizeFor(const CompressedPixelStorage& storage, const Vector3i& blockSize, const UnsignedInt blockDataSize, const Math::Vector& size) { + std::pair, Math::Vector3> dataProperties = compressedDataProperties(storage, blockSize, blockDataSize, Vector3i::pad(size, 1)); /* Smallest line/rectangle/cube that covers the area. Same logic as in imageDataSizeFor() above. */ @@ -137,13 +171,17 @@ template std::pair co if(dataProperties.first.z()) dataOffset += dataProperties.first.z(); else if(dataProperties.first.y()) { - if(!image.storage().imageHeight()) + if(!storage.imageHeight()) dataOffset += dataProperties.first.y(); } else if(dataProperties.first.x()) { - if(!image.storage().rowLength()) + if(!storage.rowLength()) dataOffset += dataProperties.first.x(); } - return {dataOffset, dataProperties.second.product()*image.storage().compressedBlockDataSize()}; + return {dataOffset, dataProperties.second.product()*blockDataSize}; +} + +template std::pair compressedImageDataOffsetSizeFor(const T& image, const Math::Vector& size) { + return compressedImageDataOffsetSizeFor(CompressedImageTraits::storage(image), image.blockSize(), image.blockDataSize(), size); } /* Used in image query functions */ @@ -152,6 +190,12 @@ template std::size_t compressedImageDataSizeFor return r.first + r.second; } +/* Used in data size assertions */ +template inline std::size_t compressedImageDataSize(const T& image) { + auto r = compressedImageDataOffsetSizeFor(image, image.size()); + return r.first + r.second; +} + template std::ptrdiff_t pixelStorageSkipOffsetFor(const T& image, const Math::Vector& size) { return image.storage().dataProperties(image.pixelSize(), Vector3i::pad(size, 1)).first.sum(); } diff --git a/src/Magnum/PixelStorage.h b/src/Magnum/PixelStorage.h index 1ede8fde4..fe9b36b7d 100644 --- a/src/Magnum/PixelStorage.h +++ b/src/Magnum/PixelStorage.h @@ -177,31 +177,40 @@ class MAGNUM_EXPORT CompressedPixelStorage: public PixelStorage { return !operator==(other); } - /** @brief Compressed block size */ + /** @brief Compressed block size in pixels */ constexpr Vector3i compressedBlockSize() const { return _blockSize; } /** - * @brief Set compressed block size + * @brief Set compressed block size in pixels * - * If set to @cpp 0 @ce for given dimension, size information from - * particular compressed format is used. Default is @cpp 0 @ce in all - * dimensions. - * @see @ref Magnum::compressedPixelFormatBlockSize() + * Expected to either match size information for a particular + * compressed format of image the storage is used with or be set to + * @cpp 0 @ce. Default is @cpp 0 @ce in all dimensions. + * @see @ref compressedPixelFormatBlockSize(), + * @ref CompressedImageView::blockSize(), + * @ref CompressedImage::blockSize(), + * @ref Trade::ImageData::compressedBlockSize(), + * @ref GL::CompressedBufferImage::blockSize(), */ CompressedPixelStorage& setCompressedBlockSize(const Vector3i& size) { _blockSize = size; return *this; } - /** @brief Compressed block data size (in bytes) */ + /** @brief Compressed block data size in bytes */ constexpr Int compressedBlockDataSize() const { return _blockDataSize; } /** - * @brief Set compressed block data size (in bytes) + * @brief Set compressed block data size in bytes * - * If set to @cpp 0 @ce, size information from particular compressed - * format is used. Default is @cpp 0 @ce in all dimensions. - * @see @ref Magnum::compressedPixelFormatBlockDataSize() + * Expected to either match size information for a particular + * compressed format of image the storage is used with or be set to + * @cpp 0 @ce. Default is @cpp 0 @ce. + * @see @ref compressedPixelFormatBlockDataSize(), + * @ref CompressedImageView::blockDataSize(), + * @ref CompressedImage::blockDataSize(), + * @ref Trade::ImageData::compressedBlockDataSize(), + * @ref GL::CompressedBufferImage::blockSize(), */ CompressedPixelStorage& setCompressedBlockDataSize(Int size) { _blockDataSize = size; diff --git a/src/Magnum/Test/ImageTest.cpp b/src/Magnum/Test/ImageTest.cpp index 0784b5dd0..9544bb620 100644 --- a/src/Magnum/Test/ImageTest.cpp +++ b/src/Magnum/Test/ImageTest.cpp @@ -56,6 +56,8 @@ struct ImageTest: TestSuite::Tester { void constructInvalidPixelSize(); void constructInvalidSize(); void constructInvalidCubeMap(); + void constructCompressedUnknownImplementationSpecificBlockSize(); + void constructCompressedInvalidBlockSize(); void constructCompressedInvalidSize(); void constructCompressedInvalidCubeMap(); @@ -115,6 +117,8 @@ ImageTest::ImageTest() { &ImageTest::constructInvalidPixelSize, &ImageTest::constructInvalidSize, &ImageTest::constructInvalidCubeMap, + &ImageTest::constructCompressedUnknownImplementationSpecificBlockSize, + &ImageTest::constructCompressedInvalidBlockSize, &ImageTest::constructCompressedInvalidSize, &ImageTest::constructCompressedInvalidCubeMap, @@ -168,6 +172,20 @@ namespace GL { } enum class CompressedPixelFormat { RGBS3tcDxt1 = 21 }; + Vector3i compressedPixelFormatBlockSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::RGBS3tcDxt1); + return {4, 4, 1}; + } + UnsignedInt compressedPixelFormatBlockDataSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::RGBS3tcDxt1); + return 8; + } } namespace Vk { @@ -182,6 +200,22 @@ namespace Vk { #endif return 12; } + + enum class CompressedPixelFormat { Astc5x5x4RGBAF = 111 }; + Vector3i compressedPixelFormatBlockSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::Astc5x5x4RGBAF); + return {5, 5, 4}; + } + UnsignedInt compressedPixelFormatBlockDataSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::Astc5x5x4RGBAF); + return 16; + } } void ImageTest::constructGeneric() { @@ -394,28 +428,34 @@ void ImageTest::constructImplementationSpecificPlaceholder() { void ImageTest::constructCompressedGeneric() { { - auto data = new char[8]; - CompressedImage2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array}; + auto data = new char[6*8]; + CompressedImage2D a{ + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, + Containers::Array{data, 6*8}, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 6*8); } { - auto data = new char[8]; - CompressedImage2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array}; + auto data = new char[8*16]; + CompressedImage2D a{ + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); } } @@ -424,8 +464,10 @@ void ImageTest::constructCompressedGenericPlaceholder() { CompressedImage2D a; CORRADE_COMPARE(a.flags(), ImageFlags2D{}); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), CompressedPixelFormat{}); + CORRADE_COMPARE(a.blockSize(), (Vector3i{})); + CORRADE_COMPARE(a.blockDataSize(), 0); CORRADE_COMPARE(a.size(), Vector2i{}); CORRADE_COMPARE(a.data(), static_cast(nullptr)); } { @@ -433,12 +475,14 @@ void ImageTest::constructCompressedGenericPlaceholder() { CompressedPixelStorage{} /* Even with skip it shouldn't assert on data size */ .setSkip({1, 0, 0}) - .setCompressedBlockSize(Vector3i{4})}; + .setRowLength(20)}; CORRADE_COMPARE(a.flags(), ImageFlags2D{}); CORRADE_COMPARE(a.storage().skip(), (Vector3i{1, 0, 0})); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.storage().rowLength(), 20); CORRADE_COMPARE(a.format(), CompressedPixelFormat{}); + CORRADE_COMPARE(a.blockSize(), (Vector3i{})); + CORRADE_COMPARE(a.blockDataSize(), 0); CORRADE_COMPARE(a.size(), Vector2i{}); CORRADE_COMPARE(a.data(), static_cast(nullptr)); } @@ -447,31 +491,54 @@ void ImageTest::constructCompressedGenericPlaceholder() { void ImageTest::constructCompressedImplementationSpecific() { /* Format with autodetection */ { - auto data = new char[8]; - CompressedImage2D a{GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array}; + auto data = new char[6*8]; + CompressedImage2D a{ + GL::CompressedPixelFormat::RGBS3tcDxt1, {12, 8}, + Containers::Array{data, 6*8}, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 6*8); } { - auto data = new char[8]; - CompressedImage2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array}; + auto data = new char[8*16]; + CompressedImage2D a{ + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); } - /* Manual properties not implemented yet */ + /* Manual block properties */ + { + auto data = new char[6*12]; + CompressedImage2D a{ + CompressedPixelStorage{}.setRowLength(6), + 111, {3, 4, 5}, 12, {3, 8}, Containers::Array{data, 6*12}, ImageFlag2D::Array}; + + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); + CORRADE_COMPARE(a.storage().rowLength(), 6); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + /* These deliberately don't match what compressedPixelFormatBlockSize() + above returns */ + CORRADE_COMPARE(a.blockSize(), (Vector3i{3, 4, 5})); + CORRADE_COMPARE(a.blockDataSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{3, 8})); + CORRADE_COMPARE(a.data(), static_cast(data)); + CORRADE_COMPARE(a.data().size(), 6*12); + } } void ImageTest::constructUnknownImplementationSpecificPixelSize() { @@ -539,10 +606,62 @@ void ImageTest::constructInvalidCubeMap() { "Image: expected square faces for a cube map, got {4, 3}\n"); } -void ImageTest::constructCompressedInvalidSize() { +void ImageTest::constructCompressedUnknownImplementationSpecificBlockSize() { CORRADE_SKIP_IF_NO_ASSERT(); - CORRADE_EXPECT_FAIL("Size checking for compressed image data is not implemented yet."); + Containers::String out; + Error redirectError{&out}; + CompressedImage2D{compressedPixelFormatWrap(0x666), {1, 1}, Containers::Array{NoInit, 1}}; + CORRADE_COMPARE_AS(out, + "CompressedImage: can't determine block size of an implementation-specific pixel format 0x666, pass it explicitly\n" + /* The next messages are printed because it cannot exit the + construction from the middle of the member initializer list. With + non-graceful asserts just one message is printed tho. */ + "compressedPixelFormatBlockSize(): can't determine size of an implementation-specific format 0x666\n" + "CompressedImage: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n", + TestSuite::Compare::String); +} + +void ImageTest::constructCompressedInvalidBlockSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + /* This is all okay */ + CompressedImage2D{CompressedPixelStorage{}, 666, {4, 5, 6}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + CompressedImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 6}) + .setCompressedBlockDataSize(8), + 666, {4, 5, 6}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + + /* Tested mainly in ImageViewTest, here is just a subset to verify the same + helper is used internally and a proper prefix is printed */ + Containers::String out; + Error redirectError{&out}; + CompressedImage2D{CompressedPixelStorage{}, 666, {0, 0, 0}, 4, {1, 1}, nullptr}; + CompressedImage2D{CompressedPixelStorage{}, 666, {4, 4, 4}, 0, {1, 1}, nullptr}; + CompressedImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 5}) + .setCompressedBlockDataSize(8), + 666, {4, 4, 1}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + CompressedImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(4), + 666, {4, 4, 1}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + CompressedImage2D{CompressedPixelStorage{} + .setCompressedBlockSize({0, 1, 0})}; + CompressedImage2D{CompressedPixelStorage{} + .setCompressedBlockDataSize(1)}; + CORRADE_COMPARE_AS(out, + "CompressedImage: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n" + "CompressedImage: expected block data size to be non-zero and less than 256 but got 0\n" + "CompressedImage: expected pixel storage block size to be either not set at all or equal to {4, 4, 1} but got {5, 5, 5}\n" + "CompressedImage: expected pixel storage block data size to be either not set at all or equal to 8 but got 4\n" + "CompressedImage: expected pixel storage block size to not be set at all but got {0, 1, 0}\n" + "CompressedImage: expected pixel storage block data size to not be set at all but got 1\n", + TestSuite::Compare::String); +} + +void ImageTest::constructCompressedInvalidSize() { + CORRADE_SKIP_IF_NO_ASSERT(); /* Too small for given format */ { @@ -661,70 +780,80 @@ void ImageTest::constructMoveImplementationSpecific() { } void ImageTest::constructMoveCompressedGeneric() { - auto data = new char[8]; + auto data = new char[8*16]; CompressedImage2D a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc3RGBAUnorm, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array}; + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CompressedImage2D b{Utility::move(a)}; CORRADE_COMPARE(a.data(), static_cast(nullptr)); CORRADE_COMPARE(a.size(), Vector2i{}); CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), CompressedPixelFormat::Bc3RGBAUnorm); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); - auto data2 = new char[16]; - CompressedImage2D c{CompressedPixelFormat::Bc1RGBAUnorm, {8, 4}, Containers::Array{data2, 16}}; + auto data2 = new char[2*8]; + CompressedImage2D c{CompressedPixelFormat::Bc1RGBAUnorm, {8, 4}, Containers::Array{data2, 2*8}}; c = Utility::move(b); CORRADE_COMPARE(b.data(), static_cast(data2)); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); CORRADE_COMPARE(c.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(c.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(c.format(), CompressedPixelFormat::Bc3RGBAUnorm); - CORRADE_COMPARE(c.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(c.storage().rowLength(), 20); + CORRADE_COMPARE(c.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(c.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(c.blockDataSize(), 16); + CORRADE_COMPARE(c.size(), (Vector2i{15, 10})); CORRADE_COMPARE(c.data(), static_cast(data)); - CORRADE_COMPARE(c.data().size(), 8); + CORRADE_COMPARE(c.data().size(), 8*16); CORRADE_VERIFY(std::is_nothrow_move_constructible::value); CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } void ImageTest::constructMoveCompressedImplementationSpecific() { - auto data = new char[8]; + auto data = new char[8*16]; CompressedImage2D a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array}; + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CompressedImage2D b{Utility::move(a)}; CORRADE_COMPARE(a.data(), static_cast(nullptr)); CORRADE_COMPARE(a.size(), Vector2i{}); CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); - auto data2 = new char[16]; - CompressedImage2D c{CompressedPixelFormat::Bc2RGBAUnorm, {8, 4}, Containers::Array{data2, 16}}; + auto data2 = new char[2*8]; + CompressedImage2D c{CompressedPixelFormat::Bc1RGBAUnorm, {8, 4}, Containers::Array{data2, 2*8}}; c = Utility::move(b); CORRADE_COMPARE(b.data(), static_cast(data2)); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); CORRADE_COMPARE(c.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(c.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(c.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(c.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(c.storage().rowLength(), 20); + CORRADE_COMPARE(c.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(c.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(c.blockDataSize(), 16); + CORRADE_COMPARE(c.size(), (Vector2i{15, 10})); CORRADE_COMPARE(c.data(), static_cast(data)); - CORRADE_COMPARE(c.data().size(), 8); + CORRADE_COMPARE(c.data().size(), 8*16); } template void ImageTest::toViewGeneric() { @@ -764,35 +893,41 @@ template void ImageTest::toViewImplementationSpecific() { template void ImageTest::toViewCompressedGeneric() { setTestCaseTemplateName(MutabilityTraits::name()); - auto data = new char[8]; + auto data = new char[8*16]; typename MutabilityTraits::CompressedImageType a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBUnorm, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array}; + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CompressedImageView<2, T> b = a; CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), CompressedPixelFormat::Bc1RGBUnorm); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); } template void ImageTest::toViewCompressedImplementationSpecific() { setTestCaseTemplateName(MutabilityTraits::name()); - auto data = new char[8]; + auto data = new char[8*16]; typename MutabilityTraits::CompressedImageType a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array}; + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CompressedImageView<2, T> b = a; CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); } void ImageTest::data() { @@ -804,9 +939,10 @@ void ImageTest::data() { } void ImageTest::dataCompressed() { - auto data = new char[8]; - CompressedImage2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, - Containers::Array{data, 8}}; + auto data = new char[6*8]; + CompressedImage2D a{ + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, + Containers::Array{data, 6*8}, ImageFlag2D::Array}; const CompressedImage2D& ca = a; CORRADE_COMPARE(a.data(), static_cast(data)); CORRADE_COMPARE(ca.data(), static_cast(data)); @@ -820,10 +956,10 @@ void ImageTest::dataRvalue() { } void ImageTest::dataRvalueCompressed() { - auto data = new char[8]; + auto data = new char[6*8]; Containers::Array released = CompressedImage2D{ - CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, - Containers::Array{data, 8}}.data(); + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, + Containers::Array{data, 6*8}, ImageFlag2D::Array}.data(); CORRADE_COMPARE(released.data(), static_cast(data)); } @@ -839,17 +975,15 @@ void ImageTest::dataProperties() { } void ImageTest::dataPropertiesCompressed() { - /* Yes, I know, this is totally bogus and doesn't match the BC1 format */ CompressedImage3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({3, 4, 5}) - .setCompressedBlockDataSize(16) - .setImageHeight(12) - .setSkip({5, 8, 11}), - CompressedPixelFormat::Bc1RGBAUnorm, {2, 8, 11}, - Containers::Array{1}}; + .setRowLength(12) + .setImageHeight(8) + .setSkip({8, 4, 4}), + CompressedPixelFormat::Bc1RGBAUnorm, {2, 3, 3}, + Containers::Array{NoInit, 336}}; CORRADE_COMPARE(image.dataProperties(), - (std::pair, Math::Vector3>{{2*16, 2*16, 9*16}, {1, 3, 3}})); + (std::pair, Math::Vector3>{{16, 24, 192}, {3, 2, 3}})); } void ImageTest::release() { @@ -863,8 +997,10 @@ void ImageTest::release() { } void ImageTest::releaseCompressed() { - char data[8]; - CompressedImage2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}}; + char data[6*8]; + CompressedImage2D a{ + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, + Containers::Array{data, 6*8}, ImageFlag2D::Array}; const char* const pointer = a.release().release(); CORRADE_COMPARE(pointer, data); diff --git a/src/Magnum/Test/ImageViewTest.cpp b/src/Magnum/Test/ImageViewTest.cpp index a20d5f059..45b90128e 100644 --- a/src/Magnum/Test/ImageViewTest.cpp +++ b/src/Magnum/Test/ImageViewTest.cpp @@ -64,6 +64,8 @@ struct ImageViewTest: TestSuite::Tester { void constructInvalidPixelSize(); void constructInvalidSize(); void constructInvalidCubeMap(); + void constructCompressedUnknownImplementationSpecificBlockSize(); + void constructCompressedInvalidBlockSize(); void constructCompressedInvalidSize(); void constructCompressedInvalidCubeMap(); @@ -120,6 +122,8 @@ ImageViewTest::ImageViewTest() { &ImageViewTest::constructInvalidPixelSize, &ImageViewTest::constructInvalidSize, &ImageViewTest::constructInvalidCubeMap, + &ImageViewTest::constructCompressedUnknownImplementationSpecificBlockSize, + &ImageViewTest::constructCompressedInvalidBlockSize, &ImageViewTest::constructCompressedInvalidSize, &ImageViewTest::constructCompressedInvalidCubeMap, @@ -160,6 +164,20 @@ namespace GL { } enum class CompressedPixelFormat { RGBS3tcDxt1 = 21 }; + Vector3i compressedPixelFormatBlockSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::RGBS3tcDxt1); + return {4, 4, 1}; + } + UnsignedInt compressedPixelFormatBlockDataSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::RGBS3tcDxt1); + return 8; + } } namespace Vk { @@ -174,6 +192,22 @@ namespace Vk { #endif return 12; } + + enum class CompressedPixelFormat { Astc5x5x4RGBAF = 111 }; + Vector3i compressedPixelFormatBlockSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::Astc5x5x4RGBAF); + return {5, 5, 4}; + } + UnsignedInt compressedPixelFormatBlockDataSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::Astc5x5x4RGBAF); + return 16; + } } template void ImageViewTest::constructGeneric() { @@ -376,27 +410,34 @@ template void ImageViewTest::constructCompressedGeneric() { setTestCaseTemplateName(MutabilityTraits::name()); { - T data[8]{}; - CompressedImageView<2, T> a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, data, ImageFlag2D::Array}; + T data[6*8]{}; + CompressedImageView<2, T> a{ + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, + data, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 6*8); } { - T data[8]{}; - CompressedImageView<2, T> a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + T data[8*16]{}; + CompressedImageView<2, T> a{ + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, data, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); } } @@ -404,21 +445,27 @@ template void ImageViewTest::constructCompressedGenericEmpty() { setTestCaseTemplateName(MutabilityTraits::name()); { - CompressedImageView<2, T> a{CompressedPixelFormat::Bc1RGBAUnorm, {8, 16}, ImageFlag2D::Array}; + CompressedImageView<2, T> a{CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{8, 16})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(a.data(), static_cast(nullptr)); } { - CompressedImageView<2, T> a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBAUnorm, {8, 16}, ImageFlag2D::Array}; + CompressedImageView<2, T> a{ + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{8, 16})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(nullptr)); } } @@ -428,30 +475,54 @@ template void ImageViewTest::constructCompressedImplementationSpecific( /* Format with autodetection */ { - T data[8]{}; - CompressedImageView<2, T> a{GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, + T data[6*8]{}; + CompressedImageView<2, T> a{ + GL::CompressedPixelFormat::RGBS3tcDxt1, {12, 8}, data, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 6*8); } { - T data[8]{}; - CompressedImageView<2, T> a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, data, ImageFlag2D::Array}; + T data[8*16]{}; + CompressedImageView<2, T> a{ + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + data, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); } - /* Manual properties not implemented yet */ + /* Manual block properties */ + { + T data[6*12]{}; + CompressedImageView<2, T> a{ + CompressedPixelStorage{}.setRowLength(6), + 111, {3, 4, 5}, 12, {3, 8}, data, ImageFlag2D::Array}; + + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); + CORRADE_COMPARE(a.storage().rowLength(), 6); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + /* These deliberately don't match what compressedPixelFormatBlockSize() + above returns */ + CORRADE_COMPARE(a.blockSize(), (Vector3i{3, 4, 5})); + CORRADE_COMPARE(a.blockDataSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{3, 8})); + CORRADE_COMPARE(a.data(), static_cast(data)); + CORRADE_COMPARE(a.data().size(), 6*12); + } } template void ImageViewTest::constructCompressedImplementationSpecificEmpty() { @@ -459,25 +530,47 @@ template void ImageViewTest::constructCompressedImplementationSpecificE /* Format with autodetection */ { - CompressedImageView<2, T> a{GL::CompressedPixelFormat::RGBS3tcDxt1, {8, 16}, ImageFlag2D::Array}; + CompressedImageView<2, T> a{ + GL::CompressedPixelFormat::RGBS3tcDxt1, {12, 8}, + ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.storage().rowLength(), 0); CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{8, 16})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(a.data(), static_cast(nullptr)); } { - CompressedImageView<2, T> a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 8}, ImageFlag2D::Array}; + CompressedImageView<2, T> a{ + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, ImageFlag2D::Array}; CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 8})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(nullptr)); } - /* Manual properties not implemented yet */ + /* Manual block properties */ + { + CompressedImageView<2, T> a{ + CompressedPixelStorage{}.setRowLength(6), + 111, {3, 4, 5}, 12, {3, 8}, ImageFlag2D::Array}; + + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); + CORRADE_COMPARE(a.storage().rowLength(), 6); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + /* These deliberately don't match what compressedPixelFormatBlockSize() + above returns */ + CORRADE_COMPARE(a.blockSize(), (Vector3i{3, 4, 5})); + CORRADE_COMPARE(a.blockDataSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{3, 8})); + CORRADE_COMPARE(a.data(), static_cast(nullptr)); + } } void ImageViewTest::construct3DFrom1D() { @@ -549,29 +642,35 @@ void ImageViewTest::construct3DFrom2D() { void ImageViewTest::constructCompressed3DFrom1D() { /* Copied from constructCompressedImplementationSpecific(), as that exposes - most fields */ - /** @todo S3TC doesn't have 1D compression so this might blow up once we - check for block sizes */ - const char data[8]{}; - /** @todo use a real flag once it exists */ - CompressedImageView1D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, 4, data, ImageFlag1D(0xdea0)}; + most fields. It's okay to use a 2D format for a 1D image, only the first + row of each block gets used. */ + char data[4*16]{}; + CompressedImageView1D a{ + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, 15, + data, ImageFlag1D(0xdea0)}; + CORRADE_COMPARE(a.flags(), ImageFlag1D(0xdea0)); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), 4); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), 15); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 4*16); /* Not testing the flags parameter here to be sure implicit conversion works as well, it's tested in constructCompressed3DFrom2D() below */ CompressedImageView3D b = a; CORRADE_COMPARE(b.flags(), ImageFlag3D(0xdea0)); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector3i{4, 1, 1})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + /* The size doesn't get expanded */ + CORRADE_COMPARE(b.size(), (Vector3i{15, 1, 1})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 4*16); /* Conversion the other way is not allowed (will be later, but explicitly via a slice<1>() like with StridedArrayView); conversion from const to @@ -584,25 +683,32 @@ void ImageViewTest::constructCompressed3DFrom1D() { void ImageViewTest::constructCompressed3DFrom2D() { /* Copied from constructCompressedImplementationSpecific(), as that exposes most fields */ - char data[8*2]{}; - MutableCompressedImageView2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 8}, data, ImageFlag2D::Array|ImageFlag2D(0xde00)}; + char data[8*16]{}; + MutableCompressedImageView2D a{ + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + data, ImageFlag2D::Array|ImageFlag2D(0xde00)}; + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array|ImageFlag2D(0xde00)); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 8})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8*2); + CORRADE_COMPARE(a.data().size(), 8*16); MutableCompressedImageView3D b = {a, ImageFlag3D(0x00a0)}; /* The Array flag got implicitly stripped away, the rest got passed through and combined with the flags argument */ CORRADE_COMPARE(b.flags(), ImageFlag3D(0xdea0)); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector3i{4, 8, 1})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector3i{15, 10, 1})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8*2); + CORRADE_COMPARE(b.data().size(), 8*16); /* Conversion the other way is not allowed (will be later, but explicitly via a slice<1>() like with StridedArrayView) */ @@ -638,23 +744,30 @@ void ImageViewTest::constructFromMutable() { void ImageViewTest::constructCompressedFromMutable() { /* Copied from constructCompressedImplementationSpecific(), as that exposes most fields */ - char data[8]{}; - MutableCompressedImageView2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, data, ImageFlag2D::Array}; + char data[8*16]{}; + MutableCompressedImageView2D a{ + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + data, ImageFlag2D::Array}; + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.storage().rowLength(), 20); + CORRADE_COMPARE(a.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(a.data(), static_cast(data)); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); CompressedImageView2D b = a; CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); } void ImageViewTest::constructUnknownImplementationSpecificPixelSize() { @@ -729,10 +842,126 @@ void ImageViewTest::constructInvalidCubeMap() { "ImageView: expected exactly 6 faces for a cube map, got 5\n"); } -void ImageViewTest::constructCompressedInvalidSize() { +void ImageViewTest::constructCompressedUnknownImplementationSpecificBlockSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + char data[1]; + + Containers::String out; + Error redirectError{&out}; + CompressedImageView2D{compressedPixelFormatWrap(0x666), {1, 1}, data}; + CompressedImageView2D{compressedPixelFormatWrap(0x777), {1, 1}}; + CORRADE_COMPARE_AS(out, + "CompressedImageView: can't determine block size of an implementation-specific pixel format 0x666, pass it explicitly\n" + /* The next messages are printed because it cannot exit the + construction from the middle of the member initializer list. With + non-graceful asserts just one message is printed tho. */ + "compressedPixelFormatBlockSize(): can't determine size of an implementation-specific format 0x666\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n" + + "CompressedImageView: can't determine block size of an implementation-specific pixel format 0x777, pass it explicitly\n" + /* Same */ + "compressedPixelFormatBlockSize(): can't determine size of an implementation-specific format 0x777\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n", + TestSuite::Compare::String); +} + +void ImageViewTest::constructCompressedInvalidBlockSize() { CORRADE_SKIP_IF_NO_ASSERT(); - CORRADE_EXPECT_FAIL("Size checking for compressed image data is not implemented yet."); + /* This is all okay */ + char data[8]; + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 5, 6}, 8, {1, 1}, data}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 6}) + .setCompressedBlockDataSize(8), + 666, {4, 5, 6}, 8, {1, 1}, data}; + + Containers::String out; + Error redirectError{&out}; + /* Zeros */ + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 4, 0}, 4, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 4, 0}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 0, 4}, 4, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 0, 4}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {0, 4, 4}, 4, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {0, 4, 4}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 4, 4}, 0, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {4, 4, 4}, 0, {1, 1}}; + /* Too large */ + CompressedImageView2D{CompressedPixelStorage{}, 666, {1, 1, 256}, 4, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {1, 1, 256}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {1, 256, 1}, 4, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {1, 256, 1}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {256, 1, 1}, 4, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {256, 1, 1}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {1, 1, 1}, 256, {1, 1}, nullptr}; + CompressedImageView2D{CompressedPixelStorage{}, 666, {1, 1, 1}, 256, {1, 1}}; + /* CompressedPixelStorage doesn't match what is passed */ + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 5}) + .setCompressedBlockDataSize(4), + 666, {4, 5, 6}, 4, {1, 1}, data}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 5}) + .setCompressedBlockDataSize(4), + 666, {4, 5, 6}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 6, 6}) + .setCompressedBlockDataSize(4), + 666, {4, 5, 6}, 4, {1, 1}, data}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 6, 6}) + .setCompressedBlockDataSize(4), + 666, {4, 5, 6}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 6}) + .setCompressedBlockDataSize(4), + 666, {4, 5, 6}, 4, {1, 1}, data}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 6}) + .setCompressedBlockDataSize(4), + 666, {4, 5, 6}, 4, {1, 1}}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 6}) + .setCompressedBlockDataSize(8), + 666, {4, 5, 6}, 4, {1, 1}, data}; + CompressedImageView2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 6}) + .setCompressedBlockDataSize(8), + 666, {4, 5, 6}, 4, {1, 1}}; + CORRADE_COMPARE_AS(out, + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {4, 4, 0}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {4, 4, 0}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {4, 0, 4}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {4, 0, 4}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {0, 4, 4}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {0, 4, 4}\n" + "CompressedImageView: expected block data size to be non-zero and less than 256 but got 0\n" + "CompressedImageView: expected block data size to be non-zero and less than 256 but got 0\n" + + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {1, 1, 256}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {1, 1, 256}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {1, 256, 1}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {1, 256, 1}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {256, 1, 1}\n" + "CompressedImageView: expected block size to be greater than zero and less than 256 but got {256, 1, 1}\n" + "CompressedImageView: expected block data size to be non-zero and less than 256 but got 256\n" + "CompressedImageView: expected block data size to be non-zero and less than 256 but got 256\n" + + "CompressedImageView: expected pixel storage block size to be either not set at all or equal to {4, 5, 6} but got {4, 5, 5}\n" + "CompressedImageView: expected pixel storage block size to be either not set at all or equal to {4, 5, 6} but got {4, 5, 5}\n" + "CompressedImageView: expected pixel storage block size to be either not set at all or equal to {4, 5, 6} but got {4, 6, 6}\n" + "CompressedImageView: expected pixel storage block size to be either not set at all or equal to {4, 5, 6} but got {4, 6, 6}\n" + "CompressedImageView: expected pixel storage block size to be either not set at all or equal to {4, 5, 6} but got {5, 5, 6}\n" + "CompressedImageView: expected pixel storage block size to be either not set at all or equal to {4, 5, 6} but got {5, 5, 6}\n" + "CompressedImageView: expected pixel storage block data size to be either not set at all or equal to 4 but got 8\n" + "CompressedImageView: expected pixel storage block data size to be either not set at all or equal to 4 but got 8\n", + TestSuite::Compare::String); +} + +void ImageViewTest::constructCompressedInvalidSize() { + CORRADE_SKIP_IF_NO_ASSERT(); const char data[15]{}; @@ -785,18 +1014,16 @@ void ImageViewTest::dataProperties() { } void ImageViewTest::dataPropertiesCompressed() { - /* Yes, I know, this is totally bogus and doesn't match the BC1 format */ - const char data[1]{}; + const char data[336]{}; CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({3, 4, 5}) - .setCompressedBlockDataSize(16) - .setImageHeight(12) - .setSkip({5, 8, 11}), - CompressedPixelFormat::Bc1RGBAUnorm, {2, 8, 11}, + .setRowLength(12) + .setImageHeight(8) + .setSkip({8, 4, 4}), + CompressedPixelFormat::Bc1RGBAUnorm, {2, 3, 3}, data}; CORRADE_COMPARE(image.dataProperties(), - (std::pair, Math::Vector3>{{2*16, 2*16, 9*16}, {1, 3, 3}})); + (std::pair, Math::Vector3>{{16, 24, 192}, {3, 2, 3}})); } template void ImageViewTest::setData() { @@ -811,23 +1038,23 @@ template void ImageViewTest::setData() { CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), PixelFormat::RGB8Snorm); CORRADE_COMPARE(a.size(), Vector2i(1, 3)); - CORRADE_COMPARE(a.data(), &data2[0]); + CORRADE_COMPARE(a.data(), static_cast(data2)); } template void ImageViewTest::setDataCompressed() { setTestCaseTemplateName(MutabilityTraits::name()); - T data[8]{}; + T data[2*8]{}; CompressedImageView<2, T> a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelStorage{}.setRowLength(8), CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, data}; - T data2[16]{}; + T data2[2*8]{}; a.setData(data2); - CORRADE_COMPARE(a.storage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.storage().rowLength(), 8); CORRADE_COMPARE(a.format(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.data(), &data2[0]); + CORRADE_COMPARE(a.data(), static_cast(data2)); } void ImageViewTest::setDataInvalidSize() { @@ -845,8 +1072,6 @@ void ImageViewTest::setDataInvalidSize() { void ImageViewTest::setDataCompressedInvalidSize() { CORRADE_SKIP_IF_NO_ASSERT(); - CORRADE_EXPECT_FAIL("Size checking for compressed image data is not implemented yet."); - const char data[11]{}; /* Too small for given format */ diff --git a/src/Magnum/Test/PixelStorageTest.cpp b/src/Magnum/Test/PixelStorageTest.cpp index 8a099581f..5eec37604 100644 --- a/src/Magnum/Test/PixelStorageTest.cpp +++ b/src/Magnum/Test/PixelStorageTest.cpp @@ -302,10 +302,10 @@ void PixelStorageTest::dataOffsetSizeCompressed1D() { /* Image size in whole blocks, no skip */ { CompressedImageView1D image{ - CompressedPixelStorage{} - .setCompressedBlockSize({5, 1, 1}) - .setCompressedBlockDataSize(8), + CompressedPixelStorage{}, 42069, /* custom format */ + {5, 1, 1}, + 8, 1, /* this is ignored, the passed size is used instead */ data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, (Math::Vector<1, Int>{55})), (std::pair{ @@ -316,10 +316,10 @@ void PixelStorageTest::dataOffsetSizeCompressed1D() { } { CompressedImageView1D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 1, 1}) - .setCompressedBlockDataSize(8) .setSkip({10, 0, 0}), 42069, + {5, 1, 1}, + 8, 1, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Math::Vector<1, Int>{35}); @@ -336,10 +336,10 @@ void PixelStorageTest::dataOffsetSizeCompressed1D() { } { CompressedImageView1D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 1, 1}) - .setCompressedBlockDataSize(8) .setSkip({10, 0, 0}), 42069, + {5, 1, 1}, + 8, 1, data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, (Math::Vector<1, Int>{35})), (std::pair{ @@ -356,10 +356,10 @@ void PixelStorageTest::dataOffsetSizeCompressed2D() { /* Image size in whole blocks, no skip */ { CompressedImageView2D image{ - CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 1}) - .setCompressedBlockDataSize(8), + CompressedPixelStorage{}, 42069, /* custom format */ + {5, 4, 1}, + 8, {1, 1}, /* this is ignored, the passed size is used instead */ data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, Vector2i{55, 28}), (std::pair{ @@ -370,11 +370,11 @@ void PixelStorageTest::dataOffsetSizeCompressed2D() { } { CompressedImageView2D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 1}) - .setCompressedBlockDataSize(8) .setRowLength(45) .setSkip({10, 0, 0}), 42069, + {5, 4, 1}, + 8, {1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector2i{35, 20}); @@ -392,10 +392,10 @@ void PixelStorageTest::dataOffsetSizeCompressed2D() { } { CompressedImageView2D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 1}) - .setCompressedBlockDataSize(8) .setSkip({0, 8, 0}), 42069, + {5, 4, 1}, + 8, {1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector2i{35, 20}); @@ -412,11 +412,11 @@ void PixelStorageTest::dataOffsetSizeCompressed2D() { } { CompressedImageView2D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 1}) - .setCompressedBlockDataSize(8) .setRowLength(45) .setSkip({0, 8, 0}), 42069, + {5, 4, 1}, + 8, {1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector2i{35, 20}); @@ -433,11 +433,11 @@ void PixelStorageTest::dataOffsetSizeCompressed2D() { } { CompressedImageView2D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 1}) - .setCompressedBlockDataSize(8) .setRowLength(45) .setSkip({10, 8, 0}), 42069, + {5, 4, 1}, + 8, {1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector2i{35, 20}); @@ -454,11 +454,11 @@ void PixelStorageTest::dataOffsetSizeCompressed2D() { } { CompressedImageView2D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 1}) - .setCompressedBlockDataSize(8) .setRowLength(41) .setSkip({10, 8, 0}), 42069, + {5, 4, 1}, + 8, {1, 1}, data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, Vector2i{35, 20}), (std::pair{ @@ -475,10 +475,10 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { /* Image size in whole blocks, no offset */ { CompressedImageView3D image{ - CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16), + CompressedPixelStorage{}, 42069, /* custom format */ + {5, 4, 2}, + 16, {1, 1, 1}, /* this is ignored, the passed size is used instead */ data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{45, 28, 6}), (std::pair{ @@ -489,11 +489,11 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(45) .setSkip({10, 0, 0}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}); @@ -511,12 +511,12 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(45) .setImageHeight(28) .setSkip({20, 0, 0}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}); @@ -534,11 +534,11 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setImageHeight(28) .setSkip({0, 8, 0}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}); @@ -556,12 +556,12 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(45) .setImageHeight(28) .setSkip({0, 8, 0}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}); @@ -579,12 +579,12 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(45) .setImageHeight(28) .setSkip({0, 0, 4}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}); @@ -601,12 +601,12 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(45) .setImageHeight(28) .setSkip({10, 8, 4}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; std::pair out = Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}); @@ -624,12 +624,12 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } { CompressedImageView3D image{ CompressedPixelStorage{} - .setCompressedBlockSize({5, 4, 2}) - .setCompressedBlockDataSize(16) .setRowLength(41) .setImageHeight(27) .setSkip({10, 8, 4}), 42069, + {5, 4, 2}, + 16, {1, 1, 1}, data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{35, 20, 6}), (std::pair{ @@ -639,9 +639,14 @@ void PixelStorageTest::dataOffsetSizeCompressed3D() { } void PixelStorageTest::dataOffsetSizeCompressedZeroSize() { - const CompressedImage3D image{CompressedPixelStorage{} - .setCompressedBlockSize({4, 4, 1}) - .setCompressedBlockDataSize(16)}; + char data[16]; + const CompressedImageView3D image{ + CompressedPixelStorage{}, + 42069, /* custom format */ + {4, 4, 1}, + 16, + {1, 1, 1}, /* this is ignored, the passed size is used instead */ + data}; CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{0, 4, 4}), (std::pair{})); CORRADE_COMPARE(Implementation::compressedImageDataOffsetSizeFor(image, Vector3i{4, 0, 4}), diff --git a/src/Magnum/Trade/ImageData.cpp b/src/Magnum/Trade/ImageData.cpp index 87a62fce1..919c4f4d2 100644 --- a/src/Magnum/Trade/ImageData.cpp +++ b/src/Magnum/Trade/ImageData.cpp @@ -72,32 +72,68 @@ template ImageData::ImageData(const PixelSto _dataFlags = dataFlags; } -template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{true}, _flags{flags}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{Utility::move(data)}, _importerState{importerState} { +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, format, ( + /* Have to do it like this to avoid an ungraceful assertion from + compressedPixelFormatBlockDataSize() / compressedPixelFormatBlockSize() + afterwards */ #ifndef CORRADE_NO_ASSERT + isCompressedPixelFormatImplementationSpecific(format) ? + (CORRADE_CONSTEXPR_ASSERT(false, "Trade::ImageData: can't determine block size of an implementation-specific pixel format" << Debug::hex << compressedPixelFormatUnwrap(format) << Debug::nospace << ", pass it explicitly"), Vector3i{}) : + #endif + compressedPixelFormatBlockSize(format) + ), ( + #ifndef CORRADE_NO_ASSERT + isCompressedPixelFormatImplementationSpecific(format) ? 0 : + #endif + compressedPixelFormatBlockDataSize(format) + ), size, Utility::move(data), flags, importerState} {} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{true}, _flags{flags}, _compressedStorage{storage}, _compressedFormat{format}, _blockSize{Vector3ub{blockSize}}, _blockDataSize{UnsignedByte(blockDataSize)}, _size{size}, _data{Utility::move(data)}, _importerState{importerState} { + #ifndef CORRADE_NO_ASSERT + const bool passed = + Magnum::Implementation::checkBlockProperties("Trade::ImageData:", blockSize, blockDataSize); + #ifdef CORRADE_GRACEFUL_ASSERT + /* If the above check fails on a build with graceful assertions, the data + size check below could then die on division by zero. Exit early in that + case. */ + /** @todo any better idea to handle this? ugh */ + if(!passed) return; + #else + static_cast(passed); + #endif + Magnum::Implementation::checkBlockPropertiesForStorage("Trade::ImageData:", blockSize, blockDataSize, storage); + CORRADE_ASSERT(Magnum::Implementation::compressedImageDataSize(*this) <= _data.size(), "Trade::ImageData: data too small, got" << _data.size() << "but expected at least" << Magnum::Implementation::compressedImageDataSize(*this) << "bytes", ); Magnum::Implementation::checkImageFlagsForSize("Trade::ImageData:", flags, size); #endif } +template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), blockSize, blockDataSize, size, Utility::move(data), flags, importerState} {} + template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, flags, importerState} { CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); _dataFlags = dataFlags; } -template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{{}, format, size, Utility::move(data), flags, importerState} {} +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, format, blockSize, blockDataSize, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, flags, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} -template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{{}, format, size, dataFlags, data, flags, importerState} {} +template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const Vector3i& blockSize, const UnsignedInt blockDataSize, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), blockSize, blockDataSize, size, dataFlags, data, flags, importerState} {} -template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), size, Utility::move(data), flags, importerState} {} +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{{}, format, size, Utility::move(data), flags, importerState} {} -template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), size, dataFlags, data, flags, importerState} {} +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{{}, format, size, dataFlags, data, flags, importerState} {} template ImageData::ImageData(ImageData&& other) noexcept: _dataFlags{other._dataFlags}, _compressed{Utility::move(other._compressed)}, _flags{Utility::move(other._flags)}, _size{Utility::move(other._size)}, _data{Utility::move(other._data)}, _importerState{Utility::move(other._importerState)} { if(_compressed) { - new(&_compressedStorage) CompressedPixelStorage{Utility::move(other._compressedStorage)}; - _compressedFormat = Utility::move(other._compressedFormat); - } - else { + new(&_compressedStorage) CompressedPixelStorage{other._compressedStorage}; + _compressedFormat = other._compressedFormat; + _blockSize = other._blockSize; + _blockDataSize = other._blockDataSize; + } else { new(&_storage) PixelStorage{Utility::move(other._storage)}; _format = Utility::move(other._format); _formatExtra = Utility::move(other._formatExtra); @@ -116,20 +152,26 @@ template ImageData& ImageData::o swap(_dataFlags, other._dataFlags); swap(_compressed, other._compressed); swap(_flags, other._flags); + /* Because the CompressedPixelStorage is larger than the uncompressed, copy it if either of the sides is compressed to ensure no compressed properties are lost. The _storage / _compressedStorage and _format / _compressedFormat are unions of trivially copyable contents so - copying a type that's not there should be fine. */ + copying a type that's not there should be fine. Then, + _compressedBlockDataSize and _pixelSize have compatible layout, so + swapping one with the other is fine, but _compressedBlockSize is smaller + than _formatExtra, so swapping _formatExtra always. */ if(_compressed || other._compressed) { swap(_compressedStorage, other._compressedStorage); swap(_compressedFormat, other._compressedFormat); + swap(_blockDataSize, other._blockDataSize); } else { swap(_storage, other._storage); swap(_format, other._format); + swap(_pixelSize, other._pixelSize); } swap(_formatExtra, other._formatExtra); - swap(_pixelSize, other._pixelSize); + swap(_size, other._size); swap(_data, other._data); swap(_importerState, other._importerState); @@ -166,11 +208,26 @@ template UnsignedInt ImageData::pixelSize() return _pixelSize; } +template Vector3i ImageData::blockSize() const { + CORRADE_ASSERT(_compressed, "Trade::ImageData::blockSize(): the image is not compressed", {}); + return Vector3i{_blockSize}; +} + +template UnsignedInt ImageData::blockDataSize() const { + CORRADE_ASSERT(_compressed, "Trade::ImageData::blockDataSize(): the image is not compressed", {}); + return _blockDataSize; +} + template std::pair, VectorTypeFor> ImageData::dataProperties() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::dataProperties(): the image is compressed", {}); return Magnum::Implementation::imageDataProperties(*this); } +template std::pair, VectorTypeFor> ImageData::compressedDataProperties() const { + CORRADE_ASSERT(_compressed, "Trade::ImageData::compressedDataProperties(): the image is not compressed", {}); + return Magnum::Implementation::compressedImageDataProperties(*this); +} + template Containers::ArrayView ImageData::mutableData() & { CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, "Trade::ImageData::mutableData(): the image is not mutable", {}); @@ -202,20 +259,16 @@ template ImageData::operator BasicMutableIma } template ImageData::operator BasicCompressedImageView() const { - CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicCompressedImageView{_compressedStorage, _compressedFormat, _size})); - return BasicCompressedImageView{ - _compressedStorage, - _compressedFormat, _size, _data, _flags}; + CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicCompressedImageView{_compressedStorage, _compressedFormat, Vector3i{_blockSize}, _blockDataSize, _size})); + return BasicCompressedImageView{_compressedStorage, _compressedFormat, Vector3i{_blockSize}, _blockDataSize, _size, _data, _flags}; } template ImageData::operator BasicMutableCompressedImageView() { CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, "Trade::ImageData: the image is not mutable", - (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); - CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); - return BasicMutableCompressedImageView{ - _compressedStorage, - _compressedFormat, _size, _data, _flags}; + (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, Vector3i{_blockSize}, _blockDataSize, _size})); + CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, Vector3i{_blockSize}, _blockDataSize, _size})); + return BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, Vector3i{_blockSize}, _blockDataSize, _size, _data, _flags}; } template Containers::Array ImageData::release() { diff --git a/src/Magnum/Trade/ImageData.h b/src/Magnum/Trade/ImageData.h index e1e7d5123..d6a0e6297 100644 --- a/src/Magnum/Trade/ImageData.h +++ b/src/Magnum/Trade/ImageData.h @@ -476,6 +476,24 @@ template class ImageData { * @param flags Image layout flags * @param importerState Importer-specific state * @m_since_latest + * + * The @p data array is expected to be of proper size for given + * parameters. For a 3D image, if @p flags contain + * @ref ImageFlag3D::CubeMap, the @p size is expected to match its + * restrictions. + * + * The @p format is expected to not be implementation-specific, use the + * @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::Array&&, ImageFlags, const void*) + * overload to explicitly pass pass an implementation-specific + * @ref CompressedPixelFormat along with its block properties, or the + * @ref ImageData(CompressedPixelStorage, T, const VectorTypeFor&, Containers::Array&&, ImageFlags, const void*) + * overload with the original implementation-specific enum type to have + * the pixel size determined implicitly. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * properties of given @p format. */ explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; @@ -564,20 +582,65 @@ template class ImageData { #endif /** - * @brief Construct a compressed image data + * @brief Construct a compressed image data with an implementation-specific pixel format * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data - * @param size Image size + * @param blockSize Size of a compressed block in given format, + * in pixels + * @param blockDataSize Size of a compressed block in given format, + * in bytes + * @param size Image size, in pixels * @param data Image data * @param flags Image layout flags * @param importerState Importer-specific state * @m_since_latest * - * Uses @ref compressedPixelFormatWrap() internally to convert - * @p format to @ref CompressedPixelFormat. + * Unlike with @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, ImageFlags, const void*), + * where block size is determined automatically + * @ref compressedPixelFormatBlockSize() and + * @ref compressedPixelFormatBlockDataSize(), this allows you to + * specify an implementation-specific pixel format and block properties + * directly. Uses @ref compressedPixelFormatWrap() internally to wrap + * @p format in @ref CompressedPixelFormat. The @p blockSize and + * @p blockDataSize is expected to be greater than @cpp 0 @ce and less + * than @cpp 256 @ce. Note that the blocks can be 3D even for 2D images + * and 2D or 3D even for 1D images, in which case only the first slice + * in the extra dimensions is used. + * + * @ref CompressedPixelStorage::compressedBlockSize() and + * @relativeref{CompressedPixelStorage,compressedBlockDataSize()} in + * @p storage are expected to be either both zero or exactly matching + * @p blockSize and @p blockDataSize. + * + * The @p data array is expected to be of proper size for given + * parameters. For a 3D image, if @p flags contain + * @ref ImageFlag3D::CubeMap, the @p size is expected to match its + * restrictions. + */ + explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref compressedPixelFormatWrap(). + */ + explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data with an implementation-specific format + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param data Image data + * @param flags Image layout flags + * @param importerState Importer-specific state + * @m_since_latest * - * For a 3D image, if @p flags contain @ref ImageFlag3D::CubeMap, the - * @p size is expected to match its restrictions. + * Uses ADL to find a corresponding @cpp compressedPixelFormatBlockSize(T) @ce + * and @cpp compressedPixelFormatBlockDataSize(T) @ce overloads, then + * calls @ref ImageData(CompressedPixelStorage, UnsignedInt, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::Array&&, ImageFlags, const void*) + * with determined block size properties. */ template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; @@ -591,22 +654,52 @@ template class ImageData { #endif /** - * @brief Construct a non-owned compressed image data + * @brief Construct a non-owned compressed image data with an implementation-specific pixel format * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data - * @param size Image size + * @param blockSize Size of a compressed block in given format, + * in pixels + * @param blockDataSize Size of a compressed block in given format, + * in bytes + * @param size Image size, in pixels * @param dataFlags Data flags - * @param data View on image data + * @param data Image data * @param flags Image layout flags * @param importerState Importer-specific state * @m_since_latest * - * Compared to @ref ImageData(CompressedPixelStorage, T, const VectorTypeFor&, Containers::Array&&, ImageFlags, const void*) + * Compared to @ref ImageData(CompressedPixelStorage, UnsignedInt, const Vector3i&, UnsignedInt, const VectorTypeFor&, Containers::Array&&, ImageFlags, const void*) * creates an instance that doesn't own the passed data. The * @p dataFlags parameter can contain @ref DataFlag::Mutable to * indicate the external data can be modified, and is expected to *not* * have @ref DataFlag::Owned set. */ + explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref compressedPixelFormatWrap(). + */ + explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const Vector3i& blockSize, UnsignedInt blockDataSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned compressed image data with implementation-specific format + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param flags Image layout flags + * @param importerState Importer-specific state + * @m_since_latest + * + * Uses ADL to find a corresponding @cpp compressedPixelFormatBlockSize(T) @ce + * and @cpp compressedPixelFormatBlockDataSize(T) @ce overloads, then + * calls @ref ImageData(CompressedPixelStorage, UnsignedInt, const Vector3i&, UnsignedInt, const VectorTypeFor&, DataFlags, Containers::ArrayView, ImageFlags, const void*) + * with determined block size properties. + */ template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, ImageFlags flags = {}, const void* importerState = nullptr) noexcept; #ifdef MAGNUM_BUILD_DEPRECATED @@ -759,6 +852,28 @@ template class ImageData { */ UnsignedInt pixelSize() const; + /** + * @brief Size of a block in pixels + * @m_since_latest + * + * The image is expected to be compressed. Note that the blocks can be + * 3D even for 2D images and 2D or 3D even for 1D images, in which case + * only the first slice in the extra dimensions is used. + * @see @ref isCompressed(), @ref blockDataSize(), + * @ref compressedPixelFormatBlockSize() + */ + Vector3i blockSize() const; + + /** + * @brief Size of a block in bytes + * @m_since_latest + * + * The image is expected to be compressed. + * @see @ref isCompressed(), @ref blockSize(), + * @ref compressedPixelFormatBlockDataSize() + */ + UnsignedInt blockDataSize() const; + /** @brief Image size in pixels */ /* Unlike other getters this one is a const& so it's possible to slice to the sizes when all images are in an array, for example for use @@ -774,9 +889,15 @@ template class ImageData { */ std::pair, VectorTypeFor> dataProperties() const; - /* compressed data properties are not available because the importers - are not setting any block size pixel storage properties to avoid - needless state changes -- thus the calculation can't be done */ + /** + * @brief Compressed image data properties + * @m_since_latest + * + * The image is expected to be compressed. See + * @ref CompressedPixelStorage::dataProperties() for more information. + * @see @ref isCompressed() + */ + std::pair, VectorTypeFor> compressedDataProperties() const; /** * @brief Raw image data @@ -896,10 +1017,6 @@ template class ImageData { friend AbstractImporter; friend AbstractImageConverter; - explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, ImageFlags flags, const void* importerState = nullptr) noexcept; - - explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, ImageFlags flags, const void* importerState = nullptr) noexcept; - DataFlags _dataFlags; bool _compressed; ImageFlags _flags; @@ -911,8 +1028,14 @@ template class ImageData { PixelFormat _format; CompressedPixelFormat _compressedFormat; }; - UnsignedInt _formatExtra; - UnsignedByte _pixelSize; + union { + UnsignedInt _formatExtra; + Vector3ub _blockSize; + }; + union { + UnsignedByte _pixelSize; + UnsignedByte _blockDataSize; + }; /* 3 bytes free */ VectorTypeFor _size; Containers::Array _data; @@ -948,12 +1071,12 @@ template template ImageData::ImageD "format types larger than 32bits are not supported"); } -template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, Utility::move(data), flags, importerState} { +template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), compressedPixelFormatBlockSize(format), compressedPixelFormatBlockDataSize(format), size, Utility::move(data), flags, importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } -template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, dataFlags, data, flags, importerState} { +template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const ImageFlags flags, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), compressedPixelFormatBlockSize(format), compressedPixelFormatBlockDataSize(format), size, dataFlags, data, flags, importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } diff --git a/src/Magnum/Trade/Test/ImageDataTest.cpp b/src/Magnum/Trade/Test/ImageDataTest.cpp index f7bc84510..fc6aef471 100644 --- a/src/Magnum/Trade/Test/ImageDataTest.cpp +++ b/src/Magnum/Trade/Test/ImageDataTest.cpp @@ -62,6 +62,8 @@ struct ImageDataTest: TestSuite::Tester { void constructInvalidPixelSize(); void constructInvalidSize(); void constructInvalidCubeMap(); + void constructCompressedUnknownImplementationSpecificBlockSize(); + void constructCompressedInvalidBlockSize(); void constructCompressedInvalidSize(); void constructCompressedInvalidCubeMap(); @@ -90,6 +92,7 @@ struct ImageDataTest: TestSuite::Tester { void mutableAccessNotAllowed(); void dataProperties(); + void dataPropertiesCompressed(); void release(); void releaseCompressed(); @@ -140,6 +143,8 @@ ImageDataTest::ImageDataTest() { &ImageDataTest::constructInvalidPixelSize, &ImageDataTest::constructInvalidSize, &ImageDataTest::constructInvalidCubeMap, + &ImageDataTest::constructCompressedUnknownImplementationSpecificBlockSize, + &ImageDataTest::constructCompressedInvalidBlockSize, &ImageDataTest::constructCompressedInvalidSize, &ImageDataTest::constructCompressedInvalidCubeMap, @@ -172,6 +177,7 @@ ImageDataTest::ImageDataTest() { &ImageDataTest::mutableAccessNotAllowed, &ImageDataTest::dataProperties, + &ImageDataTest::dataPropertiesCompressed, &ImageDataTest::release, &ImageDataTest::releaseCompressed, @@ -198,6 +204,20 @@ namespace GL { } enum class CompressedPixelFormat { RGBS3tcDxt1 = 21 }; + Vector3i compressedPixelFormatBlockSize(CompressedPixelFormat format) { + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::RGBS3tcDxt1); + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + return {4, 4, 1}; + } + UnsignedInt compressedPixelFormatBlockDataSize(CompressedPixelFormat format) { + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::RGBS3tcDxt1); + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + return 8; + } } namespace Vk { @@ -212,6 +232,22 @@ namespace Vk { #endif return 12; } + + enum class CompressedPixelFormat { Astc5x5x4RGBAF = 111 }; + Vector3i compressedPixelFormatBlockSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::Astc5x5x4RGBAF); + return {5, 5, 4}; + } + UnsignedInt compressedPixelFormatBlockDataSize(CompressedPixelFormat format) { + #ifdef CORRADE_NO_ASSERT + static_cast(format); + #endif + CORRADE_INTERNAL_ASSERT(format == CompressedPixelFormat::Astc5x5x4RGBAF); + return 16; + } } void ImageDataTest::constructGeneric() { @@ -333,39 +369,45 @@ void ImageDataTest::constructImplementationSpecific() { void ImageDataTest::constructCompressedGeneric() { { - auto data = new char[8]; + auto data = new char[7*8]; int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ - ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array, &state}; + ImageData2D a{ + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, + Containers::Array{data, 7*8}, ImageFlag2D::Array, &state}; CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 0); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(static_cast(a.data().data()), data); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 7*8); CORRADE_COMPARE(static_cast(a.mutableData().data()), data); - CORRADE_COMPARE(a.mutableData().size(), 8); + CORRADE_COMPARE(a.mutableData().size(), 7*8); CORRADE_COMPARE(a.importerState(), &state); } { - auto data = new char[8]; + auto data = new char[8*16]; int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ - ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array, &state}; + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array, &state}; CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 20); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(static_cast(a.data().data()), data); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); CORRADE_COMPARE(static_cast(a.mutableData().data()), data); - CORRADE_COMPARE(a.mutableData().size(), 8); + CORRADE_COMPARE(a.mutableData().size(), 8*16); CORRADE_COMPARE(a.importerState(), &state); } } @@ -373,26 +415,51 @@ void ImageDataTest::constructCompressedGeneric() { void ImageDataTest::constructCompressedImplementationSpecific() { /* Format with autodetection */ { - auto data = new char[8]; + auto data = new char[8*8]; int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ - ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, - Containers::Array{data, 8}, ImageFlag2D::Array, &state}; + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(16), + GL::CompressedPixelFormat::RGBS3tcDxt1, {12, 8}, + Containers::Array{data, 8*8}, ImageFlag2D::Array, &state}; CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 16); CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(static_cast(a.data().data()), data); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*8); CORRADE_COMPARE(static_cast(a.mutableData().data()), data); - CORRADE_COMPARE(a.mutableData().size(), 8); + CORRADE_COMPARE(a.mutableData().size(), 8*8); CORRADE_COMPARE(a.importerState(), &state); } - /* Manual properties not implemented yet */ + /* Manual block properties */ + { + auto data = new char[6*12]; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(6), + 111, {3, 4, 5}, 12, {3, 8}, Containers::Array{data, 6*12}, + ImageFlag2D::Array, &state}; + + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 6); + CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{3, 4, 5})); + CORRADE_COMPARE(a.blockDataSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{3, 8})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 6*12); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 6*12); + CORRADE_COMPARE(a.importerState(), &state); + } } void ImageDataTest::constructGenericNotOwned() { @@ -533,42 +600,48 @@ void ImageDataTest::constructCompressedGenericNotOwned() { setTestCaseDescription(instanceData.name); { - char data[8]; - int state; - ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + char data[6*8]; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + ImageData2D a{ + CompressedPixelFormat::Bc1RGBAUnorm, {12, 8}, instanceData.dataFlags, data, ImageFlag2D::Array, &state}; CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 0); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(static_cast(a.data().data()), data); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 6*8); if(instanceData.dataFlags & DataFlag::Mutable) { CORRADE_COMPARE(static_cast(a.mutableData().data()), data); - CORRADE_COMPARE(a.mutableData().size(), 8); + CORRADE_COMPARE(a.mutableData().size(), 6*8); } CORRADE_COMPARE(a.importerState(), &state); } { - char data[8]; - int state; - ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + char data[8*16]; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, instanceData.dataFlags, data, ImageFlag2D::Array, &state}; CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); - CORRADE_COMPARE(a.size(), Vector2i(4, 4)); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 20); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(a.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(a.blockDataSize(), 16); + CORRADE_COMPARE(a.size(), (Vector2i{15, 10})); CORRADE_COMPARE(static_cast(a.data().data()), data); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*16); if(instanceData.dataFlags & DataFlag::Mutable) { CORRADE_COMPARE(static_cast(a.mutableData().data()), data); - CORRADE_COMPARE(a.mutableData().size(), 8); + CORRADE_COMPARE(a.mutableData().size(), 8*16); } CORRADE_COMPARE(a.importerState(), &state); } @@ -580,28 +653,55 @@ void ImageDataTest::constructCompressedImplementationSpecificNotOwned() { /* Format with autodetection */ { - char data[8]; - int state; - ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, + char data[8*8]; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(16), + GL::CompressedPixelFormat::RGBS3tcDxt1, {12, 8}, instanceData.dataFlags, data, ImageFlag2D::Array, &state}; CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 16); CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(a.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(a.blockDataSize(), 8); + CORRADE_COMPARE(a.size(), (Vector2i{12, 8})); CORRADE_COMPARE(static_cast(a.data().data()), data); - CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(a.data().size(), 8*8); if(instanceData.dataFlags & DataFlag::Mutable) { CORRADE_COMPARE(static_cast(a.mutableData().data()), data); - CORRADE_COMPARE(a.mutableData().size(), 8); + CORRADE_COMPARE(a.mutableData().size(), 8*8); } CORRADE_COMPARE(a.importerState(), &state); } - /* Manual properties not implemented yet */ + /* Manual block properties */ + { + char data[6*12]; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(6), + 111, {3, 4, 5}, 12, {3, 8}, + instanceData.dataFlags, data, ImageFlag2D::Array, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.flags(), ImageFlag2D::Array); + CORRADE_COMPARE(a.compressedStorage().rowLength(), 6); + CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(a.blockSize(), (Vector3i{3, 4, 5})); + CORRADE_COMPARE(a.blockDataSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{3, 8})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 6*12); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 6*12); + } + CORRADE_COMPARE(a.importerState(), &state); + } } void ImageDataTest::constructGenericNotOwnedFlagOwned() { @@ -639,9 +739,13 @@ void ImageDataTest::constructCompressedGenericNotOwnedFlagOwned() { Containers::String out; Error redirectError{&out}; - ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; - ImageData2D{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + ImageData2D{ + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + DataFlag::Owned, data}; + ImageData2D{ + CompressedPixelStorage{}.setRowLength(4), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + DataFlag::Owned, data}; CORRADE_COMPARE(out, "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); @@ -654,8 +758,10 @@ void ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned() Containers::String out; Error redirectError{&out}; - ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, DataFlag::Owned, data}; + ImageData2D a{ + CompressedPixelStorage{}.setRowLength(4), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, + DataFlag::Owned, data}; CORRADE_COMPARE(out, "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); } @@ -725,10 +831,77 @@ void ImageDataTest::constructInvalidCubeMap() { "Trade::ImageData: expected square faces for a cube map, got {4, 3}\n"); } -void ImageDataTest::constructCompressedInvalidSize() { +void ImageDataTest::constructCompressedUnknownImplementationSpecificBlockSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + const char data[1]{}; + + Containers::String out; + Error redirectError{&out}; + ImageData2D{compressedPixelFormatWrap(0x666), {1, 1}, Containers::Array{NoInit, 1}}; + ImageData2D{compressedPixelFormatWrap(0x777), {1, 1}, DataFlags{}, data}; + CORRADE_COMPARE_AS(out, + "Trade::ImageData: can't determine block size of an implementation-specific pixel format 0x666, pass it explicitly\n" + /* The next messages are printed because it cannot exit the + construction from the middle of the member initializer list. It does + so with non-graceful asserts tho and just one message is printed. */ + "Trade::ImageData: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n" + + "Trade::ImageData: can't determine block size of an implementation-specific pixel format 0x777, pass it explicitly\n" + "Trade::ImageData: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n", + TestSuite::Compare::String); +} + +void ImageDataTest::constructCompressedInvalidBlockSize() { CORRADE_SKIP_IF_NO_ASSERT(); - CORRADE_EXPECT_FAIL("Size checking for compressed image data is not implemented yet."); + /* This is all okay */ + ImageData2D{CompressedPixelStorage{}, 666, {4, 5, 6}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + ImageData2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 5, 6}) + .setCompressedBlockDataSize(8), + 666, {4, 5, 6}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + + const char data[8]{}; + + /* Tested mainly in ImageViewTest, here is just a subset to verify the same + helper is used internally and a proper prefix is printed */ + Containers::String out; + Error redirectError{&out}; + ImageData2D{CompressedPixelStorage{}, 666, {0, 0, 0}, 4, {1, 1}, nullptr}; + ImageData2D{CompressedPixelStorage{}, 666, {0, 0, 0}, 4, {1, 1}, {}, nullptr}; + ImageData2D{CompressedPixelStorage{}, 666, {4, 4, 4}, 0, {1, 1}, nullptr}; + ImageData2D{CompressedPixelStorage{}, 666, {4, 4, 4}, 0, {1, 1}, {}, nullptr}; + ImageData2D{CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 5}) + .setCompressedBlockDataSize(8), + 666, {4, 4, 1}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + ImageData2D{CompressedPixelStorage{} + .setCompressedBlockSize({5, 5, 5}) + .setCompressedBlockDataSize(8), + 666, {4, 4, 1}, 8, {1, 1}, {}, data}; + ImageData2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(4), + 666, {4, 4, 1}, 8, {1, 1}, Containers::Array{NoInit, 8}}; + ImageData2D{CompressedPixelStorage{} + .setCompressedBlockSize({4, 4, 1}) + .setCompressedBlockDataSize(4), + 666, {4, 4, 1}, 8, {1, 1}, {}, data}; + CORRADE_COMPARE_AS(out, + "Trade::ImageData: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n" + "Trade::ImageData: expected block size to be greater than zero and less than 256 but got {0, 0, 0}\n" + "Trade::ImageData: expected block data size to be non-zero and less than 256 but got 0\n" + "Trade::ImageData: expected block data size to be non-zero and less than 256 but got 0\n" + "Trade::ImageData: expected pixel storage block size to be either not set at all or equal to {4, 4, 1} but got {5, 5, 5}\n" + "Trade::ImageData: expected pixel storage block size to be either not set at all or equal to {4, 4, 1} but got {5, 5, 5}\n" + "Trade::ImageData: expected pixel storage block data size to be either not set at all or equal to 8 but got 4\n" + "Trade::ImageData: expected pixel storage block data size to be either not set at all or equal to 8 but got 4\n", + TestSuite::Compare::String); +} + +void ImageDataTest::constructCompressedInvalidSize() { + CORRADE_SKIP_IF_NO_ASSERT(); /* Too small for given format */ { @@ -856,11 +1029,12 @@ void ImageDataTest::constructMoveImplementationSpecific() { } void ImageDataTest::constructMoveCompressedGeneric() { - auto data = new char[8]; + auto data = new char[8*16]; int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ ImageData2D a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc3RGBAUnorm, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array, &state}; + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array, &state}; ImageData2D b{Utility::move(a)}; CORRADE_COMPARE(a.data(), static_cast(nullptr)); @@ -869,11 +1043,13 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.compressedStorage().rowLength(), 20); + CORRADE_COMPARE(b.compressedFormat(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); CORRADE_COMPARE(b.importerState(), &state); auto data2 = new char[16]; @@ -886,20 +1062,23 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(c.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); - CORRADE_COMPARE(c.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(c.compressedStorage().rowLength(), 20); + CORRADE_COMPARE(c.compressedFormat(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(c.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(c.blockDataSize(), 16); + CORRADE_COMPARE(c.size(), (Vector2i{15, 10})); CORRADE_COMPARE(c.data(), static_cast(data)); - CORRADE_COMPARE(c.data().size(), 8); + CORRADE_COMPARE(c.data().size(), 8*16); CORRADE_COMPARE(c.importerState(), &state); } void ImageDataTest::constructMoveCompressedImplementationSpecific() { - auto data = new char[8]; + auto data = new char[8*16]; int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ ImageData2D a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array, &state}; + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array, &state}; ImageData2D b{Utility::move(a)}; CORRADE_COMPARE(a.data(), static_cast(nullptr)); @@ -908,15 +1087,17 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.compressedStorage().rowLength(), 20); + CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); CORRADE_COMPARE(b.importerState(), &state); auto data2 = new char[16]; - ImageData2D c{CompressedPixelFormat::Bc2RGBAUnorm, {8, 4}, Containers::Array{data2, 16}}; + ImageData2D c{CompressedPixelFormat::Bc1RGBAUnorm, {8, 4}, Containers::Array{data2, 16}}; c = Utility::move(b); CORRADE_COMPARE(b.data(), static_cast(data2)); @@ -925,11 +1106,13 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(c.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(c.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(c.compressedStorage().rowLength(), 20); + CORRADE_COMPARE(c.compressedFormat(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(c.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(c.blockDataSize(), 16); + CORRADE_COMPARE(c.size(), (Vector2i{15, 10})); CORRADE_COMPARE(c.data(), static_cast(data)); - CORRADE_COMPARE(c.data().size(), 8); + CORRADE_COMPARE(c.data().size(), 8*16); CORRADE_COMPARE(c.importerState(), &state); } @@ -958,12 +1141,13 @@ void ImageDataTest::constructMoveAttachState() { } void ImageDataTest::constructMoveCompressedAttachState() { - auto data = new char[8]; + auto data = new char[8*8]; /* GCC 11 pointlessly complains that "maybe uninitialized" w/o the {} */ int stateOld{}, stateNew{}; ImageData2D a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array, &stateOld}; + CompressedPixelStorage{}.setRowLength(16), + GL::CompressedPixelFormat::RGBS3tcDxt1, {12, 8}, + Containers::Array{data, 8*8}, ImageFlag2D::Array, &stateOld}; ImageData2D b{Utility::move(a), &stateNew}; CORRADE_COMPARE(a.data(), static_cast(nullptr)); @@ -972,11 +1156,13 @@ void ImageDataTest::constructMoveCompressedAttachState() { CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(b.compressedStorage().rowLength(), 16); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.blockSize(), (Vector3i{4, 4, 1})); + CORRADE_COMPARE(b.blockDataSize(), 8); + CORRADE_COMPARE(b.size(), (Vector2i{12, 8})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*8); CORRADE_COMPARE(b.importerState(), &stateNew); } @@ -1000,7 +1186,7 @@ void ImageDataTest::moveCompressedToUncompressed() { .setRowLength(2) .setImageHeight(6) .setSkip({0, 1, 0}), - PixelFormat::R8I, 1337, 1, {2, 5}, Containers::Array{data2, 24}, ImageFlag2D(0x80), &state2}; + PixelFormat::R8I, 0x12345678, 1, {2, 5}, Containers::Array{data2, 24}, ImageFlag2D(0x80), &state2}; /* The operation should swap the contents completely, not just partially because one is compressed and the other not */ @@ -1014,7 +1200,7 @@ void ImageDataTest::moveCompressedToUncompressed() { CORRADE_COMPARE(a.storage().imageHeight(), 6); CORRADE_COMPARE(a.storage().skip(), (Vector3i{0, 1, 0})); CORRADE_COMPARE(a.format(), PixelFormat::R8I); - CORRADE_COMPARE(a.formatExtra(), 1337); + CORRADE_COMPARE(a.formatExtra(), 0x12345678); CORRADE_COMPARE(a.pixelSize(), 1); CORRADE_COMPARE(a.size(), (Vector2i{2, 5})); CORRADE_COMPARE(a.data(), static_cast(data2)); @@ -1045,7 +1231,7 @@ void ImageDataTest::moveUncompressedToCompressed() { .setRowLength(2) .setImageHeight(6) .setSkip({0, 1, 0}), - PixelFormat::R8I, 1337, 1, {2, 5}, Containers::Array{data, 24}, ImageFlag2D::Array, &state}; + PixelFormat::R8I, 0x12345678, 1, {2, 5}, Containers::Array{data, 24}, ImageFlag2D::Array, &state}; auto data2 = new char[8*16]; int state2{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ @@ -1084,7 +1270,7 @@ void ImageDataTest::moveUncompressedToCompressed() { CORRADE_COMPARE(b.storage().imageHeight(), 6); CORRADE_COMPARE(b.storage().skip(), (Vector3i{0, 1, 0})); CORRADE_COMPARE(b.format(), PixelFormat::R8I); - CORRADE_COMPARE(b.formatExtra(), 1337); + CORRADE_COMPARE(b.formatExtra(), 0x12345678); CORRADE_COMPARE(b.pixelSize(), 1); CORRADE_COMPARE(b.size(), (Vector2i{2, 5})); CORRADE_COMPARE(b.data(), static_cast(data)); @@ -1103,6 +1289,9 @@ void ImageDataTest::propertiesInvalid() { Error redirectError{&out}; uncompressed.compressedStorage(); uncompressed.compressedFormat(); + uncompressed.blockSize(); + uncompressed.blockDataSize(); + uncompressed.compressedDataProperties(); compressed.storage(); compressed.format(); compressed.formatExtra(); @@ -1117,6 +1306,9 @@ void ImageDataTest::propertiesInvalid() { CORRADE_COMPARE_AS(out, "Trade::ImageData::compressedStorage(): the image is not compressed\n" "Trade::ImageData::compressedFormat(): the image is not compressed\n" + "Trade::ImageData::blockSize(): the image is not compressed\n" + "Trade::ImageData::blockDataSize(): the image is not compressed\n" + "Trade::ImageData::compressedDataProperties(): the image is not compressed\n" "Trade::ImageData::storage(): the image is compressed\n" "Trade::ImageData::format(): the image is compressed\n" "Trade::ImageData::formatExtra(): the image is compressed\n" @@ -1164,35 +1356,41 @@ template void ImageDataTest::toViewImplementationSpecific() { template void ImageDataTest::toViewCompressedGeneric() { setTestCaseTemplateName(MutabilityTraits::name()); - auto data = new char[8]; + auto data = new char[8*16]; typename MutabilityTraits::ImageType a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - CompressedPixelFormat::Bc1RGBUnorm, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array}; + CompressedPixelStorage{}.setRowLength(20), + CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CompressedImageView<2, T> b = a; CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), CompressedPixelFormat::Bc1RGBUnorm); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), CompressedPixelFormat::Astc5x5x4RGBAF); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); } template void ImageDataTest::toViewCompressedImplementationSpecific() { setTestCaseTemplateName(MutabilityTraits::name()); - auto data = new char[8]; + auto data = new char[8*16]; typename MutabilityTraits::ImageType a{ - CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), - GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, ImageFlag2D::Array}; + CompressedPixelStorage{}.setRowLength(20), + Vk::CompressedPixelFormat::Astc5x5x4RGBAF, {15, 10}, + Containers::Array{data, 8*16}, ImageFlag2D::Array}; CompressedImageView<2, T> b = a; CORRADE_COMPARE(b.flags(), ImageFlag2D::Array); - CORRADE_COMPARE(b.storage().compressedBlockSize(), Vector3i{4}); - CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); - CORRADE_COMPARE(b.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(b.storage().rowLength(), 20); + CORRADE_COMPARE(b.format(), compressedPixelFormatWrap(Vk::CompressedPixelFormat::Astc5x5x4RGBAF)); + CORRADE_COMPARE(b.blockSize(), (Vector3i{5, 5, 4})); + CORRADE_COMPARE(b.blockDataSize(), 16); + CORRADE_COMPARE(b.size(), (Vector2i{15, 10})); CORRADE_COMPARE(b.data(), static_cast(data)); - CORRADE_COMPARE(b.data().size(), 8); + CORRADE_COMPARE(b.data().size(), 8*16); } void ImageDataTest::data() { @@ -1214,18 +1412,19 @@ void ImageDataTest::mutableAccessNotAllowed() { CORRADE_SKIP_IF_NO_ASSERT(); const char data[4*4]{}; - ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, data}; + ImageData2D uncompressed{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, data}; + ImageData2D compressed{CompressedPixelFormat::Bc1RGBAUnorm, {2, 2}, DataFlags{}, data}; Containers::String out; Error redirectError{&out}; - a.mutableData(); - a.mutablePixels(); + uncompressed.mutableData(); + uncompressed.mutablePixels(); /* Can't do just MutableImageView2D(a) because the compiler then treats it as a function. Can't do MutableImageView2D{a} because that doesn't work on old Clang. So it's this mess, then. Sigh. */ - auto b = MutableImageView2D(a); + auto b = MutableImageView2D(uncompressed); static_cast(b); - auto c = MutableCompressedImageView2D(a); + auto c = MutableCompressedImageView2D(compressed); static_cast(c); /* a.mutablePixels() calls non-templated mutablePixels(), so assume there it will blow up correctly as well (can't test because it asserts @@ -1248,6 +1447,18 @@ void ImageDataTest::dataProperties() { (std::pair, Math::Vector3>{{3, 16, 32}, {8, 4, 6}})); } +void ImageDataTest::dataPropertiesCompressed() { + ImageData3D image{ + CompressedPixelStorage{} + .setRowLength(12) + .setImageHeight(8) + .setSkip({8, 4, 4}), + CompressedPixelFormat::Bc1RGBAUnorm, {2, 3, 3}, + Containers::Array{NoInit, 336}}; + CORRADE_COMPARE(image.compressedDataProperties(), + (std::pair, Math::Vector3>{{16, 24, 192}, {3, 2, 3}})); +} + void ImageDataTest::release() { char data[] = {'b', 'e', 'e', 'r'}; Trade::ImageData2D a{PixelFormat::RGBA8Unorm, {1, 1}, Containers::Array{data, 4}};