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 Rasterization verifier utils.
22 *//*--------------------------------------------------------------------*/
23
24 #include "tcuRasterizationVerifier.hpp"
25 #include "tcuVector.hpp"
26 #include "tcuSurface.hpp"
27 #include "tcuTestLog.hpp"
28 #include "tcuTextureUtil.hpp"
29 #include "tcuVectorUtil.hpp"
30 #include "tcuFloat.hpp"
31
32 #include "deMath.h"
33 #include "deStringUtil.hpp"
34
35 #include "rrRasterizer.hpp"
36
37 #include <limits>
38
39 namespace tcu
40 {
41 namespace
42 {
43
44 bool verifyLineGroupInterpolationWithProjectedWeights(const tcu::Surface &surface, const LineSceneSpec &scene,
45 const RasterizationArguments &args, tcu::TestLog &log);
46
lineLineIntersect(const tcu::Vector<int64_t,2> & line0Beg,const tcu::Vector<int64_t,2> & line0End,const tcu::Vector<int64_t,2> & line1Beg,const tcu::Vector<int64_t,2> & line1End)47 bool lineLineIntersect(const tcu::Vector<int64_t, 2> &line0Beg, const tcu::Vector<int64_t, 2> &line0End,
48 const tcu::Vector<int64_t, 2> &line1Beg, const tcu::Vector<int64_t, 2> &line1End)
49 {
50 typedef tcu::Vector<int64_t, 2> I64Vec2;
51
52 // Lines do not intersect if the other line's endpoints are on the same side
53 // otherwise, the do intersect
54
55 // Test line 0
56 {
57 const I64Vec2 line = line0End - line0Beg;
58 const I64Vec2 v0 = line1Beg - line0Beg;
59 const I64Vec2 v1 = line1End - line0Beg;
60 const int64_t crossProduct0 = (line.x() * v0.y() - line.y() * v0.x());
61 const int64_t crossProduct1 = (line.x() * v1.y() - line.y() * v1.x());
62
63 // check signs
64 if ((crossProduct0 < 0 && crossProduct1 < 0) || (crossProduct0 > 0 && crossProduct1 > 0))
65 return false;
66 }
67
68 // Test line 1
69 {
70 const I64Vec2 line = line1End - line1Beg;
71 const I64Vec2 v0 = line0Beg - line1Beg;
72 const I64Vec2 v1 = line0End - line1Beg;
73 const int64_t crossProduct0 = (line.x() * v0.y() - line.y() * v0.x());
74 const int64_t crossProduct1 = (line.x() * v1.y() - line.y() * v1.x());
75
76 // check signs
77 if ((crossProduct0 < 0 && crossProduct1 < 0) || (crossProduct0 > 0 && crossProduct1 > 0))
78 return false;
79 }
80
81 return true;
82 }
83
isTriangleClockwise(const tcu::Vec4 & p0,const tcu::Vec4 & p1,const tcu::Vec4 & p2)84 bool isTriangleClockwise(const tcu::Vec4 &p0, const tcu::Vec4 &p1, const tcu::Vec4 &p2)
85 {
86 const tcu::Vec2 u(p1.x() / p1.w() - p0.x() / p0.w(), p1.y() / p1.w() - p0.y() / p0.w());
87 const tcu::Vec2 v(p2.x() / p2.w() - p0.x() / p0.w(), p2.y() / p2.w() - p0.y() / p0.w());
88 const float crossProduct = (u.x() * v.y() - u.y() * v.x());
89
90 return crossProduct > 0.0f;
91 }
92
compareColors(const tcu::RGBA & colorA,const tcu::RGBA & colorB,int redBits,int greenBits,int blueBits)93 bool compareColors(const tcu::RGBA &colorA, const tcu::RGBA &colorB, int redBits, int greenBits, int blueBits)
94 {
95 const int thresholdRed = 1 << (8 - redBits);
96 const int thresholdGreen = 1 << (8 - greenBits);
97 const int thresholdBlue = 1 << (8 - blueBits);
98
99 return deAbs32(colorA.getRed() - colorB.getRed()) <= thresholdRed &&
100 deAbs32(colorA.getGreen() - colorB.getGreen()) <= thresholdGreen &&
101 deAbs32(colorA.getBlue() - colorB.getBlue()) <= thresholdBlue;
102 }
103
pixelNearLineSegment(const tcu::IVec2 & pixel,const tcu::Vec2 & p0,const tcu::Vec2 & p1)104 bool pixelNearLineSegment(const tcu::IVec2 &pixel, const tcu::Vec2 &p0, const tcu::Vec2 &p1)
105 {
106 const tcu::Vec2 pixelCenterPosition = tcu::Vec2((float)pixel.x() + 0.5f, (float)pixel.y() + 0.5f);
107
108 // "Near" = Distance from the line to the pixel is less than 2 * pixel_max_radius. (pixel_max_radius = sqrt(2) / 2)
109 const float maxPixelDistance = 1.414f;
110 const float maxPixelDistanceSquared = 2.0f;
111
112 // Near the line
113 {
114 const tcu::Vec2 line = p1 - p0;
115 const tcu::Vec2 v = pixelCenterPosition - p0;
116 const float crossProduct = (line.x() * v.y() - line.y() * v.x());
117
118 // distance to line: (line x v) / |line|
119 // |(line x v) / |line|| > maxPixelDistance
120 // ==> (line x v)^2 / |line|^2 > maxPixelDistance^2
121 // ==> (line x v)^2 > maxPixelDistance^2 * |line|^2
122
123 if (crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(line))
124 return false;
125 }
126
127 // Between the endpoints
128 {
129 // distance from line endpoint 1 to pixel is less than line length + maxPixelDistance
130 const float maxDistance = tcu::length(p1 - p0) + maxPixelDistance;
131
132 if (tcu::length(pixelCenterPosition - p0) > maxDistance)
133 return false;
134 if (tcu::length(pixelCenterPosition - p1) > maxDistance)
135 return false;
136 }
137
138 return true;
139 }
140
pixelOnlyOnASharedEdge(const tcu::IVec2 & pixel,const TriangleSceneSpec::SceneTriangle & triangle,const tcu::IVec2 & viewportSize)141 bool pixelOnlyOnASharedEdge(const tcu::IVec2 &pixel, const TriangleSceneSpec::SceneTriangle &triangle,
142 const tcu::IVec2 &viewportSize)
143 {
144 if (triangle.sharedEdge[0] || triangle.sharedEdge[1] || triangle.sharedEdge[2])
145 {
146 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = {
147 tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(),
148 triangle.positions[0].y() / triangle.positions[0].w()),
149 tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(),
150 triangle.positions[1].y() / triangle.positions[1].w()),
151 tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(),
152 triangle.positions[2].y() / triangle.positions[2].w()),
153 };
154 const tcu::Vec2 triangleScreenSpace[3] = {
155 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
156 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
157 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
158 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
159 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
160 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
161 };
162
163 const bool pixelOnEdge0 = pixelNearLineSegment(pixel, triangleScreenSpace[0], triangleScreenSpace[1]);
164 const bool pixelOnEdge1 = pixelNearLineSegment(pixel, triangleScreenSpace[1], triangleScreenSpace[2]);
165 const bool pixelOnEdge2 = pixelNearLineSegment(pixel, triangleScreenSpace[2], triangleScreenSpace[0]);
166
167 // If the pixel is on a multiple edges return false
168
169 if (pixelOnEdge0 && !pixelOnEdge1 && !pixelOnEdge2)
170 return triangle.sharedEdge[0];
171 if (!pixelOnEdge0 && pixelOnEdge1 && !pixelOnEdge2)
172 return triangle.sharedEdge[1];
173 if (!pixelOnEdge0 && !pixelOnEdge1 && pixelOnEdge2)
174 return triangle.sharedEdge[2];
175 }
176
177 return false;
178 }
179
triangleArea(const tcu::Vec2 & s0,const tcu::Vec2 & s1,const tcu::Vec2 & s2)180 float triangleArea(const tcu::Vec2 &s0, const tcu::Vec2 &s1, const tcu::Vec2 &s2)
181 {
182 const tcu::Vec2 u(s1.x() - s0.x(), s1.y() - s0.y());
183 const tcu::Vec2 v(s2.x() - s0.x(), s2.y() - s0.y());
184 const float crossProduct = (u.x() * v.y() - u.y() * v.x());
185
186 return crossProduct / 2.0f;
187 }
188
getTriangleAABB(const TriangleSceneSpec::SceneTriangle & triangle,const tcu::IVec2 & viewportSize)189 tcu::IVec4 getTriangleAABB(const TriangleSceneSpec::SceneTriangle &triangle, const tcu::IVec2 &viewportSize)
190 {
191 const tcu::Vec2 normalizedDeviceSpace[3] = {
192 tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(),
193 triangle.positions[0].y() / triangle.positions[0].w()),
194 tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(),
195 triangle.positions[1].y() / triangle.positions[1].w()),
196 tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(),
197 triangle.positions[2].y() / triangle.positions[2].w()),
198 };
199 const tcu::Vec2 screenSpace[3] = {
200 (normalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
201 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
202 (normalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
203 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
204 (normalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
205 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
206 };
207
208 tcu::IVec4 aabb;
209
210 aabb.x() = (int)deFloatFloor(de::min(de::min(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
211 aabb.y() = (int)deFloatFloor(de::min(de::min(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
212 aabb.z() = (int)deFloatCeil(de::max(de::max(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
213 aabb.w() = (int)deFloatCeil(de::max(de::max(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
214
215 return aabb;
216 }
217
getExponentEpsilonFromULP(int valueExponent,uint32_t ulp)218 float getExponentEpsilonFromULP(int valueExponent, uint32_t ulp)
219 {
220 DE_ASSERT(ulp < (1u << 10));
221
222 // assume mediump precision, using ulp as ulps in a 10 bit mantissa
223 return tcu::Float32::construct(+1, valueExponent, (1u << 23) + (ulp << (23 - 10))).asFloat() -
224 tcu::Float32::construct(+1, valueExponent, (1u << 23)).asFloat();
225 }
226
getValueEpsilonFromULP(float value,uint32_t ulp)227 float getValueEpsilonFromULP(float value, uint32_t ulp)
228 {
229 DE_ASSERT(value != std::numeric_limits<float>::infinity() && value != -std::numeric_limits<float>::infinity());
230
231 const int exponent = tcu::Float32(value).exponent();
232 return getExponentEpsilonFromULP(exponent, ulp);
233 }
234
getMaxValueWithinError(float value,uint32_t ulp)235 float getMaxValueWithinError(float value, uint32_t ulp)
236 {
237 if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
238 return value;
239
240 return value + getValueEpsilonFromULP(value, ulp);
241 }
242
getMinValueWithinError(float value,uint32_t ulp)243 float getMinValueWithinError(float value, uint32_t ulp)
244 {
245 if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
246 return value;
247
248 return value - getValueEpsilonFromULP(value, ulp);
249 }
250
getMinFlushToZero(float value)251 float getMinFlushToZero(float value)
252 {
253 // flush to zero if that decreases the value
254 // assume mediump precision
255 if (value > 0.0f && value < tcu::Float32::construct(+1, -14, 1u << 23).asFloat())
256 return 0.0f;
257 return value;
258 }
259
getMaxFlushToZero(float value)260 float getMaxFlushToZero(float value)
261 {
262 // flush to zero if that increases the value
263 // assume mediump precision
264 if (value < 0.0f && value > tcu::Float32::construct(-1, -14, 1u << 23).asFloat())
265 return 0.0f;
266 return value;
267 }
268
convertRGB8ToNativeFormat(const tcu::RGBA & color,const RasterizationArguments & args)269 tcu::IVec3 convertRGB8ToNativeFormat(const tcu::RGBA &color, const RasterizationArguments &args)
270 {
271 tcu::IVec3 pixelNativeColor;
272
273 for (int channelNdx = 0; channelNdx < 3; ++channelNdx)
274 {
275 const int channelBitCount = (channelNdx == 0) ? (args.redBits) :
276 (channelNdx == 1) ? (args.greenBits) :
277 (args.blueBits);
278 const int channelPixelValue = (channelNdx == 0) ? (color.getRed()) :
279 (channelNdx == 1) ? (color.getGreen()) :
280 (color.getBlue());
281
282 if (channelBitCount <= 8)
283 pixelNativeColor[channelNdx] = channelPixelValue >> (8 - channelBitCount);
284 else if (channelBitCount == 8)
285 pixelNativeColor[channelNdx] = channelPixelValue;
286 else
287 {
288 // just in case someone comes up with 8+ bits framebuffers pixel formats. But as
289 // we can only read in rgba8, we have to guess the trailing bits. Guessing 0.
290 pixelNativeColor[channelNdx] = channelPixelValue << (channelBitCount - 8);
291 }
292 }
293
294 return pixelNativeColor;
295 }
296
297 /*--------------------------------------------------------------------*//*!
298 * Returns the maximum value of x / y, where x c [minDividend, maxDividend]
299 * and y c [minDivisor, maxDivisor]
300 *//*--------------------------------------------------------------------*/
maximalRangeDivision(float minDividend,float maxDividend,float minDivisor,float maxDivisor)301 float maximalRangeDivision(float minDividend, float maxDividend, float minDivisor, float maxDivisor)
302 {
303 DE_ASSERT(minDividend <= maxDividend);
304 DE_ASSERT(minDivisor <= maxDivisor);
305
306 // special cases
307 if (minDividend == 0.0f && maxDividend == 0.0f)
308 return 0.0f;
309 if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
310 return std::numeric_limits<float>::infinity();
311
312 return de::max(de::max(minDividend / minDivisor, minDividend / maxDivisor),
313 de::max(maxDividend / minDivisor, maxDividend / maxDivisor));
314 }
315
316 /*--------------------------------------------------------------------*//*!
317 * Returns the minimum value of x / y, where x c [minDividend, maxDividend]
318 * and y c [minDivisor, maxDivisor]
319 *//*--------------------------------------------------------------------*/
minimalRangeDivision(float minDividend,float maxDividend,float minDivisor,float maxDivisor)320 float minimalRangeDivision(float minDividend, float maxDividend, float minDivisor, float maxDivisor)
321 {
322 DE_ASSERT(minDividend <= maxDividend);
323 DE_ASSERT(minDivisor <= maxDivisor);
324
325 // special cases
326 if (minDividend == 0.0f && maxDividend == 0.0f)
327 return 0.0f;
328 if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
329 return -std::numeric_limits<float>::infinity();
330
331 return de::min(de::min(minDividend / minDivisor, minDividend / maxDivisor),
332 de::min(maxDividend / minDivisor, maxDividend / maxDivisor));
333 }
334
isLineXMajor(const tcu::Vec2 & lineScreenSpaceP0,const tcu::Vec2 & lineScreenSpaceP1)335 static bool isLineXMajor(const tcu::Vec2 &lineScreenSpaceP0, const tcu::Vec2 &lineScreenSpaceP1)
336 {
337 return de::abs(lineScreenSpaceP1.x() - lineScreenSpaceP0.x()) >=
338 de::abs(lineScreenSpaceP1.y() - lineScreenSpaceP0.y());
339 }
340
isPackedSSLineXMajor(const tcu::Vec4 & packedLine)341 static bool isPackedSSLineXMajor(const tcu::Vec4 &packedLine)
342 {
343 const tcu::Vec2 lineScreenSpaceP0 = packedLine.swizzle(0, 1);
344 const tcu::Vec2 lineScreenSpaceP1 = packedLine.swizzle(2, 3);
345
346 return isLineXMajor(lineScreenSpaceP0, lineScreenSpaceP1);
347 }
348
349 struct InterpolationRange
350 {
351 tcu::Vec3 max;
352 tcu::Vec3 min;
353 };
354
355 struct LineInterpolationRange
356 {
357 tcu::Vec2 max;
358 tcu::Vec2 min;
359 };
360
calcTriangleInterpolationWeights(const tcu::Vec4 & p0,const tcu::Vec4 & p1,const tcu::Vec4 & p2,const tcu::Vec2 & ndpixel)361 InterpolationRange calcTriangleInterpolationWeights(const tcu::Vec4 &p0, const tcu::Vec4 &p1, const tcu::Vec4 &p2,
362 const tcu::Vec2 &ndpixel)
363 {
364 const int roundError = 1;
365 const int barycentricError = 3;
366 const int divError = 8;
367
368 const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
369 const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
370 const tcu::Vec2 nd2 = p2.swizzle(0, 1) / p2.w();
371
372 const float ka = triangleArea(ndpixel, nd1, nd2);
373 const float kb = triangleArea(ndpixel, nd2, nd0);
374 const float kc = triangleArea(ndpixel, nd0, nd1);
375
376 const float kaMax = getMaxFlushToZero(getMaxValueWithinError(ka, barycentricError));
377 const float kbMax = getMaxFlushToZero(getMaxValueWithinError(kb, barycentricError));
378 const float kcMax = getMaxFlushToZero(getMaxValueWithinError(kc, barycentricError));
379 const float kaMin = getMinFlushToZero(getMinValueWithinError(ka, barycentricError));
380 const float kbMin = getMinFlushToZero(getMinValueWithinError(kb, barycentricError));
381 const float kcMin = getMinFlushToZero(getMinValueWithinError(kc, barycentricError));
382 DE_ASSERT(kaMin <= kaMax);
383 DE_ASSERT(kbMin <= kbMax);
384 DE_ASSERT(kcMin <= kcMax);
385
386 // calculate weights: vec3(ka / p0.w, kb / p1.w, kc / p2.w) / (ka / p0.w + kb / p1.w + kc / p2.w)
387 const float maxPreDivisionValues[3] = {
388 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kaMax / p0.w()), divError)),
389 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kbMax / p1.w()), divError)),
390 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kcMax / p2.w()), divError)),
391 };
392 const float minPreDivisionValues[3] = {
393 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kaMin / p0.w()), divError)),
394 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kbMin / p1.w()), divError)),
395 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kcMin / p2.w()), divError)),
396 };
397 DE_ASSERT(minPreDivisionValues[0] <= maxPreDivisionValues[0]);
398 DE_ASSERT(minPreDivisionValues[1] <= maxPreDivisionValues[1]);
399 DE_ASSERT(minPreDivisionValues[2] <= maxPreDivisionValues[2]);
400
401 const float maxDivisor = getMaxFlushToZero(getMaxValueWithinError(
402 maxPreDivisionValues[0] + maxPreDivisionValues[1] + maxPreDivisionValues[2], 2 * roundError));
403 const float minDivisor = getMinFlushToZero(getMinValueWithinError(
404 minPreDivisionValues[0] + minPreDivisionValues[1] + minPreDivisionValues[2], 2 * roundError));
405 DE_ASSERT(minDivisor <= maxDivisor);
406
407 InterpolationRange returnValue;
408
409 returnValue.max.x() = getMaxFlushToZero(
410 getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0],
411 minDivisor, maxDivisor)),
412 divError));
413 returnValue.max.y() = getMaxFlushToZero(
414 getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1],
415 minDivisor, maxDivisor)),
416 divError));
417 returnValue.max.z() = getMaxFlushToZero(
418 getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2],
419 minDivisor, maxDivisor)),
420 divError));
421 returnValue.min.x() = getMinFlushToZero(
422 getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0],
423 minDivisor, maxDivisor)),
424 divError));
425 returnValue.min.y() = getMinFlushToZero(
426 getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1],
427 minDivisor, maxDivisor)),
428 divError));
429 returnValue.min.z() = getMinFlushToZero(
430 getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2],
431 minDivisor, maxDivisor)),
432 divError));
433
434 DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
435 DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
436 DE_ASSERT(returnValue.min.z() <= returnValue.max.z());
437
438 return returnValue;
439 }
440
calcLineInterpolationWeights(const tcu::Vec2 & pa,float wa,const tcu::Vec2 & pb,float wb,const tcu::Vec2 & pr)441 LineInterpolationRange calcLineInterpolationWeights(const tcu::Vec2 &pa, float wa, const tcu::Vec2 &pb, float wb,
442 const tcu::Vec2 &pr)
443 {
444 const int roundError = 1;
445 const int divError = 3;
446
447 // calc weights:
448 // (1-t) / wa t / wb
449 // ------------------- , -------------------
450 // (1-t) / wa + t / wb (1-t) / wa + t / wb
451
452 // Allow 1 ULP
453 const float dividend = tcu::dot(pr - pa, pb - pa);
454 const float dividendMax = getMaxValueWithinError(dividend, 1);
455 const float dividendMin = getMinValueWithinError(dividend, 1);
456 DE_ASSERT(dividendMin <= dividendMax);
457
458 // Assuming lengthSquared will not be implemented as sqrt(x)^2, allow 1 ULP
459 const float divisor = tcu::lengthSquared(pb - pa);
460 const float divisorMax = getMaxValueWithinError(divisor, 1);
461 const float divisorMin = getMinValueWithinError(divisor, 1);
462 DE_ASSERT(divisorMin <= divisorMax);
463
464 // Allow 3 ULP precision for division
465 const float tMax =
466 getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
467 const float tMin =
468 getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
469 DE_ASSERT(tMin <= tMax);
470
471 const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError);
472 const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError);
473 DE_ASSERT(perspectiveTMin <= perspectiveTMax);
474
475 const float perspectiveInvTMax =
476 getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
477 const float perspectiveInvTMin =
478 getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
479 DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);
480
481 const float perspectiveDivisorMax = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
482 const float perspectiveDivisorMin = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
483 DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
484
485 LineInterpolationRange returnValue;
486 returnValue.max.x() = getMaxValueWithinError(
487 maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax),
488 divError);
489 returnValue.max.y() = getMaxValueWithinError(
490 maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
491 returnValue.min.x() = getMinValueWithinError(
492 minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax),
493 divError);
494 returnValue.min.y() = getMinValueWithinError(
495 minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
496
497 DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
498 DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
499
500 return returnValue;
501 }
502
calcLineInterpolationWeightsAxisProjected(const tcu::Vec2 & pa,float wa,const tcu::Vec2 & pb,float wb,const tcu::Vec2 & pr)503 LineInterpolationRange calcLineInterpolationWeightsAxisProjected(const tcu::Vec2 &pa, float wa, const tcu::Vec2 &pb,
504 float wb, const tcu::Vec2 &pr)
505 {
506 const int roundError = 1;
507 const int divError = 3;
508 const bool isXMajor = isLineXMajor(pa, pb);
509 const int majorAxisNdx = (isXMajor) ? (0) : (1);
510
511 // calc weights:
512 // (1-t) / wa t / wb
513 // ------------------- , -------------------
514 // (1-t) / wa + t / wb (1-t) / wa + t / wb
515
516 // Use axis projected (inaccurate) method, i.e. for X-major lines:
517 // (xd - xa) * (xb - xa) xd - xa
518 // t = --------------------- == -------
519 // ( xb - xa ) ^ 2 xb - xa
520
521 // Allow 1 ULP
522 const float dividend = (pr[majorAxisNdx] - pa[majorAxisNdx]);
523 const float dividendMax = getMaxValueWithinError(dividend, 1);
524 const float dividendMin = getMinValueWithinError(dividend, 1);
525 DE_ASSERT(dividendMin <= dividendMax);
526
527 // Allow 1 ULP
528 const float divisor = (pb[majorAxisNdx] - pa[majorAxisNdx]);
529 const float divisorMax = getMaxValueWithinError(divisor, 1);
530 const float divisorMin = getMinValueWithinError(divisor, 1);
531 DE_ASSERT(divisorMin <= divisorMax);
532
533 // Allow 3 ULP precision for division
534 const float tMax =
535 getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
536 const float tMin =
537 getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
538 DE_ASSERT(tMin <= tMax);
539
540 const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError);
541 const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError);
542 DE_ASSERT(perspectiveTMin <= perspectiveTMax);
543
544 const float perspectiveInvTMax =
545 getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
546 const float perspectiveInvTMin =
547 getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
548 DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);
549
550 const float perspectiveDivisorMax = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
551 const float perspectiveDivisorMin = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
552 DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
553
554 LineInterpolationRange returnValue;
555 returnValue.max.x() = getMaxValueWithinError(
556 maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax),
557 divError);
558 returnValue.max.y() = getMaxValueWithinError(
559 maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
560 returnValue.min.x() = getMinValueWithinError(
561 minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax),
562 divError);
563 returnValue.min.y() = getMinValueWithinError(
564 minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
565
566 DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
567 DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
568
569 return returnValue;
570 }
571
572 template <typename WeightEquation>
calcSingleSampleLineInterpolationRangeWithWeightEquation(const tcu::Vec2 & pa,float wa,const tcu::Vec2 & pb,float wb,const tcu::IVec2 & pixel,int subpixelBits,WeightEquation weightEquation)573 LineInterpolationRange calcSingleSampleLineInterpolationRangeWithWeightEquation(const tcu::Vec2 &pa, float wa,
574 const tcu::Vec2 &pb, float wb,
575 const tcu::IVec2 &pixel,
576 int subpixelBits,
577 WeightEquation weightEquation)
578 {
579 // allow interpolation weights anywhere in the central subpixels
580 const float testSquareSize = (2.0f / (float)(1UL << subpixelBits));
581 const float testSquarePos = (0.5f - testSquareSize / 2);
582
583 const tcu::Vec2 corners[4] = {
584 tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f, (float)pixel.y() + testSquarePos + 0.0f),
585 tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f, (float)pixel.y() + testSquarePos + testSquareSize),
586 tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize, (float)pixel.y() + testSquarePos + testSquareSize),
587 tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize, (float)pixel.y() + testSquarePos + 0.0f),
588 };
589
590 // calculate interpolation as a line
591 const LineInterpolationRange weights[4] = {
592 weightEquation(pa, wa, pb, wb, corners[0]),
593 weightEquation(pa, wa, pb, wb, corners[1]),
594 weightEquation(pa, wa, pb, wb, corners[2]),
595 weightEquation(pa, wa, pb, wb, corners[3]),
596 };
597
598 const tcu::Vec2 minWeights =
599 tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
600 const tcu::Vec2 maxWeights =
601 tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
602
603 LineInterpolationRange result;
604 result.min = minWeights;
605 result.max = maxWeights;
606 return result;
607 }
608
calcSingleSampleLineInterpolationRange(const tcu::Vec2 & pa,float wa,const tcu::Vec2 & pb,float wb,const tcu::IVec2 & pixel,int subpixelBits)609 LineInterpolationRange calcSingleSampleLineInterpolationRange(const tcu::Vec2 &pa, float wa, const tcu::Vec2 &pb,
610 float wb, const tcu::IVec2 &pixel, int subpixelBits)
611 {
612 return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits,
613 calcLineInterpolationWeights);
614 }
615
calcSingleSampleLineInterpolationRangeAxisProjected(const tcu::Vec2 & pa,float wa,const tcu::Vec2 & pb,float wb,const tcu::IVec2 & pixel,int subpixelBits)616 LineInterpolationRange calcSingleSampleLineInterpolationRangeAxisProjected(const tcu::Vec2 &pa, float wa,
617 const tcu::Vec2 &pb, float wb,
618 const tcu::IVec2 &pixel, int subpixelBits)
619 {
620 return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits,
621 calcLineInterpolationWeightsAxisProjected);
622 }
623
624 struct TriangleInterpolator
625 {
626 const TriangleSceneSpec &scene;
627
TriangleInterpolatortcu::__anon407e9c5d0111::TriangleInterpolator628 TriangleInterpolator(const TriangleSceneSpec &scene_) : scene(scene_)
629 {
630 }
631
interpolatetcu::__anon407e9c5d0111::TriangleInterpolator632 InterpolationRange interpolate(int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize,
633 bool multisample, int subpixelBits) const
634 {
635 // allow anywhere in the pixel area in multisample
636 // allow only in the center subpixels (4 subpixels) in singlesample
637 const float testSquareSize = (multisample) ? (1.0f) : (2.0f / (float)(1UL << subpixelBits));
638 const float testSquarePos = (multisample) ? (0.0f) : (0.5f - testSquareSize / 2);
639 const tcu::Vec2 corners[4] = {
640 tcu::Vec2(((float)pixel.x() + testSquarePos + 0.0f) / (float)viewportSize.x() * 2.0f - 1.0f,
641 ((float)pixel.y() + testSquarePos + 0.0f) / (float)viewportSize.y() * 2.0f - 1.0f),
642 tcu::Vec2(((float)pixel.x() + testSquarePos + 0.0f) / (float)viewportSize.x() * 2.0f - 1.0f,
643 ((float)pixel.y() + testSquarePos + testSquareSize) / (float)viewportSize.y() * 2.0f - 1.0f),
644 tcu::Vec2(((float)pixel.x() + testSquarePos + testSquareSize) / (float)viewportSize.x() * 2.0f - 1.0f,
645 ((float)pixel.y() + testSquarePos + testSquareSize) / (float)viewportSize.y() * 2.0f - 1.0f),
646 tcu::Vec2(((float)pixel.x() + testSquarePos + testSquareSize) / (float)viewportSize.x() * 2.0f - 1.0f,
647 ((float)pixel.y() + testSquarePos + 0.0f) / (float)viewportSize.y() * 2.0f - 1.0f),
648 };
649 const InterpolationRange weights[4] = {
650 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0],
651 scene.triangles[primitiveNdx].positions[1],
652 scene.triangles[primitiveNdx].positions[2], corners[0]),
653 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0],
654 scene.triangles[primitiveNdx].positions[1],
655 scene.triangles[primitiveNdx].positions[2], corners[1]),
656 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0],
657 scene.triangles[primitiveNdx].positions[1],
658 scene.triangles[primitiveNdx].positions[2], corners[2]),
659 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0],
660 scene.triangles[primitiveNdx].positions[1],
661 scene.triangles[primitiveNdx].positions[2], corners[3]),
662 };
663
664 InterpolationRange result;
665 result.min = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
666 result.max = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
667 return result;
668 }
669 };
670
671 /*--------------------------------------------------------------------*//*!
672 * Used only by verifyMultisampleLineGroupInterpolation to calculate
673 * correct line interpolations for the triangulated lines.
674 *//*--------------------------------------------------------------------*/
675 struct MultisampleLineInterpolator
676 {
677 const LineSceneSpec &scene;
678
MultisampleLineInterpolatortcu::__anon407e9c5d0111::MultisampleLineInterpolator679 MultisampleLineInterpolator(const LineSceneSpec &scene_) : scene(scene_)
680 {
681 }
682
interpolatetcu::__anon407e9c5d0111::MultisampleLineInterpolator683 InterpolationRange interpolate(int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize,
684 bool multisample, int subpixelBits) const
685 {
686 DE_UNREF(multisample);
687 DE_UNREF(subpixelBits);
688
689 // in triangulation, one line emits two triangles
690 const int lineNdx = primitiveNdx / 2;
691
692 // allow interpolation weights anywhere in the pixel
693 const tcu::Vec2 corners[4] = {
694 tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 0.0f),
695 tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 1.0f),
696 tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 1.0f),
697 tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 0.0f),
698 };
699
700 const float wa = scene.lines[lineNdx].positions[0].w();
701 const float wb = scene.lines[lineNdx].positions[1].w();
702 const tcu::Vec2 pa =
703 tcu::Vec2((scene.lines[lineNdx].positions[0].x() / wa + 1.0f) * 0.5f * (float)viewportSize.x(),
704 (scene.lines[lineNdx].positions[0].y() / wa + 1.0f) * 0.5f * (float)viewportSize.y());
705 const tcu::Vec2 pb =
706 tcu::Vec2((scene.lines[lineNdx].positions[1].x() / wb + 1.0f) * 0.5f * (float)viewportSize.x(),
707 (scene.lines[lineNdx].positions[1].y() / wb + 1.0f) * 0.5f * (float)viewportSize.y());
708
709 // calculate interpolation as a line
710 const LineInterpolationRange weights[4] = {
711 calcLineInterpolationWeights(pa, wa, pb, wb, corners[0]),
712 calcLineInterpolationWeights(pa, wa, pb, wb, corners[1]),
713 calcLineInterpolationWeights(pa, wa, pb, wb, corners[2]),
714 calcLineInterpolationWeights(pa, wa, pb, wb, corners[3]),
715 };
716
717 const tcu::Vec2 minWeights =
718 tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
719 const tcu::Vec2 maxWeights =
720 tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
721
722 // convert to three-component form. For all triangles, the vertex 0 is always emitted by the line starting point, and vertex 2 by the ending point
723 InterpolationRange result;
724 result.min = tcu::Vec3(minWeights.x(), 0.0f, minWeights.y());
725 result.max = tcu::Vec3(maxWeights.x(), 0.0f, maxWeights.y());
726 return result;
727 }
728 };
729
730 template <typename Interpolator>
verifyTriangleGroupInterpolationWithInterpolator(const tcu::Surface & surface,const TriangleSceneSpec & scene,const RasterizationArguments & args,VerifyTriangleGroupInterpolationLogStash & logStash,const Interpolator & interpolator)731 bool verifyTriangleGroupInterpolationWithInterpolator(const tcu::Surface &surface, const TriangleSceneSpec &scene,
732 const RasterizationArguments &args,
733 VerifyTriangleGroupInterpolationLogStash &logStash,
734 const Interpolator &interpolator)
735 {
736 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255);
737 const bool multisampled = (args.numSamples != 0);
738 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
739 const int errorFloodThreshold = 4;
740 int errorCount = 0;
741 int invalidPixels = 0;
742 int subPixelBits = args.subpixelBits;
743 tcu::Surface errorMask(surface.getWidth(), surface.getHeight());
744
745 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
746
747 // log format
748
749 logStash.messages.push_back(std::string("Verifying rasterization result. Native format is RGB" +
750 de::toString(args.redBits) + de::toString(args.greenBits) +
751 de::toString(args.blueBits)));
752 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
753 logStash.messages.push_back(
754 std::string("Warning! More than 8 bits in a color channel, this may produce false negatives."));
755
756 // subpixel bits in a valid range?
757
758 if (subPixelBits < 0)
759 {
760 logStash.messages.push_back(
761 std::string("Invalid subpixel count (" + de::toString(subPixelBits) + "), assuming 0"));
762 subPixelBits = 0;
763 }
764 else if (subPixelBits > 16)
765 {
766 // At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
767 logStash.messages.push_back(
768 std::string("Subpixel count is greater than 16 (" + de::toString(subPixelBits) +
769 ")."
770 " Checking results using less strict 16 bit requirements. This may produce false positives."));
771 subPixelBits = 16;
772 }
773
774 // check pixels
775
776 for (int y = 0; y < surface.getHeight(); ++y)
777 for (int x = 0; x < surface.getWidth(); ++x)
778 {
779 const tcu::RGBA color = surface.getPixel(x, y);
780 bool stackBottomFound = false;
781 int stackSize = 0;
782 tcu::Vec4 colorStackMin;
783 tcu::Vec4 colorStackMax;
784
785 // Iterate triangle coverage front to back, find the stack of pontentially contributing fragments
786 for (int triNdx = (int)scene.triangles.size() - 1; triNdx >= 0; --triNdx)
787 {
788 const CoverageType coverage = calculateTriangleCoverage(
789 scene.triangles[triNdx].positions[0], scene.triangles[triNdx].positions[1],
790 scene.triangles[triNdx].positions[2], tcu::IVec2(x, y), viewportSize, subPixelBits, multisampled);
791
792 if (coverage == COVERAGE_FULL || coverage == COVERAGE_PARTIAL)
793 {
794 // potentially contributes to the result fragment's value
795 const InterpolationRange weights =
796 interpolator.interpolate(triNdx, tcu::IVec2(x, y), viewportSize, multisampled, subPixelBits);
797
798 const tcu::Vec4 fragmentColorMax =
799 de::clamp(weights.max.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
800 de::clamp(weights.max.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
801 de::clamp(weights.max.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
802 const tcu::Vec4 fragmentColorMin =
803 de::clamp(weights.min.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
804 de::clamp(weights.min.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
805 de::clamp(weights.min.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
806
807 if (stackSize++ == 0)
808 {
809 // first triangle, set the values properly
810 colorStackMin = fragmentColorMin;
811 colorStackMax = fragmentColorMax;
812 }
813 else
814 {
815 // contributing triangle
816 colorStackMin = tcu::min(colorStackMin, fragmentColorMin);
817 colorStackMax = tcu::max(colorStackMax, fragmentColorMax);
818 }
819
820 if (coverage == COVERAGE_FULL)
821 {
822 // loop terminates, this is the bottommost fragment
823 stackBottomFound = true;
824 break;
825 }
826 }
827 }
828
829 // Partial coverage == background may be visible
830 if (stackSize != 0 && !stackBottomFound)
831 {
832 stackSize++;
833 colorStackMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
834 }
835
836 // Is the result image color in the valid range.
837 if (stackSize == 0)
838 {
839 // No coverage, allow only background (black, value=0)
840 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args);
841 const int threshold = 1;
842
843 if (pixelNativeColor.x() > threshold || pixelNativeColor.y() > threshold ||
844 pixelNativeColor.z() > threshold)
845 {
846 ++errorCount;
847
848 // don't fill the logs with too much data
849 if (errorCount < errorFloodThreshold)
850 {
851 std::ostringstream str;
852
853 str << "Found an invalid pixel at (" << x << "," << y << ")\n"
854 << "\tPixel color:\t\t" << color << "\n"
855 << "\tExpected background color.\n";
856
857 logStash.messages.push_back(str.str());
858 }
859
860 ++invalidPixels;
861 errorMask.setPixel(x, y, invalidPixelColor);
862 }
863 }
864 else
865 {
866 DE_ASSERT(stackSize);
867
868 // Each additional step in the stack may cause conversion error of 1 bit due to undefined rounding direction
869 const int thresholdRed = stackSize - 1;
870 const int thresholdGreen = stackSize - 1;
871 const int thresholdBlue = stackSize - 1;
872
873 const tcu::Vec3 valueRangeMin = tcu::Vec3(colorStackMin.xyz());
874 const tcu::Vec3 valueRangeMax = tcu::Vec3(colorStackMax.xyz());
875
876 const tcu::IVec3 formatLimit((1 << args.redBits) - 1, (1 << args.greenBits) - 1,
877 (1 << args.blueBits) - 1);
878 const tcu::Vec3 colorMinF(
879 de::clamp(valueRangeMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
880 de::clamp(valueRangeMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
881 de::clamp(valueRangeMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
882 const tcu::Vec3 colorMaxF(
883 de::clamp(valueRangeMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
884 de::clamp(valueRangeMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
885 de::clamp(valueRangeMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
886 const tcu::IVec3 colorMin((int)deFloatFloor(colorMinF.x()), (int)deFloatFloor(colorMinF.y()),
887 (int)deFloatFloor(colorMinF.z()));
888 const tcu::IVec3 colorMax((int)deFloatCeil(colorMaxF.x()), (int)deFloatCeil(colorMaxF.y()),
889 (int)deFloatCeil(colorMaxF.z()));
890
891 // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
892 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args);
893
894 // Validity check
895 if (pixelNativeColor.x() < colorMin.x() - thresholdRed ||
896 pixelNativeColor.y() < colorMin.y() - thresholdGreen ||
897 pixelNativeColor.z() < colorMin.z() - thresholdBlue ||
898 pixelNativeColor.x() > colorMax.x() + thresholdRed ||
899 pixelNativeColor.y() > colorMax.y() + thresholdGreen ||
900 pixelNativeColor.z() > colorMax.z() + thresholdBlue)
901 {
902 ++errorCount;
903
904 // don't fill the logs with too much data
905 if (errorCount <= errorFloodThreshold)
906 {
907 std::ostringstream str;
908
909 str << "Found an invalid pixel at (" << x << "," << y << ")\n"
910 << "\tPixel color:\t\t" << color << "\n"
911 << "\tNative color:\t\t" << pixelNativeColor << "\n"
912 << "\tAllowed error:\t\t" << tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue) << "\n"
913 << "\tReference native color min: "
914 << tcu::clamp(colorMin - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue),
915 tcu::IVec3(0, 0, 0), formatLimit)
916 << "\n"
917 << "\tReference native color max: "
918 << tcu::clamp(colorMax + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue),
919 tcu::IVec3(0, 0, 0), formatLimit)
920 << "\n"
921 << "\tReference native float min: "
922 << tcu::clamp(colorMinF -
923 tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(),
924 tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>())
925 << "\n"
926 << "\tReference native float max: "
927 << tcu::clamp(colorMaxF +
928 tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(),
929 tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>())
930 << "\n"
931 << "\tFmin:\t"
932 << tcu::clamp(valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f))
933 << "\n"
934 << "\tFmax:\t"
935 << tcu::clamp(valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f))
936 << "\n";
937 logStash.messages.push_back(str.str());
938 }
939
940 ++invalidPixels;
941 errorMask.setPixel(x, y, invalidPixelColor);
942 }
943 }
944 }
945
946 // don't just hide failures
947 if (errorCount > errorFloodThreshold)
948 logStash.messages.push_back(
949 std::string("Omitted " + de::toString(errorCount - errorFloodThreshold) + " pixel error description(s)."));
950
951 logStash.success = (invalidPixels == 0);
952 logStash.invalidPixels = invalidPixels;
953
954 // report result
955 if (!logStash.success)
956 logStash.errorMask = errorMask;
957
958 return logStash.success;
959 }
960
calculateIntersectionParameter(const tcu::Vec2 line[2],float w,int componentNdx)961 float calculateIntersectionParameter(const tcu::Vec2 line[2], float w, int componentNdx)
962 {
963 DE_ASSERT(componentNdx < 2);
964 if (line[1][componentNdx] == line[0][componentNdx])
965 return -1.0f;
966
967 return (w - line[0][componentNdx]) / (line[1][componentNdx] - line[0][componentNdx]);
968 }
969
970 // Clips the given line with a ((-w, -w), (-w, w), (w, w), (w, -w)) rectangle
applyClippingBox(tcu::Vec2 line[2],float w)971 void applyClippingBox(tcu::Vec2 line[2], float w)
972 {
973 for (int side = 0; side < 4; ++side)
974 {
975 const int sign = ((side / 2) * -2) + 1;
976 const int component = side % 2;
977 const float t = calculateIntersectionParameter(line, w * (float)sign, component);
978
979 if ((t > 0) && (t < 1))
980 {
981 const float newCoord = t * line[1][1 - component] + (1 - t) * line[0][1 - component];
982
983 if (line[1][component] > (w * (float)sign))
984 {
985 line[1 - side / 2][component] = w * (float)sign;
986 line[1 - side / 2][1 - component] = newCoord;
987 }
988 else
989 {
990 line[side / 2][component] = w * (float)sign;
991 line[side / 2][1 - component] = newCoord;
992 }
993 }
994 }
995 }
996
997 enum ClipMode
998 {
999 CLIPMODE_NO_CLIPPING = 0,
1000 CLIPMODE_USE_CLIPPING_BOX,
1001
1002 CLIPMODE_LAST
1003 };
1004
verifyMultisampleLineGroupRasterization(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,ClipMode clipMode,VerifyTriangleGroupRasterizationLogStash * logStash,const bool vulkanLinesTest,const bool strictMode,const bool carryRemainder)1005 bool verifyMultisampleLineGroupRasterization(const tcu::Surface &surface, const LineSceneSpec &scene,
1006 const RasterizationArguments &args, tcu::TestLog &log, ClipMode clipMode,
1007 VerifyTriangleGroupRasterizationLogStash *logStash,
1008 const bool vulkanLinesTest, const bool strictMode,
1009 const bool carryRemainder)
1010 {
1011 // Multisampled line == 2 triangles
1012
1013 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
1014 const float halfLineWidth = scene.lineWidth * 0.5f;
1015 TriangleSceneSpec triangleScene;
1016
1017 uint32_t stippleCounter = 0;
1018 float leftoverPhase = 0.0f;
1019
1020 triangleScene.triangles.resize(2 * scene.lines.size());
1021 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
1022 {
1023
1024 if (!scene.isStrip)
1025 {
1026 // reset stipple at the start of each line segment
1027 stippleCounter = 0;
1028 leftoverPhase = 0;
1029 }
1030
1031 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
1032 tcu::Vec2 lineNormalizedDeviceSpace[2] = {
1033 tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(),
1034 scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
1035 tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(),
1036 scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
1037 };
1038
1039 if (clipMode == CLIPMODE_USE_CLIPPING_BOX)
1040 {
1041 applyClippingBox(lineNormalizedDeviceSpace, 1.0f);
1042 }
1043
1044 const tcu::Vec2 lineScreenSpace[2] = {
1045 (lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
1046 (lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
1047 };
1048
1049 const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
1050 const tcu::Vec2 lineNormalDir = (strictMode || scene.isRectangular) ? tcu::Vec2(lineDir.y(), -lineDir.x()) :
1051 isLineXMajor(lineScreenSpace[0], lineScreenSpace[1]) ? tcu::Vec2(0.0f, 1.0f) :
1052 tcu::Vec2(1.0f, 0.0f);
1053
1054 if (scene.stippleEnable)
1055 {
1056 float lineLength = tcu::distance(lineScreenSpace[0], lineScreenSpace[1]);
1057 float lineOffset = 0.0f;
1058
1059 while (lineOffset < lineLength)
1060 {
1061 float d0 = (float)lineOffset;
1062 float d1 = d0 + 1.0f;
1063
1064 if (carryRemainder)
1065 {
1066 // "leftoverPhase" carries over a fractional stipple phase that was "unused"
1067 // by the last line segment in the strip, if it wasn't an integer length.
1068 if (leftoverPhase > lineLength)
1069 {
1070 DE_ASSERT(d0 == 0.0f);
1071 d1 = lineLength;
1072 leftoverPhase -= lineLength;
1073 }
1074 else if (leftoverPhase != 0.0f)
1075 {
1076 DE_ASSERT(d0 == 0.0f);
1077 d1 = leftoverPhase;
1078 leftoverPhase = 0.0f;
1079 }
1080 else
1081 {
1082 if (d0 + 1.0f > lineLength)
1083 {
1084 d1 = lineLength;
1085 leftoverPhase = d0 + 1.0f - lineLength;
1086 }
1087 else
1088 d1 = d0 + 1.0f;
1089 }
1090 }
1091 else
1092 {
1093 if (d1 > lineLength)
1094 d1 = lineLength;
1095 }
1096
1097 // set offset for next iteration
1098 lineOffset = d1;
1099
1100 int stippleBit = (stippleCounter / scene.stippleFactor) % 16;
1101 bool stipplePass = (scene.stipplePattern & (1 << stippleBit)) != 0;
1102
1103 if (leftoverPhase == 0)
1104 stippleCounter++;
1105
1106 if (!stipplePass)
1107 continue;
1108
1109 d0 /= lineLength;
1110 d1 /= lineLength;
1111
1112 tcu::Vec2 l0 = mix(lineScreenSpace[0], lineScreenSpace[1], d0);
1113 tcu::Vec2 l1 = mix(lineScreenSpace[0], lineScreenSpace[1], d1);
1114
1115 const tcu::Vec2 lineQuadScreenSpace[4] = {
1116 l0 + lineNormalDir * halfLineWidth,
1117 l0 - lineNormalDir * halfLineWidth,
1118 l1 - lineNormalDir * halfLineWidth,
1119 l1 + lineNormalDir * halfLineWidth,
1120 };
1121 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = {
1122 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1123 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1124 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1125 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1126 };
1127
1128 TriangleSceneSpec::SceneTriangle tri;
1129
1130 tri.positions[0] =
1131 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
1132 tri.sharedEdge[0] = (d0 != 0.0f);
1133 tri.positions[1] =
1134 tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);
1135 tri.sharedEdge[1] = false;
1136 tri.positions[2] =
1137 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
1138 tri.sharedEdge[2] = true;
1139
1140 triangleScene.triangles.push_back(tri);
1141
1142 tri.positions[0] =
1143 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
1144 tri.sharedEdge[0] = true;
1145 tri.positions[1] =
1146 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
1147 tri.sharedEdge[1] = (d1 != 1.0f);
1148 tri.positions[2] =
1149 tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);
1150 tri.sharedEdge[2] = false;
1151
1152 triangleScene.triangles.push_back(tri);
1153 }
1154 }
1155 else
1156 {
1157 const tcu::Vec2 lineQuadScreenSpace[4] = {
1158 lineScreenSpace[0] + lineNormalDir * halfLineWidth,
1159 lineScreenSpace[0] - lineNormalDir * halfLineWidth,
1160 lineScreenSpace[1] - lineNormalDir * halfLineWidth,
1161 lineScreenSpace[1] + lineNormalDir * halfLineWidth,
1162 };
1163 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = {
1164 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1165 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1166 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1167 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1168 };
1169
1170 triangleScene.triangles[lineNdx * 2 + 0].positions[0] =
1171 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
1172 triangleScene.triangles[lineNdx * 2 + 0].sharedEdge[0] = false;
1173 triangleScene.triangles[lineNdx * 2 + 0].positions[1] =
1174 tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);
1175 triangleScene.triangles[lineNdx * 2 + 0].sharedEdge[1] = false;
1176 triangleScene.triangles[lineNdx * 2 + 0].positions[2] =
1177 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
1178 triangleScene.triangles[lineNdx * 2 + 0].sharedEdge[2] = true;
1179
1180 triangleScene.triangles[lineNdx * 2 + 1].positions[0] =
1181 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
1182 triangleScene.triangles[lineNdx * 2 + 1].sharedEdge[0] = true;
1183 triangleScene.triangles[lineNdx * 2 + 1].positions[1] =
1184 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
1185 triangleScene.triangles[lineNdx * 2 + 1].sharedEdge[1] = false;
1186 triangleScene.triangles[lineNdx * 2 + 1].positions[2] =
1187 tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);
1188 triangleScene.triangles[lineNdx * 2 + 1].sharedEdge[2] = false;
1189 }
1190 }
1191
1192 if (logStash != nullptr)
1193 {
1194 logStash->messages.push_back(
1195 "Rasterization clipping mode: " +
1196 std::string(clipMode == CLIPMODE_USE_CLIPPING_BOX ? "CLIPMODE_USE_CLIPPING_BOX" : "CLIPMODE_NO_CLIPPING") +
1197 ".");
1198 logStash->messages.push_back(
1199 "Rasterization line draw strictness mode: " + std::string(strictMode ? "strict" : "non-strict") + ".");
1200 }
1201
1202 return verifyTriangleGroupRasterization(surface, triangleScene, args, log, scene.verificationMode, logStash,
1203 vulkanLinesTest);
1204 }
1205
verifyMultisampleLineGroupRasterization(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,ClipMode clipMode,VerifyTriangleGroupRasterizationLogStash * logStash,const bool vulkanLinesTest,const bool strictMode)1206 bool verifyMultisampleLineGroupRasterization(const tcu::Surface &surface, const LineSceneSpec &scene,
1207 const RasterizationArguments &args, tcu::TestLog &log, ClipMode clipMode,
1208 VerifyTriangleGroupRasterizationLogStash *logStash,
1209 const bool vulkanLinesTest, const bool strictMode)
1210 {
1211 if (scene.stippleEnable)
1212 return verifyMultisampleLineGroupRasterization(surface, scene, args, log, clipMode, logStash, vulkanLinesTest,
1213 strictMode, true) ||
1214 verifyMultisampleLineGroupRasterization(surface, scene, args, log, clipMode, logStash, vulkanLinesTest,
1215 strictMode, false);
1216 else
1217 return verifyMultisampleLineGroupRasterization(surface, scene, args, log, clipMode, logStash, vulkanLinesTest,
1218 strictMode, true);
1219 }
1220
verifyMultisampleLineGroupInterpolationInternal(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,VerifyTriangleGroupInterpolationLogStash & logStash,const bool strictMode)1221 static bool verifyMultisampleLineGroupInterpolationInternal(const tcu::Surface &surface, const LineSceneSpec &scene,
1222 const RasterizationArguments &args,
1223 VerifyTriangleGroupInterpolationLogStash &logStash,
1224 const bool strictMode)
1225 {
1226 // Multisampled line == 2 triangles
1227
1228 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
1229 const float halfLineWidth = scene.lineWidth * 0.5f;
1230 TriangleSceneSpec triangleScene;
1231
1232 triangleScene.triangles.resize(2 * scene.lines.size());
1233 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
1234 {
1235 // Need the w-coordinates a couple of times
1236 const float wa = scene.lines[lineNdx].positions[0].w();
1237 const float wb = scene.lines[lineNdx].positions[1].w();
1238
1239 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
1240 const tcu::Vec2 lineNormalizedDeviceSpace[2] = {
1241 tcu::Vec2(scene.lines[lineNdx].positions[0].x() / wa, scene.lines[lineNdx].positions[0].y() / wa),
1242 tcu::Vec2(scene.lines[lineNdx].positions[1].x() / wb, scene.lines[lineNdx].positions[1].y() / wb),
1243 };
1244 const tcu::Vec2 lineScreenSpace[2] = {
1245 (lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
1246 (lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
1247 };
1248
1249 const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
1250 const tcu::Vec2 lineNormalDir = (strictMode || scene.isRectangular) ? tcu::Vec2(lineDir.y(), -lineDir.x()) :
1251 isLineXMajor(lineScreenSpace[0], lineScreenSpace[1]) ? tcu::Vec2(0.0f, 1.0f) :
1252 tcu::Vec2(1.0f, 0.0f);
1253
1254 const tcu::Vec2 lineQuadScreenSpace[4] = {
1255 lineScreenSpace[0] + lineNormalDir * halfLineWidth,
1256 lineScreenSpace[0] - lineNormalDir * halfLineWidth,
1257 lineScreenSpace[1] - lineNormalDir * halfLineWidth,
1258 lineScreenSpace[1] + lineNormalDir * halfLineWidth,
1259 };
1260 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = {
1261 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1262 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1263 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1264 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1265 };
1266
1267 // Re-construct un-projected geometry using the quantised positions
1268 const tcu::Vec4 lineQuadUnprojected[4] = {
1269 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x() * wa, lineQuadNormalizedDeviceSpace[0].y() * wa, 0.0f, wa),
1270 tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x() * wa, lineQuadNormalizedDeviceSpace[1].y() * wa, 0.0f, wa),
1271 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x() * wb, lineQuadNormalizedDeviceSpace[2].y() * wb, 0.0f, wb),
1272 tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x() * wb, lineQuadNormalizedDeviceSpace[3].y() * wb, 0.0f, wb),
1273 };
1274
1275 triangleScene.triangles[lineNdx * 2 + 0].positions[0] = lineQuadUnprojected[0];
1276 triangleScene.triangles[lineNdx * 2 + 0].positions[1] = lineQuadUnprojected[1];
1277 triangleScene.triangles[lineNdx * 2 + 0].positions[2] = lineQuadUnprojected[2];
1278
1279 triangleScene.triangles[lineNdx * 2 + 0].sharedEdge[0] = false;
1280 triangleScene.triangles[lineNdx * 2 + 0].sharedEdge[1] = false;
1281 triangleScene.triangles[lineNdx * 2 + 0].sharedEdge[2] = true;
1282
1283 triangleScene.triangles[lineNdx * 2 + 0].colors[0] = scene.lines[lineNdx].colors[0];
1284 triangleScene.triangles[lineNdx * 2 + 0].colors[1] = scene.lines[lineNdx].colors[0];
1285 triangleScene.triangles[lineNdx * 2 + 0].colors[2] = scene.lines[lineNdx].colors[1];
1286
1287 triangleScene.triangles[lineNdx * 2 + 1].positions[0] = lineQuadUnprojected[0];
1288 triangleScene.triangles[lineNdx * 2 + 1].positions[1] = lineQuadUnprojected[2];
1289 triangleScene.triangles[lineNdx * 2 + 1].positions[2] = lineQuadUnprojected[3];
1290
1291 triangleScene.triangles[lineNdx * 2 + 1].sharedEdge[0] = true;
1292 triangleScene.triangles[lineNdx * 2 + 1].sharedEdge[1] = false;
1293 triangleScene.triangles[lineNdx * 2 + 1].sharedEdge[2] = false;
1294
1295 triangleScene.triangles[lineNdx * 2 + 1].colors[0] = scene.lines[lineNdx].colors[0];
1296 triangleScene.triangles[lineNdx * 2 + 1].colors[1] = scene.lines[lineNdx].colors[1];
1297 triangleScene.triangles[lineNdx * 2 + 1].colors[2] = scene.lines[lineNdx].colors[1];
1298 }
1299
1300 if (strictMode)
1301 {
1302 // Strict mode interpolation should be purely in the direction of the line-segment
1303 logStash.messages.push_back("Verify using line interpolator");
1304 return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, logStash,
1305 MultisampleLineInterpolator(scene));
1306 }
1307 else
1308 {
1309 // For non-strict lines some allowance needs to be inplace for a few different styles of implementation.
1310 //
1311 // Some implementations duplicate the attributes at the endpoints to the corners of the triangle
1312 // deconstruted parallelogram. Gradients along the line will be seen to travel in the major axis,
1313 // with values effectively duplicated in the minor axis direction. In other cases, implementations
1314 // will use the original parameters of the line to calculate attribute interpolation so it will
1315 // follow the direction of the line-segment.
1316 logStash.messages.push_back("Verify using triangle interpolator");
1317 if (!verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, logStash,
1318 TriangleInterpolator(triangleScene)))
1319 {
1320 logStash.messages.push_back("Verify using line interpolator");
1321 return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, logStash,
1322 MultisampleLineInterpolator(scene));
1323 }
1324 return true;
1325 }
1326 }
1327
logTriangleGroupnterpolationStash(const tcu::Surface & surface,tcu::TestLog & log,VerifyTriangleGroupInterpolationLogStash & logStash)1328 static void logTriangleGroupnterpolationStash(const tcu::Surface &surface, tcu::TestLog &log,
1329 VerifyTriangleGroupInterpolationLogStash &logStash)
1330 {
1331 // Output results
1332 log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage;
1333
1334 for (size_t msgNdx = 0; msgNdx < logStash.messages.size(); ++msgNdx)
1335 log << tcu::TestLog::Message << logStash.messages[msgNdx] << tcu::TestLog::EndMessage;
1336
1337 // report result
1338 if (!logStash.success)
1339 {
1340 log << tcu::TestLog::Message << logStash.invalidPixels << " invalid pixel(s) found."
1341 << tcu::TestLog::EndMessage;
1342 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
1343 << tcu::TestLog::Image("Result", "Result", surface)
1344 << tcu::TestLog::Image("ErrorMask", "ErrorMask", logStash.errorMask) << tcu::TestLog::EndImageSet;
1345 }
1346 else
1347 {
1348 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
1349 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
1350 << tcu::TestLog::Image("Result", "Result", surface) << tcu::TestLog::EndImageSet;
1351 }
1352 }
1353
verifyMultisampleLineGroupInterpolation(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,const bool strictMode=true,const bool allowBresenhamForNonStrictLines=false)1354 static bool verifyMultisampleLineGroupInterpolation(const tcu::Surface &surface, const LineSceneSpec &scene,
1355 const RasterizationArguments &args, tcu::TestLog &log,
1356 const bool strictMode = true,
1357 const bool allowBresenhamForNonStrictLines = false)
1358 {
1359 bool result = false;
1360 VerifyTriangleGroupInterpolationLogStash nonStrictModeLogStash;
1361 VerifyTriangleGroupInterpolationLogStash strictModeLogStash;
1362
1363 nonStrictModeLogStash.messages.push_back("Non-strict line draw mode.");
1364 strictModeLogStash.messages.push_back("Strict mode line draw mode.");
1365
1366 if (strictMode)
1367 {
1368 result = verifyMultisampleLineGroupInterpolationInternal(surface, scene, args, strictModeLogStash, strictMode);
1369
1370 logTriangleGroupnterpolationStash(surface, log, strictModeLogStash);
1371 }
1372 else
1373 {
1374 if (verifyMultisampleLineGroupInterpolationInternal(surface, scene, args, nonStrictModeLogStash, false))
1375 {
1376 logTriangleGroupnterpolationStash(surface, log, nonStrictModeLogStash);
1377
1378 result = true;
1379 }
1380 else if (verifyMultisampleLineGroupInterpolationInternal(surface, scene, args, strictModeLogStash, true))
1381 {
1382 logTriangleGroupnterpolationStash(surface, log, strictModeLogStash);
1383
1384 result = true;
1385 }
1386 else
1387 {
1388 logTriangleGroupnterpolationStash(surface, log, nonStrictModeLogStash);
1389 logTriangleGroupnterpolationStash(surface, log, strictModeLogStash);
1390 }
1391
1392 // In the non-strict line case, bresenham is also permissable, though not specified. This is due
1393 // to a change in how lines are specified in Vulkan versus GLES; in GLES bresenham lines using the
1394 // diamond-exit rule were the preferred way to draw single pixel non-antialiased lines, and not all
1395 // GLES implementations are able to disable this behaviour.
1396 if (result == false)
1397 {
1398 log << tcu::TestLog::Message
1399 << "Checking line rasterisation using verifySinglesampleNarrowLineGroupInterpolation for nonStrict "
1400 "lines"
1401 << tcu::TestLog::EndMessage;
1402 if (args.numSamples <= 1 && allowBresenhamForNonStrictLines &&
1403 verifyLineGroupInterpolationWithProjectedWeights(surface, scene, args, log))
1404 {
1405 log << tcu::TestLog::Message
1406 << "verifySinglesampleNarrowLineGroupInterpolation for nonStrict lines Passed"
1407 << tcu::TestLog::EndMessage;
1408
1409 result = true;
1410 }
1411 }
1412 }
1413
1414 return result;
1415 }
1416
verifyMultisamplePointGroupRasterization(const tcu::Surface & surface,const PointSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)1417 bool verifyMultisamplePointGroupRasterization(const tcu::Surface &surface, const PointSceneSpec &scene,
1418 const RasterizationArguments &args, tcu::TestLog &log)
1419 {
1420 // Multisampled point == 2 triangles
1421
1422 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
1423 TriangleSceneSpec triangleScene;
1424
1425 triangleScene.triangles.resize(2 * scene.points.size());
1426 for (int pointNdx = 0; pointNdx < (int)scene.points.size(); ++pointNdx)
1427 {
1428 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
1429 const tcu::Vec2 pointNormalizedDeviceSpace =
1430 tcu::Vec2(scene.points[pointNdx].position.x() / scene.points[pointNdx].position.w(),
1431 scene.points[pointNdx].position.y() / scene.points[pointNdx].position.w());
1432 const tcu::Vec2 pointScreenSpace = (pointNormalizedDeviceSpace + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize;
1433 const float offset = scene.points[pointNdx].pointSize * 0.5f;
1434 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = {
1435 (pointScreenSpace + tcu::Vec2(-offset, -offset)) / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1436 (pointScreenSpace + tcu::Vec2(-offset, offset)) / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1437 (pointScreenSpace + tcu::Vec2(offset, offset)) / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1438 (pointScreenSpace + tcu::Vec2(offset, -offset)) / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
1439 };
1440
1441 triangleScene.triangles[pointNdx * 2 + 0].positions[0] =
1442 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
1443 triangleScene.triangles[pointNdx * 2 + 0].sharedEdge[0] = false;
1444 triangleScene.triangles[pointNdx * 2 + 0].positions[1] =
1445 tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);
1446 triangleScene.triangles[pointNdx * 2 + 0].sharedEdge[1] = false;
1447 triangleScene.triangles[pointNdx * 2 + 0].positions[2] =
1448 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
1449 triangleScene.triangles[pointNdx * 2 + 0].sharedEdge[2] = true;
1450
1451 triangleScene.triangles[pointNdx * 2 + 1].positions[0] =
1452 tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
1453 triangleScene.triangles[pointNdx * 2 + 1].sharedEdge[0] = true;
1454 triangleScene.triangles[pointNdx * 2 + 1].positions[1] =
1455 tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
1456 triangleScene.triangles[pointNdx * 2 + 1].sharedEdge[1] = false;
1457 triangleScene.triangles[pointNdx * 2 + 1].positions[2] =
1458 tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);
1459 triangleScene.triangles[pointNdx * 2 + 1].sharedEdge[2] = false;
1460 }
1461
1462 return verifyTriangleGroupRasterization(surface, triangleScene, args, log);
1463 }
1464
genScreenSpaceLines(std::vector<tcu::Vec4> & screenspaceLines,const std::vector<LineSceneSpec::SceneLine> & lines,const tcu::IVec2 & viewportSize)1465 void genScreenSpaceLines(std::vector<tcu::Vec4> &screenspaceLines, const std::vector<LineSceneSpec::SceneLine> &lines,
1466 const tcu::IVec2 &viewportSize)
1467 {
1468 DE_ASSERT(screenspaceLines.size() == lines.size());
1469
1470 for (int lineNdx = 0; lineNdx < (int)lines.size(); ++lineNdx)
1471 {
1472 const tcu::Vec2 lineNormalizedDeviceSpace[2] = {
1473 tcu::Vec2(lines[lineNdx].positions[0].x() / lines[lineNdx].positions[0].w(),
1474 lines[lineNdx].positions[0].y() / lines[lineNdx].positions[0].w()),
1475 tcu::Vec2(lines[lineNdx].positions[1].x() / lines[lineNdx].positions[1].w(),
1476 lines[lineNdx].positions[1].y() / lines[lineNdx].positions[1].w()),
1477 };
1478 const tcu::Vec4 lineScreenSpace[2] = {
1479 tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)viewportSize.x(),
1480 (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)viewportSize.y(), 0.0f, 1.0f),
1481 tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)viewportSize.x(),
1482 (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)viewportSize.y(), 0.0f, 1.0f),
1483 };
1484
1485 screenspaceLines[lineNdx] =
1486 tcu::Vec4(lineScreenSpace[0].x(), lineScreenSpace[0].y(), lineScreenSpace[1].x(), lineScreenSpace[1].y());
1487 }
1488 }
1489
verifySinglesampleLineGroupRasterization(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)1490 bool verifySinglesampleLineGroupRasterization(const tcu::Surface &surface, const LineSceneSpec &scene,
1491 const RasterizationArguments &args, tcu::TestLog &log)
1492 {
1493 DE_ASSERT(scene.lines.size() < 255); // indices are stored as unsigned 8-bit ints
1494
1495 bool allOK = true;
1496 bool overdrawInReference = false;
1497 int referenceFragments = 0;
1498 int resultFragments = 0;
1499 int lineWidth = deFloorFloatToInt32(scene.lineWidth + 0.5f);
1500 bool lineWidthHasFrac = deFloatFrac(scene.lineWidth) > 0;
1501 std::vector<bool> lineIsXMajor(scene.lines.size());
1502 std::vector<tcu::Vec4> screenspaceLines(scene.lines.size());
1503
1504 // Reference renderer produces correct fragments using the diamond-rule. Make 2D int array, each cell contains the highest index (first index = 1) of the overlapping lines or 0 if no line intersects the pixel
1505 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8),
1506 surface.getWidth(), surface.getHeight());
1507 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
1508
1509 tcu::Surface errorMask(surface.getWidth(), surface.getHeight());
1510 tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 255, 0, 255));
1511
1512 genScreenSpaceLines(screenspaceLines, scene.lines, tcu::IVec2(surface.getWidth(), surface.getHeight()));
1513
1514 rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight()),
1515 args.subpixelBits);
1516 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
1517 {
1518 rasterizer.init(tcu::Vec4(screenspaceLines[lineNdx][0], screenspaceLines[lineNdx][1], 0.0f, 1.0f),
1519 tcu::Vec4(screenspaceLines[lineNdx][2], screenspaceLines[lineNdx][3], 0.0f, 1.0f),
1520 scene.lineWidth, scene.stippleFactor, scene.stipplePattern);
1521
1522 if (!scene.isStrip)
1523 rasterizer.resetStipple();
1524
1525 // calculate majority of later use
1526 lineIsXMajor[lineNdx] = isPackedSSLineXMajor(screenspaceLines[lineNdx]);
1527
1528 for (;;)
1529 {
1530 const int maxPackets = 32;
1531 int numRasterized = 0;
1532 rr::FragmentPacket packets[maxPackets];
1533
1534 rasterizer.rasterize(packets, nullptr, maxPackets, numRasterized);
1535
1536 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
1537 {
1538 for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
1539 {
1540 if ((uint32_t)packets[packetNdx].coverage & (1 << fragNdx))
1541 {
1542 const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx % 2, fragNdx / 2);
1543
1544 // Check for overdraw
1545 if (!overdrawInReference)
1546 overdrawInReference =
1547 referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x() != 0;
1548
1549 // Output pixel
1550 referenceLineMap.getAccess().setPixel(tcu::IVec4(lineNdx + 1, 0, 0, 0), fragPos.x(),
1551 fragPos.y());
1552 }
1553 }
1554 }
1555
1556 if (numRasterized != maxPackets)
1557 break;
1558 }
1559 }
1560
1561 // Requirement 1: The coordinates of a fragment produced by the algorithm may not deviate by more than one unit
1562 bool missingFragments = false;
1563 {
1564 log << tcu::TestLog::Message << "Searching for deviating fragments." << tcu::TestLog::EndMessage;
1565
1566 for (int y = 0; y < referenceLineMap.getHeight(); ++y)
1567 for (int x = 0; x < referenceLineMap.getWidth(); ++x)
1568 {
1569 const bool reference = referenceLineMap.getAccess().getPixelInt(x, y).x() != 0;
1570 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits,
1571 args.greenBits, args.blueBits);
1572
1573 if (reference)
1574 ++referenceFragments;
1575 if (result)
1576 ++resultFragments;
1577
1578 if (reference == result)
1579 continue;
1580
1581 // Reference fragment here, matching result fragment must be nearby
1582 if (reference && !result)
1583 {
1584 bool foundFragment = false;
1585
1586 if (x == 0 || y == 0 || x == referenceLineMap.getWidth() - 1 ||
1587 y == referenceLineMap.getHeight() - 1)
1588 {
1589 // image boundary, missing fragment could be over the image edge
1590 foundFragment = true;
1591 }
1592
1593 // find nearby fragment
1594 for (int dy = -1; dy < 2 && !foundFragment; ++dy)
1595 for (int dx = -1; dx < 2 && !foundFragment; ++dx)
1596 {
1597 if (compareColors(surface.getPixel(x + dx, y + dy), tcu::RGBA::white(), args.redBits,
1598 args.greenBits, args.blueBits))
1599 foundFragment = true;
1600 }
1601
1602 if (!foundFragment)
1603 {
1604 missingFragments = true;
1605 errorMask.setPixel(x, y, tcu::RGBA::red());
1606 }
1607 }
1608 }
1609
1610 if (missingFragments)
1611 {
1612 log << tcu::TestLog::Message << "Invalid deviations found - missing fragments." << tcu::TestLog::EndMessage;
1613 allOK = false;
1614 }
1615 else
1616 {
1617 log << tcu::TestLog::Message << "No invalid deviations found." << tcu::TestLog::EndMessage;
1618 }
1619 }
1620
1621 // Requirement 2: The total number of fragments produced by the algorithm may differ from
1622 // that produced by the diamond-exit rule by no more than one.
1623 {
1624 // Check is not valid if the primitives intersect or otherwise share same fragments
1625 if (!overdrawInReference)
1626 {
1627 int allowedDeviation =
1628 (int)scene.lines.size() * lineWidth; // one pixel per primitive in the major direction
1629
1630 log << tcu::TestLog::Message << "Verifying fragment counts:\n"
1631 << "\tDiamond-exit rule: " << referenceFragments << " fragments.\n"
1632 << "\tResult image: " << resultFragments << " fragments.\n"
1633 << "\tAllowing deviation of " << allowedDeviation << " fragments.\n"
1634 << tcu::TestLog::EndMessage;
1635
1636 if (deAbs32(referenceFragments - resultFragments) > allowedDeviation)
1637 {
1638 tcu::Surface reference(surface.getWidth(), surface.getHeight());
1639
1640 // show a helpful reference image
1641 tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255));
1642 for (int y = 0; y < surface.getHeight(); ++y)
1643 for (int x = 0; x < surface.getWidth(); ++x)
1644 if (referenceLineMap.getAccess().getPixelInt(x, y).x())
1645 reference.setPixel(x, y, tcu::RGBA::white());
1646
1647 log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage;
1648 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
1649 << tcu::TestLog::Image("Reference", "Reference", reference)
1650 << tcu::TestLog::Image("Result", "Result", surface) << tcu::TestLog::EndImageSet;
1651
1652 allOK = false;
1653 }
1654 else
1655 {
1656 log << tcu::TestLog::Message << "Fragment count is valid." << tcu::TestLog::EndMessage;
1657 }
1658 }
1659 else
1660 {
1661 log << tcu::TestLog::Message
1662 << "Overdraw in scene. Fragment count cannot be verified. Skipping fragment count checks."
1663 << tcu::TestLog::EndMessage;
1664 }
1665 }
1666
1667 // Requirement 3: Line width must be constant
1668 {
1669 bool invalidWidthFound = false;
1670
1671 log << tcu::TestLog::Message << "Verifying line widths of the x-major lines." << tcu::TestLog::EndMessage;
1672 for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
1673 {
1674 bool fullyVisibleLine = false;
1675 bool previousPixelUndefined = false;
1676 int currentLine = 0;
1677 int currentWidth = 1;
1678
1679 for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
1680 {
1681 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits,
1682 args.greenBits, args.blueBits);
1683 int lineID = 0;
1684
1685 // Which line does this fragment belong to?
1686
1687 if (result)
1688 {
1689 bool multipleNearbyLines = false;
1690 bool renderAtSurfaceEdge = false;
1691
1692 renderAtSurfaceEdge = (x == 1) || (x == referenceLineMap.getWidth() - 2);
1693
1694 for (int dy = -1; dy < 2; ++dy)
1695 for (int dx = -1; dx < 2; ++dx)
1696 {
1697 const int nearbyID = referenceLineMap.getAccess().getPixelInt(x + dx, y + dy).x();
1698 if (nearbyID)
1699 {
1700 if (lineID && lineID != nearbyID)
1701 multipleNearbyLines = true;
1702 }
1703 }
1704
1705 if (multipleNearbyLines || renderAtSurfaceEdge)
1706 {
1707 // Another line is too close, don't try to calculate width here
1708 // Or the render result is outside of surface range
1709 previousPixelUndefined = true;
1710 continue;
1711 }
1712 }
1713
1714 // Only line with id of lineID is nearby
1715
1716 if (previousPixelUndefined)
1717 {
1718 // The line might have been overdrawn or not
1719 currentLine = lineID;
1720 currentWidth = 1;
1721 fullyVisibleLine = false;
1722 previousPixelUndefined = false;
1723 }
1724 else if (lineID == currentLine)
1725 {
1726 // Current line continues
1727 ++currentWidth;
1728 }
1729 else if (lineID > currentLine)
1730 {
1731 // Another line was drawn over or the line ends
1732 currentLine = lineID;
1733 currentWidth = 1;
1734 fullyVisibleLine = true;
1735 }
1736 else
1737 {
1738 // The line ends
1739 if (fullyVisibleLine && !lineIsXMajor[currentLine - 1])
1740 {
1741 // check width
1742 if (lineWidthHasFrac && currentWidth + 1 == lineWidth)
1743 {
1744 log << tcu::TestLog::Message << "\tAllowing width of " << currentWidth
1745 << " due to fractional line width" << tcu::TestLog::EndMessage;
1746 }
1747 else if (currentWidth != lineWidth)
1748 {
1749 log << tcu::TestLog::Message << "\tInvalid line width at (" << x - currentWidth << ", " << y
1750 << ") - (" << x - 1 << ", " << y << "). Detected width of " << currentWidth
1751 << ", expected " << lineWidth << tcu::TestLog::EndMessage;
1752 invalidWidthFound = true;
1753 }
1754 }
1755
1756 currentLine = lineID;
1757 currentWidth = 1;
1758 fullyVisibleLine = false;
1759 }
1760 }
1761 }
1762
1763 log << tcu::TestLog::Message << "Verifying line widths of the y-major lines." << tcu::TestLog::EndMessage;
1764 for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
1765 {
1766 bool fullyVisibleLine = false;
1767 bool previousPixelUndefined = false;
1768 int currentLine = 0;
1769 int currentWidth = 1;
1770
1771 for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
1772 {
1773 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits,
1774 args.greenBits, args.blueBits);
1775 int lineID = 0;
1776
1777 // Which line does this fragment belong to?
1778
1779 if (result)
1780 {
1781 bool multipleNearbyLines = false;
1782 bool renderAtSurfaceEdge = false;
1783
1784 renderAtSurfaceEdge = (y == 1) || (y == referenceLineMap.getWidth() - 2);
1785
1786 for (int dy = -1; dy < 2; ++dy)
1787 for (int dx = -1; dx < 2; ++dx)
1788 {
1789 const int nearbyID = referenceLineMap.getAccess().getPixelInt(x + dx, y + dy).x();
1790 if (nearbyID)
1791 {
1792 if (lineID && lineID != nearbyID)
1793 multipleNearbyLines = true;
1794 lineID = nearbyID;
1795 }
1796 }
1797
1798 if (multipleNearbyLines || renderAtSurfaceEdge)
1799 {
1800 // Another line is too close, don't try to calculate width here
1801 // Or the render result is outside of surface range
1802 previousPixelUndefined = true;
1803 continue;
1804 }
1805 }
1806
1807 // Only line with id of lineID is nearby
1808
1809 if (previousPixelUndefined)
1810 {
1811 // The line might have been overdrawn or not
1812 currentLine = lineID;
1813 currentWidth = 1;
1814 fullyVisibleLine = false;
1815 previousPixelUndefined = false;
1816 }
1817 else if (lineID == currentLine)
1818 {
1819 // Current line continues
1820 ++currentWidth;
1821 }
1822 else if (lineID > currentLine)
1823 {
1824 // Another line was drawn over or the line ends
1825 currentLine = lineID;
1826 currentWidth = 1;
1827 fullyVisibleLine = true;
1828 }
1829 else
1830 {
1831 // The line ends
1832 if (fullyVisibleLine && lineIsXMajor[currentLine - 1])
1833 {
1834 // check width
1835 if (lineWidthHasFrac && currentWidth + 1 == lineWidth)
1836 {
1837 log << tcu::TestLog::Message << "\tAllowing width of " << currentWidth
1838 << " due to fractional line width" << tcu::TestLog::EndMessage;
1839 }
1840 else if (currentWidth != lineWidth)
1841 {
1842 log << tcu::TestLog::Message << "\tInvalid line width at (" << x << ", " << y - currentWidth
1843 << ") - (" << x << ", " << y - 1 << "). Detected width of " << currentWidth
1844 << ", expected " << lineWidth << tcu::TestLog::EndMessage;
1845 invalidWidthFound = true;
1846 }
1847 }
1848
1849 currentLine = lineID;
1850 currentWidth = 1;
1851 fullyVisibleLine = false;
1852 }
1853 }
1854 }
1855
1856 if (invalidWidthFound)
1857 {
1858 log << tcu::TestLog::Message << "Invalid line width found, image is not valid." << tcu::TestLog::EndMessage;
1859 allOK = false;
1860 }
1861 else
1862 {
1863 log << tcu::TestLog::Message << "Line widths are valid." << tcu::TestLog::EndMessage;
1864 }
1865 }
1866
1867 //\todo [2013-10-24 jarkko].
1868 //Requirement 4. If two line segments share a common endpoint, and both segments are either
1869 //x-major (both left-to-right or both right-to-left) or y-major (both bottom-totop
1870 //or both top-to-bottom), then rasterizing both segments may not produce
1871 //duplicate fragments, nor may any fragments be omitted so as to interrupt
1872 //continuity of the connected segments.
1873
1874 {
1875 tcu::Surface reference(surface.getWidth(), surface.getHeight());
1876 tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255));
1877 for (int y = 0; y < surface.getHeight(); ++y)
1878 for (int x = 0; x < surface.getWidth(); ++x)
1879 if (referenceLineMap.getAccess().getPixelInt(x, y).x())
1880 reference.setPixel(x, y, tcu::RGBA::white());
1881 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
1882 << tcu::TestLog::Image("Reference", "Reference", reference)
1883 << tcu::TestLog::Image("Result", "Result", surface);
1884 if (missingFragments)
1885 log << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask);
1886 log << tcu::TestLog::EndImageSet;
1887 }
1888
1889 return allOK;
1890 }
1891
1892 struct SingleSampleNarrowLineCandidate
1893 {
1894 int lineNdx;
1895 tcu::IVec3 colorMin;
1896 tcu::IVec3 colorMax;
1897 tcu::Vec3 colorMinF;
1898 tcu::Vec3 colorMaxF;
1899 tcu::Vec3 valueRangeMin;
1900 tcu::Vec3 valueRangeMax;
1901 };
1902
setMaskMapCoverageBitForLine(int bitNdx,const tcu::Vec2 & screenSpaceP0,const tcu::Vec2 & screenSpaceP1,float lineWidth,tcu::PixelBufferAccess maskMap,const int subpixelBits)1903 void setMaskMapCoverageBitForLine(int bitNdx, const tcu::Vec2 &screenSpaceP0, const tcu::Vec2 &screenSpaceP1,
1904 float lineWidth, tcu::PixelBufferAccess maskMap, const int subpixelBits)
1905 {
1906 enum
1907 {
1908 MAX_PACKETS = 32,
1909 };
1910
1911 rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, maskMap.getWidth(), maskMap.getHeight()), subpixelBits);
1912 int numRasterized = MAX_PACKETS;
1913 rr::FragmentPacket packets[MAX_PACKETS];
1914
1915 rasterizer.init(tcu::Vec4(screenSpaceP0.x(), screenSpaceP0.y(), 0.0f, 1.0f),
1916 tcu::Vec4(screenSpaceP1.x(), screenSpaceP1.y(), 0.0f, 1.0f), lineWidth, 1, 0xFFFF);
1917
1918 while (numRasterized == MAX_PACKETS)
1919 {
1920 rasterizer.rasterize(packets, nullptr, MAX_PACKETS, numRasterized);
1921
1922 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
1923 {
1924 for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
1925 {
1926 if ((uint32_t)packets[packetNdx].coverage & (1 << fragNdx))
1927 {
1928 const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx % 2, fragNdx / 2);
1929
1930 DE_ASSERT(deInBounds32(fragPos.x(), 0, maskMap.getWidth()));
1931 DE_ASSERT(deInBounds32(fragPos.y(), 0, maskMap.getHeight()));
1932
1933 const uint32_t previousMask = maskMap.getPixelUint(fragPos.x(), fragPos.y()).x();
1934 const uint32_t newMask = (previousMask) | ((uint32_t)1u << bitNdx);
1935
1936 maskMap.setPixel(tcu::UVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y());
1937 }
1938 }
1939 }
1940 }
1941 }
1942
setMaskMapCoverageBitForLines(const std::vector<tcu::Vec4> & screenspaceLines,float lineWidth,tcu::PixelBufferAccess maskMap,const int subpixelBits)1943 void setMaskMapCoverageBitForLines(const std::vector<tcu::Vec4> &screenspaceLines, float lineWidth,
1944 tcu::PixelBufferAccess maskMap, const int subpixelBits)
1945 {
1946 for (int lineNdx = 0; lineNdx < (int)screenspaceLines.size(); ++lineNdx)
1947 {
1948 const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1);
1949 const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3);
1950
1951 setMaskMapCoverageBitForLine(lineNdx, pa, pb, lineWidth, maskMap, subpixelBits);
1952 }
1953 }
1954
1955 // verify line interpolation assuming line pixels are interpolated independently depending only on screen space location
verifyLineGroupPixelIndependentInterpolation(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,LineInterpolationMethod interpolationMethod)1956 bool verifyLineGroupPixelIndependentInterpolation(const tcu::Surface &surface, const LineSceneSpec &scene,
1957 const RasterizationArguments &args, tcu::TestLog &log,
1958 LineInterpolationMethod interpolationMethod)
1959 {
1960 DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints
1961 DE_ASSERT(interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT ||
1962 interpolationMethod == LINEINTERPOLATION_PROJECTED);
1963
1964 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255);
1965 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
1966 const int errorFloodThreshold = 4;
1967 int errorCount = 0;
1968 tcu::Surface errorMask(surface.getWidth(), surface.getHeight());
1969 int invalidPixels = 0;
1970 std::vector<tcu::Vec4> screenspaceLines(scene.lines.size()); //!< packed (x0, y0, x1, y1)
1971
1972 // Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
1973 // The map is used to find lines with potential coverage to a given pixel
1974 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8),
1975 surface.getWidth(), surface.getHeight());
1976
1977 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
1978 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
1979
1980 // log format
1981
1982 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits
1983 << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
1984 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
1985 log << tcu::TestLog::Message
1986 << "Warning! More than 8 bits in a color channel, this may produce false negatives."
1987 << tcu::TestLog::EndMessage;
1988
1989 // prepare lookup map
1990
1991 genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize);
1992 setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess(), args.subpixelBits);
1993
1994 // Find all possible lines with coverage, check pixel color matches one of them
1995
1996 for (int y = 1; y < surface.getHeight() - 1; ++y)
1997 for (int x = 1; x < surface.getWidth() - 1; ++x)
1998 {
1999 const tcu::RGBA color = surface.getPixel(x, y);
2000 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(
2001 color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
2002 int lineCoverageSet = 0; // !< lines that may cover this fragment
2003 int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment
2004 bool matchFound = false;
2005 const tcu::IVec3 formatLimit((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
2006
2007 std::vector<SingleSampleNarrowLineCandidate> candidates;
2008
2009 // Find lines with possible coverage
2010
2011 for (int dy = -1; dy < 2; ++dy)
2012 for (int dx = -1; dx < 2; ++dx)
2013 {
2014 const int coverage = referenceLineMap.getAccess().getPixelInt(x + dx, y + dy).x();
2015
2016 lineCoverageSet |= coverage;
2017 lineSurroundingCoverage &= coverage;
2018 }
2019
2020 // background color is possible?
2021 if (lineSurroundingCoverage == 0 &&
2022 compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits))
2023 continue;
2024
2025 // Check those lines
2026
2027 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
2028 {
2029 if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
2030 {
2031 const float wa = scene.lines[lineNdx].positions[0].w();
2032 const float wb = scene.lines[lineNdx].positions[1].w();
2033 const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1);
2034 const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3);
2035
2036 const LineInterpolationRange range = (interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT) ?
2037 (calcSingleSampleLineInterpolationRange(
2038 pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits)) :
2039 (calcSingleSampleLineInterpolationRangeAxisProjected(
2040 pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits));
2041
2042 const tcu::Vec4 valueMin = de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] +
2043 de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
2044 const tcu::Vec4 valueMax = de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] +
2045 de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
2046
2047 const tcu::Vec3 colorMinF(
2048 de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
2049 de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
2050 de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
2051 const tcu::Vec3 colorMaxF(
2052 de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
2053 de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
2054 de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
2055 const tcu::IVec3 colorMin((int)deFloatFloor(colorMinF.x()), (int)deFloatFloor(colorMinF.y()),
2056 (int)deFloatFloor(colorMinF.z()));
2057 const tcu::IVec3 colorMax((int)deFloatCeil(colorMaxF.x()), (int)deFloatCeil(colorMaxF.y()),
2058 (int)deFloatCeil(colorMaxF.z()));
2059
2060 // Verify validity
2061 if (pixelNativeColor.x() < colorMin.x() || pixelNativeColor.y() < colorMin.y() ||
2062 pixelNativeColor.z() < colorMin.z() || pixelNativeColor.x() > colorMax.x() ||
2063 pixelNativeColor.y() > colorMax.y() || pixelNativeColor.z() > colorMax.z())
2064 {
2065 if (errorCount < errorFloodThreshold)
2066 {
2067 // Store candidate information for logging
2068 SingleSampleNarrowLineCandidate candidate;
2069
2070 candidate.lineNdx = lineNdx;
2071 candidate.colorMin = colorMin;
2072 candidate.colorMax = colorMax;
2073 candidate.colorMinF = colorMinF;
2074 candidate.colorMaxF = colorMaxF;
2075 candidate.valueRangeMin = valueMin.swizzle(0, 1, 2);
2076 candidate.valueRangeMax = valueMax.swizzle(0, 1, 2);
2077
2078 candidates.push_back(candidate);
2079 }
2080 }
2081 else
2082 {
2083 matchFound = true;
2084 break;
2085 }
2086 }
2087 }
2088
2089 if (matchFound)
2090 continue;
2091
2092 // invalid fragment
2093 ++invalidPixels;
2094 errorMask.setPixel(x, y, invalidPixelColor);
2095
2096 ++errorCount;
2097
2098 // don't fill the logs with too much data
2099 if (errorCount < errorFloodThreshold)
2100 {
2101 log << tcu::TestLog::Message << "Found an invalid pixel at (" << x << "," << y << "), "
2102 << (int)candidates.size() << " candidate reference value(s) found:\n"
2103 << "\tPixel color:\t\t" << color << "\n"
2104 << "\tNative color:\t\t" << pixelNativeColor << "\n"
2105 << tcu::TestLog::EndMessage;
2106
2107 for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx)
2108 {
2109 const SingleSampleNarrowLineCandidate &candidate = candidates[candidateNdx];
2110
2111 log << tcu::TestLog::Message << "\tCandidate (line " << candidate.lineNdx << "):\n"
2112 << "\t\tReference native color min: "
2113 << tcu::clamp(candidate.colorMin, tcu::IVec3(0, 0, 0), formatLimit) << "\n"
2114 << "\t\tReference native color max: "
2115 << tcu::clamp(candidate.colorMax, tcu::IVec3(0, 0, 0), formatLimit) << "\n"
2116 << "\t\tReference native float min: "
2117 << tcu::clamp(candidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>())
2118 << "\n"
2119 << "\t\tReference native float max: "
2120 << tcu::clamp(candidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>())
2121 << "\n"
2122 << "\t\tFmin:\t"
2123 << tcu::clamp(candidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f))
2124 << "\n"
2125 << "\t\tFmax:\t"
2126 << tcu::clamp(candidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f))
2127 << "\n"
2128 << tcu::TestLog::EndMessage;
2129 }
2130 }
2131 }
2132
2133 // don't just hide failures
2134 if (errorCount > errorFloodThreshold)
2135 log << tcu::TestLog::Message << "Omitted " << (errorCount - errorFloodThreshold)
2136 << " pixel error description(s)." << tcu::TestLog::EndMessage;
2137
2138 // report result
2139 if (invalidPixels)
2140 {
2141 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
2142 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
2143 << tcu::TestLog::Image("Result", "Result", surface)
2144 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) << tcu::TestLog::EndImageSet;
2145
2146 return false;
2147 }
2148 else
2149 {
2150 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
2151 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
2152 << tcu::TestLog::Image("Result", "Result", surface) << tcu::TestLog::EndImageSet;
2153
2154 return true;
2155 }
2156 }
2157
verifySinglesampleNarrowLineGroupInterpolation(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)2158 bool verifySinglesampleNarrowLineGroupInterpolation(const tcu::Surface &surface, const LineSceneSpec &scene,
2159 const RasterizationArguments &args, tcu::TestLog &log)
2160 {
2161 DE_ASSERT(scene.lineWidth == 1.0f);
2162 return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_STRICTLY_CORRECT);
2163 }
2164
verifyLineGroupInterpolationWithNonProjectedWeights(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)2165 bool verifyLineGroupInterpolationWithNonProjectedWeights(const tcu::Surface &surface, const LineSceneSpec &scene,
2166 const RasterizationArguments &args, tcu::TestLog &log)
2167 {
2168 return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_STRICTLY_CORRECT);
2169 }
2170
verifyLineGroupInterpolationWithProjectedWeights(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)2171 bool verifyLineGroupInterpolationWithProjectedWeights(const tcu::Surface &surface, const LineSceneSpec &scene,
2172 const RasterizationArguments &args, tcu::TestLog &log)
2173 {
2174 return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_PROJECTED);
2175 }
2176
2177 struct SingleSampleWideLineCandidate
2178 {
2179 struct InterpolationPointCandidate
2180 {
2181 tcu::IVec2 interpolationPoint;
2182 tcu::IVec3 colorMin;
2183 tcu::IVec3 colorMax;
2184 tcu::Vec3 colorMinF;
2185 tcu::Vec3 colorMaxF;
2186 tcu::Vec3 valueRangeMin;
2187 tcu::Vec3 valueRangeMax;
2188 };
2189
2190 int lineNdx;
2191 int numCandidates;
2192 InterpolationPointCandidate interpolationCandidates[3];
2193 };
2194
2195 // return point on line at a given position on a given axis
getLineCoordAtAxisCoord(const tcu::Vec2 & pa,const tcu::Vec2 & pb,bool isXAxis,float axisCoord)2196 tcu::Vec2 getLineCoordAtAxisCoord(const tcu::Vec2 &pa, const tcu::Vec2 &pb, bool isXAxis, float axisCoord)
2197 {
2198 const int fixedCoordNdx = (isXAxis) ? (0) : (1);
2199 const int varyingCoordNdx = (isXAxis) ? (1) : (0);
2200
2201 const float fixedDifference = pb[fixedCoordNdx] - pa[fixedCoordNdx];
2202 const float varyingDifference = pb[varyingCoordNdx] - pa[varyingCoordNdx];
2203
2204 DE_ASSERT(fixedDifference != 0.0f);
2205
2206 const float resultFixedCoord = axisCoord;
2207 const float resultVaryingCoord =
2208 pa[varyingCoordNdx] + (axisCoord - pa[fixedCoordNdx]) * (varyingDifference / fixedDifference);
2209
2210 return (isXAxis) ? (tcu::Vec2(resultFixedCoord, resultVaryingCoord)) :
2211 (tcu::Vec2(resultVaryingCoord, resultFixedCoord));
2212 }
2213
isBlack(const tcu::RGBA & c)2214 bool isBlack(const tcu::RGBA &c)
2215 {
2216 return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
2217 }
2218
verifySinglesampleWideLineGroupInterpolation(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)2219 bool verifySinglesampleWideLineGroupInterpolation(const tcu::Surface &surface, const LineSceneSpec &scene,
2220 const RasterizationArguments &args, tcu::TestLog &log)
2221 {
2222 DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints
2223
2224 enum
2225 {
2226 FLAG_ROOT_NOT_SET = (1u << 16)
2227 };
2228
2229 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255);
2230 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
2231 const int errorFloodThreshold = 4;
2232 int errorCount = 0;
2233 tcu::Surface errorMask(surface.getWidth(), surface.getHeight());
2234 int invalidPixels = 0;
2235 std::vector<tcu::Vec4> effectiveLines(scene.lines.size()); //!< packed (x0, y0, x1, y1)
2236 std::vector<bool> lineIsXMajor(scene.lines.size());
2237
2238 // for each line, for every distinct major direction fragment, store root pixel location (along
2239 // minor direction);
2240 std::vector<std::vector<uint32_t>> rootPixelLocation(
2241 scene.lines.size()); //!< packed [16b - flags] [16b - coordinate]
2242
2243 // log format
2244
2245 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits
2246 << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
2247 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
2248 log << tcu::TestLog::Message
2249 << "Warning! More than 8 bits in a color channel, this may produce false negatives."
2250 << tcu::TestLog::EndMessage;
2251
2252 // Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
2253 // The map is used to find lines with potential coverage to a given pixel
2254 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8),
2255 surface.getWidth(), surface.getHeight());
2256 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
2257
2258 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
2259
2260 // calculate mask and effective line coordinates
2261 {
2262 std::vector<tcu::Vec4> screenspaceLines(scene.lines.size());
2263
2264 genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize);
2265 setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess(),
2266 args.subpixelBits);
2267
2268 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
2269 {
2270 const tcu::Vec2 lineScreenSpaceP0 = screenspaceLines[lineNdx].swizzle(0, 1);
2271 const tcu::Vec2 lineScreenSpaceP1 = screenspaceLines[lineNdx].swizzle(2, 3);
2272 const bool isXMajor = isPackedSSLineXMajor(screenspaceLines[lineNdx]);
2273
2274 lineIsXMajor[lineNdx] = isXMajor;
2275
2276 // wide line interpolations are calculated for a line moved in minor direction
2277 {
2278 const float offsetLength = (scene.lineWidth - 1.0f) / 2.0f;
2279 const tcu::Vec2 offsetDirection = (isXMajor) ? (tcu::Vec2(0.0f, -1.0f)) : (tcu::Vec2(-1.0f, 0.0f));
2280 const tcu::Vec2 offset = offsetDirection * offsetLength;
2281
2282 effectiveLines[lineNdx] =
2283 tcu::Vec4(lineScreenSpaceP0.x() + offset.x(), lineScreenSpaceP0.y() + offset.y(),
2284 lineScreenSpaceP1.x() + offset.x(), lineScreenSpaceP1.y() + offset.y());
2285 }
2286 }
2287 }
2288
2289 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
2290 {
2291 // Calculate root pixel lookup table for this line. Since the implementation's fragment
2292 // major coordinate range might not be a subset of the correct line range (they are allowed
2293 // to vary by one pixel), we must extend the domain to cover whole viewport along major
2294 // dimension.
2295 //
2296 // Expanding line strip to (effectively) infinite line might result in exit-diamnod set
2297 // that is not a superset of the exit-diamond set of the line strip. In practice, this
2298 // won't be an issue, since the allow-one-pixel-variation rule should tolerate this even
2299 // if the original and extended line would resolve differently a diamond the line just
2300 // touches (precision lost in expansion changes enter/exit status).
2301
2302 {
2303 const bool isXMajor = lineIsXMajor[lineNdx];
2304 const int majorSize = (isXMajor) ? (surface.getWidth()) : (surface.getHeight());
2305 rr::LineExitDiamondGenerator diamondGenerator(args.subpixelBits);
2306 rr::LineExitDiamond diamonds[32];
2307 int numRasterized = DE_LENGTH_OF_ARRAY(diamonds);
2308
2309 // Expand to effectively infinite line (endpoints are just one pixel over viewport boundaries)
2310 const tcu::Vec2 expandedP0 = getLineCoordAtAxisCoord(
2311 effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, -1.0f);
2312 const tcu::Vec2 expandedP1 =
2313 getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3),
2314 isXMajor, (float)majorSize + 1.0f);
2315
2316 diamondGenerator.init(tcu::Vec4(expandedP0.x(), expandedP0.y(), 0.0f, 1.0f),
2317 tcu::Vec4(expandedP1.x(), expandedP1.y(), 0.0f, 1.0f));
2318
2319 rootPixelLocation[lineNdx].resize(majorSize, FLAG_ROOT_NOT_SET);
2320
2321 while (numRasterized == DE_LENGTH_OF_ARRAY(diamonds))
2322 {
2323 diamondGenerator.rasterize(diamonds, DE_LENGTH_OF_ARRAY(diamonds), numRasterized);
2324
2325 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
2326 {
2327 const tcu::IVec2 fragPos = diamonds[packetNdx].position;
2328 const int majorPos = (isXMajor) ? (fragPos.x()) : (fragPos.y());
2329 const int rootPos = (isXMajor) ? (fragPos.y()) : (fragPos.x());
2330 const uint32_t packed = (uint32_t)((uint16_t)((int16_t)rootPos));
2331
2332 // infinite line will generate some diamonds outside the viewport
2333 if (deInBounds32(majorPos, 0, majorSize))
2334 {
2335 DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) != 0u);
2336 rootPixelLocation[lineNdx][majorPos] = packed;
2337 }
2338 }
2339 }
2340
2341 // Filled whole lookup table
2342 for (int majorPos = 0; majorPos < majorSize; ++majorPos)
2343 DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) == 0u);
2344 }
2345 }
2346
2347 // Find all possible lines with coverage, check pixel color matches one of them
2348
2349 for (int y = 1; y < surface.getHeight() - 1; ++y)
2350 for (int x = 1; x < surface.getWidth() - 1; ++x)
2351 {
2352 const tcu::RGBA color = surface.getPixel(x, y);
2353 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(
2354 color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
2355 int lineCoverageSet = 0; // !< lines that may cover this fragment
2356 int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment
2357 bool matchFound = false;
2358 const tcu::IVec3 formatLimit((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
2359
2360 std::vector<SingleSampleWideLineCandidate> candidates;
2361
2362 // Find lines with possible coverage
2363
2364 for (int dy = -1; dy < 2; ++dy)
2365 for (int dx = -1; dx < 2; ++dx)
2366 {
2367 const int coverage = referenceLineMap.getAccess().getPixelInt(x + dx, y + dy).x();
2368
2369 lineCoverageSet |= coverage;
2370 lineSurroundingCoverage &= coverage;
2371 }
2372
2373 // background color is possible?
2374 if (lineSurroundingCoverage == 0 &&
2375 compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits))
2376 continue;
2377
2378 // Check those lines
2379
2380 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
2381 {
2382 if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
2383 {
2384 const float wa = scene.lines[lineNdx].positions[0].w();
2385 const float wb = scene.lines[lineNdx].positions[1].w();
2386 const tcu::Vec2 pa = effectiveLines[lineNdx].swizzle(0, 1);
2387 const tcu::Vec2 pb = effectiveLines[lineNdx].swizzle(2, 3);
2388
2389 // \note Wide line fragments are generated by replicating the root fragment for each
2390 // fragment column (row for y-major). Calculate interpolation at the root
2391 // fragment.
2392 const bool isXMajor = lineIsXMajor[lineNdx];
2393 const int majorPosition = (isXMajor) ? (x) : (y);
2394 const uint32_t minorInfoPacked = rootPixelLocation[lineNdx][majorPosition];
2395 const int minorPosition = (int)((int16_t)((uint16_t)(minorInfoPacked & 0xFFFFu)));
2396 const tcu::IVec2 idealRootPos = (isXMajor) ? (tcu::IVec2(majorPosition, minorPosition)) :
2397 (tcu::IVec2(minorPosition, majorPosition));
2398 const tcu::IVec2 minorDirection = (isXMajor) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));
2399
2400 SingleSampleWideLineCandidate candidate;
2401
2402 candidate.lineNdx = lineNdx;
2403 candidate.numCandidates = 0;
2404 DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates) == 3);
2405
2406 // Interpolation happens at the root fragment, which is then replicated in minor
2407 // direction. Search for implementation's root position near accurate root.
2408 for (int minorOffset = -1; minorOffset < 2; ++minorOffset)
2409 {
2410 const tcu::IVec2 rootPosition = idealRootPos + minorOffset * minorDirection;
2411
2412 // A fragment can be root fragment only if it exists
2413 // \note root fragment can "exist" outside viewport
2414 // \note no pixel format theshold since in this case allowing only black is more conservative
2415 if (deInBounds32(rootPosition.x(), 0, surface.getWidth()) &&
2416 deInBounds32(rootPosition.y(), 0, surface.getHeight()) &&
2417 isBlack(surface.getPixel(rootPosition.x(), rootPosition.y())))
2418 {
2419 continue;
2420 }
2421
2422 const LineInterpolationRange range =
2423 calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, rootPosition, args.subpixelBits);
2424
2425 const tcu::Vec4 valueMin =
2426 de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] +
2427 de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
2428 const tcu::Vec4 valueMax =
2429 de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] +
2430 de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
2431
2432 const tcu::Vec3 colorMinF(
2433 de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
2434 de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
2435 de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
2436 const tcu::Vec3 colorMaxF(
2437 de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
2438 de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
2439 de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
2440 const tcu::IVec3 colorMin((int)deFloatFloor(colorMinF.x()), (int)deFloatFloor(colorMinF.y()),
2441 (int)deFloatFloor(colorMinF.z()));
2442 const tcu::IVec3 colorMax((int)deFloatCeil(colorMaxF.x()), (int)deFloatCeil(colorMaxF.y()),
2443 (int)deFloatCeil(colorMaxF.z()));
2444
2445 // Verify validity
2446 if (pixelNativeColor.x() < colorMin.x() || pixelNativeColor.y() < colorMin.y() ||
2447 pixelNativeColor.z() < colorMin.z() || pixelNativeColor.x() > colorMax.x() ||
2448 pixelNativeColor.y() > colorMax.y() || pixelNativeColor.z() > colorMax.z())
2449 {
2450 if (errorCount < errorFloodThreshold)
2451 {
2452 // Store candidate information for logging
2453 SingleSampleWideLineCandidate::InterpolationPointCandidate &interpolationCandidate =
2454 candidate.interpolationCandidates[candidate.numCandidates++];
2455 DE_ASSERT(candidate.numCandidates <=
2456 DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates));
2457
2458 interpolationCandidate.interpolationPoint = rootPosition;
2459 interpolationCandidate.colorMin = colorMin;
2460 interpolationCandidate.colorMax = colorMax;
2461 interpolationCandidate.colorMinF = colorMinF;
2462 interpolationCandidate.colorMaxF = colorMaxF;
2463 interpolationCandidate.valueRangeMin = valueMin.swizzle(0, 1, 2);
2464 interpolationCandidate.valueRangeMax = valueMax.swizzle(0, 1, 2);
2465 }
2466 }
2467 else
2468 {
2469 matchFound = true;
2470 break;
2471 }
2472 }
2473
2474 if (!matchFound)
2475 {
2476 // store info for logging
2477 if (errorCount < errorFloodThreshold && candidate.numCandidates > 0)
2478 candidates.push_back(candidate);
2479 }
2480 else
2481 {
2482 // no need to check other lines
2483 break;
2484 }
2485 }
2486 }
2487
2488 if (matchFound)
2489 continue;
2490
2491 // invalid fragment
2492 ++invalidPixels;
2493 errorMask.setPixel(x, y, invalidPixelColor);
2494
2495 ++errorCount;
2496
2497 // don't fill the logs with too much data
2498 if (errorCount < errorFloodThreshold)
2499 {
2500 tcu::MessageBuilder msg(&log);
2501
2502 msg << "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size()
2503 << " candidate reference value(s) found:\n"
2504 << "\tPixel color:\t\t" << color << "\n"
2505 << "\tNative color:\t\t" << pixelNativeColor << "\n";
2506
2507 for (int lineCandidateNdx = 0; lineCandidateNdx < (int)candidates.size(); ++lineCandidateNdx)
2508 {
2509 const SingleSampleWideLineCandidate &candidate = candidates[lineCandidateNdx];
2510
2511 msg << "\tCandidate line (line " << candidate.lineNdx << "):\n";
2512
2513 for (int interpolationCandidateNdx = 0; interpolationCandidateNdx < candidate.numCandidates;
2514 ++interpolationCandidateNdx)
2515 {
2516 const SingleSampleWideLineCandidate::InterpolationPointCandidate &interpolationCandidate =
2517 candidate.interpolationCandidates[interpolationCandidateNdx];
2518
2519 msg << "\t\tCandidate interpolation point (index " << interpolationCandidateNdx << "):\n"
2520 << "\t\t\tRoot fragment position (non-replicated fragment): "
2521 << interpolationCandidate.interpolationPoint << ":\n"
2522 << "\t\t\tReference native color min: "
2523 << tcu::clamp(interpolationCandidate.colorMin, tcu::IVec3(0, 0, 0), formatLimit) << "\n"
2524 << "\t\t\tReference native color max: "
2525 << tcu::clamp(interpolationCandidate.colorMax, tcu::IVec3(0, 0, 0), formatLimit) << "\n"
2526 << "\t\t\tReference native float min: "
2527 << tcu::clamp(interpolationCandidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f),
2528 formatLimit.cast<float>())
2529 << "\n"
2530 << "\t\t\tReference native float max: "
2531 << tcu::clamp(interpolationCandidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f),
2532 formatLimit.cast<float>())
2533 << "\n"
2534 << "\t\t\tFmin:\t"
2535 << tcu::clamp(interpolationCandidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f),
2536 tcu::Vec3(1.0f, 1.0f, 1.0f))
2537 << "\n"
2538 << "\t\t\tFmax:\t"
2539 << tcu::clamp(interpolationCandidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f),
2540 tcu::Vec3(1.0f, 1.0f, 1.0f))
2541 << "\n";
2542 }
2543 }
2544
2545 msg << tcu::TestLog::EndMessage;
2546 }
2547 }
2548
2549 // don't just hide failures
2550 if (errorCount > errorFloodThreshold)
2551 log << tcu::TestLog::Message << "Omitted " << (errorCount - errorFloodThreshold)
2552 << " pixel error description(s)." << tcu::TestLog::EndMessage;
2553
2554 // report result
2555 if (invalidPixels)
2556 {
2557 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
2558 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
2559 << tcu::TestLog::Image("Result", "Result", surface)
2560 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) << tcu::TestLog::EndImageSet;
2561
2562 return false;
2563 }
2564 else
2565 {
2566 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
2567 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
2568 << tcu::TestLog::Image("Result", "Result", surface) << tcu::TestLog::EndImageSet;
2569
2570 return true;
2571 }
2572 }
2573
2574 } // namespace
2575
calculateTriangleCoverage(const tcu::Vec4 & p0,const tcu::Vec4 & p1,const tcu::Vec4 & p2,const tcu::IVec2 & pixel,const tcu::IVec2 & viewportSize,int subpixelBits,bool multisample)2576 CoverageType calculateTriangleCoverage(const tcu::Vec4 &p0, const tcu::Vec4 &p1, const tcu::Vec4 &p2,
2577 const tcu::IVec2 &pixel, const tcu::IVec2 &viewportSize, int subpixelBits,
2578 bool multisample)
2579 {
2580 using tcu::I64Vec2;
2581
2582 const uint64_t numSubPixels = ((uint64_t)1) << subpixelBits;
2583 const uint64_t pixelHitBoxSize =
2584 (multisample) ? (numSubPixels) :
2585 5; //!< 5 = ceil(6 * sqrt(2) / 2) to account for a 3 subpixel fuzz around pixel center
2586 const bool order = isTriangleClockwise(p0, p1, p2); //!< clockwise / counter-clockwise
2587 const tcu::Vec4 &orderedP0 = p0; //!< vertices of a clockwise triangle
2588 const tcu::Vec4 &orderedP1 = (order) ? (p1) : (p2);
2589 const tcu::Vec4 &orderedP2 = (order) ? (p2) : (p1);
2590 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = {
2591 tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()),
2592 tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()),
2593 tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()),
2594 };
2595 const tcu::Vec2 triangleScreenSpace[3] = {
2596 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
2597 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
2598 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
2599 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
2600 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
2601 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
2602 };
2603
2604 // Broad bounding box - pixel check
2605 {
2606 const float minX =
2607 de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
2608 const float minY =
2609 de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
2610 const float maxX =
2611 de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
2612 const float maxY =
2613 de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
2614
2615 if ((float)pixel.x() > maxX + 1 || (float)pixel.y() > maxY + 1 || (float)pixel.x() < minX - 1 ||
2616 (float)pixel.y() < minY - 1)
2617 return COVERAGE_NONE;
2618 }
2619
2620 // Broad triangle - pixel area intersection
2621 {
2622 const DVec2 pixelCenterPosition =
2623 DVec2((double)pixel.x(), (double)pixel.y()) * DVec2((double)numSubPixels, (double)numSubPixels) +
2624 DVec2((double)numSubPixels / 2, (double)numSubPixels / 2);
2625 const DVec2 triangleSubPixelSpace[3] = {
2626 DVec2(triangleScreenSpace[0].x() * (double)numSubPixels, triangleScreenSpace[0].y() * (double)numSubPixels),
2627 DVec2(triangleScreenSpace[1].x() * (double)numSubPixels, triangleScreenSpace[1].y() * (double)numSubPixels),
2628 DVec2(triangleScreenSpace[2].x() * (double)numSubPixels, triangleScreenSpace[2].y() * (double)numSubPixels),
2629 };
2630
2631 // Check (using cross product) if pixel center is
2632 // a) too far from any edge
2633 // b) fully inside all edges
2634 bool insideAllEdges = true;
2635 for (int vtxNdx = 0; vtxNdx < 3; ++vtxNdx)
2636 {
2637 const int otherVtxNdx = (vtxNdx + 1) % 3;
2638 const double maxPixelDistanceSquared =
2639 (double)(pixelHitBoxSize *
2640 pixelHitBoxSize); // Max distance from the pixel center from within the pixel is (sqrt(2) * boxWidth/2). Use 2x value for rounding tolerance
2641 const DVec2 edge = triangleSubPixelSpace[otherVtxNdx] - triangleSubPixelSpace[vtxNdx];
2642 const DVec2 v = pixelCenterPosition - triangleSubPixelSpace[vtxNdx];
2643 const double crossProduct = (edge.x() * v.y() - edge.y() * v.x());
2644
2645 // distance from edge: (edge x v) / |edge|
2646 // (edge x v) / |edge| > maxPixelDistance
2647 // ==> (edge x v)^2 / edge^2 > maxPixelDistance^2 | edge x v > 0
2648 // ==> (edge x v)^2 > maxPixelDistance^2 * edge^2
2649 if (crossProduct < 0 && crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(edge))
2650 return COVERAGE_NONE;
2651 if (crossProduct < 0 || crossProduct * crossProduct < maxPixelDistanceSquared * tcu::lengthSquared(edge))
2652 insideAllEdges = false;
2653 }
2654
2655 if (insideAllEdges)
2656 return COVERAGE_FULL;
2657 }
2658
2659 // Accurate intersection for edge pixels
2660 {
2661 // In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center.
2662 const I64Vec2 pixelCorners[4] = {
2663 I64Vec2((pixel.x() + 0) * numSubPixels, (pixel.y() + 0) * numSubPixels),
2664 I64Vec2((pixel.x() + 1) * numSubPixels, (pixel.y() + 0) * numSubPixels),
2665 I64Vec2((pixel.x() + 1) * numSubPixels, (pixel.y() + 1) * numSubPixels),
2666 I64Vec2((pixel.x() + 0) * numSubPixels, (pixel.y() + 1) * numSubPixels),
2667 };
2668
2669 // 3 subpixel tolerance around pixel center to account for accumulated errors during various line rasterization methods
2670 const I64Vec2 pixelCenterCorners[4] = {
2671 I64Vec2(pixel.x() * numSubPixels + numSubPixels / 2 - 3, pixel.y() * numSubPixels + numSubPixels / 2 - 3),
2672 I64Vec2(pixel.x() * numSubPixels + numSubPixels / 2 + 3, pixel.y() * numSubPixels + numSubPixels / 2 - 3),
2673 I64Vec2(pixel.x() * numSubPixels + numSubPixels / 2 + 3, pixel.y() * numSubPixels + numSubPixels / 2 + 3),
2674 I64Vec2(pixel.x() * numSubPixels + numSubPixels / 2 - 3, pixel.y() * numSubPixels + numSubPixels / 2 + 3),
2675 };
2676
2677 // both rounding directions
2678 const I64Vec2 triangleSubPixelSpaceFloor[3] = {
2679 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels),
2680 deFloorFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
2681 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels),
2682 deFloorFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
2683 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels),
2684 deFloorFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
2685 };
2686 const I64Vec2 triangleSubPixelSpaceCeil[3] = {
2687 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels),
2688 deCeilFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
2689 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels),
2690 deCeilFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
2691 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels),
2692 deCeilFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
2693 };
2694 const I64Vec2 *const corners = (multisample) ? (pixelCorners) : (pixelCenterCorners);
2695
2696 // Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside
2697
2698 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
2699 for (int startRounding = 0; startRounding < 4; ++startRounding)
2700 for (int endRounding = 0; endRounding < 4; ++endRounding)
2701 {
2702 const int nextEdgeNdx = (edgeNdx + 1) % 3;
2703 const I64Vec2 startPos((startRounding & 0x01) ? (triangleSubPixelSpaceFloor[edgeNdx].x()) :
2704 (triangleSubPixelSpaceCeil[edgeNdx].x()),
2705 (startRounding & 0x02) ? (triangleSubPixelSpaceFloor[edgeNdx].y()) :
2706 (triangleSubPixelSpaceCeil[edgeNdx].y()));
2707 const I64Vec2 endPos((endRounding & 0x01) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].x()) :
2708 (triangleSubPixelSpaceCeil[nextEdgeNdx].x()),
2709 (endRounding & 0x02) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].y()) :
2710 (triangleSubPixelSpaceCeil[nextEdgeNdx].y()));
2711
2712 for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx)
2713 {
2714 const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4;
2715
2716 if (lineLineIntersect(startPos, endPos, corners[pixelEdgeNdx], corners[pixelEdgeEnd]))
2717 return COVERAGE_PARTIAL;
2718 }
2719 }
2720
2721 // fully inside or outside
2722 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
2723 {
2724 const int nextEdgeNdx = (edgeNdx + 1) % 3;
2725 const I64Vec2 &startPos = triangleSubPixelSpaceFloor[edgeNdx];
2726 const I64Vec2 &endPos = triangleSubPixelSpaceFloor[nextEdgeNdx];
2727 const I64Vec2 edge = endPos - startPos;
2728 const I64Vec2 v = corners[0] - endPos;
2729 const int64_t crossProduct = (edge.x() * v.y() - edge.y() * v.x());
2730
2731 // a corner of the pixel is outside => "fully inside" option is impossible
2732 if (crossProduct < 0)
2733 return COVERAGE_NONE;
2734 }
2735
2736 return COVERAGE_FULL;
2737 }
2738 }
2739
calculateUnderestimateLineCoverage(const tcu::Vec4 & p0,const tcu::Vec4 & p1,const float lineWidth,const tcu::IVec2 & pixel,const tcu::IVec2 & viewportSize)2740 CoverageType calculateUnderestimateLineCoverage(const tcu::Vec4 &p0, const tcu::Vec4 &p1, const float lineWidth,
2741 const tcu::IVec2 &pixel, const tcu::IVec2 &viewportSize)
2742 {
2743 DE_ASSERT(viewportSize.x() == viewportSize.y() && viewportSize.x() > 0);
2744 DE_ASSERT(p0.w() == 1.0f && p1.w() == 1.0f);
2745
2746 const Vec2 p = Vec2(p0.x(), p0.y());
2747 const Vec2 q = Vec2(p1.x(), p1.y());
2748 const Vec2 pq = Vec2(p1.x() - p0.x(), p1.y() - p0.y());
2749 const Vec2 pqn = normalize(pq);
2750 const Vec2 lw = 0.5f * lineWidth * pqn;
2751 const Vec2 n = Vec2(lw.y(), -lw.x());
2752 const Vec2 vp = Vec2(float(viewportSize.x()), float(viewportSize.y()));
2753 const Vec2 a = 0.5f * (p + Vec2(1.0f, 1.0f)) * vp + n;
2754 const Vec2 b = 0.5f * (p + Vec2(1.0f, 1.0f)) * vp - n;
2755 const Vec2 c = 0.5f * (q + Vec2(1.0f, 1.0f)) * vp - n;
2756 const Vec2 ba = b - a;
2757 const Vec2 bc = b - c;
2758 const float det = ba.x() * bc.y() - ba.y() * bc.x();
2759 int within = 0;
2760
2761 if (det != 0.0f)
2762 {
2763 for (int cornerNdx = 0; cornerNdx < 4; ++cornerNdx)
2764 {
2765 const int pixelCornerOffsetX = ((cornerNdx & 1) ? 1 : 0);
2766 const int pixelCornerOffsetY = ((cornerNdx & 2) ? 1 : 0);
2767 const Vec2 f = Vec2(float(pixel.x() + pixelCornerOffsetX), float(pixel.y() + pixelCornerOffsetY));
2768 const Vec2 bf = b - f;
2769 const float alpha = (bf.x() * bc.y() - bc.x() * bf.y()) / det;
2770 const float beta = (ba.x() * bf.y() - bf.x() * ba.y()) / det;
2771 bool cornerWithin = de::inRange(alpha, 0.0f, 1.0f) && de::inRange(beta, 0.0f, 1.0f);
2772
2773 if (cornerWithin)
2774 within++;
2775 }
2776 }
2777
2778 if (within == 0)
2779 return COVERAGE_NONE;
2780 else if (within == 4)
2781 return COVERAGE_FULL;
2782 else
2783 return COVERAGE_PARTIAL;
2784 }
2785
calculateUnderestimateTriangleCoverage(const tcu::Vec4 & p0,const tcu::Vec4 & p1,const tcu::Vec4 & p2,const tcu::IVec2 & pixel,int subpixelBits,const tcu::IVec2 & viewportSize)2786 CoverageType calculateUnderestimateTriangleCoverage(const tcu::Vec4 &p0, const tcu::Vec4 &p1, const tcu::Vec4 &p2,
2787 const tcu::IVec2 &pixel, int subpixelBits,
2788 const tcu::IVec2 &viewportSize)
2789 {
2790 using tcu::I64Vec2;
2791
2792 const uint64_t numSubPixels = ((uint64_t)1) << subpixelBits;
2793 const bool order = isTriangleClockwise(p0, p1, p2); //!< clockwise / counter-clockwise
2794 const tcu::Vec4 &orderedP0 = p0; //!< vertices of a clockwise triangle
2795 const tcu::Vec4 &orderedP1 = (order) ? (p1) : (p2);
2796 const tcu::Vec4 &orderedP2 = (order) ? (p2) : (p1);
2797 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = {
2798 tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()),
2799 tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()),
2800 tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()),
2801 };
2802 const tcu::Vec2 triangleScreenSpace[3] = {
2803 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
2804 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
2805 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
2806 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
2807 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f *
2808 tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
2809 };
2810
2811 // Broad bounding box - pixel check
2812 {
2813 const float minX =
2814 de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
2815 const float minY =
2816 de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
2817 const float maxX =
2818 de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
2819 const float maxY =
2820 de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
2821
2822 if ((float)pixel.x() > maxX + 1 || (float)pixel.y() > maxY + 1 || (float)pixel.x() < minX - 1 ||
2823 (float)pixel.y() < minY - 1)
2824 return COVERAGE_NONE;
2825 }
2826
2827 // Accurate intersection for edge pixels
2828 {
2829 // In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center.
2830 const I64Vec2 pixelCorners[4] = {
2831 I64Vec2((pixel.x() + 0) * numSubPixels, (pixel.y() + 0) * numSubPixels),
2832 I64Vec2((pixel.x() + 1) * numSubPixels, (pixel.y() + 0) * numSubPixels),
2833 I64Vec2((pixel.x() + 1) * numSubPixels, (pixel.y() + 1) * numSubPixels),
2834 I64Vec2((pixel.x() + 0) * numSubPixels, (pixel.y() + 1) * numSubPixels),
2835 };
2836 // both rounding directions
2837 const I64Vec2 triangleSubPixelSpaceFloor[3] = {
2838 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels),
2839 deFloorFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
2840 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels),
2841 deFloorFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
2842 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels),
2843 deFloorFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
2844 };
2845 const I64Vec2 triangleSubPixelSpaceCeil[3] = {
2846 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels),
2847 deCeilFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
2848 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels),
2849 deCeilFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
2850 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels),
2851 deCeilFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
2852 };
2853
2854 // Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside
2855
2856 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
2857 for (int startRounding = 0; startRounding < 4; ++startRounding)
2858 for (int endRounding = 0; endRounding < 4; ++endRounding)
2859 {
2860 const int nextEdgeNdx = (edgeNdx + 1) % 3;
2861 const I64Vec2 startPos((startRounding & 0x01) ? (triangleSubPixelSpaceFloor[edgeNdx].x()) :
2862 (triangleSubPixelSpaceCeil[edgeNdx].x()),
2863 (startRounding & 0x02) ? (triangleSubPixelSpaceFloor[edgeNdx].y()) :
2864 (triangleSubPixelSpaceCeil[edgeNdx].y()));
2865 const I64Vec2 endPos((endRounding & 0x01) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].x()) :
2866 (triangleSubPixelSpaceCeil[nextEdgeNdx].x()),
2867 (endRounding & 0x02) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].y()) :
2868 (triangleSubPixelSpaceCeil[nextEdgeNdx].y()));
2869
2870 for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx)
2871 {
2872 const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4;
2873
2874 if (lineLineIntersect(startPos, endPos, pixelCorners[pixelEdgeNdx], pixelCorners[pixelEdgeEnd]))
2875 return COVERAGE_PARTIAL;
2876 }
2877 }
2878
2879 // fully inside or outside
2880 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
2881 {
2882 const int nextEdgeNdx = (edgeNdx + 1) % 3;
2883 const I64Vec2 &startPos = triangleSubPixelSpaceFloor[edgeNdx];
2884 const I64Vec2 &endPos = triangleSubPixelSpaceFloor[nextEdgeNdx];
2885 const I64Vec2 edge = endPos - startPos;
2886 const I64Vec2 v = pixelCorners[0] - endPos;
2887 const int64_t crossProduct = (edge.x() * v.y() - edge.y() * v.x());
2888
2889 // a corner of the pixel is outside => "fully inside" option is impossible
2890 if (crossProduct < 0)
2891 return COVERAGE_NONE;
2892 }
2893
2894 return COVERAGE_FULL;
2895 }
2896 }
2897
logTriangleGroupRasterizationStash(const tcu::Surface & surface,tcu::TestLog & log,VerifyTriangleGroupRasterizationLogStash & logStash)2898 static void logTriangleGroupRasterizationStash(const tcu::Surface &surface, tcu::TestLog &log,
2899 VerifyTriangleGroupRasterizationLogStash &logStash)
2900 {
2901 // Output results
2902 log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage;
2903
2904 for (size_t msgNdx = 0; msgNdx < logStash.messages.size(); ++msgNdx)
2905 log << tcu::TestLog::Message << logStash.messages[msgNdx] << tcu::TestLog::EndMessage;
2906
2907 if (!logStash.result)
2908 {
2909 log << tcu::TestLog::Message << "Invalid pixels found:\n\t" << logStash.missingPixels
2910 << " missing pixels. (Marked with purple)\n\t" << logStash.unexpectedPixels
2911 << " incorrectly filled pixels. (Marked with red)\n\t"
2912 << "Unknown (subpixel on edge) pixels are marked with yellow." << tcu::TestLog::EndMessage;
2913 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
2914 << tcu::TestLog::Image("Result", "Result", surface)
2915 << tcu::TestLog::Image("ErrorMask", "ErrorMask", logStash.errorMask) << tcu::TestLog::EndImageSet;
2916 }
2917 else
2918 {
2919 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
2920 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
2921 << tcu::TestLog::Image("Result", "Result", surface) << tcu::TestLog::EndImageSet;
2922 }
2923 }
2924
verifyTriangleGroupRasterization(const tcu::Surface & surface,const TriangleSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,VerificationMode mode,VerifyTriangleGroupRasterizationLogStash * logStash,const bool vulkanLinesTest)2925 bool verifyTriangleGroupRasterization(const tcu::Surface &surface, const TriangleSceneSpec &scene,
2926 const RasterizationArguments &args, tcu::TestLog &log, VerificationMode mode,
2927 VerifyTriangleGroupRasterizationLogStash *logStash, const bool vulkanLinesTest)
2928 {
2929 DE_ASSERT(mode < VERIFICATIONMODE_LAST);
2930
2931 const tcu::RGBA backGroundColor = tcu::RGBA(0, 0, 0, 255);
2932 const tcu::RGBA triangleColor = tcu::RGBA(255, 255, 255, 255);
2933 const tcu::RGBA missingPixelColor = tcu::RGBA(255, 0, 255, 255);
2934 const tcu::RGBA unexpectedPixelColor = tcu::RGBA(255, 0, 0, 255);
2935 const tcu::RGBA partialPixelColor = tcu::RGBA(255, 255, 0, 255);
2936 const tcu::RGBA primitivePixelColor = tcu::RGBA(30, 30, 30, 255);
2937 const int weakVerificationThreshold = 10;
2938 const int weakerVerificationThreshold = 25;
2939 const bool multisampled = (args.numSamples != 0);
2940 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
2941 int missingPixels = 0;
2942 int unexpectedPixels = 0;
2943 int subPixelBits = args.subpixelBits;
2944 tcu::TextureLevel coverageMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8),
2945 surface.getWidth(), surface.getHeight());
2946 tcu::Surface errorMask(surface.getWidth(), surface.getHeight());
2947 bool result = false;
2948
2949 // subpixel bits in a valid range?
2950
2951 if (subPixelBits < 0)
2952 {
2953 log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0"
2954 << tcu::TestLog::EndMessage;
2955 subPixelBits = 0;
2956 }
2957 else if (subPixelBits > 16)
2958 {
2959 // At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
2960 log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits
2961 << "). Checking results using less strict 16 bit requirements. This may produce false positives."
2962 << tcu::TestLog::EndMessage;
2963 subPixelBits = 16;
2964 }
2965
2966 // generate coverage map
2967
2968 tcu::clear(coverageMap.getAccess(), tcu::IVec4(COVERAGE_NONE, 0, 0, 0));
2969
2970 for (int triNdx = 0; triNdx < (int)scene.triangles.size(); ++triNdx)
2971 {
2972 const tcu::IVec4 aabb = getTriangleAABB(scene.triangles[triNdx], viewportSize);
2973
2974 for (int y = de::max(0, aabb.y()); y <= de::min(aabb.w(), coverageMap.getHeight() - 1); ++y)
2975 for (int x = de::max(0, aabb.x()); x <= de::min(aabb.z(), coverageMap.getWidth() - 1); ++x)
2976 {
2977 if (coverageMap.getAccess().getPixelUint(x, y).x() == COVERAGE_FULL)
2978 continue;
2979
2980 const CoverageType coverage = calculateTriangleCoverage(
2981 scene.triangles[triNdx].positions[0], scene.triangles[triNdx].positions[1],
2982 scene.triangles[triNdx].positions[2], tcu::IVec2(x, y), viewportSize, subPixelBits, multisampled);
2983
2984 if (coverage == COVERAGE_FULL)
2985 {
2986 coverageMap.getAccess().setPixel(tcu::IVec4(COVERAGE_FULL, 0, 0, 0), x, y);
2987 }
2988 else if (coverage == COVERAGE_PARTIAL)
2989 {
2990 CoverageType resultCoverage = COVERAGE_PARTIAL;
2991
2992 // Sharing an edge with another triangle?
2993 // There should always be such a triangle, but the pixel in the other triangle might be
2994 // on multiple edges, some of which are not shared. In these cases the coverage cannot be determined.
2995 // Assume full coverage if the pixel is only on a shared edge in shared triangle too.
2996 if (pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[triNdx], viewportSize))
2997 {
2998 bool friendFound = false;
2999 for (int friendTriNdx = 0; friendTriNdx < (int)scene.triangles.size(); ++friendTriNdx)
3000 {
3001 if (friendTriNdx == triNdx)
3002 continue;
3003
3004 const CoverageType friendCoverage = calculateTriangleCoverage(
3005 scene.triangles[friendTriNdx].positions[0], scene.triangles[friendTriNdx].positions[1],
3006 scene.triangles[friendTriNdx].positions[2], tcu::IVec2(x, y), viewportSize,
3007 subPixelBits, multisampled);
3008
3009 if (friendCoverage != COVERAGE_NONE &&
3010 pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[friendTriNdx], viewportSize))
3011 {
3012 friendFound = true;
3013 break;
3014 }
3015 }
3016
3017 if (friendFound)
3018 resultCoverage = COVERAGE_FULL;
3019 }
3020
3021 coverageMap.getAccess().setPixel(tcu::IVec4(resultCoverage, 0, 0, 0), x, y);
3022 }
3023 }
3024 }
3025
3026 // check pixels
3027
3028 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
3029
3030 // Use these to quick check there is something drawn when a test expects something else than an empty picture.
3031 bool referenceEmpty = true;
3032 bool resultEmpty = true;
3033
3034 for (int y = 0; y < surface.getHeight(); ++y)
3035 for (int x = 0; x < surface.getWidth(); ++x)
3036 {
3037 const tcu::RGBA color = surface.getPixel(x, y);
3038 const bool imageNoCoverage =
3039 compareColors(color, backGroundColor, args.redBits, args.greenBits, args.blueBits);
3040 const bool imageFullCoverage =
3041 compareColors(color, triangleColor, args.redBits, args.greenBits, args.blueBits);
3042 CoverageType referenceCoverage = (CoverageType)coverageMap.getAccess().getPixelUint(x, y).x();
3043
3044 if (!imageNoCoverage)
3045 resultEmpty = false;
3046
3047 switch (referenceCoverage)
3048 {
3049 case COVERAGE_NONE:
3050 if (!imageNoCoverage)
3051 {
3052 // coverage where there should not be
3053 ++unexpectedPixels;
3054 errorMask.setPixel(x, y, unexpectedPixelColor);
3055 }
3056 break;
3057
3058 case COVERAGE_PARTIAL:
3059 {
3060 referenceEmpty = false;
3061 bool foundFragment = false;
3062 if (vulkanLinesTest == true)
3063 {
3064 for (int dy = -1; dy < 2 && !foundFragment; ++dy)
3065 for (int dx = -1; dx < 2 && !foundFragment; ++dx)
3066 {
3067 if (x + dx >= 0 && x + dx != surface.getWidth() && y + dy >= 0 &&
3068 y + dy != surface.getHeight() &&
3069 (CoverageType)coverageMap.getAccess().getPixelUint(x + dx, y + dy).x() != COVERAGE_NONE)
3070 {
3071 const tcu::RGBA color2 = surface.getPixel(x + dx, y + dy);
3072 if (compareColors(color2, triangleColor, args.redBits, args.greenBits, args.blueBits))
3073 foundFragment = true;
3074 }
3075 }
3076 }
3077 // anything goes
3078 if (foundFragment == false)
3079 {
3080 errorMask.setPixel(x, y, partialPixelColor);
3081 if (vulkanLinesTest == true)
3082 ++missingPixels;
3083 }
3084 }
3085 break;
3086
3087 case COVERAGE_FULL:
3088 referenceEmpty = false;
3089 if (!imageFullCoverage)
3090 {
3091 // no coverage where there should be
3092 ++missingPixels;
3093 errorMask.setPixel(x, y, missingPixelColor);
3094 }
3095 else
3096 {
3097 errorMask.setPixel(x, y, primitivePixelColor);
3098 }
3099 break;
3100
3101 default:
3102 DE_ASSERT(false);
3103 }
3104 }
3105
3106 if (((mode == VERIFICATIONMODE_STRICT) && (missingPixels + unexpectedPixels > 0)) ||
3107 ((mode == VERIFICATIONMODE_WEAK) && (missingPixels + unexpectedPixels > weakVerificationThreshold)) ||
3108 ((mode == VERIFICATIONMODE_WEAKER) && (missingPixels + unexpectedPixels > weakerVerificationThreshold)) ||
3109 ((mode == VERIFICATIONMODE_SMOOTH) && (missingPixels > weakVerificationThreshold)) ||
3110 referenceEmpty != resultEmpty)
3111 {
3112 result = false;
3113 }
3114 else
3115 {
3116 result = true;
3117 }
3118
3119 // Output or stash results
3120 {
3121 VerifyTriangleGroupRasterizationLogStash *tempLogStash =
3122 (logStash == nullptr) ? new VerifyTriangleGroupRasterizationLogStash : logStash;
3123
3124 tempLogStash->result = result;
3125 tempLogStash->missingPixels = missingPixels;
3126 tempLogStash->unexpectedPixels = unexpectedPixels;
3127 tempLogStash->errorMask = errorMask;
3128
3129 if (logStash == nullptr)
3130 {
3131 logTriangleGroupRasterizationStash(surface, log, *tempLogStash);
3132 delete tempLogStash;
3133 }
3134 }
3135
3136 return result;
3137 }
3138
verifyLineGroupRasterization(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)3139 bool verifyLineGroupRasterization(const tcu::Surface &surface, const LineSceneSpec &scene,
3140 const RasterizationArguments &args, tcu::TestLog &log)
3141 {
3142 const bool multisampled = args.numSamples != 0;
3143
3144 if (multisampled)
3145 return verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING, nullptr, false,
3146 true);
3147 else
3148 return verifySinglesampleLineGroupRasterization(surface, scene, args, log);
3149 }
3150
verifyClippedTriangulatedLineGroupRasterization(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)3151 bool verifyClippedTriangulatedLineGroupRasterization(const tcu::Surface &surface, const LineSceneSpec &scene,
3152 const RasterizationArguments &args, tcu::TestLog &log)
3153 {
3154 return verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX, nullptr, false,
3155 true);
3156 }
3157
verifyRelaxedLineGroupRasterization(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,const bool vulkanLinesTest,const bool strict)3158 bool verifyRelaxedLineGroupRasterization(const tcu::Surface &surface, const LineSceneSpec &scene,
3159 const RasterizationArguments &args, tcu::TestLog &log,
3160 const bool vulkanLinesTest, const bool strict)
3161 {
3162 VerifyTriangleGroupRasterizationLogStash useClippingLogStash;
3163 VerifyTriangleGroupRasterizationLogStash noClippingLogStash;
3164 VerifyTriangleGroupRasterizationLogStash useClippingForcedStrictLogStash;
3165 VerifyTriangleGroupRasterizationLogStash noClippingForcedStrictLogStash;
3166
3167 if (verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX,
3168 &useClippingLogStash, vulkanLinesTest, strict))
3169 {
3170 logTriangleGroupRasterizationStash(surface, log, useClippingLogStash);
3171
3172 return true;
3173 }
3174 else if (verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING,
3175 &noClippingLogStash, vulkanLinesTest, strict))
3176 {
3177 logTriangleGroupRasterizationStash(surface, log, noClippingLogStash);
3178
3179 return true;
3180 }
3181 else if (strict == false &&
3182 verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX,
3183 &useClippingForcedStrictLogStash, vulkanLinesTest, true))
3184 {
3185 logTriangleGroupRasterizationStash(surface, log, useClippingForcedStrictLogStash);
3186
3187 return true;
3188 }
3189 else if (strict == false &&
3190 verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING,
3191 &noClippingForcedStrictLogStash, vulkanLinesTest, true))
3192 {
3193 logTriangleGroupRasterizationStash(surface, log, noClippingForcedStrictLogStash);
3194
3195 return true;
3196 }
3197 else if (strict == false && args.numSamples == 0 && verifyLineGroupRasterization(surface, scene, args, log))
3198 {
3199 return true;
3200 }
3201 else
3202 {
3203 log << tcu::TestLog::Message << "Relaxed rasterization failed, details follow." << tcu::TestLog::EndMessage;
3204
3205 logTriangleGroupRasterizationStash(surface, log, useClippingLogStash);
3206 logTriangleGroupRasterizationStash(surface, log, noClippingLogStash);
3207
3208 if (strict == false)
3209 {
3210 logTriangleGroupRasterizationStash(surface, log, useClippingForcedStrictLogStash);
3211 logTriangleGroupRasterizationStash(surface, log, noClippingForcedStrictLogStash);
3212 }
3213
3214 return false;
3215 }
3216 }
3217
verifyPointGroupRasterization(const tcu::Surface & surface,const PointSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)3218 bool verifyPointGroupRasterization(const tcu::Surface &surface, const PointSceneSpec &scene,
3219 const RasterizationArguments &args, tcu::TestLog &log)
3220 {
3221 // Splitting to triangles is a valid solution in multisampled cases and even in non-multisample cases too.
3222 return verifyMultisamplePointGroupRasterization(surface, scene, args, log);
3223 }
3224
verifyTriangleGroupInterpolation(const tcu::Surface & surface,const TriangleSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)3225 bool verifyTriangleGroupInterpolation(const tcu::Surface &surface, const TriangleSceneSpec &scene,
3226 const RasterizationArguments &args, tcu::TestLog &log)
3227 {
3228 VerifyTriangleGroupInterpolationLogStash logStash;
3229 const bool result =
3230 verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, logStash, TriangleInterpolator(scene));
3231
3232 logTriangleGroupnterpolationStash(surface, log, logStash);
3233
3234 return result;
3235 }
3236
verifyLineGroupInterpolation(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log)3237 LineInterpolationMethod verifyLineGroupInterpolation(const tcu::Surface &surface, const LineSceneSpec &scene,
3238 const RasterizationArguments &args, tcu::TestLog &log)
3239 {
3240 const bool multisampled = args.numSamples != 0;
3241
3242 if (multisampled)
3243 {
3244 if (verifyMultisampleLineGroupInterpolation(surface, scene, args, log))
3245 return LINEINTERPOLATION_STRICTLY_CORRECT;
3246 return LINEINTERPOLATION_INCORRECT;
3247 }
3248 else
3249 {
3250 const bool isNarrow = (scene.lineWidth == 1.0f);
3251
3252 // accurate interpolation
3253 if (isNarrow)
3254 {
3255 if (verifySinglesampleNarrowLineGroupInterpolation(surface, scene, args, log))
3256 return LINEINTERPOLATION_STRICTLY_CORRECT;
3257 }
3258 else
3259 {
3260 if (verifySinglesampleWideLineGroupInterpolation(surface, scene, args, log))
3261 return LINEINTERPOLATION_STRICTLY_CORRECT;
3262
3263 if (scene.allowNonProjectedInterpolation &&
3264 verifyLineGroupInterpolationWithNonProjectedWeights(surface, scene, args, log))
3265 return LINEINTERPOLATION_STRICTLY_CORRECT;
3266 }
3267
3268 // check with projected (inaccurate) interpolation
3269 log << tcu::TestLog::Message
3270 << "Accurate verification failed, checking with projected weights (inaccurate equation)."
3271 << tcu::TestLog::EndMessage;
3272 if (verifyLineGroupInterpolationWithProjectedWeights(surface, scene, args, log))
3273 return LINEINTERPOLATION_PROJECTED;
3274
3275 return LINEINTERPOLATION_INCORRECT;
3276 }
3277 }
3278
verifyTriangulatedLineGroupInterpolation(const tcu::Surface & surface,const LineSceneSpec & scene,const RasterizationArguments & args,tcu::TestLog & log,const bool strictMode,const bool allowBresenhamForNonStrictLines)3279 bool verifyTriangulatedLineGroupInterpolation(const tcu::Surface &surface, const LineSceneSpec &scene,
3280 const RasterizationArguments &args, tcu::TestLog &log,
3281 const bool strictMode, const bool allowBresenhamForNonStrictLines)
3282 {
3283 return verifyMultisampleLineGroupInterpolation(surface, scene, args, log, strictMode,
3284 allowBresenhamForNonStrictLines);
3285 }
3286
3287 } // namespace tcu
3288