Lower correlation threshold in flush-finish tests again am: 6455e6f987 am: 2e18b48b04...
[platform/upstream/VK-GL-CTS.git] / framework / common / tcuFuzzyImageCompare.cpp
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 Fuzzy image comparison.
22  *//*--------------------------------------------------------------------*/
23
24 #include "tcuFuzzyImageCompare.hpp"
25 #include "tcuTexture.hpp"
26 #include "tcuTextureUtil.hpp"
27 #include "deMath.h"
28 #include "deRandom.hpp"
29
30 #include <vector>
31
32 namespace tcu
33 {
34
35 enum
36 {
37         MIN_ERR_THRESHOLD       = 4 // Magic to make small differences go away
38 };
39
40 using std::vector;
41
42 template<int Channel>
43 static inline deUint8 getChannel (deUint32 color)
44 {
45         return (deUint8)((color >> (Channel*8)) & 0xff);
46 }
47
48 static inline deUint8 getChannel (deUint32 color, int channel)
49 {
50         return (deUint8)((color >> (channel*8)) & 0xff);
51 }
52
53 static inline deUint32 setChannel (deUint32 color, int channel, deUint8 val)
54 {
55         return (color & ~(0xffu << (8*channel))) | (val << (8*channel));
56 }
57
58 static inline Vec4 toFloatVec (deUint32 color)
59 {
60         return Vec4((float)getChannel<0>(color), (float)getChannel<1>(color), (float)getChannel<2>(color), (float)getChannel<3>(color));
61 }
62
63 static inline deUint8 roundToUint8Sat (float v)
64 {
65         return (deUint8)de::clamp((int)(v + 0.5f), 0, 255);
66 }
67
68 static inline deUint32 toColor (Vec4 v)
69 {
70         return roundToUint8Sat(v[0]) | (roundToUint8Sat(v[1]) << 8) | (roundToUint8Sat(v[2]) << 16) | (roundToUint8Sat(v[3]) << 24);
71 }
72
73 template<int NumChannels>
74 static inline deUint32 readUnorm8 (const tcu::ConstPixelBufferAccess& src, int x, int y)
75 {
76         const deUint8*  ptr     = (const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*NumChannels;
77         deUint32                v       = 0;
78
79         for (int c = 0; c < NumChannels; c++)
80                 v |= ptr[c] << (c*8);
81
82         if (NumChannels < 4)
83                 v |= 0xffu << 24;
84
85         return v;
86 }
87
88 #if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
89 template<>
90 inline deUint32 readUnorm8<4> (const tcu::ConstPixelBufferAccess& src, int x, int y)
91 {
92         return *(const deUint32*)((const deUint8*)src.getDataPtr() + src.getRowPitch()*y + x*4);
93 }
94 #endif
95
96 template<int NumChannels>
97 static inline void writeUnorm8 (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
98 {
99         deUint8* ptr = (deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*NumChannels;
100
101         for (int c = 0; c < NumChannels; c++)
102                 ptr[c] = getChannel(val, c);
103 }
104
105 #if (DE_ENDIANNESS == DE_LITTLE_ENDIAN)
106 template<>
107 inline void writeUnorm8<4> (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
108 {
109         *(deUint32*)((deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*4) = val;
110 }
111 #endif
112
113 static inline deUint32 colorDistSquared (deUint32 pa, deUint32 pb)
114 {
115         const int       r       = de::max<int>(de::abs((int)getChannel<0>(pa) - (int)getChannel<0>(pb)) - MIN_ERR_THRESHOLD, 0);
116         const int       g       = de::max<int>(de::abs((int)getChannel<1>(pa) - (int)getChannel<1>(pb)) - MIN_ERR_THRESHOLD, 0);
117         const int       b       = de::max<int>(de::abs((int)getChannel<2>(pa) - (int)getChannel<2>(pb)) - MIN_ERR_THRESHOLD, 0);
118         const int       a       = de::max<int>(de::abs((int)getChannel<3>(pa) - (int)getChannel<3>(pb)) - MIN_ERR_THRESHOLD, 0);
119
120         return deUint32(r*r + g*g + b*b + a*a);
121 }
122
123 template<int NumChannels>
124 inline deUint32 bilinearSample (const ConstPixelBufferAccess& src, float u, float v)
125 {
126         int w = src.getWidth();
127         int h = src.getHeight();
128
129         int x0 = deFloorFloatToInt32(u-0.5f);
130         int x1 = x0+1;
131         int y0 = deFloorFloatToInt32(v-0.5f);
132         int y1 = y0+1;
133
134         int i0 = de::clamp(x0, 0, w-1);
135         int i1 = de::clamp(x1, 0, w-1);
136         int j0 = de::clamp(y0, 0, h-1);
137         int j1 = de::clamp(y1, 0, h-1);
138
139         float a = deFloatFrac(u-0.5f);
140         float b = deFloatFrac(v-0.5f);
141
142         deUint32 p00    = readUnorm8<NumChannels>(src, i0, j0);
143         deUint32 p10    = readUnorm8<NumChannels>(src, i1, j0);
144         deUint32 p01    = readUnorm8<NumChannels>(src, i0, j1);
145         deUint32 p11    = readUnorm8<NumChannels>(src, i1, j1);
146         deUint32 dst    = 0;
147
148         // Interpolate.
149         for (int c = 0; c < NumChannels; c++)
150         {
151                 float f = (getChannel(p00, c)*(1.0f-a)*(1.0f-b)) +
152                                   (getChannel(p10, c)*(     a)*(1.0f-b)) +
153                                   (getChannel(p01, c)*(1.0f-a)*(     b)) +
154                                   (getChannel(p11, c)*(     a)*(     b));
155                 dst = setChannel(dst, c, roundToUint8Sat(f));
156         }
157
158         return dst;
159 }
160
161 template<int DstChannels, int SrcChannels>
162 static void separableConvolve (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, int shiftX, int shiftY, const std::vector<float>& kernelX, const std::vector<float>& kernelY)
163 {
164         DE_ASSERT(dst.getWidth() == src.getWidth() && dst.getHeight() == src.getHeight());
165
166         TextureLevel            tmp                     (dst.getFormat(), dst.getHeight(), dst.getWidth());
167         PixelBufferAccess       tmpAccess       = tmp.getAccess();
168
169         int kw = (int)kernelX.size();
170         int kh = (int)kernelY.size();
171
172         // Horizontal pass
173         // \note Temporary surface is written in column-wise order
174         for (int j = 0; j < src.getHeight(); j++)
175         {
176                 for (int i = 0; i < src.getWidth(); i++)
177                 {
178                         Vec4 sum(0);
179
180                         for (int kx = 0; kx < kw; kx++)
181                         {
182                                 float           f = kernelX[kw-kx-1];
183                                 deUint32        p = readUnorm8<SrcChannels>(src, de::clamp(i+kx-shiftX, 0, src.getWidth()-1), j);
184
185                                 sum += toFloatVec(p)*f;
186                         }
187
188                         writeUnorm8<DstChannels>(tmpAccess, j, i, toColor(sum));
189                 }
190         }
191
192         // Vertical pass
193         for (int j = 0; j < src.getHeight(); j++)
194         {
195                 for (int i = 0; i < src.getWidth(); i++)
196                 {
197                         Vec4 sum(0.0f);
198
199                         for (int ky = 0; ky < kh; ky++)
200                         {
201                                 float           f = kernelY[kh-ky-1];
202                                 deUint32        p = readUnorm8<DstChannels>(tmpAccess, de::clamp(j+ky-shiftY, 0, tmp.getWidth()-1), i);
203
204                                 sum += toFloatVec(p)*f;
205                         }
206
207                         writeUnorm8<DstChannels>(dst, i, j, toColor(sum));
208                 }
209         }
210 }
211
212 template<int NumChannels>
213 static deUint32 distSquaredToNeighbor (de::Random& rnd, deUint32 pixel, const ConstPixelBufferAccess& surface, int x, int y)
214 {
215         // (x, y) + (0, 0)
216         deUint32        minDist         = colorDistSquared(pixel, readUnorm8<NumChannels>(surface, x, y));
217
218         if (minDist == 0)
219                 return minDist;
220
221         // Area around (x, y)
222         static const int s_coords[][2] =
223         {
224                 {-1, -1},
225                 { 0, -1},
226                 {+1, -1},
227                 {-1,  0},
228                 {+1,  0},
229                 {-1, +1},
230                 { 0, +1},
231                 {+1, +1}
232         };
233
234         for (int d = 0; d < (int)DE_LENGTH_OF_ARRAY(s_coords); d++)
235         {
236                 int dx = x + s_coords[d][0];
237                 int dy = y + s_coords[d][1];
238
239                 if (!deInBounds32(dx, 0, surface.getWidth()) || !deInBounds32(dy, 0, surface.getHeight()))
240                         continue;
241
242                 minDist = de::min(minDist, colorDistSquared(pixel, readUnorm8<NumChannels>(surface, dx, dy)));
243                 if (minDist == 0)
244                         return minDist;
245         }
246
247         // Random bilinear-interpolated samples around (x, y)
248         for (int s = 0; s < 32; s++)
249         {
250                 float dx = (float)x + rnd.getFloat()*2.0f - 0.5f;
251                 float dy = (float)y + rnd.getFloat()*2.0f - 0.5f;
252
253                 deUint32 sample = bilinearSample<NumChannels>(surface, dx, dy);
254
255                 minDist = de::min(minDist, colorDistSquared(pixel, sample));
256                 if (minDist == 0)
257                         return minDist;
258         }
259
260         return minDist;
261 }
262
263 static inline float toGrayscale (const Vec4& c)
264 {
265         return 0.2126f*c[0] + 0.7152f*c[1] + 0.0722f*c[2];
266 }
267
268 static bool isFormatSupported (const TextureFormat& format)
269 {
270         return format.type == TextureFormat::UNORM_INT8 && (format.order == TextureFormat::RGB || format.order == TextureFormat::RGBA);
271 }
272
273 float fuzzyCompare (const FuzzyCompareParams& params, const ConstPixelBufferAccess& ref, const ConstPixelBufferAccess& cmp, const PixelBufferAccess& errorMask)
274 {
275         DE_ASSERT(ref.getWidth() == cmp.getWidth() && ref.getHeight() == cmp.getHeight());
276         DE_ASSERT(errorMask.getWidth() == ref.getWidth() && errorMask.getHeight() == ref.getHeight());
277
278         if (!isFormatSupported(ref.getFormat()) || !isFormatSupported(cmp.getFormat()))
279                 throw InternalError("Unsupported format in fuzzy comparison", DE_NULL, __FILE__, __LINE__);
280
281         int                     width   = ref.getWidth();
282         int                     height  = ref.getHeight();
283         de::Random      rnd             (667);
284
285         // Filtered
286         TextureLevel refFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height);
287         TextureLevel cmpFiltered(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), width, height);
288
289         // Kernel = {0.1, 0.8, 0.1}
290         vector<float> kernel(3);
291         kernel[0] = kernel[2] = 0.1f; kernel[1]= 0.8f;
292         int shift = (int)(kernel.size() - 1) / 2;
293
294         switch (ref.getFormat().order)
295         {
296                 case TextureFormat::RGBA:       separableConvolve<4, 4>(refFiltered, ref, shift, shift, kernel, kernel);        break;
297                 case TextureFormat::RGB:        separableConvolve<4, 3>(refFiltered, ref, shift, shift, kernel, kernel);        break;
298                 default:
299                         DE_ASSERT(DE_FALSE);
300         }
301
302         switch (cmp.getFormat().order)
303         {
304                 case TextureFormat::RGBA:       separableConvolve<4, 4>(cmpFiltered, cmp, shift, shift, kernel, kernel);        break;
305                 case TextureFormat::RGB:        separableConvolve<4, 3>(cmpFiltered, cmp, shift, shift, kernel, kernel);        break;
306                 default:
307                         DE_ASSERT(DE_FALSE);
308         }
309
310         int                     numSamples      = 0;
311         deUint64        distSum4        = 0ull;
312
313         // Clear error mask to green.
314         clear(errorMask, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
315
316         ConstPixelBufferAccess refAccess = refFiltered.getAccess();
317         ConstPixelBufferAccess cmpAccess = cmpFiltered.getAccess();
318
319         for (int y = 1; y < height-1; y++)
320         {
321                 for (int x = 1; x < width-1; x += 1 + (int)rnd.getInt(0, params.maxSampleSkip))
322                 {
323                         const deUint32  minDist2RefToCmp        = distSquaredToNeighbor<4>(rnd, readUnorm8<4>(refAccess, x, y), cmpAccess, x, y);
324                         const deUint32  minDist2CmpToRef        = distSquaredToNeighbor<4>(rnd, readUnorm8<4>(cmpAccess, x, y), refAccess, x, y);
325                         const deUint32  minDist2                        = de::min(minDist2RefToCmp, minDist2CmpToRef);
326                         const deUint64  newSum4                         = distSum4 + minDist2*minDist2;
327
328                         distSum4         = (newSum4 >= distSum4) ? newSum4 : ~0ull; // In case of overflow
329                         numSamples      += 1;
330
331                         // Build error image.
332                         {
333                                 const int       scale   = 255-MIN_ERR_THRESHOLD;
334                                 const float     err2    = float(minDist2) / float(scale*scale);
335                                 const float     err4    = err2*err2;
336                                 const float     red             = err4 * 500.0f;
337                                 const float     luma    = toGrayscale(cmp.getPixel(x, y));
338                                 const float     rF              = 0.7f + 0.3f*luma;
339
340                                 errorMask.setPixel(Vec4(red*rF, (1.0f-red)*rF, 0.0f, 1.0f), x, y);
341                         }
342                 }
343         }
344
345         {
346                 // Scale error sum based on number of samples taken
347                 const double    pSamples        = double((width-2) * (height-2)) / double(numSamples);
348                 const deUint64  colScale        = deUint64(255-MIN_ERR_THRESHOLD);
349                 const deUint64  colScale4       = colScale*colScale*colScale*colScale;
350
351                 return float(double(distSum4) / double(colScale4) * pSamples);
352         }
353 }
354
355 } // tcu