1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
3 * ----------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Bilinear image comparison.
22 *//*--------------------------------------------------------------------*/
23
24#include "tcuBilinearImageCompare.hpp"
25#include "tcuTexture.hpp"
26#include "tcuTextureUtil.hpp"
27#include "tcuRGBA.hpp"
28
29namespace tcu
30{
31
32namespace
33{
34
35enum
36{
37	NUM_SUBPIXEL_BITS	= 8	//!< Number of subpixel bits used when doing bilinear interpolation.
38};
39
40// \note Algorithm assumes that colors are packed to 32-bit values as dictated by
41//		 tcu::RGBA::*_SHIFT values.
42
43template<int Channel>
44static inline deUint8 getChannel (deUint32 color)
45{
46	return (deUint8)((color >> (Channel*8)) & 0xff);
47}
48
49#if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
50inline deUint32 readRGBA8Raw (const ConstPixelBufferAccess& src, deUint32 x, deUint32 y)
51{
52	return *(const deUint32*)((const deUint8*)src.getDataPtr() + y*src.getRowPitch() + x*4);
53}
54#else
55inline deUint32 readRGBA8Raw (const ConstPixelBufferAccess& src, deUint32 x, deUint32 y)
56{
57	return deReverseBytes32(*(const deUint32*)((const deUint8*)src.getDataPtr() + y*src.getRowPitch() + x*4));
58}
59#endif
60
61inline RGBA readRGBA8 (const ConstPixelBufferAccess& src, deUint32 x, deUint32 y)
62{
63	deUint32 raw = readRGBA8Raw(src, x, y);
64	deUint32 res = 0;
65
66	res |= getChannel<0>(raw) << RGBA::RED_SHIFT;
67	res |= getChannel<1>(raw) << RGBA::GREEN_SHIFT;
68	res |= getChannel<2>(raw) << RGBA::BLUE_SHIFT;
69	res |= getChannel<3>(raw) << RGBA::ALPHA_SHIFT;
70
71	return RGBA(res);
72}
73
74inline deUint8 interpolateChannel (deUint32 fx1, deUint32 fy1, deUint8 p00, deUint8 p01, deUint8 p10, deUint8 p11)
75{
76	const deUint32 fx0		= (1u<<NUM_SUBPIXEL_BITS) - fx1;
77	const deUint32 fy0		= (1u<<NUM_SUBPIXEL_BITS) - fy1;
78	const deUint32 half		= 1u<<(NUM_SUBPIXEL_BITS*2 - 1);
79	const deUint32 sum		= fx0*fy0*p00 + fx1*fy0*p10 + fx0*fy1*p01 + fx1*fy1*p11;
80	const deUint32 rounded	= (sum + half) >> (NUM_SUBPIXEL_BITS*2);
81
82	DE_ASSERT(de::inRange<deUint32>(rounded, 0, 0xff));
83	return (deUint8)rounded;
84}
85
86RGBA bilinearSampleRGBA8 (const ConstPixelBufferAccess& access, deUint32 u, deUint32 v)
87{
88	deUint32	x0		= u>>NUM_SUBPIXEL_BITS;
89	deUint32	y0		= v>>NUM_SUBPIXEL_BITS;
90	deUint32	x1		= x0+1; //de::min(x0+1, (deUint32)(access.getWidth()-1));
91	deUint32	y1		= y0+1; //de::min(y0+1, (deUint32)(access.getHeight()-1));
92
93	DE_ASSERT(x1 < (deUint32)access.getWidth());
94	DE_ASSERT(y1 < (deUint32)access.getHeight());
95
96	deUint32	fx1		= u-(x0<<NUM_SUBPIXEL_BITS);
97	deUint32	fy1		= v-(y0<<NUM_SUBPIXEL_BITS);
98
99	deUint32	p00		= readRGBA8Raw(access, x0, y0);
100	deUint32	p10		= readRGBA8Raw(access, x1, y0);
101	deUint32	p01		= readRGBA8Raw(access, x0, y1);
102	deUint32	p11		= readRGBA8Raw(access, x1, y1);
103
104	deUint32	res		= 0;
105
106	res |= interpolateChannel(fx1, fy1, getChannel<0>(p00), getChannel<0>(p01), getChannel<0>(p10), getChannel<0>(p11)) << RGBA::RED_SHIFT;
107	res |= interpolateChannel(fx1, fy1, getChannel<1>(p00), getChannel<1>(p01), getChannel<1>(p10), getChannel<1>(p11)) << RGBA::GREEN_SHIFT;
108	res |= interpolateChannel(fx1, fy1, getChannel<2>(p00), getChannel<2>(p01), getChannel<2>(p10), getChannel<2>(p11)) << RGBA::BLUE_SHIFT;
109	res |= interpolateChannel(fx1, fy1, getChannel<3>(p00), getChannel<3>(p01), getChannel<3>(p10), getChannel<3>(p11)) << RGBA::ALPHA_SHIFT;
110
111	return RGBA(res);
112}
113
114bool comparePixelRGBA8 (const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const RGBA threshold, int x, int y)
115{
116	const RGBA resPix = readRGBA8(result, (deUint32)x, (deUint32)y);
117
118	// Step 1: Compare result pixel to 3x3 neighborhood pixels in reference.
119	{
120		const deUint32	x0		= (deUint32)de::max(x-1, 0);
121		const deUint32	x1		= (deUint32)x;
122		const deUint32	x2		= (deUint32)de::min(x+1, reference.getWidth()-1);
123		const deUint32	y0		= (deUint32)de::max(y-1, 0);
124		const deUint32	y1		= (deUint32)y;
125		const deUint32	y2		= (deUint32)de::min(y+1, reference.getHeight()-1);
126
127		if (compareThreshold(resPix, readRGBA8(reference, x1, y1), threshold) ||
128			compareThreshold(resPix, readRGBA8(reference, x0, y1), threshold) ||
129			compareThreshold(resPix, readRGBA8(reference, x2, y1), threshold) ||
130			compareThreshold(resPix, readRGBA8(reference, x0, y0), threshold) ||
131			compareThreshold(resPix, readRGBA8(reference, x1, y0), threshold) ||
132			compareThreshold(resPix, readRGBA8(reference, x2, y0), threshold) ||
133			compareThreshold(resPix, readRGBA8(reference, x0, y2), threshold) ||
134			compareThreshold(resPix, readRGBA8(reference, x1, y2), threshold) ||
135			compareThreshold(resPix, readRGBA8(reference, x2, y2), threshold))
136			return true;
137	}
138
139	// Step 2: Compare using bilinear sampling.
140	{
141		// \todo [pyry] Optimize sample positions!
142		static const deUint32 s_offsets[][2] =
143		{
144			{ 226, 186 },
145			{ 335, 235 },
146			{ 279, 334 },
147			{ 178, 272 },
148			{ 112, 202 },
149			{ 306, 117 },
150			{ 396, 299 },
151			{ 206, 382 },
152			{ 146,  96 },
153			{ 423, 155 },
154			{ 361, 412 },
155			{  84, 339 },
156			{  48, 130 },
157			{ 367,  43 },
158			{ 455, 367 },
159			{ 105, 439 },
160			{  83,  46 },
161			{ 217,  24 },
162			{ 461,  71 },
163			{ 450, 459 },
164			{ 239, 469 },
165			{  67, 267 },
166			{ 459, 255 },
167			{  13, 416 },
168			{  10, 192 },
169			{ 141, 502 },
170			{ 503, 304 },
171			{ 380, 506 }
172		};
173
174		for (int sampleNdx = 0; sampleNdx < DE_LENGTH_OF_ARRAY(s_offsets); sampleNdx++)
175		{
176			const int u = ((x-1)<<NUM_SUBPIXEL_BITS) + (int)s_offsets[sampleNdx][0];
177			const int v = ((y-1)<<NUM_SUBPIXEL_BITS) + (int)s_offsets[sampleNdx][1];
178
179			if (!de::inBounds(u, 0, (reference.getWidth()-1)<<NUM_SUBPIXEL_BITS) ||
180				!de::inBounds(v, 0, (reference.getHeight()-1)<<NUM_SUBPIXEL_BITS))
181				continue;
182
183			if (compareThreshold(resPix, bilinearSampleRGBA8(reference, (deUint32)u, (deUint32)v), threshold))
184				return true;
185		}
186	}
187
188	return false;
189}
190
191bool bilinearCompareRGBA8 (const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const PixelBufferAccess& errorMask, const RGBA threshold)
192{
193	DE_ASSERT(reference.getFormat() == TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8) &&
194			  result.getFormat()	== TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8));
195
196	// Clear error mask first to green (faster this way).
197	clear(errorMask, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
198
199	bool allOk = true;
200
201	for (int y = 0; y < reference.getHeight(); y++)
202	{
203		for (int x = 0; x < reference.getWidth(); x++)
204		{
205			if (!comparePixelRGBA8(reference, result, threshold, x, y) &&
206				!comparePixelRGBA8(result, reference, threshold, x, y))
207			{
208				allOk = false;
209				errorMask.setPixel(Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y);
210			}
211		}
212	}
213
214	return allOk;
215}
216
217} // anonymous
218
219bool bilinearCompare (const ConstPixelBufferAccess& reference, const ConstPixelBufferAccess& result, const PixelBufferAccess& errorMask, const RGBA threshold)
220{
221	DE_ASSERT(reference.getWidth()	== result.getWidth()	&&
222			  reference.getHeight()	== result.getHeight()	&&
223			  reference.getDepth()	== result.getDepth()	&&
224			  reference.getFormat()	== result.getFormat());
225	DE_ASSERT(reference.getWidth()	== errorMask.getWidth()		&&
226			  reference.getHeight()	== errorMask.getHeight()	&&
227			  reference.getDepth()	== errorMask.getDepth());
228
229	if (reference.getFormat() == TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8))
230		return bilinearCompareRGBA8(reference, result, errorMask, threshold);
231	else
232		throw InternalError("Unsupported format for bilinear comparison");
233}
234
235} // tcu
236