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 #include "deMath.h" 32 33 #include "rrRasterizer.hpp" 34 35 #include <limits> 36 37 namespace tcu 38 { 39 namespace 40 { 41 42 bool lineLineIntersect (const tcu::Vector<deInt64, 2>& line0Beg, const tcu::Vector<deInt64, 2>& line0End, const tcu::Vector<deInt64, 2>& line1Beg, const tcu::Vector<deInt64, 2>& line1End) 43 { 44 typedef tcu::Vector<deInt64, 2> I64Vec2; 45 46 // Lines do not intersect if the other line's endpoints are on the same side 47 // otherwise, the do intersect 48 49 // Test line 0 50 { 51 const I64Vec2 line = line0End - line0Beg; 52 const I64Vec2 v0 = line1Beg - line0Beg; 53 const I64Vec2 v1 = line1End - line0Beg; 54 const deInt64 crossProduct0 = (line.x() * v0.y() - line.y() * v0.x()); 55 const deInt64 crossProduct1 = (line.x() * v1.y() - line.y() * v1.x()); 56 57 // check signs 58 if ((crossProduct0 < 0 && crossProduct1 < 0) || 59 (crossProduct0 > 0 && crossProduct1 > 0)) 60 return false; 61 } 62 63 // Test line 1 64 { 65 const I64Vec2 line = line1End - line1Beg; 66 const I64Vec2 v0 = line0Beg - line1Beg; 67 const I64Vec2 v1 = line0End - line1Beg; 68 const deInt64 crossProduct0 = (line.x() * v0.y() - line.y() * v0.x()); 69 const deInt64 crossProduct1 = (line.x() * v1.y() - line.y() * v1.x()); 70 71 // check signs 72 if ((crossProduct0 < 0 && crossProduct1 < 0) || 73 (crossProduct0 > 0 && crossProduct1 > 0)) 74 return false; 75 } 76 77 return true; 78 } 79 80 bool isTriangleClockwise (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2) 81 { 82 const tcu::Vec2 u (p1.x() / p1.w() - p0.x() / p0.w(), p1.y() / p1.w() - p0.y() / p0.w()); 83 const tcu::Vec2 v (p2.x() / p2.w() - p0.x() / p0.w(), p2.y() / p2.w() - p0.y() / p0.w()); 84 const float crossProduct = (u.x() * v.y() - u.y() * v.x()); 85 86 return crossProduct > 0.0f; 87 } 88 89 bool compareColors (const tcu::RGBA& colorA, const tcu::RGBA& colorB, int redBits, int greenBits, int blueBits) 90 { 91 const int thresholdRed = 1 << (8 - redBits); 92 const int thresholdGreen = 1 << (8 - greenBits); 93 const int thresholdBlue = 1 << (8 - blueBits); 94 95 return deAbs32(colorA.getRed() - colorB.getRed()) <= thresholdRed && 96 deAbs32(colorA.getGreen() - colorB.getGreen()) <= thresholdGreen && 97 deAbs32(colorA.getBlue() - colorB.getBlue()) <= thresholdBlue; 98 } 99 100 bool pixelNearLineSegment (const tcu::IVec2& pixel, const tcu::Vec2& p0, const tcu::Vec2& p1) 101 { 102 const tcu::Vec2 pixelCenterPosition = tcu::Vec2((float)pixel.x() + 0.5f, (float)pixel.y() + 0.5f); 103 104 // "Near" = Distance from the line to the pixel is less than 2 * pixel_max_radius. (pixel_max_radius = sqrt(2) / 2) 105 const float maxPixelDistance = 1.414f; 106 const float maxPixelDistanceSquared = 2.0f; 107 108 // Near the line 109 { 110 const tcu::Vec2 line = p1 - p0; 111 const tcu::Vec2 v = pixelCenterPosition - p0; 112 const float crossProduct = (line.x() * v.y() - line.y() * v.x()); 113 114 // distance to line: (line x v) / |line| 115 // |(line x v) / |line|| > maxPixelDistance 116 // ==> (line x v)^2 / |line|^2 > maxPixelDistance^2 117 // ==> (line x v)^2 > maxPixelDistance^2 * |line|^2 118 119 if (crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(line)) 120 return false; 121 } 122 123 // Between the endpoints 124 { 125 // distance from line endpoint 1 to pixel is less than line length + maxPixelDistance 126 const float maxDistance = tcu::length(p1 - p0) + maxPixelDistance; 127 128 if (tcu::length(pixelCenterPosition - p0) > maxDistance) 129 return false; 130 if (tcu::length(pixelCenterPosition - p1) > maxDistance) 131 return false; 132 } 133 134 return true; 135 } 136 137 bool pixelOnlyOnASharedEdge (const tcu::IVec2& pixel, const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize) 138 { 139 if (triangle.sharedEdge[0] || triangle.sharedEdge[1] || triangle.sharedEdge[2]) 140 { 141 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = 142 { 143 tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()), 144 tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()), 145 tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()), 146 }; 147 const tcu::Vec2 triangleScreenSpace[3] = 148 { 149 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 150 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 151 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 152 }; 153 154 const bool pixelOnEdge0 = pixelNearLineSegment(pixel, triangleScreenSpace[0], triangleScreenSpace[1]); 155 const bool pixelOnEdge1 = pixelNearLineSegment(pixel, triangleScreenSpace[1], triangleScreenSpace[2]); 156 const bool pixelOnEdge2 = pixelNearLineSegment(pixel, triangleScreenSpace[2], triangleScreenSpace[0]); 157 158 // If the pixel is on a multiple edges return false 159 160 if (pixelOnEdge0 && !pixelOnEdge1 && !pixelOnEdge2) 161 return triangle.sharedEdge[0]; 162 if (!pixelOnEdge0 && pixelOnEdge1 && !pixelOnEdge2) 163 return triangle.sharedEdge[1]; 164 if (!pixelOnEdge0 && !pixelOnEdge1 && pixelOnEdge2) 165 return triangle.sharedEdge[2]; 166 } 167 168 return false; 169 } 170 171 float triangleArea (const tcu::Vec2& s0, const tcu::Vec2& s1, const tcu::Vec2& s2) 172 { 173 const tcu::Vec2 u (s1.x() - s0.x(), s1.y() - s0.y()); 174 const tcu::Vec2 v (s2.x() - s0.x(), s2.y() - s0.y()); 175 const float crossProduct = (u.x() * v.y() - u.y() * v.x()); 176 177 return crossProduct / 2.0f; 178 } 179 180 tcu::IVec4 getTriangleAABB (const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize) 181 { 182 const tcu::Vec2 normalizedDeviceSpace[3] = 183 { 184 tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()), 185 tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()), 186 tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()), 187 }; 188 const tcu::Vec2 screenSpace[3] = 189 { 190 (normalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 191 (normalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 192 (normalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 193 }; 194 195 tcu::IVec4 aabb; 196 197 aabb.x() = (int)deFloatFloor(de::min(de::min(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x())); 198 aabb.y() = (int)deFloatFloor(de::min(de::min(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y())); 199 aabb.z() = (int)deFloatCeil (de::max(de::max(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x())); 200 aabb.w() = (int)deFloatCeil (de::max(de::max(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y())); 201 202 return aabb; 203 } 204 205 float getExponentEpsilonFromULP (int valueExponent, deUint32 ulp) 206 { 207 DE_ASSERT(ulp < (1u<<10)); 208 209 // assume mediump precision, using ulp as ulps in a 10 bit mantissa 210 return tcu::Float32::construct(+1, valueExponent, (1u<<23) + (ulp << (23 - 10))).asFloat() - tcu::Float32::construct(+1, valueExponent, (1u<<23)).asFloat(); 211 } 212 213 float getValueEpsilonFromULP (float value, deUint32 ulp) 214 { 215 DE_ASSERT(value != std::numeric_limits<float>::infinity() && value != -std::numeric_limits<float>::infinity()); 216 217 const int exponent = tcu::Float32(value).exponent(); 218 return getExponentEpsilonFromULP(exponent, ulp); 219 } 220 221 float getMaxValueWithinError (float value, deUint32 ulp) 222 { 223 if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity()) 224 return value; 225 226 return value + getValueEpsilonFromULP(value, ulp); 227 } 228 229 float getMinValueWithinError (float value, deUint32 ulp) 230 { 231 if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity()) 232 return value; 233 234 return value - getValueEpsilonFromULP(value, ulp); 235 } 236 237 float getMinFlushToZero (float value) 238 { 239 // flush to zero if that decreases the value 240 // assume mediump precision 241 if (value > 0.0f && value < tcu::Float32::construct(+1, -14, 1u<<23).asFloat()) 242 return 0.0f; 243 return value; 244 } 245 246 float getMaxFlushToZero (float value) 247 { 248 // flush to zero if that increases the value 249 // assume mediump precision 250 if (value < 0.0f && value > tcu::Float32::construct(-1, -14, 1u<<23).asFloat()) 251 return 0.0f; 252 return value; 253 } 254 255 tcu::IVec3 convertRGB8ToNativeFormat (const tcu::RGBA& color, const RasterizationArguments& args) 256 { 257 tcu::IVec3 pixelNativeColor; 258 259 for (int channelNdx = 0; channelNdx < 3; ++channelNdx) 260 { 261 const int channelBitCount = (channelNdx == 0) ? (args.redBits) : (channelNdx == 1) ? (args.greenBits) : (args.blueBits); 262 const int channelPixelValue = (channelNdx == 0) ? (color.getRed()) : (channelNdx == 1) ? (color.getGreen()) : (color.getBlue()); 263 264 if (channelBitCount <= 8) 265 pixelNativeColor[channelNdx] = channelPixelValue >> (8 - channelBitCount); 266 else if (channelBitCount == 8) 267 pixelNativeColor[channelNdx] = channelPixelValue; 268 else 269 { 270 // just in case someone comes up with 8+ bits framebuffers pixel formats. But as 271 // we can only read in rgba8, we have to guess the trailing bits. Guessing 0. 272 pixelNativeColor[channelNdx] = channelPixelValue << (channelBitCount - 8); 273 } 274 } 275 276 return pixelNativeColor; 277 } 278 279 /*--------------------------------------------------------------------*//*! 280 * Returns the maximum value of x / y, where x c [minDividend, maxDividend] 281 * and y c [minDivisor, maxDivisor] 282 *//*--------------------------------------------------------------------*/ 283 float maximalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor) 284 { 285 DE_ASSERT(minDividend <= maxDividend); 286 DE_ASSERT(minDivisor <= maxDivisor); 287 288 // special cases 289 if (minDividend == 0.0f && maxDividend == 0.0f) 290 return 0.0f; 291 if (minDivisor <= 0.0f && maxDivisor >= 0.0f) 292 return std::numeric_limits<float>::infinity(); 293 294 return de::max(de::max(minDividend / minDivisor, minDividend / maxDivisor), de::max(maxDividend / minDivisor, maxDividend / maxDivisor)); 295 } 296 297 /*--------------------------------------------------------------------*//*! 298 * Returns the minimum value of x / y, where x c [minDividend, maxDividend] 299 * and y c [minDivisor, maxDivisor] 300 *//*--------------------------------------------------------------------*/ 301 float minimalRangeDivision (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::min(de::min(minDividend / minDivisor, minDividend / maxDivisor), de::min(maxDividend / minDivisor, maxDividend / maxDivisor)); 313 } 314 315 static bool isLineXMajor (const tcu::Vec2& lineScreenSpaceP0, const tcu::Vec2& lineScreenSpaceP1) 316 { 317 return de::abs(lineScreenSpaceP1.x() - lineScreenSpaceP0.x()) >= de::abs(lineScreenSpaceP1.y() - lineScreenSpaceP0.y()); 318 } 319 320 static bool isPackedSSLineXMajor (const tcu::Vec4& packedLine) 321 { 322 const tcu::Vec2 lineScreenSpaceP0 = packedLine.swizzle(0, 1); 323 const tcu::Vec2 lineScreenSpaceP1 = packedLine.swizzle(2, 3); 324 325 return isLineXMajor(lineScreenSpaceP0, lineScreenSpaceP1); 326 } 327 328 struct InterpolationRange 329 { 330 tcu::Vec3 max; 331 tcu::Vec3 min; 332 }; 333 334 struct LineInterpolationRange 335 { 336 tcu::Vec2 max; 337 tcu::Vec2 min; 338 }; 339 340 InterpolationRange calcTriangleInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::Vec2& ndpixel) 341 { 342 const int roundError = 1; 343 const int barycentricError = 3; 344 const int divError = 8; 345 346 const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w(); 347 const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w(); 348 const tcu::Vec2 nd2 = p2.swizzle(0, 1) / p2.w(); 349 350 const float ka = triangleArea(ndpixel, nd1, nd2); 351 const float kb = triangleArea(ndpixel, nd2, nd0); 352 const float kc = triangleArea(ndpixel, nd0, nd1); 353 354 const float kaMax = getMaxFlushToZero(getMaxValueWithinError(ka, barycentricError)); 355 const float kbMax = getMaxFlushToZero(getMaxValueWithinError(kb, barycentricError)); 356 const float kcMax = getMaxFlushToZero(getMaxValueWithinError(kc, barycentricError)); 357 const float kaMin = getMinFlushToZero(getMinValueWithinError(ka, barycentricError)); 358 const float kbMin = getMinFlushToZero(getMinValueWithinError(kb, barycentricError)); 359 const float kcMin = getMinFlushToZero(getMinValueWithinError(kc, barycentricError)); 360 DE_ASSERT(kaMin <= kaMax); 361 DE_ASSERT(kbMin <= kbMax); 362 DE_ASSERT(kcMin <= kcMax); 363 364 // calculate weights: vec3(ka / p0.w, kb / p1.w, kc / p2.w) / (ka / p0.w + kb / p1.w + kc / p2.w) 365 const float maxPreDivisionValues[3] = 366 { 367 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kaMax / p0.w()), divError)), 368 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kbMax / p1.w()), divError)), 369 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kcMax / p2.w()), divError)), 370 }; 371 const float minPreDivisionValues[3] = 372 { 373 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kaMin / p0.w()), divError)), 374 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kbMin / p1.w()), divError)), 375 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kcMin / p2.w()), divError)), 376 }; 377 DE_ASSERT(minPreDivisionValues[0] <= maxPreDivisionValues[0]); 378 DE_ASSERT(minPreDivisionValues[1] <= maxPreDivisionValues[1]); 379 DE_ASSERT(minPreDivisionValues[2] <= maxPreDivisionValues[2]); 380 381 const float maxDivisor = getMaxFlushToZero(getMaxValueWithinError(maxPreDivisionValues[0] + maxPreDivisionValues[1] + maxPreDivisionValues[2], 2*roundError)); 382 const float minDivisor = getMinFlushToZero(getMinValueWithinError(minPreDivisionValues[0] + minPreDivisionValues[1] + minPreDivisionValues[2], 2*roundError)); 383 DE_ASSERT(minDivisor <= maxDivisor); 384 385 InterpolationRange returnValue; 386 387 returnValue.max.x() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError)); 388 returnValue.max.y() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError)); 389 returnValue.max.z() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError)); 390 returnValue.min.x() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError)); 391 returnValue.min.y() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError)); 392 returnValue.min.z() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError)); 393 394 DE_ASSERT(returnValue.min.x() <= returnValue.max.x()); 395 DE_ASSERT(returnValue.min.y() <= returnValue.max.y()); 396 DE_ASSERT(returnValue.min.z() <= returnValue.max.z()); 397 398 return returnValue; 399 } 400 401 LineInterpolationRange calcLineInterpolationWeights (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr) 402 { 403 const int roundError = 1; 404 const int divError = 3; 405 406 // calc weights: 407 // (1-t) / wa t / wb 408 // ------------------- , ------------------- 409 // (1-t) / wa + t / wb (1-t) / wa + t / wb 410 411 // Allow 1 ULP 412 const float dividend = tcu::dot(pr - pa, pb - pa); 413 const float dividendMax = getMaxValueWithinError(dividend, 1); 414 const float dividendMin = getMinValueWithinError(dividend, 1); 415 DE_ASSERT(dividendMin <= dividendMax); 416 417 // Assuming lengthSquared will not be implemented as sqrt(x)^2, allow 1 ULP 418 const float divisor = tcu::lengthSquared(pb - pa); 419 const float divisorMax = getMaxValueWithinError(divisor, 1); 420 const float divisorMin = getMinValueWithinError(divisor, 1); 421 DE_ASSERT(divisorMin <= divisorMax); 422 423 // Allow 3 ULP precision for division 424 const float tMax = getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError); 425 const float tMin = getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError); 426 DE_ASSERT(tMin <= tMax); 427 428 const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError); 429 const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError); 430 DE_ASSERT(perspectiveTMin <= perspectiveTMax); 431 432 const float perspectiveInvTMax = getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError); 433 const float perspectiveInvTMin = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError); 434 DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax); 435 436 const float perspectiveDivisorMax = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError); 437 const float perspectiveDivisorMin = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError); 438 DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax); 439 440 LineInterpolationRange returnValue; 441 returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 442 returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 443 returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 444 returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 445 446 DE_ASSERT(returnValue.min.x() <= returnValue.max.x()); 447 DE_ASSERT(returnValue.min.y() <= returnValue.max.y()); 448 449 return returnValue; 450 } 451 452 LineInterpolationRange calcLineInterpolationWeightsAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr) 453 { 454 const int roundError = 1; 455 const int divError = 3; 456 const bool isXMajor = isLineXMajor(pa, pb); 457 const int majorAxisNdx = (isXMajor) ? (0) : (1); 458 459 // calc weights: 460 // (1-t) / wa t / wb 461 // ------------------- , ------------------- 462 // (1-t) / wa + t / wb (1-t) / wa + t / wb 463 464 // Use axis projected (inaccurate) method, i.e. for X-major lines: 465 // (xd - xa) * (xb - xa) xd - xa 466 // t = --------------------- == ------- 467 // ( xb - xa ) ^ 2 xb - xa 468 469 // Allow 1 ULP 470 const float dividend = (pr[majorAxisNdx] - pa[majorAxisNdx]); 471 const float dividendMax = getMaxValueWithinError(dividend, 1); 472 const float dividendMin = getMinValueWithinError(dividend, 1); 473 DE_ASSERT(dividendMin <= dividendMax); 474 475 // Allow 1 ULP 476 const float divisor = (pb[majorAxisNdx] - pa[majorAxisNdx]); 477 const float divisorMax = getMaxValueWithinError(divisor, 1); 478 const float divisorMin = getMinValueWithinError(divisor, 1); 479 DE_ASSERT(divisorMin <= divisorMax); 480 481 // Allow 3 ULP precision for division 482 const float tMax = getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError); 483 const float tMin = getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError); 484 DE_ASSERT(tMin <= tMax); 485 486 const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError); 487 const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError); 488 DE_ASSERT(perspectiveTMin <= perspectiveTMax); 489 490 const float perspectiveInvTMax = getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError); 491 const float perspectiveInvTMin = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError); 492 DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax); 493 494 const float perspectiveDivisorMax = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError); 495 const float perspectiveDivisorMin = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError); 496 DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax); 497 498 LineInterpolationRange returnValue; 499 returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 500 returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 501 returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 502 returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 503 504 DE_ASSERT(returnValue.min.x() <= returnValue.max.x()); 505 DE_ASSERT(returnValue.min.y() <= returnValue.max.y()); 506 507 return returnValue; 508 } 509 510 template <typename WeightEquation> 511 LineInterpolationRange calcSingleSampleLineInterpolationRangeWithWeightEquation (const tcu::Vec2& pa, 512 float wa, 513 const tcu::Vec2& pb, 514 float wb, 515 const tcu::IVec2& pixel, 516 int subpixelBits, 517 WeightEquation weightEquation) 518 { 519 // allow interpolation weights anywhere in the central subpixels 520 const float testSquareSize = (2.0f / (float)(1UL << subpixelBits)); 521 const float testSquarePos = (0.5f - testSquareSize / 2); 522 523 const tcu::Vec2 corners[4] = 524 { 525 tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f, (float)pixel.y() + testSquarePos + 0.0f), 526 tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f, (float)pixel.y() + testSquarePos + testSquareSize), 527 tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize, (float)pixel.y() + testSquarePos + testSquareSize), 528 tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize, (float)pixel.y() + testSquarePos + 0.0f), 529 }; 530 531 // calculate interpolation as a line 532 const LineInterpolationRange weights[4] = 533 { 534 weightEquation(pa, wa, pb, wb, corners[0]), 535 weightEquation(pa, wa, pb, wb, corners[1]), 536 weightEquation(pa, wa, pb, wb, corners[2]), 537 weightEquation(pa, wa, pb, wb, corners[3]), 538 }; 539 540 const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min)); 541 const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max)); 542 543 LineInterpolationRange result; 544 result.min = minWeights; 545 result.max = maxWeights; 546 return result; 547 } 548 549 LineInterpolationRange calcSingleSampleLineInterpolationRange (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::IVec2& pixel, int subpixelBits) 550 { 551 return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits, calcLineInterpolationWeights); 552 } 553 554 LineInterpolationRange calcSingleSampleLineInterpolationRangeAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::IVec2& pixel, int subpixelBits) 555 { 556 return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits, calcLineInterpolationWeightsAxisProjected); 557 } 558 559 struct TriangleInterpolator 560 { 561 const TriangleSceneSpec& scene; 562 563 TriangleInterpolator (const TriangleSceneSpec& scene_) 564 : scene(scene_) 565 { 566 } 567 568 InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const 569 { 570 // allow anywhere in the pixel area in multisample 571 // allow only in the center subpixels (4 subpixels) in singlesample 572 const float testSquareSize = (multisample) ? (1.0f) : (2.0f / (float)(1UL << subpixelBits)); 573 const float testSquarePos = (multisample) ? (0.0f) : (0.5f - testSquareSize / 2); 574 const tcu::Vec2 corners[4] = 575 { 576 tcu::Vec2(((float)pixel.x() + testSquarePos + 0.0f) / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + 0.0f ) / (float)viewportSize.y() * 2.0f - 1.0f), 577 tcu::Vec2(((float)pixel.x() + testSquarePos + 0.0f) / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + testSquareSize) / (float)viewportSize.y() * 2.0f - 1.0f), 578 tcu::Vec2(((float)pixel.x() + testSquarePos + testSquareSize) / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + testSquareSize) / (float)viewportSize.y() * 2.0f - 1.0f), 579 tcu::Vec2(((float)pixel.x() + testSquarePos + testSquareSize) / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + 0.0f ) / (float)viewportSize.y() * 2.0f - 1.0f), 580 }; 581 const InterpolationRange weights[4] = 582 { 583 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[0]), 584 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[1]), 585 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[2]), 586 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[3]), 587 }; 588 589 InterpolationRange result; 590 result.min = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min)); 591 result.max = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max)); 592 return result; 593 } 594 }; 595 596 /*--------------------------------------------------------------------*//*! 597 * Used only by verifyMultisampleLineGroupInterpolation to calculate 598 * correct line interpolations for the triangulated lines. 599 *//*--------------------------------------------------------------------*/ 600 struct MultisampleLineInterpolator 601 { 602 const LineSceneSpec& scene; 603 604 MultisampleLineInterpolator (const LineSceneSpec& scene_) 605 : scene(scene_) 606 { 607 } 608 609 InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const 610 { 611 DE_UNREF(multisample); 612 DE_UNREF(subpixelBits); 613 614 // in triangulation, one line emits two triangles 615 const int lineNdx = primitiveNdx / 2; 616 617 // allow interpolation weights anywhere in the pixel 618 const tcu::Vec2 corners[4] = 619 { 620 tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 0.0f), 621 tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 1.0f), 622 tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 1.0f), 623 tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 0.0f), 624 }; 625 626 const float wa = scene.lines[lineNdx].positions[0].w(); 627 const float wb = scene.lines[lineNdx].positions[1].w(); 628 const tcu::Vec2 pa = tcu::Vec2((scene.lines[lineNdx].positions[0].x() / wa + 1.0f) * 0.5f * (float)viewportSize.x(), 629 (scene.lines[lineNdx].positions[0].y() / wa + 1.0f) * 0.5f * (float)viewportSize.y()); 630 const tcu::Vec2 pb = tcu::Vec2((scene.lines[lineNdx].positions[1].x() / wb + 1.0f) * 0.5f * (float)viewportSize.x(), 631 (scene.lines[lineNdx].positions[1].y() / wb + 1.0f) * 0.5f * (float)viewportSize.y()); 632 633 // calculate interpolation as a line 634 const LineInterpolationRange weights[4] = 635 { 636 calcLineInterpolationWeights(pa, wa, pb, wb, corners[0]), 637 calcLineInterpolationWeights(pa, wa, pb, wb, corners[1]), 638 calcLineInterpolationWeights(pa, wa, pb, wb, corners[2]), 639 calcLineInterpolationWeights(pa, wa, pb, wb, corners[3]), 640 }; 641 642 const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min)); 643 const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max)); 644 645 // 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 646 InterpolationRange result; 647 result.min = tcu::Vec3(minWeights.x(), 0.0f, minWeights.y()); 648 result.max = tcu::Vec3(maxWeights.x(), 0.0f, maxWeights.y()); 649 return result; 650 } 651 }; 652 653 template <typename Interpolator> 654 bool verifyTriangleGroupInterpolationWithInterpolator (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, const Interpolator& interpolator) 655 { 656 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255); 657 const bool multisampled = (args.numSamples != 0); 658 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 659 const int errorFloodThreshold = 4; 660 int errorCount = 0; 661 int invalidPixels = 0; 662 int subPixelBits = args.subpixelBits; 663 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 664 665 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 666 667 // log format 668 669 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage; 670 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8) 671 log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage; 672 673 // subpixel bits in in a valid range? 674 675 if (subPixelBits < 0) 676 { 677 log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage; 678 subPixelBits = 0; 679 } 680 else if (subPixelBits > 16) 681 { 682 // At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict 683 log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits << "). Checking results using less strict 16 bit requirements. This may produce false positives." << tcu::TestLog::EndMessage; 684 subPixelBits = 16; 685 } 686 687 // check pixels 688 689 for (int y = 0; y < surface.getHeight(); ++y) 690 for (int x = 0; x < surface.getWidth(); ++x) 691 { 692 const tcu::RGBA color = surface.getPixel(x, y); 693 bool stackBottomFound = false; 694 int stackSize = 0; 695 tcu::Vec4 colorStackMin; 696 tcu::Vec4 colorStackMax; 697 698 // Iterate triangle coverage front to back, find the stack of pontentially contributing fragments 699 for (int triNdx = (int)scene.triangles.size() - 1; triNdx >= 0; --triNdx) 700 { 701 const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0], 702 scene.triangles[triNdx].positions[1], 703 scene.triangles[triNdx].positions[2], 704 tcu::IVec2(x, y), 705 viewportSize, 706 subPixelBits, 707 multisampled); 708 709 if (coverage == COVERAGE_FULL || coverage == COVERAGE_PARTIAL) 710 { 711 // potentially contributes to the result fragment's value 712 const InterpolationRange weights = interpolator.interpolate(triNdx, tcu::IVec2(x, y), viewportSize, multisampled, subPixelBits); 713 714 const tcu::Vec4 fragmentColorMax = de::clamp(weights.max.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] + 715 de::clamp(weights.max.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] + 716 de::clamp(weights.max.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2]; 717 const tcu::Vec4 fragmentColorMin = de::clamp(weights.min.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] + 718 de::clamp(weights.min.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] + 719 de::clamp(weights.min.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2]; 720 721 if (stackSize++ == 0) 722 { 723 // first triangle, set the values properly 724 colorStackMin = fragmentColorMin; 725 colorStackMax = fragmentColorMax; 726 } 727 else 728 { 729 // contributing triangle 730 colorStackMin = tcu::min(colorStackMin, fragmentColorMin); 731 colorStackMax = tcu::max(colorStackMax, fragmentColorMax); 732 } 733 734 if (coverage == COVERAGE_FULL) 735 { 736 // loop terminates, this is the bottommost fragment 737 stackBottomFound = true; 738 break; 739 } 740 } 741 } 742 743 // Partial coverage == background may be visible 744 if (stackSize != 0 && !stackBottomFound) 745 { 746 stackSize++; 747 colorStackMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f); 748 } 749 750 // Is the result image color in the valid range. 751 if (stackSize == 0) 752 { 753 // No coverage, allow only background (black, value=0) 754 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); 755 const int threshold = 1; 756 757 if (pixelNativeColor.x() > threshold || 758 pixelNativeColor.y() > threshold || 759 pixelNativeColor.z() > threshold) 760 { 761 ++errorCount; 762 763 // don't fill the logs with too much data 764 if (errorCount < errorFloodThreshold) 765 { 766 log << tcu::TestLog::Message 767 << "Found an invalid pixel at (" << x << "," << y << ")\n" 768 << "\tPixel color:\t\t" << color << "\n" 769 << "\tExpected background color.\n" 770 << tcu::TestLog::EndMessage; 771 } 772 773 ++invalidPixels; 774 errorMask.setPixel(x, y, invalidPixelColor); 775 } 776 } 777 else 778 { 779 DE_ASSERT(stackSize); 780 781 // Each additional step in the stack may cause conversion error of 1 bit due to undefined rounding direction 782 const int thresholdRed = stackSize - 1; 783 const int thresholdGreen = stackSize - 1; 784 const int thresholdBlue = stackSize - 1; 785 786 const tcu::Vec3 valueRangeMin = tcu::Vec3(colorStackMin.xyz()); 787 const tcu::Vec3 valueRangeMax = tcu::Vec3(colorStackMax.xyz()); 788 789 const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1); 790 const tcu::Vec3 colorMinF (de::clamp(valueRangeMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()), 791 de::clamp(valueRangeMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()), 792 de::clamp(valueRangeMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z())); 793 const tcu::Vec3 colorMaxF (de::clamp(valueRangeMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()), 794 de::clamp(valueRangeMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()), 795 de::clamp(valueRangeMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z())); 796 const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()), 797 (int)deFloatFloor(colorMinF.y()), 798 (int)deFloatFloor(colorMinF.z())); 799 const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()), 800 (int)deFloatCeil (colorMaxF.y()), 801 (int)deFloatCeil (colorMaxF.z())); 802 803 // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565 804 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); 805 806 // Validity check 807 if (pixelNativeColor.x() < colorMin.x() - thresholdRed || 808 pixelNativeColor.y() < colorMin.y() - thresholdGreen || 809 pixelNativeColor.z() < colorMin.z() - thresholdBlue || 810 pixelNativeColor.x() > colorMax.x() + thresholdRed || 811 pixelNativeColor.y() > colorMax.y() + thresholdGreen || 812 pixelNativeColor.z() > colorMax.z() + thresholdBlue) 813 { 814 ++errorCount; 815 816 // don't fill the logs with too much data 817 if (errorCount <= errorFloodThreshold) 818 { 819 log << tcu::TestLog::Message 820 << "Found an invalid pixel at (" << x << "," << y << ")\n" 821 << "\tPixel color:\t\t" << color << "\n" 822 << "\tNative color:\t\t" << pixelNativeColor << "\n" 823 << "\tAllowed error:\t\t" << tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue) << "\n" 824 << "\tReference native color min: " << tcu::clamp(colorMin - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n" 825 << "\tReference native color max: " << tcu::clamp(colorMax + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n" 826 << "\tReference native float min: " << tcu::clamp(colorMinF - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(), tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 827 << "\tReference native float max: " << tcu::clamp(colorMaxF + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(), tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 828 << "\tFmin:\t" << tcu::clamp(valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 829 << "\tFmax:\t" << tcu::clamp(valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 830 << tcu::TestLog::EndMessage; 831 } 832 833 ++invalidPixels; 834 errorMask.setPixel(x, y, invalidPixelColor); 835 } 836 } 837 } 838 839 // don't just hide failures 840 if (errorCount > errorFloodThreshold) 841 log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage; 842 843 // report result 844 if (invalidPixels) 845 { 846 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage; 847 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 848 << tcu::TestLog::Image("Result", "Result", surface) 849 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 850 << tcu::TestLog::EndImageSet; 851 852 return false; 853 } 854 else 855 { 856 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 857 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 858 << tcu::TestLog::Image("Result", "Result", surface) 859 << tcu::TestLog::EndImageSet; 860 861 return true; 862 } 863 } 864 865 866 float calculateIntersectionParameter (const tcu::Vec2 line[2], float w, int componentNdx) 867 { 868 DE_ASSERT(componentNdx < 2); 869 if (line[1][componentNdx] == line[0][componentNdx]) 870 return -1.0f; 871 872 return (w - line[0][componentNdx]) / (line[1][componentNdx] - line[0][componentNdx]); 873 } 874 875 // Clips the given line with a ((-w, -w), (-w, w), (w, w), (w, -w)) rectangle 876 void applyClippingBox (tcu::Vec2 line[2], float w) 877 { 878 for (int side = 0; side < 4; ++side) 879 { 880 const int sign = ((side / 2) * -2) + 1; 881 const int component = side % 2; 882 const float t = calculateIntersectionParameter(line, w * (float)sign, component); 883 884 if ((t > 0) && (t < 1)) 885 { 886 const float newCoord = t * line[1][1 - component] + (1 - t) * line[0][1 - component]; 887 888 if (line[1][component] > (w * (float)sign)) 889 { 890 line[1 - side / 2][component] = w * (float)sign; 891 line[1 - side / 2][1 - component] = newCoord; 892 } 893 else 894 { 895 line[side / 2][component] = w * (float)sign; 896 line[side / 2][1 - component] = newCoord; 897 } 898 } 899 } 900 } 901 902 enum ClipMode 903 { 904 CLIPMODE_NO_CLIPPING = 0, 905 CLIPMODE_USE_CLIPPING_BOX, 906 907 CLIPMODE_LAST 908 }; 909 910 bool verifyMultisampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, ClipMode clipMode, VerifyTriangleGroupRasterizationLogStash* logStash = DE_NULL) 911 { 912 // Multisampled line == 2 triangles 913 914 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight()); 915 const float halfLineWidth = scene.lineWidth * 0.5f; 916 TriangleSceneSpec triangleScene; 917 918 triangleScene.triangles.resize(2 * scene.lines.size()); 919 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 920 { 921 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles 922 tcu::Vec2 lineNormalizedDeviceSpace[2] = 923 { 924 tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()), 925 tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()), 926 }; 927 928 if (clipMode == CLIPMODE_USE_CLIPPING_BOX) 929 { 930 applyClippingBox(lineNormalizedDeviceSpace, 1.0f); 931 } 932 933 const tcu::Vec2 lineScreenSpace[2] = 934 { 935 (lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 936 (lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 937 }; 938 939 const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]); 940 const tcu::Vec2 lineNormalDir = tcu::Vec2(lineDir.y(), -lineDir.x()); 941 942 const tcu::Vec2 lineQuadScreenSpace[4] = 943 { 944 lineScreenSpace[0] + lineNormalDir * halfLineWidth, 945 lineScreenSpace[0] - lineNormalDir * halfLineWidth, 946 lineScreenSpace[1] - lineNormalDir * halfLineWidth, 947 lineScreenSpace[1] + lineNormalDir * halfLineWidth, 948 }; 949 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = 950 { 951 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 952 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 953 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 954 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 955 }; 956 957 triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false; 958 triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f); triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false; 959 triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true; 960 961 triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true; 962 triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false; 963 triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f); triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false; 964 } 965 966 return verifyTriangleGroupRasterization(surface, triangleScene, args, log, VERIFICATIONMODE_STRICT, logStash); 967 } 968 969 bool verifyMultisampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 970 { 971 // Multisampled line == 2 triangles 972 973 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight()); 974 const float halfLineWidth = scene.lineWidth * 0.5f; 975 TriangleSceneSpec triangleScene; 976 977 triangleScene.triangles.resize(2 * scene.lines.size()); 978 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 979 { 980 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles 981 const tcu::Vec2 lineNormalizedDeviceSpace[2] = 982 { 983 tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()), 984 tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()), 985 }; 986 const tcu::Vec2 lineScreenSpace[2] = 987 { 988 (lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 989 (lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 990 }; 991 992 const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]); 993 const tcu::Vec2 lineNormalDir = tcu::Vec2(lineDir.y(), -lineDir.x()); 994 995 const tcu::Vec2 lineQuadScreenSpace[4] = 996 { 997 lineScreenSpace[0] + lineNormalDir * halfLineWidth, 998 lineScreenSpace[0] - lineNormalDir * halfLineWidth, 999 lineScreenSpace[1] - lineNormalDir * halfLineWidth, 1000 lineScreenSpace[1] + lineNormalDir * halfLineWidth, 1001 }; 1002 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = 1003 { 1004 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1005 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1006 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1007 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1008 }; 1009 1010 triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); 1011 triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f); 1012 triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); 1013 1014 triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false; 1015 triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false; 1016 triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true; 1017 1018 triangleScene.triangles[lineNdx*2 + 0].colors[0] = scene.lines[lineNdx].colors[0]; 1019 triangleScene.triangles[lineNdx*2 + 0].colors[1] = scene.lines[lineNdx].colors[0]; 1020 triangleScene.triangles[lineNdx*2 + 0].colors[2] = scene.lines[lineNdx].colors[1]; 1021 1022 triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); 1023 triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); 1024 triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f); 1025 1026 triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true; 1027 triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false; 1028 triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false; 1029 1030 triangleScene.triangles[lineNdx*2 + 1].colors[0] = scene.lines[lineNdx].colors[0]; 1031 triangleScene.triangles[lineNdx*2 + 1].colors[1] = scene.lines[lineNdx].colors[1]; 1032 triangleScene.triangles[lineNdx*2 + 1].colors[2] = scene.lines[lineNdx].colors[1]; 1033 } 1034 1035 return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, log, MultisampleLineInterpolator(scene)); 1036 } 1037 1038 bool verifyMultisamplePointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1039 { 1040 // Multisampled point == 2 triangles 1041 1042 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight()); 1043 TriangleSceneSpec triangleScene; 1044 1045 triangleScene.triangles.resize(2 * scene.points.size()); 1046 for (int pointNdx = 0; pointNdx < (int)scene.points.size(); ++pointNdx) 1047 { 1048 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles 1049 const tcu::Vec2 pointNormalizedDeviceSpace = tcu::Vec2(scene.points[pointNdx].position.x() / scene.points[pointNdx].position.w(), scene.points[pointNdx].position.y() / scene.points[pointNdx].position.w()); 1050 const tcu::Vec2 pointScreenSpace = (pointNormalizedDeviceSpace + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize; 1051 const float offset = scene.points[pointNdx].pointSize * 0.5f; 1052 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = 1053 { 1054 (pointScreenSpace + tcu::Vec2(-offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1055 (pointScreenSpace + tcu::Vec2(-offset, offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1056 (pointScreenSpace + tcu::Vec2( offset, offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1057 (pointScreenSpace + tcu::Vec2( offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 1058 }; 1059 1060 triangleScene.triangles[pointNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); triangleScene.triangles[pointNdx*2 + 0].sharedEdge[0] = false; 1061 triangleScene.triangles[pointNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f); triangleScene.triangles[pointNdx*2 + 0].sharedEdge[1] = false; 1062 triangleScene.triangles[pointNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); triangleScene.triangles[pointNdx*2 + 0].sharedEdge[2] = true; 1063 1064 triangleScene.triangles[pointNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); triangleScene.triangles[pointNdx*2 + 1].sharedEdge[0] = true; 1065 triangleScene.triangles[pointNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); triangleScene.triangles[pointNdx*2 + 1].sharedEdge[1] = false; 1066 triangleScene.triangles[pointNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f); triangleScene.triangles[pointNdx*2 + 1].sharedEdge[2] = false; 1067 } 1068 1069 return verifyTriangleGroupRasterization(surface, triangleScene, args, log); 1070 } 1071 1072 void genScreenSpaceLines (std::vector<tcu::Vec4>& screenspaceLines, const std::vector<LineSceneSpec::SceneLine>& lines, const tcu::IVec2& viewportSize) 1073 { 1074 DE_ASSERT(screenspaceLines.size() == lines.size()); 1075 1076 for (int lineNdx = 0; lineNdx < (int)lines.size(); ++lineNdx) 1077 { 1078 const tcu::Vec2 lineNormalizedDeviceSpace[2] = 1079 { 1080 tcu::Vec2(lines[lineNdx].positions[0].x() / lines[lineNdx].positions[0].w(), lines[lineNdx].positions[0].y() / lines[lineNdx].positions[0].w()), 1081 tcu::Vec2(lines[lineNdx].positions[1].x() / lines[lineNdx].positions[1].w(), lines[lineNdx].positions[1].y() / lines[lineNdx].positions[1].w()), 1082 }; 1083 const tcu::Vec4 lineScreenSpace[2] = 1084 { 1085 tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)viewportSize.x(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)viewportSize.y(), 0.0f, 1.0f), 1086 tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)viewportSize.x(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)viewportSize.y(), 0.0f, 1.0f), 1087 }; 1088 1089 screenspaceLines[lineNdx] = tcu::Vec4(lineScreenSpace[0].x(), lineScreenSpace[0].y(), lineScreenSpace[1].x(), lineScreenSpace[1].y()); 1090 } 1091 } 1092 1093 bool verifySinglesampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1094 { 1095 DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases 1096 DE_ASSERT(scene.lines.size() < 255); // indices are stored as unsigned 8-bit ints 1097 1098 bool allOK = true; 1099 bool overdrawInReference = false; 1100 int referenceFragments = 0; 1101 int resultFragments = 0; 1102 int lineWidth = deFloorFloatToInt32(scene.lineWidth + 0.5f); 1103 bool imageShown = false; 1104 std::vector<bool> lineIsXMajor (scene.lines.size()); 1105 std::vector<tcu::Vec4> screenspaceLines(scene.lines.size()); 1106 1107 // 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 1108 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 1109 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0)); 1110 1111 genScreenSpaceLines(screenspaceLines, scene.lines, tcu::IVec2(surface.getWidth(), surface.getHeight())); 1112 1113 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1114 { 1115 rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight())); 1116 rasterizer.init(tcu::Vec4(screenspaceLines[lineNdx][0], 1117 screenspaceLines[lineNdx][1], 1118 0.0f, 1119 1.0f), 1120 tcu::Vec4(screenspaceLines[lineNdx][2], 1121 screenspaceLines[lineNdx][3], 1122 0.0f, 1123 1.0f), 1124 scene.lineWidth); 1125 1126 // calculate majority of later use 1127 lineIsXMajor[lineNdx] = isPackedSSLineXMajor(screenspaceLines[lineNdx]); 1128 1129 for (;;) 1130 { 1131 const int maxPackets = 32; 1132 int numRasterized = 0; 1133 rr::FragmentPacket packets[maxPackets]; 1134 1135 rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized); 1136 1137 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx) 1138 { 1139 for (int fragNdx = 0; fragNdx < 4; ++fragNdx) 1140 { 1141 if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx)) 1142 { 1143 const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2); 1144 1145 // Check for overdraw 1146 if (!overdrawInReference) 1147 overdrawInReference = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x() != 0; 1148 1149 // Output pixel 1150 referenceLineMap.getAccess().setPixel(tcu::IVec4(lineNdx + 1, 0, 0, 0), fragPos.x(), fragPos.y()); 1151 } 1152 } 1153 } 1154 1155 if (numRasterized != maxPackets) 1156 break; 1157 } 1158 } 1159 1160 // Requirement 1: The coordinates of a fragment produced by the algorithm may not deviate by more than one unit 1161 { 1162 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 1163 bool missingFragments = false; 1164 1165 tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 255, 0, 255)); 1166 1167 log << tcu::TestLog::Message << "Searching for deviating fragments." << tcu::TestLog::EndMessage; 1168 1169 for (int y = 0; y < referenceLineMap.getHeight(); ++y) 1170 for (int x = 0; x < referenceLineMap.getWidth(); ++x) 1171 { 1172 const bool reference = referenceLineMap.getAccess().getPixelInt(x, y).x() != 0; 1173 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits); 1174 1175 if (reference) 1176 ++referenceFragments; 1177 if (result) 1178 ++resultFragments; 1179 1180 if (reference == result) 1181 continue; 1182 1183 // Reference fragment here, matching result fragment must be nearby 1184 if (reference && !result) 1185 { 1186 bool foundFragment = false; 1187 1188 if (x == 0 || y == 0 || x == referenceLineMap.getWidth() - 1 || y == referenceLineMap.getHeight() -1) 1189 { 1190 // image boundary, missing fragment could be over the image edge 1191 foundFragment = true; 1192 } 1193 1194 // find nearby fragment 1195 for (int dy = -1; dy < 2 && !foundFragment; ++dy) 1196 for (int dx = -1; dx < 2 && !foundFragment; ++dx) 1197 { 1198 if (compareColors(surface.getPixel(x+dx, y+dy), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits)) 1199 foundFragment = true; 1200 } 1201 1202 if (!foundFragment) 1203 { 1204 missingFragments = true; 1205 errorMask.setPixel(x, y, tcu::RGBA::red()); 1206 } 1207 } 1208 } 1209 1210 if (missingFragments) 1211 { 1212 log << tcu::TestLog::Message << "Invalid deviation(s) found." << tcu::TestLog::EndMessage; 1213 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1214 << tcu::TestLog::Image("Result", "Result", surface) 1215 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 1216 << tcu::TestLog::EndImageSet; 1217 1218 imageShown = true; 1219 allOK = false; 1220 } 1221 else 1222 { 1223 log << tcu::TestLog::Message << "No invalid deviations found." << tcu::TestLog::EndMessage; 1224 } 1225 } 1226 1227 // Requirement 2: The total number of fragments produced by the algorithm may differ from 1228 // that produced by the diamond-exit rule by no more than one. 1229 { 1230 // Check is not valid if the primitives intersect or otherwise share same fragments 1231 if (!overdrawInReference) 1232 { 1233 int allowedDeviation = (int)scene.lines.size() * lineWidth; // one pixel per primitive in the major direction 1234 1235 log << tcu::TestLog::Message << "Verifying fragment counts:\n" 1236 << "\tDiamond-exit rule: " << referenceFragments << " fragments.\n" 1237 << "\tResult image: " << resultFragments << " fragments.\n" 1238 << "\tAllowing deviation of " << allowedDeviation << " fragments.\n" 1239 << tcu::TestLog::EndMessage; 1240 1241 if (deAbs32(referenceFragments - resultFragments) > allowedDeviation) 1242 { 1243 tcu::Surface reference(surface.getWidth(), surface.getHeight()); 1244 1245 // show a helpful reference image 1246 tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255)); 1247 for (int y = 0; y < surface.getHeight(); ++y) 1248 for (int x = 0; x < surface.getWidth(); ++x) 1249 if (referenceLineMap.getAccess().getPixelInt(x, y).x()) 1250 reference.setPixel(x, y, tcu::RGBA::white()); 1251 1252 log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage; 1253 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1254 << tcu::TestLog::Image("Reference", "Reference", reference) 1255 << tcu::TestLog::Image("Result", "Result", surface) 1256 << tcu::TestLog::EndImageSet; 1257 1258 allOK = false; 1259 imageShown = true; 1260 } 1261 else 1262 { 1263 log << tcu::TestLog::Message << "Fragment count is valid." << tcu::TestLog::EndMessage; 1264 } 1265 } 1266 else 1267 { 1268 log << tcu::TestLog::Message << "Overdraw in scene. Fragment count cannot be verified. Skipping fragment count checks." << tcu::TestLog::EndMessage; 1269 } 1270 } 1271 1272 // Requirement 3: Line width must be constant 1273 { 1274 bool invalidWidthFound = false; 1275 1276 log << tcu::TestLog::Message << "Verifying line widths of the x-major lines." << tcu::TestLog::EndMessage; 1277 for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y) 1278 { 1279 bool fullyVisibleLine = false; 1280 bool previousPixelUndefined = false; 1281 int currentLine = 0; 1282 int currentWidth = 1; 1283 1284 for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x) 1285 { 1286 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits); 1287 int lineID = 0; 1288 1289 // Which line does this fragment belong to? 1290 1291 if (result) 1292 { 1293 bool multipleNearbyLines = false; 1294 bool renderAtSurfaceEdge = false; 1295 1296 renderAtSurfaceEdge = (x == 1) || (x == referenceLineMap.getWidth() - 2); 1297 1298 for (int dy = -1; dy < 2; ++dy) 1299 for (int dx = -1; dx < 2; ++dx) 1300 { 1301 const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1302 if (nearbyID) 1303 { 1304 if (lineID && lineID != nearbyID) 1305 multipleNearbyLines = true; 1306 lineID = nearbyID; 1307 } 1308 } 1309 1310 if (multipleNearbyLines || renderAtSurfaceEdge) 1311 { 1312 // Another line is too close, don't try to calculate width here 1313 // Or the render result is outside of surface range 1314 previousPixelUndefined = true; 1315 continue; 1316 } 1317 } 1318 1319 // Only line with id of lineID is nearby 1320 1321 if (previousPixelUndefined) 1322 { 1323 // The line might have been overdrawn or not 1324 currentLine = lineID; 1325 currentWidth = 1; 1326 fullyVisibleLine = false; 1327 previousPixelUndefined = false; 1328 } 1329 else if (lineID == currentLine) 1330 { 1331 // Current line continues 1332 ++currentWidth; 1333 } 1334 else if (lineID > currentLine) 1335 { 1336 // Another line was drawn over or the line ends 1337 currentLine = lineID; 1338 currentWidth = 1; 1339 fullyVisibleLine = true; 1340 } 1341 else 1342 { 1343 // The line ends 1344 if (fullyVisibleLine && !lineIsXMajor[currentLine-1]) 1345 { 1346 // check width 1347 if (currentWidth != lineWidth) 1348 { 1349 log << tcu::TestLog::Message << "\tInvalid line width at (" << x - currentWidth << ", " << y << ") - (" << x - 1 << ", " << y << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage; 1350 invalidWidthFound = true; 1351 } 1352 } 1353 1354 currentLine = lineID; 1355 currentWidth = 1; 1356 fullyVisibleLine = false; 1357 } 1358 } 1359 } 1360 1361 log << tcu::TestLog::Message << "Verifying line widths of the y-major lines." << tcu::TestLog::EndMessage; 1362 for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x) 1363 { 1364 bool fullyVisibleLine = false; 1365 bool previousPixelUndefined = false; 1366 int currentLine = 0; 1367 int currentWidth = 1; 1368 1369 for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y) 1370 { 1371 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits); 1372 int lineID = 0; 1373 1374 // Which line does this fragment belong to? 1375 1376 if (result) 1377 { 1378 bool multipleNearbyLines = false; 1379 bool renderAtSurfaceEdge = false; 1380 1381 renderAtSurfaceEdge = (y == 1) || (y == referenceLineMap.getWidth() - 2); 1382 1383 for (int dy = -1; dy < 2; ++dy) 1384 for (int dx = -1; dx < 2; ++dx) 1385 { 1386 const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1387 if (nearbyID) 1388 { 1389 if (lineID && lineID != nearbyID) 1390 multipleNearbyLines = true; 1391 lineID = nearbyID; 1392 } 1393 } 1394 1395 if (multipleNearbyLines || renderAtSurfaceEdge) 1396 { 1397 // Another line is too close, don't try to calculate width here 1398 // Or the render result is outside of surface range 1399 previousPixelUndefined = true; 1400 continue; 1401 } 1402 } 1403 1404 // Only line with id of lineID is nearby 1405 1406 if (previousPixelUndefined) 1407 { 1408 // The line might have been overdrawn or not 1409 currentLine = lineID; 1410 currentWidth = 1; 1411 fullyVisibleLine = false; 1412 previousPixelUndefined = false; 1413 } 1414 else if (lineID == currentLine) 1415 { 1416 // Current line continues 1417 ++currentWidth; 1418 } 1419 else if (lineID > currentLine) 1420 { 1421 // Another line was drawn over or the line ends 1422 currentLine = lineID; 1423 currentWidth = 1; 1424 fullyVisibleLine = true; 1425 } 1426 else 1427 { 1428 // The line ends 1429 if (fullyVisibleLine && lineIsXMajor[currentLine-1]) 1430 { 1431 // check width 1432 if (currentWidth != lineWidth) 1433 { 1434 log << tcu::TestLog::Message << "\tInvalid line width at (" << x << ", " << y - currentWidth << ") - (" << x << ", " << y - 1 << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage; 1435 invalidWidthFound = true; 1436 } 1437 } 1438 1439 currentLine = lineID; 1440 currentWidth = 1; 1441 fullyVisibleLine = false; 1442 } 1443 } 1444 } 1445 1446 if (invalidWidthFound) 1447 { 1448 log << tcu::TestLog::Message << "Invalid line width found, image is not valid." << tcu::TestLog::EndMessage; 1449 allOK = false; 1450 } 1451 else 1452 { 1453 log << tcu::TestLog::Message << "Line widths are valid." << tcu::TestLog::EndMessage; 1454 } 1455 } 1456 1457 //\todo [2013-10-24 jarkko]. 1458 //Requirement 4. If two line segments share a common endpoint, and both segments are either 1459 //x-major (both left-to-right or both right-to-left) or y-major (both bottom-totop 1460 //or both top-to-bottom), then rasterizing both segments may not produce 1461 //duplicate fragments, nor may any fragments be omitted so as to interrupt 1462 //continuity of the connected segments. 1463 1464 if (!imageShown) 1465 { 1466 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1467 << tcu::TestLog::Image("Result", "Result", surface) 1468 << tcu::TestLog::EndImageSet; 1469 } 1470 1471 return allOK; 1472 } 1473 1474 struct SingleSampleNarrowLineCandidate 1475 { 1476 int lineNdx; 1477 tcu::IVec3 colorMin; 1478 tcu::IVec3 colorMax; 1479 tcu::Vec3 colorMinF; 1480 tcu::Vec3 colorMaxF; 1481 tcu::Vec3 valueRangeMin; 1482 tcu::Vec3 valueRangeMax; 1483 }; 1484 1485 void setMaskMapCoverageBitForLine (int bitNdx, const tcu::Vec2& screenSpaceP0, const tcu::Vec2& screenSpaceP1, float lineWidth, tcu::PixelBufferAccess maskMap) 1486 { 1487 enum 1488 { 1489 MAX_PACKETS = 32, 1490 }; 1491 1492 rr::SingleSampleLineRasterizer rasterizer (tcu::IVec4(0, 0, maskMap.getWidth(), maskMap.getHeight())); 1493 int numRasterized = MAX_PACKETS; 1494 rr::FragmentPacket packets[MAX_PACKETS]; 1495 1496 rasterizer.init(tcu::Vec4(screenSpaceP0.x(), screenSpaceP0.y(), 0.0f, 1.0f), 1497 tcu::Vec4(screenSpaceP1.x(), screenSpaceP1.y(), 0.0f, 1.0f), 1498 lineWidth); 1499 1500 while (numRasterized == MAX_PACKETS) 1501 { 1502 rasterizer.rasterize(packets, DE_NULL, MAX_PACKETS, numRasterized); 1503 1504 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx) 1505 { 1506 for (int fragNdx = 0; fragNdx < 4; ++fragNdx) 1507 { 1508 if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx)) 1509 { 1510 const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2); 1511 1512 DE_ASSERT(deInBounds32(fragPos.x(), 0, maskMap.getWidth())); 1513 DE_ASSERT(deInBounds32(fragPos.y(), 0, maskMap.getHeight())); 1514 1515 const deUint32 previousMask = maskMap.getPixelUint(fragPos.x(), fragPos.y()).x(); 1516 const deUint32 newMask = (previousMask) | ((deUint32)1u << bitNdx); 1517 1518 maskMap.setPixel(tcu::UVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y()); 1519 } 1520 } 1521 } 1522 } 1523 } 1524 1525 void setMaskMapCoverageBitForLines (const std::vector<tcu::Vec4>& screenspaceLines, float lineWidth, tcu::PixelBufferAccess maskMap) 1526 { 1527 for (int lineNdx = 0; lineNdx < (int)screenspaceLines.size(); ++lineNdx) 1528 { 1529 const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1); 1530 const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3); 1531 1532 setMaskMapCoverageBitForLine(lineNdx, pa, pb, lineWidth, maskMap); 1533 } 1534 } 1535 1536 // verify line interpolation assuming line pixels are interpolated independently depending only on screen space location 1537 bool verifyLineGroupPixelIndependentInterpolation (const tcu::Surface& surface, 1538 const LineSceneSpec& scene, 1539 const RasterizationArguments& args, 1540 tcu::TestLog& log, 1541 LineInterpolationMethod interpolationMethod) 1542 { 1543 DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints 1544 DE_ASSERT(interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT || interpolationMethod == LINEINTERPOLATION_PROJECTED); 1545 1546 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255); 1547 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 1548 const int errorFloodThreshold = 4; 1549 int errorCount = 0; 1550 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 1551 int invalidPixels = 0; 1552 std::vector<tcu::Vec4> screenspaceLines (scene.lines.size()); //!< packed (x0, y0, x1, y1) 1553 1554 // Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield 1555 // The map is used to find lines with potential coverage to a given pixel 1556 tcu::TextureLevel referenceLineMap (tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 1557 1558 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0)); 1559 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 1560 1561 // log format 1562 1563 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage; 1564 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8) 1565 log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage; 1566 1567 // prepare lookup map 1568 1569 genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize); 1570 setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess()); 1571 1572 // Find all possible lines with coverage, check pixel color matches one of them 1573 1574 for (int y = 1; y < surface.getHeight() - 1; ++y) 1575 for (int x = 1; x < surface.getWidth() - 1; ++x) 1576 { 1577 const tcu::RGBA color = surface.getPixel(x, y); 1578 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565 1579 int lineCoverageSet = 0; // !< lines that may cover this fragment 1580 int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment 1581 bool matchFound = false; 1582 const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1); 1583 1584 std::vector<SingleSampleNarrowLineCandidate> candidates; 1585 1586 // Find lines with possible coverage 1587 1588 for (int dy = -1; dy < 2; ++dy) 1589 for (int dx = -1; dx < 2; ++dx) 1590 { 1591 const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1592 1593 lineCoverageSet |= coverage; 1594 lineSurroundingCoverage &= coverage; 1595 } 1596 1597 // background color is possible? 1598 if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits)) 1599 continue; 1600 1601 // Check those lines 1602 1603 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1604 { 1605 if (((lineCoverageSet >> lineNdx) & 0x01) != 0) 1606 { 1607 const float wa = scene.lines[lineNdx].positions[0].w(); 1608 const float wb = scene.lines[lineNdx].positions[1].w(); 1609 const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1); 1610 const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3); 1611 1612 const LineInterpolationRange range = (interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT) 1613 ? (calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits)) 1614 : (calcSingleSampleLineInterpolationRangeAxisProjected(pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits)); 1615 1616 const tcu::Vec4 valueMin = de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1]; 1617 const tcu::Vec4 valueMax = de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1]; 1618 1619 const tcu::Vec3 colorMinF (de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()), 1620 de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()), 1621 de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z())); 1622 const tcu::Vec3 colorMaxF (de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()), 1623 de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()), 1624 de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z())); 1625 const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()), 1626 (int)deFloatFloor(colorMinF.y()), 1627 (int)deFloatFloor(colorMinF.z())); 1628 const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()), 1629 (int)deFloatCeil (colorMaxF.y()), 1630 (int)deFloatCeil (colorMaxF.z())); 1631 1632 // Verify validity 1633 if (pixelNativeColor.x() < colorMin.x() || 1634 pixelNativeColor.y() < colorMin.y() || 1635 pixelNativeColor.z() < colorMin.z() || 1636 pixelNativeColor.x() > colorMax.x() || 1637 pixelNativeColor.y() > colorMax.y() || 1638 pixelNativeColor.z() > colorMax.z()) 1639 { 1640 if (errorCount < errorFloodThreshold) 1641 { 1642 // Store candidate information for logging 1643 SingleSampleNarrowLineCandidate candidate; 1644 1645 candidate.lineNdx = lineNdx; 1646 candidate.colorMin = colorMin; 1647 candidate.colorMax = colorMax; 1648 candidate.colorMinF = colorMinF; 1649 candidate.colorMaxF = colorMaxF; 1650 candidate.valueRangeMin = valueMin.swizzle(0, 1, 2); 1651 candidate.valueRangeMax = valueMax.swizzle(0, 1, 2); 1652 1653 candidates.push_back(candidate); 1654 } 1655 } 1656 else 1657 { 1658 matchFound = true; 1659 break; 1660 } 1661 } 1662 } 1663 1664 if (matchFound) 1665 continue; 1666 1667 // invalid fragment 1668 ++invalidPixels; 1669 errorMask.setPixel(x, y, invalidPixelColor); 1670 1671 ++errorCount; 1672 1673 // don't fill the logs with too much data 1674 if (errorCount < errorFloodThreshold) 1675 { 1676 log << tcu::TestLog::Message 1677 << "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n" 1678 << "\tPixel color:\t\t" << color << "\n" 1679 << "\tNative color:\t\t" << pixelNativeColor << "\n" 1680 << tcu::TestLog::EndMessage; 1681 1682 for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx) 1683 { 1684 const SingleSampleNarrowLineCandidate& candidate = candidates[candidateNdx]; 1685 1686 log << tcu::TestLog::Message << "\tCandidate (line " << candidate.lineNdx << "):\n" 1687 << "\t\tReference native color min: " << tcu::clamp(candidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n" 1688 << "\t\tReference native color max: " << tcu::clamp(candidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n" 1689 << "\t\tReference native float min: " << tcu::clamp(candidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 1690 << "\t\tReference native float max: " << tcu::clamp(candidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 1691 << "\t\tFmin:\t" << tcu::clamp(candidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 1692 << "\t\tFmax:\t" << tcu::clamp(candidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 1693 << tcu::TestLog::EndMessage; 1694 } 1695 } 1696 } 1697 1698 // don't just hide failures 1699 if (errorCount > errorFloodThreshold) 1700 log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage; 1701 1702 // report result 1703 if (invalidPixels) 1704 { 1705 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage; 1706 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1707 << tcu::TestLog::Image("Result", "Result", surface) 1708 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 1709 << tcu::TestLog::EndImageSet; 1710 1711 return false; 1712 } 1713 else 1714 { 1715 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 1716 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1717 << tcu::TestLog::Image("Result", "Result", surface) 1718 << tcu::TestLog::EndImageSet; 1719 1720 return true; 1721 } 1722 } 1723 1724 bool verifySinglesampleNarrowLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1725 { 1726 DE_ASSERT(scene.lineWidth == 1.0f); 1727 return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_STRICTLY_CORRECT); 1728 } 1729 1730 bool verifyLineGroupInterpolationWithProjectedWeights (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1731 { 1732 return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_PROJECTED); 1733 } 1734 1735 struct SingleSampleWideLineCandidate 1736 { 1737 struct InterpolationPointCandidate 1738 { 1739 tcu::IVec2 interpolationPoint; 1740 tcu::IVec3 colorMin; 1741 tcu::IVec3 colorMax; 1742 tcu::Vec3 colorMinF; 1743 tcu::Vec3 colorMaxF; 1744 tcu::Vec3 valueRangeMin; 1745 tcu::Vec3 valueRangeMax; 1746 }; 1747 1748 int lineNdx; 1749 int numCandidates; 1750 InterpolationPointCandidate interpolationCandidates[3]; 1751 }; 1752 1753 // return point on line at a given position on a given axis 1754 tcu::Vec2 getLineCoordAtAxisCoord (const tcu::Vec2& pa, const tcu::Vec2& pb, bool isXAxis, float axisCoord) 1755 { 1756 const int fixedCoordNdx = (isXAxis) ? (0) : (1); 1757 const int varyingCoordNdx = (isXAxis) ? (1) : (0); 1758 1759 const float fixedDifference = pb[fixedCoordNdx] - pa[fixedCoordNdx]; 1760 const float varyingDifference = pb[varyingCoordNdx] - pa[varyingCoordNdx]; 1761 1762 DE_ASSERT(fixedDifference != 0.0f); 1763 1764 const float resultFixedCoord = axisCoord; 1765 const float resultVaryingCoord = pa[varyingCoordNdx] + (axisCoord - pa[fixedCoordNdx]) * (varyingDifference / fixedDifference); 1766 1767 return (isXAxis) ? (tcu::Vec2(resultFixedCoord, resultVaryingCoord)) 1768 : (tcu::Vec2(resultVaryingCoord, resultFixedCoord)); 1769 } 1770 1771 bool isBlack (const tcu::RGBA& c) 1772 { 1773 return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0; 1774 } 1775 1776 bool verifySinglesampleWideLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1777 { 1778 DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases 1779 DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints 1780 1781 enum 1782 { 1783 FLAG_ROOT_NOT_SET = (1u << 16) 1784 }; 1785 1786 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255); 1787 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 1788 const int errorFloodThreshold = 4; 1789 int errorCount = 0; 1790 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 1791 int invalidPixels = 0; 1792 std::vector<tcu::Vec4> effectiveLines (scene.lines.size()); //!< packed (x0, y0, x1, y1) 1793 std::vector<bool> lineIsXMajor (scene.lines.size()); 1794 1795 // for each line, for every distinct major direction fragment, store root pixel location (along 1796 // minor direction); 1797 std::vector<std::vector<deUint32> > rootPixelLocation (scene.lines.size()); //!< packed [16b - flags] [16b - coordinate] 1798 1799 // log format 1800 1801 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage; 1802 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8) 1803 log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage; 1804 1805 // Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield 1806 // The map is used to find lines with potential coverage to a given pixel 1807 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 1808 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0)); 1809 1810 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 1811 1812 // calculate mask and effective line coordinates 1813 { 1814 std::vector<tcu::Vec4> screenspaceLines(scene.lines.size()); 1815 1816 genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize); 1817 setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess()); 1818 1819 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1820 { 1821 const tcu::Vec2 lineScreenSpaceP0 = screenspaceLines[lineNdx].swizzle(0, 1); 1822 const tcu::Vec2 lineScreenSpaceP1 = screenspaceLines[lineNdx].swizzle(2, 3); 1823 const bool isXMajor = isPackedSSLineXMajor(screenspaceLines[lineNdx]); 1824 1825 lineIsXMajor[lineNdx] = isXMajor; 1826 1827 // wide line interpolations are calculated for a line moved in minor direction 1828 { 1829 const float offsetLength = (scene.lineWidth - 1.0f) / 2.0f; 1830 const tcu::Vec2 offsetDirection = (isXMajor) ? (tcu::Vec2(0.0f, -1.0f)) : (tcu::Vec2(-1.0f, 0.0f)); 1831 const tcu::Vec2 offset = offsetDirection * offsetLength; 1832 1833 effectiveLines[lineNdx] = tcu::Vec4(lineScreenSpaceP0.x() + offset.x(), 1834 lineScreenSpaceP0.y() + offset.y(), 1835 lineScreenSpaceP1.x() + offset.x(), 1836 lineScreenSpaceP1.y() + offset.y()); 1837 } 1838 } 1839 } 1840 1841 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1842 { 1843 // Calculate root pixel lookup table for this line. Since the implementation's fragment 1844 // major coordinate range might not be a subset of the correct line range (they are allowed 1845 // to vary by one pixel), we must extend the domain to cover whole viewport along major 1846 // dimension. 1847 // 1848 // Expanding line strip to (effectively) infinite line might result in exit-diamnod set 1849 // that is not a superset of the exit-diamond set of the line strip. In practice, this 1850 // won't be an issue, since the allow-one-pixel-variation rule should tolerate this even 1851 // if the original and extended line would resolve differently a diamond the line just 1852 // touches (precision lost in expansion changes enter/exit status). 1853 1854 { 1855 const bool isXMajor = lineIsXMajor[lineNdx]; 1856 const int majorSize = (isXMajor) ? (surface.getWidth()) : (surface.getHeight()); 1857 rr::LineExitDiamondGenerator diamondGenerator; 1858 rr::LineExitDiamond diamonds[32]; 1859 int numRasterized = DE_LENGTH_OF_ARRAY(diamonds); 1860 1861 // Expand to effectively infinite line (endpoints are just one pixel over viewport boundaries) 1862 const tcu::Vec2 expandedP0 = getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, -1.0f); 1863 const tcu::Vec2 expandedP1 = getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, (float)majorSize + 1.0f); 1864 1865 diamondGenerator.init(tcu::Vec4(expandedP0.x(), expandedP0.y(), 0.0f, 1.0f), 1866 tcu::Vec4(expandedP1.x(), expandedP1.y(), 0.0f, 1.0f)); 1867 1868 rootPixelLocation[lineNdx].resize(majorSize, FLAG_ROOT_NOT_SET); 1869 1870 while (numRasterized == DE_LENGTH_OF_ARRAY(diamonds)) 1871 { 1872 diamondGenerator.rasterize(diamonds, DE_LENGTH_OF_ARRAY(diamonds), numRasterized); 1873 1874 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx) 1875 { 1876 const tcu::IVec2 fragPos = diamonds[packetNdx].position; 1877 const int majorPos = (isXMajor) ? (fragPos.x()) : (fragPos.y()); 1878 const int rootPos = (isXMajor) ? (fragPos.y()) : (fragPos.x()); 1879 const deUint32 packed = (deUint32)((deUint16)((deInt16)rootPos)); 1880 1881 // infinite line will generate some diamonds outside the viewport 1882 if (deInBounds32(majorPos, 0, majorSize)) 1883 { 1884 DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) != 0u); 1885 rootPixelLocation[lineNdx][majorPos] = packed; 1886 } 1887 } 1888 } 1889 1890 // Filled whole lookup table 1891 for (int majorPos = 0; majorPos < majorSize; ++majorPos) 1892 DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) == 0u); 1893 } 1894 } 1895 1896 // Find all possible lines with coverage, check pixel color matches one of them 1897 1898 for (int y = 1; y < surface.getHeight() - 1; ++y) 1899 for (int x = 1; x < surface.getWidth() - 1; ++x) 1900 { 1901 const tcu::RGBA color = surface.getPixel(x, y); 1902 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565 1903 int lineCoverageSet = 0; // !< lines that may cover this fragment 1904 int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment 1905 bool matchFound = false; 1906 const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1); 1907 1908 std::vector<SingleSampleWideLineCandidate> candidates; 1909 1910 // Find lines with possible coverage 1911 1912 for (int dy = -1; dy < 2; ++dy) 1913 for (int dx = -1; dx < 2; ++dx) 1914 { 1915 const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1916 1917 lineCoverageSet |= coverage; 1918 lineSurroundingCoverage &= coverage; 1919 } 1920 1921 // background color is possible? 1922 if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits)) 1923 continue; 1924 1925 // Check those lines 1926 1927 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1928 { 1929 if (((lineCoverageSet >> lineNdx) & 0x01) != 0) 1930 { 1931 const float wa = scene.lines[lineNdx].positions[0].w(); 1932 const float wb = scene.lines[lineNdx].positions[1].w(); 1933 const tcu::Vec2 pa = effectiveLines[lineNdx].swizzle(0, 1); 1934 const tcu::Vec2 pb = effectiveLines[lineNdx].swizzle(2, 3); 1935 1936 // \note Wide line fragments are generated by replicating the root fragment for each 1937 // fragment column (row for y-major). Calculate interpolation at the root 1938 // fragment. 1939 const bool isXMajor = lineIsXMajor[lineNdx]; 1940 const int majorPosition = (isXMajor) ? (x) : (y); 1941 const deUint32 minorInfoPacked = rootPixelLocation[lineNdx][majorPosition]; 1942 const int minorPosition = (int)((deInt16)((deUint16)(minorInfoPacked & 0xFFFFu))); 1943 const tcu::IVec2 idealRootPos = (isXMajor) ? (tcu::IVec2(majorPosition, minorPosition)) : (tcu::IVec2(minorPosition, majorPosition)); 1944 const tcu::IVec2 minorDirection = (isXMajor) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0)); 1945 1946 SingleSampleWideLineCandidate candidate; 1947 1948 candidate.lineNdx = lineNdx; 1949 candidate.numCandidates = 0; 1950 DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates) == 3); 1951 1952 // Interpolation happens at the root fragment, which is then replicated in minor 1953 // direction. Search for implementation's root position near accurate root. 1954 for (int minorOffset = -1; minorOffset < 2; ++minorOffset) 1955 { 1956 const tcu::IVec2 rootPosition = idealRootPos + minorOffset * minorDirection; 1957 1958 // A fragment can be root fragment only if it exists 1959 // \note root fragment can "exist" outside viewport 1960 // \note no pixel format theshold since in this case allowing only black is more conservative 1961 if (deInBounds32(rootPosition.x(), 0, surface.getWidth()) && 1962 deInBounds32(rootPosition.y(), 0, surface.getHeight()) && 1963 isBlack(surface.getPixel(rootPosition.x(), rootPosition.y()))) 1964 { 1965 continue; 1966 } 1967 1968 const LineInterpolationRange range = calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, rootPosition, args.subpixelBits); 1969 1970 const tcu::Vec4 valueMin = de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1]; 1971 const tcu::Vec4 valueMax = de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1]; 1972 1973 const tcu::Vec3 colorMinF (de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()), 1974 de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()), 1975 de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z())); 1976 const tcu::Vec3 colorMaxF (de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()), 1977 de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()), 1978 de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z())); 1979 const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()), 1980 (int)deFloatFloor(colorMinF.y()), 1981 (int)deFloatFloor(colorMinF.z())); 1982 const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()), 1983 (int)deFloatCeil (colorMaxF.y()), 1984 (int)deFloatCeil (colorMaxF.z())); 1985 1986 // Verify validity 1987 if (pixelNativeColor.x() < colorMin.x() || 1988 pixelNativeColor.y() < colorMin.y() || 1989 pixelNativeColor.z() < colorMin.z() || 1990 pixelNativeColor.x() > colorMax.x() || 1991 pixelNativeColor.y() > colorMax.y() || 1992 pixelNativeColor.z() > colorMax.z()) 1993 { 1994 if (errorCount < errorFloodThreshold) 1995 { 1996 // Store candidate information for logging 1997 SingleSampleWideLineCandidate::InterpolationPointCandidate& interpolationCandidate = candidate.interpolationCandidates[candidate.numCandidates++]; 1998 DE_ASSERT(candidate.numCandidates <= DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates)); 1999 2000 interpolationCandidate.interpolationPoint = rootPosition; 2001 interpolationCandidate.colorMin = colorMin; 2002 interpolationCandidate.colorMax = colorMax; 2003 interpolationCandidate.colorMinF = colorMinF; 2004 interpolationCandidate.colorMaxF = colorMaxF; 2005 interpolationCandidate.valueRangeMin = valueMin.swizzle(0, 1, 2); 2006 interpolationCandidate.valueRangeMax = valueMax.swizzle(0, 1, 2); 2007 } 2008 } 2009 else 2010 { 2011 matchFound = true; 2012 break; 2013 } 2014 } 2015 2016 if (!matchFound) 2017 { 2018 // store info for logging 2019 if (errorCount < errorFloodThreshold && candidate.numCandidates > 0) 2020 candidates.push_back(candidate); 2021 } 2022 else 2023 { 2024 // no need to check other lines 2025 break; 2026 } 2027 } 2028 } 2029 2030 if (matchFound) 2031 continue; 2032 2033 // invalid fragment 2034 ++invalidPixels; 2035 errorMask.setPixel(x, y, invalidPixelColor); 2036 2037 ++errorCount; 2038 2039 // don't fill the logs with too much data 2040 if (errorCount < errorFloodThreshold) 2041 { 2042 tcu::MessageBuilder msg(&log); 2043 2044 msg << "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n" 2045 << "\tPixel color:\t\t" << color << "\n" 2046 << "\tNative color:\t\t" << pixelNativeColor << "\n"; 2047 2048 for (int lineCandidateNdx = 0; lineCandidateNdx < (int)candidates.size(); ++lineCandidateNdx) 2049 { 2050 const SingleSampleWideLineCandidate& candidate = candidates[lineCandidateNdx]; 2051 2052 msg << "\tCandidate line (line " << candidate.lineNdx << "):\n"; 2053 2054 for (int interpolationCandidateNdx = 0; interpolationCandidateNdx < candidate.numCandidates; ++interpolationCandidateNdx) 2055 { 2056 const SingleSampleWideLineCandidate::InterpolationPointCandidate& interpolationCandidate = candidate.interpolationCandidates[interpolationCandidateNdx]; 2057 2058 msg << "\t\tCandidate interpolation point (index " << interpolationCandidateNdx << "):\n" 2059 << "\t\t\tRoot fragment position (non-replicated fragment): " << interpolationCandidate.interpolationPoint << ":\n" 2060 << "\t\t\tReference native color min: " << tcu::clamp(interpolationCandidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n" 2061 << "\t\t\tReference native color max: " << tcu::clamp(interpolationCandidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n" 2062 << "\t\t\tReference native float min: " << tcu::clamp(interpolationCandidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 2063 << "\t\t\tReference native float max: " << tcu::clamp(interpolationCandidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 2064 << "\t\t\tFmin:\t" << tcu::clamp(interpolationCandidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 2065 << "\t\t\tFmax:\t" << tcu::clamp(interpolationCandidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"; 2066 } 2067 } 2068 2069 msg << tcu::TestLog::EndMessage; 2070 } 2071 } 2072 2073 // don't just hide failures 2074 if (errorCount > errorFloodThreshold) 2075 log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage; 2076 2077 // report result 2078 if (invalidPixels) 2079 { 2080 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage; 2081 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 2082 << tcu::TestLog::Image("Result", "Result", surface) 2083 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 2084 << tcu::TestLog::EndImageSet; 2085 2086 return false; 2087 } 2088 else 2089 { 2090 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 2091 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 2092 << tcu::TestLog::Image("Result", "Result", surface) 2093 << tcu::TestLog::EndImageSet; 2094 2095 return true; 2096 } 2097 } 2098 2099 } // anonymous 2100 2101 CoverageType 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) 2102 { 2103 typedef tcu::Vector<deInt64, 2> I64Vec2; 2104 2105 const deUint64 numSubPixels = ((deUint64)1) << subpixelBits; 2106 const deUint64 pixelHitBoxSize = (multisample) ? (numSubPixels) : (2+2); //!< allow 4 central (2x2) for non-multisample pixels. Rounding may move edges 1 subpixel to any direction. 2107 const bool order = isTriangleClockwise(p0, p1, p2); //!< clockwise / counter-clockwise 2108 const tcu::Vec4& orderedP0 = p0; //!< vertices of a clockwise triangle 2109 const tcu::Vec4& orderedP1 = (order) ? (p1) : (p2); 2110 const tcu::Vec4& orderedP2 = (order) ? (p2) : (p1); 2111 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = 2112 { 2113 tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()), 2114 tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()), 2115 tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()), 2116 }; 2117 const tcu::Vec2 triangleScreenSpace[3] = 2118 { 2119 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 2120 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 2121 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 2122 }; 2123 2124 // Broad bounding box - pixel check 2125 { 2126 const float minX = de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x()); 2127 const float minY = de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y()); 2128 const float maxX = de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x()); 2129 const float maxY = de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y()); 2130 2131 if ((float)pixel.x() > maxX + 1 || 2132 (float)pixel.y() > maxY + 1 || 2133 (float)pixel.x() < minX - 1 || 2134 (float)pixel.y() < minY - 1) 2135 return COVERAGE_NONE; 2136 } 2137 2138 // Broad triangle - pixel area intersection 2139 { 2140 const I64Vec2 pixelCenterPosition = I64Vec2(pixel.x(), pixel.y()) * I64Vec2(numSubPixels, numSubPixels) + I64Vec2(numSubPixels / 2, numSubPixels / 2); 2141 const I64Vec2 triangleSubPixelSpaceRound[3] = 2142 { 2143 I64Vec2(deRoundFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deRoundFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)), 2144 I64Vec2(deRoundFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deRoundFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)), 2145 I64Vec2(deRoundFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deRoundFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)), 2146 }; 2147 2148 // Check (using cross product) if pixel center is 2149 // a) too far from any edge 2150 // b) fully inside all edges 2151 bool insideAllEdges = true; 2152 for (int vtxNdx = 0; vtxNdx < 3; ++vtxNdx) 2153 { 2154 const int otherVtxNdx = (vtxNdx + 1) % 3; 2155 const deInt64 maxPixelDistanceSquared = pixelHitBoxSize*pixelHitBoxSize; // Max distance from the pixel center from within the pixel is (sqrt(2) * boxWidth/2). Use 2x value for rounding tolerance 2156 const I64Vec2 edge = triangleSubPixelSpaceRound[otherVtxNdx] - triangleSubPixelSpaceRound[vtxNdx]; 2157 const I64Vec2 v = pixelCenterPosition - triangleSubPixelSpaceRound[vtxNdx]; 2158 const deInt64 crossProduct = (edge.x() * v.y() - edge.y() * v.x()); 2159 2160 // distance from edge: (edge x v) / |edge| 2161 // (edge x v) / |edge| > maxPixelDistance 2162 // ==> (edge x v)^2 / edge^2 > maxPixelDistance^2 | edge x v > 0 2163 // ==> (edge x v)^2 > maxPixelDistance^2 * edge^2 2164 if (crossProduct < 0 && crossProduct*crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(edge)) 2165 return COVERAGE_NONE; 2166 if (crossProduct < 0 || crossProduct*crossProduct < maxPixelDistanceSquared * tcu::lengthSquared(edge)) 2167 insideAllEdges = false; 2168 } 2169 2170 if (insideAllEdges) 2171 return COVERAGE_FULL; 2172 } 2173 2174 // Accurate intersection for edge pixels 2175 { 2176 // In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center. 2177 const I64Vec2 pixelCorners[4] = 2178 { 2179 I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+0) * numSubPixels), 2180 I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+0) * numSubPixels), 2181 I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+1) * numSubPixels), 2182 I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+1) * numSubPixels), 2183 }; 2184 const I64Vec2 pixelCenterCorners[4] = 2185 { 2186 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 0), 2187 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 0), 2188 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 1), 2189 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 1), 2190 }; 2191 2192 // both rounding directions 2193 const I64Vec2 triangleSubPixelSpaceFloor[3] = 2194 { 2195 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)), 2196 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)), 2197 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)), 2198 }; 2199 const I64Vec2 triangleSubPixelSpaceCeil[3] = 2200 { 2201 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)), 2202 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)), 2203 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)), 2204 }; 2205 const I64Vec2* const corners = (multisample) ? (pixelCorners) : (pixelCenterCorners); 2206 2207 // Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside 2208 2209 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx) 2210 for (int startRounding = 0; startRounding < 4; ++startRounding) 2211 for (int endRounding = 0; endRounding < 4; ++endRounding) 2212 { 2213 const int nextEdgeNdx = (edgeNdx+1) % 3; 2214 const I64Vec2 startPos ((startRounding&0x01) ? (triangleSubPixelSpaceFloor[edgeNdx].x()) : (triangleSubPixelSpaceCeil[edgeNdx].x()), (startRounding&0x02) ? (triangleSubPixelSpaceFloor[edgeNdx].y()) : (triangleSubPixelSpaceCeil[edgeNdx].y())); 2215 const I64Vec2 endPos ((endRounding&0x01) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].x()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].x()), (endRounding&0x02) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].y()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].y())); 2216 2217 for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx) 2218 { 2219 const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4; 2220 2221 if (lineLineIntersect(startPos, endPos, corners[pixelEdgeNdx], corners[pixelEdgeEnd])) 2222 return COVERAGE_PARTIAL; 2223 } 2224 } 2225 2226 // fully inside or outside 2227 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx) 2228 { 2229 const int nextEdgeNdx = (edgeNdx+1) % 3; 2230 const I64Vec2& startPos = triangleSubPixelSpaceFloor[edgeNdx]; 2231 const I64Vec2& endPos = triangleSubPixelSpaceFloor[nextEdgeNdx]; 2232 const I64Vec2 edge = endPos - startPos; 2233 const I64Vec2 v = corners[0] - endPos; 2234 const deInt64 crossProduct = (edge.x() * v.y() - edge.y() * v.x()); 2235 2236 // a corner of the pixel is outside => "fully inside" option is impossible 2237 if (crossProduct < 0) 2238 return COVERAGE_NONE; 2239 } 2240 2241 return COVERAGE_FULL; 2242 } 2243 } 2244 2245 static void verifyTriangleGroupRasterizationLog (const tcu::Surface& surface, tcu::TestLog& log, VerifyTriangleGroupRasterizationLogStash& logStash) 2246 { 2247 // Output results 2248 log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage; 2249 2250 if (!logStash.result) 2251 { 2252 log << tcu::TestLog::Message << "Invalid pixels found:\n\t" 2253 << logStash.missingPixels << " missing pixels. (Marked with purple)\n\t" 2254 << logStash.unexpectedPixels << " incorrectly filled pixels. (Marked with red)\n\t" 2255 << "Unknown (subpixel on edge) pixels are marked with yellow." 2256 << tcu::TestLog::EndMessage; 2257 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 2258 << tcu::TestLog::Image("Result", "Result", surface) 2259 << tcu::TestLog::Image("ErrorMask", "ErrorMask", logStash.errorMask) 2260 << tcu::TestLog::EndImageSet; 2261 } 2262 else 2263 { 2264 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 2265 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 2266 << tcu::TestLog::Image("Result", "Result", surface) 2267 << tcu::TestLog::EndImageSet; 2268 } 2269 } 2270 2271 bool verifyTriangleGroupRasterization (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, VerificationMode mode, VerifyTriangleGroupRasterizationLogStash* logStash) 2272 { 2273 DE_ASSERT(mode < VERIFICATIONMODE_LAST); 2274 2275 const tcu::RGBA backGroundColor = tcu::RGBA(0, 0, 0, 255); 2276 const tcu::RGBA triangleColor = tcu::RGBA(255, 255, 255, 255); 2277 const tcu::RGBA missingPixelColor = tcu::RGBA(255, 0, 255, 255); 2278 const tcu::RGBA unexpectedPixelColor = tcu::RGBA(255, 0, 0, 255); 2279 const tcu::RGBA partialPixelColor = tcu::RGBA(255, 255, 0, 255); 2280 const tcu::RGBA primitivePixelColor = tcu::RGBA(30, 30, 30, 255); 2281 const int weakVerificationThreshold = 10; 2282 const bool multisampled = (args.numSamples != 0); 2283 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 2284 int missingPixels = 0; 2285 int unexpectedPixels = 0; 2286 int subPixelBits = args.subpixelBits; 2287 tcu::TextureLevel coverageMap (tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 2288 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 2289 bool result = false; 2290 2291 // subpixel bits in in a valid range? 2292 2293 if (subPixelBits < 0) 2294 { 2295 log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage; 2296 subPixelBits = 0; 2297 } 2298 else if (subPixelBits > 16) 2299 { 2300 // At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict 2301 log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits << "). Checking results using less strict 16 bit requirements. This may produce false positives." << tcu::TestLog::EndMessage; 2302 subPixelBits = 16; 2303 } 2304 2305 // generate coverage map 2306 2307 tcu::clear(coverageMap.getAccess(), tcu::IVec4(COVERAGE_NONE, 0, 0, 0)); 2308 2309 for (int triNdx = 0; triNdx < (int)scene.triangles.size(); ++triNdx) 2310 { 2311 const tcu::IVec4 aabb = getTriangleAABB(scene.triangles[triNdx], viewportSize); 2312 2313 for (int y = de::max(0, aabb.y()); y <= de::min(aabb.w(), coverageMap.getHeight() - 1); ++y) 2314 for (int x = de::max(0, aabb.x()); x <= de::min(aabb.z(), coverageMap.getWidth() - 1); ++x) 2315 { 2316 if (coverageMap.getAccess().getPixelUint(x, y).x() == COVERAGE_FULL) 2317 continue; 2318 2319 const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0], 2320 scene.triangles[triNdx].positions[1], 2321 scene.triangles[triNdx].positions[2], 2322 tcu::IVec2(x, y), 2323 viewportSize, 2324 subPixelBits, 2325 multisampled); 2326 2327 if (coverage == COVERAGE_FULL) 2328 { 2329 coverageMap.getAccess().setPixel(tcu::IVec4(COVERAGE_FULL, 0, 0, 0), x, y); 2330 } 2331 else if (coverage == COVERAGE_PARTIAL) 2332 { 2333 CoverageType resultCoverage = COVERAGE_PARTIAL; 2334 2335 // Sharing an edge with another triangle? 2336 // There should always be such a triangle, but the pixel in the other triangle might be 2337 // on multiple edges, some of which are not shared. In these cases the coverage cannot be determined. 2338 // Assume full coverage if the pixel is only on a shared edge in shared triangle too. 2339 if (pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[triNdx], viewportSize)) 2340 { 2341 bool friendFound = false; 2342 for (int friendTriNdx = 0; friendTriNdx < (int)scene.triangles.size(); ++friendTriNdx) 2343 { 2344 if (friendTriNdx != triNdx && pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[friendTriNdx], viewportSize)) 2345 { 2346 friendFound = true; 2347 break; 2348 } 2349 } 2350 2351 if (friendFound) 2352 resultCoverage = COVERAGE_FULL; 2353 } 2354 2355 coverageMap.getAccess().setPixel(tcu::IVec4(resultCoverage, 0, 0, 0), x, y); 2356 } 2357 } 2358 } 2359 2360 // check pixels 2361 2362 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 2363 2364 for (int y = 0; y < surface.getHeight(); ++y) 2365 for (int x = 0; x < surface.getWidth(); ++x) 2366 { 2367 const tcu::RGBA color = surface.getPixel(x, y); 2368 const bool imageNoCoverage = compareColors(color, backGroundColor, args.redBits, args.greenBits, args.blueBits); 2369 const bool imageFullCoverage = compareColors(color, triangleColor, args.redBits, args.greenBits, args.blueBits); 2370 CoverageType referenceCoverage = (CoverageType)coverageMap.getAccess().getPixelUint(x, y).x(); 2371 2372 switch (referenceCoverage) 2373 { 2374 case COVERAGE_NONE: 2375 if (!imageNoCoverage) 2376 { 2377 // coverage where there should not be 2378 ++unexpectedPixels; 2379 errorMask.setPixel(x, y, unexpectedPixelColor); 2380 } 2381 break; 2382 2383 case COVERAGE_PARTIAL: 2384 // anything goes 2385 errorMask.setPixel(x, y, partialPixelColor); 2386 break; 2387 2388 case COVERAGE_FULL: 2389 if (!imageFullCoverage) 2390 { 2391 // no coverage where there should be 2392 ++missingPixels; 2393 errorMask.setPixel(x, y, missingPixelColor); 2394 } 2395 else 2396 { 2397 errorMask.setPixel(x, y, primitivePixelColor); 2398 } 2399 break; 2400 2401 default: 2402 DE_ASSERT(false); 2403 }; 2404 } 2405 2406 if (((mode == VERIFICATIONMODE_STRICT) && (missingPixels + unexpectedPixels > 0)) || 2407 ((mode == VERIFICATIONMODE_WEAK) && (missingPixels + unexpectedPixels > weakVerificationThreshold))) 2408 { 2409 result = false; 2410 } 2411 else 2412 { 2413 result = true; 2414 } 2415 2416 // Output or stash results 2417 { 2418 VerifyTriangleGroupRasterizationLogStash* tempLogStash = (logStash == DE_NULL) ? new VerifyTriangleGroupRasterizationLogStash : logStash; 2419 2420 tempLogStash->result = result; 2421 tempLogStash->missingPixels = missingPixels; 2422 tempLogStash->unexpectedPixels = unexpectedPixels; 2423 tempLogStash->errorMask = errorMask; 2424 2425 if (logStash == DE_NULL) 2426 { 2427 verifyTriangleGroupRasterizationLog(surface, log, *tempLogStash); 2428 delete tempLogStash; 2429 } 2430 } 2431 2432 return result; 2433 } 2434 2435 bool verifyLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2436 { 2437 const bool multisampled = args.numSamples != 0; 2438 2439 if (multisampled) 2440 return verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING, DE_NULL); 2441 else 2442 return verifySinglesampleLineGroupRasterization(surface, scene, args, log); 2443 } 2444 2445 bool verifyClippedTriangulatedLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2446 { 2447 return verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX, DE_NULL); 2448 } 2449 2450 bool verifyRelaxedLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2451 { 2452 VerifyTriangleGroupRasterizationLogStash noClippingLogStash; 2453 VerifyTriangleGroupRasterizationLogStash useClippingLogStash; 2454 2455 if (verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX, &useClippingLogStash)) 2456 { 2457 log << tcu::TestLog::Message << "Relaxed rasterization succeeded with CLIPMODE_USE_CLIPPING_BOX, details follow." << tcu::TestLog::EndMessage; 2458 2459 verifyTriangleGroupRasterizationLog(surface, log, useClippingLogStash); 2460 2461 return true; 2462 } 2463 else if (verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING, &noClippingLogStash)) 2464 { 2465 log << tcu::TestLog::Message << "Relaxed rasterization succeeded with CLIPMODE_NO_CLIPPING, details follow." << tcu::TestLog::EndMessage; 2466 2467 verifyTriangleGroupRasterizationLog(surface, log, noClippingLogStash); 2468 2469 return true; 2470 } 2471 else 2472 { 2473 log << tcu::TestLog::Message << "Relaxed rasterization failed, details follow." << tcu::TestLog::EndMessage; 2474 2475 verifyTriangleGroupRasterizationLog(surface, log, useClippingLogStash); 2476 verifyTriangleGroupRasterizationLog(surface, log, noClippingLogStash); 2477 2478 return false; 2479 } 2480 } 2481 2482 bool verifyPointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2483 { 2484 // Splitting to triangles is a valid solution in multisampled cases and even in non-multisample cases too. 2485 return verifyMultisamplePointGroupRasterization(surface, scene, args, log); 2486 } 2487 2488 bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2489 { 2490 return verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, log, TriangleInterpolator(scene)); 2491 } 2492 2493 LineInterpolationMethod verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2494 { 2495 const bool multisampled = args.numSamples != 0; 2496 2497 if (multisampled) 2498 { 2499 if (verifyMultisampleLineGroupInterpolation(surface, scene, args, log)) 2500 return LINEINTERPOLATION_STRICTLY_CORRECT; 2501 return LINEINTERPOLATION_INCORRECT; 2502 } 2503 else 2504 { 2505 const bool isNarrow = (scene.lineWidth == 1.0f); 2506 2507 // accurate interpolation 2508 if (isNarrow) 2509 { 2510 if (verifySinglesampleNarrowLineGroupInterpolation(surface, scene, args, log)) 2511 return LINEINTERPOLATION_STRICTLY_CORRECT; 2512 } 2513 else 2514 { 2515 if (verifySinglesampleWideLineGroupInterpolation(surface, scene, args, log)) 2516 return LINEINTERPOLATION_STRICTLY_CORRECT; 2517 } 2518 2519 // check with projected (inaccurate) interpolation 2520 log << tcu::TestLog::Message << "Accurate verification failed, checking with projected weights (inaccurate equation)." << tcu::TestLog::EndMessage; 2521 if (verifyLineGroupInterpolationWithProjectedWeights(surface, scene, args, log)) 2522 return LINEINTERPOLATION_PROJECTED; 2523 2524 return LINEINTERPOLATION_INCORRECT; 2525 } 2526 } 2527 2528 bool verifyTriangulatedLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 2529 { 2530 return verifyMultisampleLineGroupInterpolation(surface, scene, args, log); 2531 } 2532 2533 } // tcu 2534