diff --git a/src/Magnum/Text/AbstractGlyphCache.cpp b/src/Magnum/Text/AbstractGlyphCache.cpp index aeec8143f..2fa965721 100644 --- a/src/Magnum/Text/AbstractGlyphCache.cpp +++ b/src/Magnum/Text/AbstractGlyphCache.cpp @@ -380,18 +380,25 @@ void AbstractGlyphCache::flushImage(const Range3Di& range) { CORRADE_ASSERT((rangeu.min() <= rangeu.max()).all() && (rangeu.max() <= Vector3ui{state.image.size()}).all(), "Text::AbstractGlyphCache::flushImage():" << Debug::packed << range << "out of range for size" << Debug::packed << state.image.size(), ); + /* Set the image including padding, to make sure the sampled glyph area + doesn't contain potentially uninitialized GPU memory */ + const Vector3i paddedMin = Math::max(Vector3i{0}, + range.min() - Vector3i{padding(), 0}); + const Vector3i paddedMax = Math::min(size(), + range.max() + Vector3i{padding(), 0}); + /** @todo ugh have slicing on images directly already */ PixelStorage storage; storage.setRowLength(state.image.size().x()) - .setSkip(range.min()); + .setSkip(paddedMin); /* Set image height only if it's an array glyph cache, as otherwise it'd cause errors on ES2 that doesn't support this pixel storage state */ if(state.image.size().z() != 1) storage.setImageHeight(state.image.size().y()); - doSetImage(range.min(), ImageView3D{ + doSetImage(paddedMin, ImageView3D{ storage, state.image.format(), - range.size(), + paddedMax - paddedMin, state.image.data()}); } diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index 4bc4945d8..8d5e8a440 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -648,10 +648,15 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { * @m_since_latest * * Call after copying glyph data to @ref image() in order to reflect - * the updates to the GPU-side data. The @p layer and @p range is - * expected to be in bounds for @ref size(). You can use - * @ref Math::join() on rectangles passed to @ref addGlyph() to - * calculate the area that spans all glyphs that were added. + * the updates to the GPU-side data. The @p range is expected to be in + * bounds for @ref size(). You can use @ref Math::join() on rectangles + * passed to @ref addGlyph() to calculate the area that spans all + * glyphs that were added. + * + * The function assumes the @p range excludes @ref padding(). The image + * data get copied to the GPU including the padding to make sure the + * padded glyph area doesn't contain leftovers of uninitialized GPU + * memory. */ void flushImage(const Range3Di& range); diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.cpp b/src/Magnum/Text/DistanceFieldGlyphCache.cpp index 7308bf74a..14d654dd4 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.cpp +++ b/src/Magnum/Text/DistanceFieldGlyphCache.cpp @@ -112,24 +112,14 @@ void DistanceFieldGlyphCache::doSetImage(const Vector2i& offset, const ImageView #endif #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) { - /* Create an image view that includes the distance field radius as - well, to be sure the edges are processed appropriately as well */ - /** @todo this feels more like a hack than a real solution, any better - idea? Tried to make TextureTools::DistanceField pad the processing - area on its own, but eventually gave up as it would (a) made the - processing do more work than necessary in many cases, with no easy - way to opt out of that, (b) wasn't really working if the input - didn't have a black strip around the edge on platforms that don't - support border clamp, (c) required the input texture to have - certain wrapping mode set */ + /* The image range was already expanded to include the padding in + flushImage() */ CORRADE_INTERNAL_ASSERT(image.storage().skip().xy() == offset); #ifdef CORRADE_NO_ASSERT static_cast(offset); #endif - const Vector2i paddedMin = Math::max(Vector2i{0}, - image.storage().skip().xy() - padding()); - const Vector2i paddedMax = Math::min(size().xy(), - image.size() + image.storage().skip().xy() + padding()); + const Vector2i paddedMin = image.storage().skip().xy(); + const Vector2i paddedMax = image.size() + image.storage().skip().xy(); /* TextureTools::DistanceField expects the input size and output rectangle size ratio to be a multiple of 2 in order for the shader diff --git a/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp b/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp index 8f0b40045..1cc102187 100644 --- a/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp +++ b/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp @@ -140,6 +140,14 @@ struct AbstractGlyphCacheTest: TestSuite::Tester { #endif }; +const struct { + const char* name; + Vector2i padding; +} FlushImageData[]{ + {"", {}}, + {"with padding", {2, 3}}, +}; + const struct { const char* name; GlyphCacheFeatures features; @@ -205,25 +213,33 @@ AbstractGlyphCacheTest::AbstractGlyphCacheTest() { &AbstractGlyphCacheTest::insertNot2D, &AbstractGlyphCacheTest::insertMultiFont, #endif + }); - &AbstractGlyphCacheTest::flushImage, - &AbstractGlyphCacheTest::flushImageWholeArea, - &AbstractGlyphCacheTest::flushImageLayer, - &AbstractGlyphCacheTest::flushImage2D, - &AbstractGlyphCacheTest::flushImage2DPassthrough2D, - &AbstractGlyphCacheTest::flushImageNotImplemented, - &AbstractGlyphCacheTest::flushImagePassthrough2DNotImplemented, - &AbstractGlyphCacheTest::flushImageOutOfRange, - &AbstractGlyphCacheTest::flushImage2DNot2D, + addInstancedTests({&AbstractGlyphCacheTest::flushImage, + &AbstractGlyphCacheTest::flushImageWholeArea, + &AbstractGlyphCacheTest::flushImageLayer, + &AbstractGlyphCacheTest::flushImage2D, + &AbstractGlyphCacheTest::flushImage2DPassthrough2D}, + Containers::arraySize(FlushImageData)); - #ifdef MAGNUM_BUILD_DEPRECATED - &AbstractGlyphCacheTest::setImage, - &AbstractGlyphCacheTest::setImageOutOfRange, + addTests({&AbstractGlyphCacheTest::flushImageNotImplemented, + &AbstractGlyphCacheTest::flushImagePassthrough2DNotImplemented}); + + addInstancedTests({&AbstractGlyphCacheTest::flushImageOutOfRange}, + Containers::arraySize(FlushImageData)); + + addTests({&AbstractGlyphCacheTest::flushImage2DNot2D}); + + #ifdef MAGNUM_BUILD_DEPRECATED + addInstancedTests({&AbstractGlyphCacheTest::setImage}, + Containers::arraySize(FlushImageData)); + + addTests({&AbstractGlyphCacheTest::setImageOutOfRange, &AbstractGlyphCacheTest::setImageInvalidFormat, - &AbstractGlyphCacheTest::setImageNot2D, - #endif + &AbstractGlyphCacheTest::setImageNot2D}); + #endif - &AbstractGlyphCacheTest::processedImage}); + addTests({&AbstractGlyphCacheTest::processedImage}); addInstancedTests({&AbstractGlyphCacheTest::processedImageNotSupported}, Containers::arraySize(ProcessedImageNotSupportedData)); @@ -1014,6 +1030,9 @@ void AbstractGlyphCacheTest::insertMultiFont() { #endif void AbstractGlyphCacheTest::flushImage() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; @@ -1021,27 +1040,56 @@ void AbstractGlyphCacheTest::flushImage() { void doSetImage(const Vector3i& offset, const ImageView3D& image) override { called = true; - char pixels0[]{ - 'a', 'b', 'c', 0, - 'd', 'e', 'f', 0, - }; - char pixels1[]{ - '0', '1', '2', 0, - '3', '4', '5', 0, - }; - CORRADE_COMPARE(offset, (Vector3i{15, 30, 3})); - CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 2})); - CORRADE_COMPARE_AS(image.pixels()[0], - (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels0}), - DebugTools::CompareImage); - CORRADE_COMPARE_AS(image.pixels()[1], - (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels1}), - DebugTools::CompareImage); + CORRADE_COMPARE(offset, (Vector3i{15, 30, 3} - Vector3i{padding(), 0})); + CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 2} + Vector3i{2*padding(), 0})); + + if(padding().isZero()) { + char pixels0[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + char pixels1[]{ + '0', '1', '2', 0, + '3', '4', '5', 0, + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels0}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS(image.pixels()[1], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels1}), + DebugTools::CompareImage); + } else { + char pixels0[]{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 'a', 'b', 'c', 0, 0, 0, + 0, 0, 'd', 'e', 'f', 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + char pixels1[]{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, '0', '1', '2', 0, 0, 0, + 0, 0, '3', '4', '5', 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {7, 8}, pixels0}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS(image.pixels()[1], + (ImageView2D{PixelFormat::R8Snorm, {7, 8}, pixels1}), + DebugTools::CompareImage); + } } bool called = false; - /* Padding should have no effect on any of this */ - } cache{PixelFormat::R8Snorm, {45, 35, 5}, {2, 3}}; + } cache{PixelFormat::R8Snorm, {45, 35, 5}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1062,8 +1110,12 @@ void AbstractGlyphCacheTest::flushImage() { } void AbstractGlyphCacheTest::flushImageWholeArea() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + /* Like above, but calling flushImage() with the whole size to test bounds - checking */ + checking. The padding doesn't affect the call in this case -- the actual + range is always the whole image. */ struct: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; @@ -1091,8 +1143,7 @@ void AbstractGlyphCacheTest::flushImageWholeArea() { } bool called = false; - /* Padding should have no effect on any of this */ - } cache{PixelFormat::R8Snorm, {3, 2, 2}, {2, 3}}; + } cache{PixelFormat::R8Snorm, {3, 2, 2}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1113,6 +1164,9 @@ void AbstractGlyphCacheTest::flushImageWholeArea() { } void AbstractGlyphCacheTest::flushImageLayer() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + /* Single slice subset of flushImage() */ struct: AbstractGlyphCache { @@ -1122,20 +1176,36 @@ void AbstractGlyphCacheTest::flushImageLayer() { void doSetImage(const Vector3i& offset, const ImageView3D& image) override { called = true; - char pixels[]{ - 'a', 'b', 'c', 0, - 'd', 'e', 'f', 0, - }; - CORRADE_COMPARE(offset, (Vector3i{15, 30, 3})); - CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 1})); - CORRADE_COMPARE_AS(image.pixels()[0], - (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), - DebugTools::CompareImage); + CORRADE_COMPARE(offset, (Vector3i{15, 30, 3} - Vector3i{padding(), 0})); + CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 1} + Vector3i{2*padding(), 0})); + + if(padding().isZero()) { + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), + DebugTools::CompareImage); + } else { + char pixels[]{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 'a', 'b', 'c', 0, 0, 0, + 0, 0, 'd', 'e', 'f', 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {7, 8}, pixels}), + DebugTools::CompareImage); + } } bool called = false; - /* Padding should have no effect on any of this */ - } cache{PixelFormat::R8Snorm, {45, 35, 5}, {2, 3}}; + } cache{PixelFormat::R8Snorm, {45, 35, 5}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1153,6 +1223,9 @@ void AbstractGlyphCacheTest::flushImageLayer() { } void AbstractGlyphCacheTest::flushImage2D() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + /* Like flushImageLayer() but reduced to two dimensions */ struct: AbstractGlyphCache { @@ -1162,20 +1235,36 @@ void AbstractGlyphCacheTest::flushImage2D() { void doSetImage(const Vector3i& offset, const ImageView3D& image) override { called = true; - char pixels[]{ - 'a', 'b', 'c', 0, - 'd', 'e', 'f', 0, - }; - CORRADE_COMPARE(offset, (Vector3i{15, 30, 0})); - CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 1})); - CORRADE_COMPARE_AS(image.pixels()[0], - (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), - DebugTools::CompareImage); + CORRADE_COMPARE(offset, (Vector3i{15, 30, 0} - Vector3i{padding(), 0})); + CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 1} + Vector3i{2*padding(), 0})); + + if(padding().isZero()) { + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), + DebugTools::CompareImage); + } else { + char pixels[]{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 'a', 'b', 'c', 0, 0, 0, + 0, 0, 'd', 'e', 'f', 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {7, 8}, pixels}), + DebugTools::CompareImage); + } } bool called = false; - /* Padding should have no effect on any of this */ - } cache{PixelFormat::R8Snorm, {45, 35}, {2, 3}}; + } cache{PixelFormat::R8Snorm, {45, 35}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1193,6 +1282,9 @@ void AbstractGlyphCacheTest::flushImage2D() { } void AbstractGlyphCacheTest::flushImage2DPassthrough2D() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + /* Like flushImage2D() but with 2D doSetImage() */ struct: AbstractGlyphCache { @@ -1202,20 +1294,36 @@ void AbstractGlyphCacheTest::flushImage2DPassthrough2D() { void doSetImage(const Vector2i& offset, const ImageView2D& image) override { called = true; - char pixels[]{ - 'a', 'b', 'c', 0, - 'd', 'e', 'f', 0, - }; - CORRADE_COMPARE(offset, (Vector2i{15, 30})); - CORRADE_COMPARE(image.size(), (Vector2i{3, 2})); - CORRADE_COMPARE_AS(image.pixels(), - (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), - DebugTools::CompareImage); + CORRADE_COMPARE(offset, (Vector2i{15, 30}) - padding()); + CORRADE_COMPARE(image.size(), (Vector2i{3, 2}) + 2*padding()); + + if(padding().isZero()) { + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + CORRADE_COMPARE_AS(image.pixels(), + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), + DebugTools::CompareImage); + } else { + char pixels[]{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 'a', 'b', 'c', 0, 0, 0, + 0, 0, 'd', 'e', 'f', 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + CORRADE_COMPARE_AS(image.pixels(), + (ImageView2D{PixelFormat::R8Snorm, {7, 8}, pixels}), + DebugTools::CompareImage); + } } bool called = false; - /* Padding should have no effect on any of this */ - } cache{PixelFormat::R8Snorm, {45, 35}, {2, 3}}; + } cache{PixelFormat::R8Snorm, {45, 35}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1271,10 +1379,13 @@ void AbstractGlyphCacheTest::flushImagePassthrough2DNotImplemented() { } void AbstractGlyphCacheTest::flushImageOutOfRange() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + CORRADE_SKIP_IF_NO_ASSERT(); - /* The padding should have no effect on this */ - DummyGlyphCache cache{PixelFormat::R32F, {1024, 512, 8}, {2, 3}}; + /* The padding should not have any effect on the check */ + DummyGlyphCache cache{PixelFormat::R32F, {1024, 512, 8}, data.padding}; std::ostringstream out; Error redirectError{&out}; @@ -1322,6 +1433,9 @@ void AbstractGlyphCacheTest::flushImage2DNot2D() { #ifdef MAGNUM_BUILD_DEPRECATED void AbstractGlyphCacheTest::setImage() { + auto&& data = FlushImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + CORRADE_IGNORE_DEPRECATED_PUSH struct: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; @@ -1330,20 +1444,38 @@ void AbstractGlyphCacheTest::setImage() { void doSetImage(const Vector2i& offset, const ImageView2D& image) override { called = true; - char pixels[]{ - 'a', 'b', 'c', 0, - 'd', 'e', 'f', 0, - }; - CORRADE_COMPARE(offset, (Vector2i{15, 30})); - CORRADE_COMPARE_AS(image, - (ImageView2D{PixelFormat::R8Unorm, {3, 2}, pixels}), - DebugTools::CompareImage); + CORRADE_COMPARE(offset, (Vector2i{15, 30}) - padding()); + CORRADE_COMPARE(image.size(), (Vector2i{3, 2}) + 2*padding()); + + if(padding().isZero()) { + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + CORRADE_COMPARE_AS(image.pixels(), + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}), + DebugTools::CompareImage); + } else { + char pixels[]{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 'a', 'b', 'c', 0, 0, 0, + 0, 0, 'd', 'e', 'f', 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + CORRADE_COMPARE_AS(image.pixels(), + (ImageView2D{PixelFormat::R8Snorm, {7, 8}, pixels}), + DebugTools::CompareImage); + } } bool called = false; /* Deliberately using the deprecated PixelFormat-less constructor to verify that passing a R8Unorm image "just works" */ - } cache{{45, 35}}; + } cache{{45, 35}, data.padding}; CORRADE_IGNORE_DEPRECATED_POP /* Capture correct function name */ diff --git a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp index 8bdcef939..0117d02fb 100644 --- a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp @@ -132,6 +132,11 @@ const UnsignedByte ExpectedData[]{ void GlyphCacheGLTest::setImage() { GlyphCache cache{{16, 8}}; + /* Fill the texture with non-zero data to verify the padding gets uploaded + as well */ + cache.texture().setSubImage(0, {}, Image2D{PixelFormat::R8Unorm, {16, 8}, Containers::Array{DirectInit, 16*8, '\xcd'}}); + MAGNUM_VERIFY_NO_GL_ERROR(); + Utility::copy( Containers::StridedArrayView2D{InputData, {4, 8}}, cache.image().pixels()[0].sliceSize({4, 8}, {4, 8})); @@ -143,24 +148,45 @@ void GlyphCacheGLTest::setImage() { MutableImageView2D actual{actual3.format(), actual3.size().xy(), actual3.data()}; MAGNUM_VERIFY_NO_GL_ERROR(); - ImageView2D expected{PixelFormat::R8Unorm, {16, 8}, ExpectedData}; + /* The CPU-side image is zero-initialized, what was set above in the + texture isn't present there */ CORRADE_COMPARE_AS(actual, - expected, + (ImageView2D{PixelFormat::R8Unorm, {16, 8}, ExpectedData}), DebugTools::CompareImage); #ifdef MAGNUM_TARGET_GLES2 CORRADE_SKIP("Luminance format used on GLES2 isn't usable for framebuffer reading, can't verify texture contents."); #else - /* Verify the actual texture. It should be the same as above. On GLES we - cannot really verify that the size matches, but at least something. */ + /* The actual texture has just the slice updated, the rest stays. On GLES + we cannot really verify that the size matches, but at least + something. */ #ifndef MAGNUM_TARGET_GLES Image2D image = cache.texture().image(0, {PixelFormat::R8Unorm}); #else Image2D image = DebugTools::textureSubImage(cache.texture(), 0, {{}, {16, 8}}, {PixelFormat::R8Unorm}); #endif MAGNUM_VERIFY_NO_GL_ERROR(); + + const UnsignedByte expectedTextureData[]{ + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + }; CORRADE_COMPARE_AS(image, - expected, + (ImageView2D{PixelFormat::R8Unorm, {16, 8}, expectedTextureData}), DebugTools::CompareImage); #endif }