/*-------------------------------------------------------------------------
 * 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 <string.h>

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()) : (0);
	const int			endY				= (acceptOutOfBoundsAsAnyValue) ? (height - maxPositionDeviation.y()) : (0);
	const int			endZ				= (acceptOutOfBoundsAsAnyValue) ? (depth  - maxPositionDeviation.z()) : (0);

	TCU_CHECK_INTERNAL(result.getWidth() == width && result.getHeight() == height && result.getDepth() == depth);

	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<deUint32>();
					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<deUint32>();
						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<deUint32>();
						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.
			return (deInt32)(f.bits() - (1u << 23u));
		}
	}
}

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<deUint32>();
				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 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<deUint32>(), 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