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