/*------------------------------------------------------------------------- * drawElements Quality Program Tester Core * ---------------------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Image comparison utilities. *//*--------------------------------------------------------------------*/ #include "tcuImageCompare.hpp" #include "tcuSurface.hpp" #include "tcuFuzzyImageCompare.hpp" #include "tcuBilinearImageCompare.hpp" #include "tcuTestLog.hpp" #include "tcuVector.hpp" #include "tcuVectorUtil.hpp" #include "tcuRGBA.hpp" #include "tcuTexture.hpp" #include "tcuTextureUtil.hpp" #include "tcuFloat.hpp" #include namespace tcu { namespace { void computeScaleAndBias (const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, tcu::Vec4& scale, tcu::Vec4& bias) { Vec4 minVal; Vec4 maxVal; const float eps = 0.0001f; { Vec4 refMin; Vec4 refMax; estimatePixelValueRange(reference, refMin, refMax); minVal = refMin; maxVal = refMax; } { Vec4 resMin; Vec4 resMax; estimatePixelValueRange(result, resMin, resMax); minVal[0] = de::min(minVal[0], resMin[0]); minVal[1] = de::min(minVal[1], resMin[1]); minVal[2] = de::min(minVal[2], resMin[2]); minVal[3] = de::min(minVal[3], resMin[3]); maxVal[0] = de::max(maxVal[0], resMax[0]); maxVal[1] = de::max(maxVal[1], resMax[1]); maxVal[2] = de::max(maxVal[2], resMax[2]); maxVal[3] = de::max(maxVal[3], resMax[3]); } for (int c = 0; c < 4; c++) { if (maxVal[c] - minVal[c] < eps) { scale[c] = (maxVal[c] < eps) ? 1.0f : (1.0f / maxVal[c]); bias[c] = (c == 3) ? (1.0f - maxVal[c]*scale[c]) : (0.0f - minVal[c]*scale[c]); } else { scale[c] = 1.0f / (maxVal[c] - minVal[c]); bias[c] = 0.0f - minVal[c]*scale[c]; } } } static int findNumPositionDeviationFailingPixels (const PixelBufferAccess& errorMask, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const UVec4& threshold, const tcu::IVec3& maxPositionDeviation, bool acceptOutOfBoundsAsAnyValue) { const tcu::IVec4 okColor (0, 255, 0, 255); const tcu::IVec4 errorColor (255, 0, 0, 255); const int width = reference.getWidth(); const int height = reference.getHeight(); const int depth = reference.getDepth(); int numFailingPixels = 0; // Accept pixels "sampling" over the image bounds pixels since "taps" could be anything const int beginX = (acceptOutOfBoundsAsAnyValue) ? (maxPositionDeviation.x()) : (0); const int beginY = (acceptOutOfBoundsAsAnyValue) ? (maxPositionDeviation.y()) : (0); const int beginZ = (acceptOutOfBoundsAsAnyValue) ? (maxPositionDeviation.z()) : (0); const int endX = (acceptOutOfBoundsAsAnyValue) ? (width - maxPositionDeviation.x()) : (width); const int endY = (acceptOutOfBoundsAsAnyValue) ? (height - maxPositionDeviation.y()) : (height); const int endZ = (acceptOutOfBoundsAsAnyValue) ? (depth - maxPositionDeviation.z()) : (depth); TCU_CHECK_INTERNAL(result.getWidth() == width && result.getHeight() == height && result.getDepth() == depth); DE_ASSERT(endX > 0 && endY > 0 && endZ > 0); // most likely a bug tcu::clear(errorMask, okColor); for (int z = beginZ; z < endZ; z++) { for (int y = beginY; y < endY; y++) { for (int x = beginX; x < endX; x++) { const IVec4 refPix = reference.getPixelInt(x, y, z); const IVec4 cmpPix = result.getPixelInt(x, y, z); // Exact match { const UVec4 diff = abs(refPix - cmpPix).cast(); const bool isOk = boolAll(lessThanEqual(diff, threshold)); if (isOk) continue; } // Find matching pixels for both result and reference pixel { bool pixelFoundForReference = false; // Find deviated result pixel for reference for (int sz = de::max(0, z - maxPositionDeviation.z()); sz <= de::min(depth - 1, z + maxPositionDeviation.z()) && !pixelFoundForReference; ++sz) for (int sy = de::max(0, y - maxPositionDeviation.y()); sy <= de::min(height - 1, y + maxPositionDeviation.y()) && !pixelFoundForReference; ++sy) for (int sx = de::max(0, x - maxPositionDeviation.x()); sx <= de::min(width - 1, x + maxPositionDeviation.x()) && !pixelFoundForReference; ++sx) { const IVec4 deviatedCmpPix = result.getPixelInt(sx, sy, sz); const UVec4 diff = abs(refPix - deviatedCmpPix).cast(); const bool isOk = boolAll(lessThanEqual(diff, threshold)); pixelFoundForReference = isOk; } if (!pixelFoundForReference) { errorMask.setPixel(errorColor, x, y, z); ++numFailingPixels; continue; } } { bool pixelFoundForResult = false; // Find deviated reference pixel for result for (int sz = de::max(0, z - maxPositionDeviation.z()); sz <= de::min(depth - 1, z + maxPositionDeviation.z()) && !pixelFoundForResult; ++sz) for (int sy = de::max(0, y - maxPositionDeviation.y()); sy <= de::min(height - 1, y + maxPositionDeviation.y()) && !pixelFoundForResult; ++sy) for (int sx = de::max(0, x - maxPositionDeviation.x()); sx <= de::min(width - 1, x + maxPositionDeviation.x()) && !pixelFoundForResult; ++sx) { const IVec4 deviatedRefPix = reference.getPixelInt(sx, sy, sz); const UVec4 diff = abs(cmpPix - deviatedRefPix).cast(); const bool isOk = boolAll(lessThanEqual(diff, threshold)); pixelFoundForResult = isOk; } if (!pixelFoundForResult) { errorMask.setPixel(errorColor, x, y, z); ++numFailingPixels; continue; } } } } } return numFailingPixels; } } // anonymous /*--------------------------------------------------------------------*//*! * \brief Fuzzy image comparison * * This image comparison is designed for comparing images rendered by 3D * graphics APIs such as OpenGL. The comparison allows small local differences * and compensates for aliasing. * * The algorithm first performs light blurring on both images and then * does per-pixel analysis. Pixels are compared to 3x3 bilinear surface * defined by adjecent pixels. This compensates for both 1-pixel deviations * in geometry and aliasing in texture data. * * Error metric is computed based on the differences. On valid images the * metric is usually <0.01. Thus good threshold values are in range 0.02 to * 0.05. * * On failure error image is generated that shows where the failing pixels * are. * * \note Currently supports only UNORM_INT8 formats * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Error metric threshold (good values are 0.02-0.05) * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool fuzzyCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, float threshold, CompareLogMode logMode) { FuzzyCompareParams params; // Use defaults. TextureLevel errorMask (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), reference.getWidth(), reference.getHeight()); float difference = fuzzyCompare(params, reference, result, errorMask.getAccess()); bool isOk = difference <= threshold; Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); if (!isOk || logMode == COMPARE_LOG_EVERYTHING) { // Generate more accurate error mask. params.maxSampleSkip = 0; fuzzyCompare(params, reference, result, errorMask.getAccess()); if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8) && reference.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computeScaleAndBias(reference, result, pixelScale, pixelBias); if (!isOk) log << TestLog::Message << "Image comparison failed: difference = " << difference << ", threshold = " << threshold << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return isOk; } /*--------------------------------------------------------------------*//*! * \brief Fuzzy image comparison * * This image comparison is designed for comparing images rendered by 3D * graphics APIs such as OpenGL. The comparison allows small local differences * and compensates for aliasing. * * The algorithm first performs light blurring on both images and then * does per-pixel analysis. Pixels are compared to 3x3 bilinear surface * defined by adjecent pixels. This compensates for both 1-pixel deviations * in geometry and aliasing in texture data. * * Error metric is computed based on the differences. On valid images the * metric is usually <0.01. Thus good threshold values are in range 0.02 to * 0.05. * * On failure error image is generated that shows where the failing pixels * are. * * \note Currently supports only UNORM_INT8 formats * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Error metric threshold (good values are 0.02-0.05) * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool fuzzyCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const Surface& reference, const Surface& result, float threshold, CompareLogMode logMode) { return fuzzyCompare(log, imageSetName, imageSetDesc, reference.getAccess(), result.getAccess(), threshold, logMode); } static deInt64 computeSquaredDiffSum (const ConstPixelBufferAccess& ref, const ConstPixelBufferAccess& cmp, const PixelBufferAccess& diffMask, int diffFactor) { TCU_CHECK_INTERNAL(ref.getFormat().type == TextureFormat::UNORM_INT8 && cmp.getFormat().type == TextureFormat::UNORM_INT8); DE_ASSERT(ref.getWidth() == cmp.getWidth() && ref.getWidth() == diffMask.getWidth()); DE_ASSERT(ref.getHeight() == cmp.getHeight() && ref.getHeight() == diffMask.getHeight()); deInt64 diffSum = 0; for (int y = 0; y < cmp.getHeight(); y++) { for (int x = 0; x < cmp.getWidth(); x++) { IVec4 a = ref.getPixelInt(x, y); IVec4 b = cmp.getPixelInt(x, y); IVec4 diff = abs(a - b); int sum = diff.x() + diff.y() + diff.z() + diff.w(); int sqSum = diff.x()*diff.x() + diff.y()*diff.y() + diff.z()*diff.z() + diff.w()*diff.w(); diffMask.setPixel(tcu::RGBA(deClamp32(sum*diffFactor, 0, 255), deClamp32(255-sum*diffFactor, 0, 255), 0, 255).toVec(), x, y); diffSum += (deInt64)sqSum; } } return diffSum; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel difference accuracy metric * * Computes accuracy metric using per-pixel differences between reference * and result images. * * \note Supports only integer- and fixed-point formats * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param bestScoreDiff Scaling factor * \param worstScoreDiff Scaling factor * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ int measurePixelDiffAccuracy (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, int bestScoreDiff, int worstScoreDiff, CompareLogMode logMode) { TextureLevel diffMask (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), reference.getWidth(), reference.getHeight()); int diffFactor = 8; deInt64 squaredSum = computeSquaredDiffSum(reference, result, diffMask.getAccess(), diffFactor); float sum = deFloatSqrt((float)squaredSum); int score = deClamp32(deFloorFloatToInt32(100.0f - (de::max(sum-(float)bestScoreDiff, 0.0f) / (float)(worstScoreDiff-bestScoreDiff))*100.0f), 0, 100); const int failThreshold = 10; Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); if (logMode == COMPARE_LOG_EVERYTHING || score <= failThreshold) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8) && reference.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computeScaleAndBias(reference, result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("DiffMask", "Difference", diffMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } if (logMode != COMPARE_LOG_ON_ERROR || score <= failThreshold) log << TestLog::Integer("DiffSum", "Squared difference sum", "", QP_KEY_TAG_NONE, squaredSum) << TestLog::Integer("Score", "Score", "", QP_KEY_TAG_QUALITY, score); return score; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel difference accuracy metric * * Computes accuracy metric using per-pixel differences between reference * and result images. * * \note Supports only integer- and fixed-point formats * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param bestScoreDiff Scaling factor * \param worstScoreDiff Scaling factor * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ int measurePixelDiffAccuracy (TestLog& log, const char* imageSetName, const char* imageSetDesc, const Surface& reference, const Surface& result, int bestScoreDiff, int worstScoreDiff, CompareLogMode logMode) { return measurePixelDiffAccuracy(log, imageSetName, imageSetDesc, reference.getAccess(), result.getAccess(), bestScoreDiff, worstScoreDiff, logMode); } /*--------------------------------------------------------------------*//*! * Returns the index of float in a float space without denormals * so that: * 1) f(0.0) = 0 * 2) f(-0.0) = 0 * 3) f(b) = f(a) + 1 <==> b = nextAfter(a) * * See computeFloatFlushRelaxedULPDiff for details *//*--------------------------------------------------------------------*/ static deInt32 getPositionOfIEEEFloatWithoutDenormals (float x) { DE_ASSERT(!deIsNaN(x)); // not sane if (x == 0.0f) return 0; else if (x < 0.0f) return -getPositionOfIEEEFloatWithoutDenormals(-x); else { DE_ASSERT(x > 0.0f); const tcu::Float32 f(x); if (f.isDenorm()) { // Denorms are flushed to zero return 0; } else { // sign is 0, and it's a normal number. Natural position is its bit // pattern but since we've collapsed the denorms, we must remove // the gap here too to keep the float enumeration continuous. // // Denormals occupy one exponent pattern. Removing one from // exponent should to the trick. Add one since the removed range // contained one representable value, 0. return (deInt32)(f.bits() - (1u << 23u) + 1u); } } } static deUint32 computeFloatFlushRelaxedULPDiff (float a, float b) { if (deIsNaN(a) && deIsNaN(b)) return 0; else if (deIsNaN(a) || deIsNaN(b)) { return 0xFFFFFFFFu; } else { // Using the "definition 5" in Muller, Jean-Michel. "On the definition of ulp (x)" (2005) // assuming a floating point space is IEEE single precision floating point space without // denormals (and signed zeros). const deInt32 aIndex = getPositionOfIEEEFloatWithoutDenormals(a); const deInt32 bIndex = getPositionOfIEEEFloatWithoutDenormals(b); return (deUint32)de::abs(aIndex - bIndex); } } static tcu::UVec4 computeFlushRelaxedULPDiff (const tcu::Vec4& a, const tcu::Vec4& b) { return tcu::UVec4(computeFloatFlushRelaxedULPDiff(a.x(), b.x()), computeFloatFlushRelaxedULPDiff(a.y(), b.y()), computeFloatFlushRelaxedULPDiff(a.z(), b.z()), computeFloatFlushRelaxedULPDiff(a.w(), b.w())); } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based comparison * * This compare computes per-pixel differences between result and reference * image. Comparison fails if any pixels exceed the given threshold value. * * This comparison uses ULP (units in last place) metric for computing the * difference between floating-point values and thus this function can * be used only for comparing floating-point texture data. In ULP calculation * the denormal numbers are allowed to be flushed to zero. * * On failure error image is generated that shows where the failing pixels * are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed difference * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool floatUlpThresholdCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const UVec4& threshold, CompareLogMode logMode) { int width = reference.getWidth(); int height = reference.getHeight(); int depth = reference.getDepth(); TextureLevel errorMaskStorage (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); UVec4 maxDiff (0, 0, 0, 0); Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); TCU_CHECK(result.getWidth() == width && result.getHeight() == height && result.getDepth() == depth); for (int z = 0; z < depth; z++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { const Vec4 refPix = reference.getPixel(x, y, z); const Vec4 cmpPix = result.getPixel(x, y, z); const UVec4 diff = computeFlushRelaxedULPDiff(refPix, cmpPix); const bool isOk = boolAll(lessThanEqual(diff, threshold)); maxDiff = max(maxDiff, diff); errorMask.setPixel(isOk ? Vec4(0.0f, 1.0f, 0.0f, 1.0f) : Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y, z); } } } bool compareOk = boolAll(lessThanEqual(maxDiff, threshold)); if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { // All formats except normalized unsigned fixed point ones need remapping in order to fit into unorm channels in logged images. if (tcu::getTextureChannelClass(reference.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT || tcu::getTextureChannelClass(result.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) { computeScaleAndBias(reference, result, pixelScale, pixelBias); log << TestLog::Message << "Result and reference images are normalized with formula p * " << pixelScale << " + " << pixelBias << TestLog::EndMessage; } if (!compareOk) log << TestLog::Message << "Image comparison failed: max difference = " << maxDiff << ", threshold = " << threshold << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based comparison * * This compare computes per-pixel differences between result and reference * image. Comparison fails if any pixels exceed the given threshold value. * * This comparison can be used for floating-point and fixed-point formats. * Difference is computed in floating-point space. * * On failure an error image is generated that shows where the failing * pixels are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed difference * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool floatThresholdCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const Vec4& threshold, CompareLogMode logMode) { int width = reference.getWidth(); int height = reference.getHeight(); int depth = reference.getDepth(); TextureLevel errorMaskStorage (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); Vec4 maxDiff (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); TCU_CHECK_INTERNAL(result.getWidth() == width && result.getHeight() == height && result.getDepth() == depth); for (int z = 0; z < depth; z++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Vec4 refPix = reference.getPixel(x, y, z); Vec4 cmpPix = result.getPixel(x, y, z); Vec4 diff = abs(refPix - cmpPix); bool isOk = boolAll(lessThanEqual(diff, threshold)); maxDiff = max(maxDiff, diff); errorMask.setPixel(isOk ? Vec4(0.0f, 1.0f, 0.0f, 1.0f) : Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y, z); } } } bool compareOk = boolAll(lessThanEqual(maxDiff, threshold)); if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { // All formats except normalized unsigned fixed point ones need remapping in order to fit into unorm channels in logged images. if (tcu::getTextureChannelClass(reference.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT || tcu::getTextureChannelClass(result.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) { computeScaleAndBias(reference, result, pixelScale, pixelBias); log << TestLog::Message << "Result and reference images are normalized with formula p * " << pixelScale << " + " << pixelBias << TestLog::EndMessage; } if (!compareOk) log << TestLog::Message << "Image comparison failed: max difference = " << maxDiff << ", threshold = " << threshold << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based comparison * * This compare computes per-pixel differences between result and reference * color. Comparison fails if any pixels exceed the given threshold value. * * This comparison can be used for floating-point and fixed-point formats. * Difference is computed in floating-point space. * * On failure an error image is generated that shows where the failing * pixels are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference color * \param result Result image * \param threshold Maximum allowed difference * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool floatThresholdCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const Vec4& reference, const ConstPixelBufferAccess& result, const Vec4& threshold, CompareLogMode logMode) { const int width = result.getWidth(); const int height = result.getHeight(); const int depth = result.getDepth(); TextureLevel errorMaskStorage (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); Vec4 maxDiff (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); for (int z = 0; z < depth; z++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { const Vec4 cmpPix = result.getPixel(x, y, z); const Vec4 diff = abs(reference - cmpPix); const bool isOk = boolAll(lessThanEqual(diff, threshold)); maxDiff = max(maxDiff, diff); errorMask.setPixel(isOk ? Vec4(0.0f, 1.0f, 0.0f, 1.0f) : Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y, z); } } } bool compareOk = boolAll(lessThanEqual(maxDiff, threshold)); if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { // All formats except normalized unsigned fixed point ones need remapping in order to fit into unorm channels in logged images. if (tcu::getTextureChannelClass(result.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) { computeScaleAndBias(result, result, pixelScale, pixelBias); log << TestLog::Message << "Result image is normalized with formula p * " << pixelScale << " + " << pixelBias << TestLog::EndMessage; } if (!compareOk) log << TestLog::Message << "Image comparison failed: max difference = " << maxDiff << ", threshold = " << threshold << ", reference = " << reference << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based comparison * * This compare computes per-pixel differences between result and reference * image. Comparison fails if any pixels exceed the given threshold value. * * This comparison can be used for integer- and fixed-point texture formats. * Difference is computed in integer space. * * On failure error image is generated that shows where the failing pixels * are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed difference * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool intThresholdCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const UVec4& threshold, CompareLogMode logMode) { int width = reference.getWidth(); int height = reference.getHeight(); int depth = reference.getDepth(); TextureLevel errorMaskStorage (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); UVec4 maxDiff (0, 0, 0, 0); Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); TCU_CHECK_INTERNAL(result.getWidth() == width && result.getHeight() == height && result.getDepth() == depth); for (int z = 0; z < depth; z++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { IVec4 refPix = reference.getPixelInt(x, y, z); IVec4 cmpPix = result.getPixelInt(x, y, z); UVec4 diff = abs(refPix - cmpPix).cast(); bool isOk = boolAll(lessThanEqual(diff, threshold)); maxDiff = max(maxDiff, diff); errorMask.setPixel(isOk ? IVec4(0, 0xff, 0, 0xff) : IVec4(0xff, 0, 0, 0xff), x, y, z); } } } bool compareOk = boolAll(lessThanEqual(maxDiff, threshold)); if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { // All formats except normalized unsigned fixed point ones need remapping in order to fit into unorm channels in logged images. if (tcu::getTextureChannelClass(reference.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT || tcu::getTextureChannelClass(result.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) { computeScaleAndBias(reference, result, pixelScale, pixelBias); log << TestLog::Message << "Result and reference images are normalized with formula p * " << pixelScale << " + " << pixelBias << TestLog::EndMessage; } if (!compareOk) log << TestLog::Message << "Image comparison failed: max difference = " << maxDiff << ", threshold = " << threshold << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel depth/stencil threshold-based comparison * * This compare computes per-pixel differences between result and reference * image. Comparison fails if any pixels exceed the given threshold value. * * This comparison can be used for depth and depth/stencil images. * Difference is computed in integer space. * * On failure error image is generated that shows where the failing pixels * are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed depth difference (stencil must be exact) * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool dsThresholdCompare(TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const float threshold, CompareLogMode logMode) { int width = reference.getWidth(); int height = reference.getHeight(); int depth = reference.getDepth(); TextureLevel errorMaskStorage(TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); float maxDiff = 0.0; bool allStencilOk = true; bool hasDepth = tcu::hasDepthComponent(result.getFormat().order); bool hasStencil = tcu::hasStencilComponent(result.getFormat().order); TCU_CHECK_INTERNAL(result.getWidth() == width && result.getHeight() == height && result.getDepth() == depth); for (int z = 0; z < depth; z++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { bool isOk = true; if (hasDepth) { float refDepth = reference.getPixDepth(x, y, z); float cmpDepth = result.getPixDepth(x, y, z); float diff = de::abs(refDepth - cmpDepth); isOk = diff <= threshold; maxDiff = (float) deMax(maxDiff, diff); } if (hasStencil) { deUint8 refStencil = (deUint8) reference.getPixStencil(x, y, z); deUint8 cmpStencil = (deUint8) result.getPixStencil(x, y, z); bool isStencilOk = (refStencil == cmpStencil); allStencilOk = allStencilOk && isStencilOk; isOk = isOk && isStencilOk; } errorMask.setPixel(isOk ? IVec4(0, 0xff, 0, 0xff) : IVec4(0xff, 0, 0, 0xff), x, y, z); } } } bool compareOk = (maxDiff <= threshold) && allStencilOk; if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { if (!compareOk) { if (maxDiff > threshold) log << TestLog::Message << "Depth comparison failed: max difference = " << maxDiff << ", threshold = " << threshold << TestLog::EndMessage; if (!allStencilOk) log << TestLog::Message << "Stencil comparison failed" << TestLog::EndMessage; } log << TestLog::ImageSet(imageSetName, imageSetDesc) // TODO: Convert depth/stencil buffers into separate depth & stencil for logging? // << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) // << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { #if 0 if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; #endif } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based deviation-ignoring comparison * * This compare computes per-pixel differences between result and reference * image. Comparison fails if there is no pixel matching the given threshold * value in the search volume. * * If the search volume contains out-of-bounds pixels, comparison can be set * to either ignore these pixels in search or to accept any pixel that has * out-of-bounds pixels in its search volume. * * This comparison can be used for integer- and fixed-point texture formats. * Difference is computed in integer space. * * On failure error image is generated that shows where the failing pixels * are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed difference * \param maxPositionDeviation Maximum allowed distance in the search * volume. * \param acceptOutOfBoundsAsAnyValue Accept any pixel in the boundary region * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool intThresholdPositionDeviationCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const UVec4& threshold, const tcu::IVec3& maxPositionDeviation, bool acceptOutOfBoundsAsAnyValue, CompareLogMode logMode) { const int width = reference.getWidth(); const int height = reference.getHeight(); const int depth = reference.getDepth(); TextureLevel errorMaskStorage (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); const int numFailingPixels = findNumPositionDeviationFailingPixels(errorMask, reference, result, threshold, maxPositionDeviation, acceptOutOfBoundsAsAnyValue); const bool compareOk = numFailingPixels == 0; Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { // All formats except normalized unsigned fixed point ones need remapping in order to fit into unorm channels in logged images. if (tcu::getTextureChannelClass(reference.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT || tcu::getTextureChannelClass(result.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) { computeScaleAndBias(reference, result, pixelScale, pixelBias); log << TestLog::Message << "Result and reference images are normalized with formula p * " << pixelScale << " + " << pixelBias << TestLog::EndMessage; } if (!compareOk) log << TestLog::Message << "Image comparison failed:\n" << "\tallowed position deviation = " << maxPositionDeviation << "\n" << "\tcolor threshold = " << threshold << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based deviation-ignoring comparison * * This compare computes per-pixel differences between result and reference * image. Pixel fails the test if there is no pixel matching the given * threshold value in the search volume. Comparison fails if the number of * failing pixels exceeds the given limit. * * If the search volume contains out-of-bounds pixels, comparison can be set * to either ignore these pixels in search or to accept any pixel that has * out-of-bounds pixels in its search volume. * * This comparison can be used for integer- and fixed-point texture formats. * Difference is computed in integer space. * * On failure error image is generated that shows where the failing pixels * are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed difference * \param maxPositionDeviation Maximum allowed distance in the search * volume. * \param acceptOutOfBoundsAsAnyValue Accept any pixel in the boundary region * \param maxAllowedFailingPixels Maximum number of failing pixels * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool intThresholdPositionDeviationErrorThresholdCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const UVec4& threshold, const tcu::IVec3& maxPositionDeviation, bool acceptOutOfBoundsAsAnyValue, int maxAllowedFailingPixels, CompareLogMode logMode) { const int width = reference.getWidth(); const int height = reference.getHeight(); const int depth = reference.getDepth(); TextureLevel errorMaskStorage (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height, depth); PixelBufferAccess errorMask = errorMaskStorage.getAccess(); const int numFailingPixels = findNumPositionDeviationFailingPixels(errorMask, reference, result, threshold, maxPositionDeviation, acceptOutOfBoundsAsAnyValue); const bool compareOk = numFailingPixels <= maxAllowedFailingPixels; Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); if (!compareOk || logMode == COMPARE_LOG_EVERYTHING) { // All formats except normalized unsigned fixed point ones need remapping in order to fit into unorm channels in logged images. if (tcu::getTextureChannelClass(reference.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT || tcu::getTextureChannelClass(result.getFormat().type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT) { computeScaleAndBias(reference, result, pixelScale, pixelBias); log << TestLog::Message << "Result and reference images are normalized with formula p * " << pixelScale << " + " << pixelBias << TestLog::EndMessage; } if (!compareOk) log << TestLog::Message << "Image comparison failed:\n" << "\tallowed position deviation = " << maxPositionDeviation << "\n" << "\tcolor threshold = " << threshold << TestLog::EndMessage; log << TestLog::Message << "Number of failing pixels = " << numFailingPixels << ", max allowed = " << maxAllowedFailingPixels << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return compareOk; } /*--------------------------------------------------------------------*//*! * \brief Per-pixel threshold-based comparison * * This compare computes per-pixel differences between result and reference * image. Comparison fails if any pixels exceed the given threshold value. * * On failure error image is generated that shows where the failing pixels * are. * * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum allowed difference * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool pixelThresholdCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const Surface& reference, const Surface& result, const RGBA& threshold, CompareLogMode logMode) { return intThresholdCompare(log, imageSetName, imageSetDesc, reference.getAccess(), result.getAccess(), threshold.toIVec().cast(), logMode); } /*--------------------------------------------------------------------*//*! * \brief Bilinear image comparison * * \todo [pyry] Describe * * On failure error image is generated that shows where the failing pixels * are. * * \note Currently supports only RGBA, UNORM_INT8 formats * \param log Test log for results * \param imageSetName Name for image set when logging results * \param imageSetDesc Description for image set * \param reference Reference image * \param result Result image * \param threshold Maximum local difference * \param logMode Logging mode * \return true if comparison passes, false otherwise *//*--------------------------------------------------------------------*/ bool bilinearCompare (TestLog& log, const char* imageSetName, const char* imageSetDesc, const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const RGBA threshold, CompareLogMode logMode) { TextureLevel errorMask (TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), reference.getWidth(), reference.getHeight()); bool isOk = bilinearCompare(reference, result, errorMask, threshold); Vec4 pixelBias (0.0f, 0.0f, 0.0f, 0.0f); Vec4 pixelScale (1.0f, 1.0f, 1.0f, 1.0f); if (!isOk || logMode == COMPARE_LOG_EVERYTHING) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8) && reference.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computeScaleAndBias(reference, result, pixelScale, pixelBias); if (!isOk) log << TestLog::Message << "Image comparison failed, threshold = " << threshold << TestLog::EndMessage; log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::Image("Reference", "Reference", reference, pixelScale, pixelBias) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; } else if (logMode == COMPARE_LOG_RESULT) { if (result.getFormat() != TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8)) computePixelScaleBias(result, pixelScale, pixelBias); log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result, pixelScale, pixelBias) << TestLog::EndImageSet; } return isOk; } } // tcu