1 /*------------------------------------------------------------------------- 2 * drawElements Quality Program OpenGL (ES) Module 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 test utils. 22 *//*--------------------------------------------------------------------*/ 23 24 #include "glsRasterizationTestUtil.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 deqp 38 { 39 namespace gls 40 { 41 namespace RasterizationTestUtil 42 { 43 namespace 44 { 45 46 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) 47 { 48 typedef tcu::Vector<deInt64, 2> I64Vec2; 49 50 // Lines do not intersect if the other line's endpoints are on the same side 51 // otherwise, the do intersect 52 53 // Test line 0 54 { 55 const I64Vec2 line = line0End - line0Beg; 56 const I64Vec2 v0 = line1Beg - line0Beg; 57 const I64Vec2 v1 = line1End - line0Beg; 58 const deInt64 crossProduct0 = (line.x() * v0.y() - line.y() * v0.x()); 59 const deInt64 crossProduct1 = (line.x() * v1.y() - line.y() * v1.x()); 60 61 // check signs 62 if ((crossProduct0 < 0 && crossProduct1 < 0) || 63 (crossProduct0 > 0 && crossProduct1 > 0)) 64 return false; 65 } 66 67 // Test line 1 68 { 69 const I64Vec2 line = line1End - line1Beg; 70 const I64Vec2 v0 = line0Beg - line1Beg; 71 const I64Vec2 v1 = line0End - line1Beg; 72 const deInt64 crossProduct0 = (line.x() * v0.y() - line.y() * v0.x()); 73 const deInt64 crossProduct1 = (line.x() * v1.y() - line.y() * v1.x()); 74 75 // check signs 76 if ((crossProduct0 < 0 && crossProduct1 < 0) || 77 (crossProduct0 > 0 && crossProduct1 > 0)) 78 return false; 79 } 80 81 return true; 82 } 83 84 bool isTriangleClockwise (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2) 85 { 86 const tcu::Vec2 u (p1.x() / p1.w() - p0.x() / p0.w(), p1.y() / p1.w() - p0.y() / p0.w()); 87 const tcu::Vec2 v (p2.x() / p2.w() - p0.x() / p0.w(), p2.y() / p2.w() - p0.y() / p0.w()); 88 const float crossProduct = (u.x() * v.y() - u.y() * v.x()); 89 90 return crossProduct > 0.0f; 91 } 92 93 bool compareColors (const tcu::RGBA& colorA, const tcu::RGBA& colorB, int redBits, int greenBits, int blueBits) 94 { 95 const int thresholdRed = 1 << (8 - redBits); 96 const int thresholdGreen = 1 << (8 - greenBits); 97 const int thresholdBlue = 1 << (8 - blueBits); 98 99 return deAbs32(colorA.getRed() - colorB.getRed()) <= thresholdRed && 100 deAbs32(colorA.getGreen() - colorB.getGreen()) <= thresholdGreen && 101 deAbs32(colorA.getBlue() - colorB.getBlue()) <= thresholdBlue; 102 } 103 104 bool pixelNearLineSegment (const tcu::IVec2& pixel, const tcu::Vec2& p0, const tcu::Vec2& p1) 105 { 106 const tcu::Vec2 pixelCenterPosition = tcu::Vec2(pixel.x() + 0.5f, pixel.y() + 0.5f); 107 108 // "Near" = Distance from the line to the pixel is less than 2 * pixel_max_radius. (pixel_max_radius = sqrt(2) / 2) 109 const float maxPixelDistance = 1.414f; 110 const float maxPixelDistanceSquared = 2.0f; 111 112 // Near the line 113 { 114 const tcu::Vec2 line = p1 - p0; 115 const tcu::Vec2 v = pixelCenterPosition - p0; 116 const float crossProduct = (line.x() * v.y() - line.y() * v.x()); 117 118 // distance to line: (line x v) / |line| 119 // |(line x v) / |line|| > maxPixelDistance 120 // ==> (line x v)^2 / |line|^2 > maxPixelDistance^2 121 // ==> (line x v)^2 > maxPixelDistance^2 * |line|^2 122 123 if (crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(line)) 124 return false; 125 } 126 127 // Between the endpoints 128 { 129 // distance from line endpoint 1 to pixel is less than line length + maxPixelDistance 130 const float maxDistance = tcu::length(p1 - p0) + maxPixelDistance; 131 132 if (tcu::length(pixelCenterPosition - p0) > maxDistance) 133 return false; 134 if (tcu::length(pixelCenterPosition - p1) > maxDistance) 135 return false; 136 } 137 138 return true; 139 } 140 141 bool pixelOnlyOnASharedEdge (const tcu::IVec2& pixel, const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize) 142 { 143 if (triangle.sharedEdge[0] || triangle.sharedEdge[1] || triangle.sharedEdge[2]) 144 { 145 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = 146 { 147 tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()), 148 tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()), 149 tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()), 150 }; 151 const tcu::Vec2 triangleScreenSpace[3] = 152 { 153 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 154 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 155 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 156 }; 157 158 const bool pixelOnEdge0 = pixelNearLineSegment(pixel, triangleScreenSpace[0], triangleScreenSpace[1]); 159 const bool pixelOnEdge1 = pixelNearLineSegment(pixel, triangleScreenSpace[1], triangleScreenSpace[2]); 160 const bool pixelOnEdge2 = pixelNearLineSegment(pixel, triangleScreenSpace[2], triangleScreenSpace[0]); 161 162 // If the pixel is on a multiple edges return false 163 164 if (pixelOnEdge0 && !pixelOnEdge1 && !pixelOnEdge2) 165 return triangle.sharedEdge[0]; 166 if (!pixelOnEdge0 && pixelOnEdge1 && !pixelOnEdge2) 167 return triangle.sharedEdge[1]; 168 if (!pixelOnEdge0 && !pixelOnEdge1 && pixelOnEdge2) 169 return triangle.sharedEdge[2]; 170 } 171 172 return false; 173 } 174 175 float triangleArea (const tcu::Vec2& s0, const tcu::Vec2& s1, const tcu::Vec2& s2) 176 { 177 const tcu::Vec2 u (s1.x() - s0.x(), s1.y() - s0.y()); 178 const tcu::Vec2 v (s2.x() - s0.x(), s2.y() - s0.y()); 179 const float crossProduct = (u.x() * v.y() - u.y() * v.x()); 180 181 return crossProduct / 2.0f; 182 } 183 184 tcu::IVec4 getTriangleAABB (const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize) 185 { 186 const tcu::Vec2 normalizedDeviceSpace[3] = 187 { 188 tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()), 189 tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()), 190 tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()), 191 }; 192 const tcu::Vec2 screenSpace[3] = 193 { 194 (normalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 195 (normalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 196 (normalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 197 }; 198 199 tcu::IVec4 aabb; 200 201 aabb.x() = (int)deFloatFloor(de::min(de::min(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x())); 202 aabb.y() = (int)deFloatFloor(de::min(de::min(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y())); 203 aabb.z() = (int)deFloatCeil (de::max(de::max(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x())); 204 aabb.w() = (int)deFloatCeil (de::max(de::max(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y())); 205 206 return aabb; 207 } 208 209 float getExponentEpsilonFromULP (int valueExponent, deUint32 ulp) 210 { 211 DE_ASSERT(ulp < (1u<<10)); 212 213 // assume mediump precision, using ulp as ulps in a 10 bit mantissa 214 return tcu::Float32::construct(+1, valueExponent, (1u<<23) + (ulp << (23 - 10))).asFloat() - tcu::Float32::construct(+1, valueExponent, (1u<<23)).asFloat(); 215 } 216 217 float getValueEpsilonFromULP (float value, deUint32 ulp) 218 { 219 DE_ASSERT(value != std::numeric_limits<float>::infinity() && value != -std::numeric_limits<float>::infinity()); 220 221 const int exponent = tcu::Float32(value).exponent(); 222 return getExponentEpsilonFromULP(exponent, ulp); 223 } 224 225 float getMaxValueWithinError (float value, deUint32 ulp) 226 { 227 if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity()) 228 return value; 229 230 return value + getValueEpsilonFromULP(value, ulp); 231 } 232 233 float getMinValueWithinError (float value, deUint32 ulp) 234 { 235 if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity()) 236 return value; 237 238 return value - getValueEpsilonFromULP(value, ulp); 239 } 240 241 float getMinFlushToZero (float value) 242 { 243 // flush to zero if that decreases the value 244 // assume mediump precision 245 if (value > 0.0f && value < tcu::Float32::construct(+1, -14, 1u<<23).asFloat()) 246 return 0.0f; 247 return value; 248 } 249 250 float getMaxFlushToZero (float value) 251 { 252 // flush to zero if that increases the value 253 // assume mediump precision 254 if (value < 0.0f && value > tcu::Float32::construct(-1, -14, 1u<<23).asFloat()) 255 return 0.0f; 256 return value; 257 } 258 259 tcu::IVec3 convertRGB8ToNativeFormat (const tcu::RGBA& color, const RasterizationArguments& args) 260 { 261 tcu::IVec3 pixelNativeColor; 262 263 for (int channelNdx = 0; channelNdx < 3; ++channelNdx) 264 { 265 const int channelBitCount = (channelNdx == 0) ? (args.redBits) : (channelNdx == 1) ? (args.greenBits) : (args.blueBits); 266 const int channelPixelValue = (channelNdx == 0) ? (color.getRed()) : (channelNdx == 1) ? (color.getGreen()) : (color.getBlue()); 267 268 if (channelBitCount <= 8) 269 pixelNativeColor[channelNdx] = channelPixelValue >> (8 - channelBitCount); 270 else if (channelBitCount == 8) 271 pixelNativeColor[channelNdx] = channelPixelValue; 272 else 273 { 274 // just in case someone comes up with 8+ bits framebuffers pixel formats. But as 275 // we can only read in rgba8, we have to guess the trailing bits. Guessing 0. 276 pixelNativeColor[channelNdx] = channelPixelValue << (channelBitCount - 8); 277 } 278 } 279 280 return pixelNativeColor; 281 } 282 283 /*--------------------------------------------------------------------*//*! 284 * Returns the maximum value of x / y, where x c [minDividend, maxDividend] 285 * and y c [minDivisor, maxDivisor] 286 *//*--------------------------------------------------------------------*/ 287 float maximalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor) 288 { 289 DE_ASSERT(minDividend <= maxDividend); 290 DE_ASSERT(minDivisor <= maxDivisor); 291 292 // special cases 293 if (minDividend == 0.0f && maxDividend == 0.0f) 294 return 0.0f; 295 if (minDivisor <= 0.0f && maxDivisor >= 0.0f) 296 return std::numeric_limits<float>::infinity(); 297 298 return de::max(de::max(minDividend / minDivisor, minDividend / maxDivisor), de::max(maxDividend / minDivisor, maxDividend / maxDivisor)); 299 } 300 301 /*--------------------------------------------------------------------*//*! 302 * Returns the minimum value of x / y, where x c [minDividend, maxDividend] 303 * and y c [minDivisor, maxDivisor] 304 *//*--------------------------------------------------------------------*/ 305 float minimalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor) 306 { 307 DE_ASSERT(minDividend <= maxDividend); 308 DE_ASSERT(minDivisor <= maxDivisor); 309 310 // special cases 311 if (minDividend == 0.0f && maxDividend == 0.0f) 312 return 0.0f; 313 if (minDivisor <= 0.0f && maxDivisor >= 0.0f) 314 return -std::numeric_limits<float>::infinity(); 315 316 return de::min(de::min(minDividend / minDivisor, minDividend / maxDivisor), de::min(maxDividend / minDivisor, maxDividend / maxDivisor)); 317 } 318 319 struct InterpolationRange 320 { 321 tcu::Vec3 max; 322 tcu::Vec3 min; 323 }; 324 325 struct LineInterpolationRange 326 { 327 tcu::Vec2 max; 328 tcu::Vec2 min; 329 }; 330 331 InterpolationRange calcTriangleInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::Vec2& ndpixel) 332 { 333 const int roundError = 1; 334 const int barycentricError = 3; 335 const int divError = 8; 336 337 const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w(); 338 const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w(); 339 const tcu::Vec2 nd2 = p2.swizzle(0, 1) / p2.w(); 340 341 const float ka = triangleArea(ndpixel, nd1, nd2); 342 const float kb = triangleArea(ndpixel, nd2, nd0); 343 const float kc = triangleArea(ndpixel, nd0, nd1); 344 345 const float kaMax = getMaxFlushToZero(getMaxValueWithinError(ka, barycentricError)); 346 const float kbMax = getMaxFlushToZero(getMaxValueWithinError(kb, barycentricError)); 347 const float kcMax = getMaxFlushToZero(getMaxValueWithinError(kc, barycentricError)); 348 const float kaMin = getMinFlushToZero(getMinValueWithinError(ka, barycentricError)); 349 const float kbMin = getMinFlushToZero(getMinValueWithinError(kb, barycentricError)); 350 const float kcMin = getMinFlushToZero(getMinValueWithinError(kc, barycentricError)); 351 DE_ASSERT(kaMin <= kaMax); 352 DE_ASSERT(kbMin <= kbMax); 353 DE_ASSERT(kcMin <= kcMax); 354 355 // calculate weights: vec3(ka / p0.w, kb / p1.w, kc / p2.w) / (ka / p0.w + kb / p1.w + kc / p2.w) 356 const float maxPreDivisionValues[3] = 357 { 358 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kaMax / p0.w()), divError)), 359 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kbMax / p1.w()), divError)), 360 getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kcMax / p2.w()), divError)), 361 }; 362 const float minPreDivisionValues[3] = 363 { 364 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kaMin / p0.w()), divError)), 365 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kbMin / p1.w()), divError)), 366 getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kcMin / p2.w()), divError)), 367 }; 368 DE_ASSERT(minPreDivisionValues[0] <= maxPreDivisionValues[0]); 369 DE_ASSERT(minPreDivisionValues[1] <= maxPreDivisionValues[1]); 370 DE_ASSERT(minPreDivisionValues[2] <= maxPreDivisionValues[2]); 371 372 const float maxDivisor = getMaxFlushToZero(getMaxValueWithinError(maxPreDivisionValues[0] + maxPreDivisionValues[1] + maxPreDivisionValues[2], 2*roundError)); 373 const float minDivisor = getMinFlushToZero(getMinValueWithinError(minPreDivisionValues[0] + minPreDivisionValues[1] + minPreDivisionValues[2], 2*roundError)); 374 DE_ASSERT(minDivisor <= maxDivisor); 375 376 InterpolationRange returnValue; 377 378 returnValue.max.x() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError)); 379 returnValue.max.y() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError)); 380 returnValue.max.z() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError)); 381 returnValue.min.x() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError)); 382 returnValue.min.y() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError)); 383 returnValue.min.z() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError)); 384 385 DE_ASSERT(returnValue.min.x() <= returnValue.max.x()); 386 DE_ASSERT(returnValue.min.y() <= returnValue.max.y()); 387 DE_ASSERT(returnValue.min.z() <= returnValue.max.z()); 388 389 return returnValue; 390 } 391 392 LineInterpolationRange calcSingleSampleLineInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec2& ndpoint) 393 { 394 const int divError = 3; 395 396 const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w(); 397 const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w(); 398 399 // project p to the line along the minor direction 400 401 const bool xMajor = (de::abs(nd0.x() - nd1.x()) >= de::abs(nd0.y() - nd1.y())); 402 const tcu::Vec2 minorDir = (xMajor) ? (tcu::Vec2(0.0f, 1.0f)) : (tcu::Vec2(1.0f, 0.0f)); 403 const tcu::Vec2 lineDir (nd1 - nd0); 404 const tcu::Vec2 d (ndpoint - nd0); 405 406 // calculate factors: vec2((1-t) / p0.w, t / p1.w) / ((1-t) / p0.w + t / p1.w) 407 408 const float tFactorMax = getMaxValueWithinError(-(1.0f / (minorDir.x()*lineDir.y() - lineDir.x()*minorDir.y())), divError); 409 const float tFactorMin = getMinValueWithinError(-(1.0f / (minorDir.x()*lineDir.y() - lineDir.x()*minorDir.y())), divError); 410 DE_ASSERT(tFactorMin <= tFactorMax); 411 412 const float tResult1 = tFactorMax * (minorDir.y()*d.x() - minorDir.x()*d.y()); 413 const float tResult2 = tFactorMin * (minorDir.y()*d.x() - minorDir.x()*d.y()); 414 const float tMax = de::max(tResult1, tResult2); 415 const float tMin = de::min(tResult1, tResult2); 416 DE_ASSERT(tMin <= tMax); 417 418 const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError); 419 const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError); 420 DE_ASSERT(perspectiveTMin <= perspectiveTMax); 421 422 const float perspectiveInvTMax = getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError); 423 const float perspectiveInvTMin = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError); 424 DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax); 425 426 const float perspectiveDivisorMax = perspectiveTMax + perspectiveInvTMax; 427 const float perspectiveDivisorMin = perspectiveTMin + perspectiveInvTMin; 428 DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax); 429 430 LineInterpolationRange returnValue; 431 returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 432 returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 433 returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 434 returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 435 436 DE_ASSERT(returnValue.min.x() <= returnValue.max.x()); 437 DE_ASSERT(returnValue.min.y() <= returnValue.max.y()); 438 439 return returnValue; 440 } 441 442 LineInterpolationRange calcMultiSampleLineInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec2& ndpoint) 443 { 444 const int divError = 3; 445 446 // calc weights: vec2((1-t) / p0.w, t / p1.w) / ((1-t) / p0.w + t / p1.w) 447 448 // highp vertex shader 449 const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w(); 450 const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w(); 451 452 // Allow 1 ULP 453 const float dividend = tcu::dot(ndpoint - nd0, nd1 - nd0); 454 const float dividendMax = getMaxValueWithinError(dividend, 1); 455 const float dividendMin = getMaxValueWithinError(dividend, 1); 456 DE_ASSERT(dividendMin <= dividendMax); 457 458 // Assuming lengthSquared will not be implemented as sqrt(x)^2, allow 1 ULP 459 const float divisor = tcu::lengthSquared(nd1 - nd0); 460 const float divisorMax = getMaxValueWithinError(divisor, 1); 461 const float divisorMin = getMaxValueWithinError(divisor, 1); 462 DE_ASSERT(divisorMin <= divisorMax); 463 464 // Allow 3 ULP precision for division 465 const float tMax = getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError); 466 const float tMin = getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError); 467 DE_ASSERT(tMin <= tMax); 468 469 const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError); 470 const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError); 471 DE_ASSERT(perspectiveTMin <= perspectiveTMax); 472 473 const float perspectiveInvTMax = getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError); 474 const float perspectiveInvTMin = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError); 475 DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax); 476 477 const float perspectiveDivisorMax = perspectiveTMax + perspectiveInvTMax; 478 const float perspectiveDivisorMin = perspectiveTMin + perspectiveInvTMin; 479 DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax); 480 481 LineInterpolationRange returnValue; 482 returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 483 returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 484 returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 485 returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError); 486 487 DE_ASSERT(returnValue.min.x() <= returnValue.max.x()); 488 DE_ASSERT(returnValue.min.y() <= returnValue.max.y()); 489 490 return returnValue; 491 } 492 493 LineInterpolationRange calcSingleSampleLineInterpolationRange (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize, int subpixelBits) 494 { 495 // allow interpolation weights anywhere in the central subpixels 496 const float testSquareSize = (2.0f / (1UL << subpixelBits)); 497 const float testSquarePos = (0.5f - testSquareSize / 2); 498 const tcu::Vec2 corners[4] = 499 { 500 tcu::Vec2((pixel.x() + testSquarePos + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f ) / viewportSize.y() * 2.0f - 1.0f), 501 tcu::Vec2((pixel.x() + testSquarePos + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f), 502 tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f), 503 tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f ) / viewportSize.y() * 2.0f - 1.0f), 504 }; 505 506 // calculate interpolation as a line 507 const LineInterpolationRange weights[4] = 508 { 509 calcSingleSampleLineInterpolationWeights(p0, p1, corners[0]), 510 calcSingleSampleLineInterpolationWeights(p0, p1, corners[1]), 511 calcSingleSampleLineInterpolationWeights(p0, p1, corners[2]), 512 calcSingleSampleLineInterpolationWeights(p0, p1, corners[3]), 513 }; 514 515 const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min)); 516 const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max)); 517 518 // 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 519 LineInterpolationRange result; 520 result.min = minWeights; 521 result.max = maxWeights; 522 return result; 523 } 524 525 struct TriangleInterpolator 526 { 527 const TriangleSceneSpec& scene; 528 529 TriangleInterpolator (const TriangleSceneSpec& scene_) 530 : scene(scene_) 531 { 532 } 533 534 InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const 535 { 536 // allow anywhere in the pixel area in multisample 537 // allow only in the center subpixels (4 subpixels) in singlesample 538 const float testSquareSize = (multisample) ? (1.0f) : (2.0f / (1UL << subpixelBits)); 539 const float testSquarePos = (multisample) ? (0.0f) : (0.5f - testSquareSize / 2); 540 const tcu::Vec2 corners[4] = 541 { 542 tcu::Vec2((pixel.x() + testSquarePos + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f ) / viewportSize.y() * 2.0f - 1.0f), 543 tcu::Vec2((pixel.x() + testSquarePos + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f), 544 tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f), 545 tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f ) / viewportSize.y() * 2.0f - 1.0f), 546 }; 547 const InterpolationRange weights[4] = 548 { 549 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[0]), 550 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[1]), 551 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[2]), 552 calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[3]), 553 }; 554 555 InterpolationRange result; 556 result.min = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min)); 557 result.max = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max)); 558 return result; 559 } 560 }; 561 562 /*--------------------------------------------------------------------*//*! 563 * Used only by verifyMultisampleLineGroupInterpolation to calculate 564 * correct line interpolations for the triangulated lines. 565 *//*--------------------------------------------------------------------*/ 566 struct MultisampleLineInterpolator 567 { 568 const LineSceneSpec& scene; 569 570 MultisampleLineInterpolator (const LineSceneSpec& scene_) 571 : scene(scene_) 572 { 573 } 574 575 InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const 576 { 577 DE_ASSERT(multisample); 578 DE_UNREF(multisample); 579 DE_UNREF(subpixelBits); 580 581 // in triangulation, one line emits two triangles 582 const int lineNdx = primitiveNdx / 2; 583 584 // allow interpolation weights anywhere in the pixel 585 const tcu::Vec2 corners[4] = 586 { 587 tcu::Vec2((pixel.x() + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 0.0f) / viewportSize.y() * 2.0f - 1.0f), 588 tcu::Vec2((pixel.x() + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 1.0f) / viewportSize.y() * 2.0f - 1.0f), 589 tcu::Vec2((pixel.x() + 1.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 1.0f) / viewportSize.y() * 2.0f - 1.0f), 590 tcu::Vec2((pixel.x() + 1.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 0.0f) / viewportSize.y() * 2.0f - 1.0f), 591 }; 592 593 // calculate interpolation as a line 594 const LineInterpolationRange weights[4] = 595 { 596 calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[0]), 597 calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[1]), 598 calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[2]), 599 calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[3]), 600 }; 601 602 const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min)); 603 const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max)); 604 605 // 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 606 InterpolationRange result; 607 result.min = tcu::Vec3(minWeights.x(), 0.0f, minWeights.y()); 608 result.max = tcu::Vec3(maxWeights.x(), 0.0f, maxWeights.y()); 609 return result; 610 } 611 }; 612 613 template <typename Interpolator> 614 bool verifyTriangleGroupInterpolationWithInterpolator (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, const Interpolator& interpolator) 615 { 616 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255); 617 const bool multisampled = (args.numSamples != 0); 618 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 619 const int errorFloodThreshold = 4; 620 int errorCount = 0; 621 int invalidPixels = 0; 622 int subPixelBits = args.subpixelBits; 623 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 624 625 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 626 627 // log format 628 629 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage; 630 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8) 631 log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage; 632 633 // subpixel bits in in a valid range? 634 635 if (subPixelBits < 0) 636 { 637 log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage; 638 subPixelBits = 0; 639 } 640 else if (subPixelBits > 16) 641 { 642 // At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict 643 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; 644 subPixelBits = 16; 645 } 646 647 // check pixels 648 649 for (int y = 0; y < surface.getHeight(); ++y) 650 for (int x = 0; x < surface.getWidth(); ++x) 651 { 652 const tcu::RGBA color = surface.getPixel(x, y); 653 bool stackBottomFound = false; 654 int stackSize = 0; 655 tcu::Vec4 colorStackMin; 656 tcu::Vec4 colorStackMax; 657 658 // Iterate triangle coverage front to back, find the stack of pontentially contributing fragments 659 for (int triNdx = (int)scene.triangles.size() - 1; triNdx >= 0; --triNdx) 660 { 661 const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0], 662 scene.triangles[triNdx].positions[1], 663 scene.triangles[triNdx].positions[2], 664 tcu::IVec2(x, y), 665 viewportSize, 666 subPixelBits, 667 multisampled); 668 669 if (coverage == COVERAGE_FULL || coverage == COVERAGE_PARTIAL) 670 { 671 // potentially contributes to the result fragment's value 672 const InterpolationRange weights = interpolator.interpolate(triNdx, tcu::IVec2(x, y), viewportSize, multisampled, subPixelBits); 673 674 const tcu::Vec4 fragmentColorMax = de::clamp(weights.max.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] + 675 de::clamp(weights.max.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] + 676 de::clamp(weights.max.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2]; 677 const tcu::Vec4 fragmentColorMin = de::clamp(weights.min.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] + 678 de::clamp(weights.min.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] + 679 de::clamp(weights.min.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2]; 680 681 if (stackSize++ == 0) 682 { 683 // first triangle, set the values properly 684 colorStackMin = fragmentColorMin; 685 colorStackMax = fragmentColorMax; 686 } 687 else 688 { 689 // contributing triangle 690 colorStackMin = tcu::min(colorStackMin, fragmentColorMin); 691 colorStackMax = tcu::max(colorStackMax, fragmentColorMax); 692 } 693 694 if (coverage == COVERAGE_FULL) 695 { 696 // loop terminates, this is the bottommost fragment 697 stackBottomFound = true; 698 break; 699 } 700 } 701 } 702 703 // Partial coverage == background may be visible 704 if (stackSize != 0 && !stackBottomFound) 705 { 706 stackSize++; 707 colorStackMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f); 708 } 709 710 // Is the result image color in the valid range. 711 if (stackSize == 0) 712 { 713 // No coverage, allow only background (black, value=0) 714 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); 715 const int threshold = 1; 716 717 if (pixelNativeColor.x() > threshold || 718 pixelNativeColor.y() > threshold || 719 pixelNativeColor.z() > threshold) 720 { 721 ++errorCount; 722 723 // don't fill the logs with too much data 724 if (errorCount < errorFloodThreshold) 725 { 726 log << tcu::TestLog::Message 727 << "Found an invalid pixel at (" << x << "," << y << ")\n" 728 << "\tPixel color:\t\t" << color << "\n" 729 << "\tExpected background color.\n" 730 << tcu::TestLog::EndMessage; 731 } 732 733 ++invalidPixels; 734 errorMask.setPixel(x, y, invalidPixelColor); 735 } 736 } 737 else 738 { 739 DE_ASSERT(stackSize); 740 741 // Each additional step in the stack may cause conversion error of 1 bit due to undefined rounding direction 742 const int thresholdRed = stackSize - 1; 743 const int thresholdGreen = stackSize - 1; 744 const int thresholdBlue = stackSize - 1; 745 746 const tcu::Vec3 valueRangeMin = tcu::Vec3(colorStackMin.xyz()); 747 const tcu::Vec3 valueRangeMax = tcu::Vec3(colorStackMax.xyz()); 748 749 const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1); 750 const tcu::Vec3 colorMinF (de::clamp(valueRangeMin.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()), 751 de::clamp(valueRangeMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()), 752 de::clamp(valueRangeMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z())); 753 const tcu::Vec3 colorMaxF (de::clamp(valueRangeMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()), 754 de::clamp(valueRangeMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()), 755 de::clamp(valueRangeMax.z() * formatLimit.z(), 0.0f, (float)formatLimit.z())); 756 const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()), 757 (int)deFloatFloor(colorMinF.y()), 758 (int)deFloatFloor(colorMinF.z())); 759 const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()), 760 (int)deFloatCeil (colorMaxF.y()), 761 (int)deFloatCeil (colorMaxF.z())); 762 763 // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565 764 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); 765 766 // Validity check 767 if (pixelNativeColor.x() < colorMin.x() - thresholdRed || 768 pixelNativeColor.y() < colorMin.y() - thresholdGreen || 769 pixelNativeColor.z() < colorMin.z() - thresholdBlue || 770 pixelNativeColor.x() > colorMax.x() + thresholdRed || 771 pixelNativeColor.y() > colorMax.y() + thresholdGreen || 772 pixelNativeColor.z() > colorMax.z() + thresholdBlue) 773 { 774 ++errorCount; 775 776 // don't fill the logs with too much data 777 if (errorCount <= errorFloodThreshold) 778 { 779 log << tcu::TestLog::Message 780 << "Found an invalid pixel at (" << x << "," << y << ")\n" 781 << "\tPixel color:\t\t" << color << "\n" 782 << "\tNative color:\t\t" << pixelNativeColor << "\n" 783 << "\tAllowed error:\t\t" << tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue) << "\n" 784 << "\tReference native color min: " << tcu::clamp(colorMin - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n" 785 << "\tReference native color max: " << tcu::clamp(colorMax + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n" 786 << "\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" 787 << "\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" 788 << "\tFmin:\t" << tcu::clamp(valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 789 << "\tFmax:\t" << tcu::clamp(valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 790 << tcu::TestLog::EndMessage; 791 } 792 793 ++invalidPixels; 794 errorMask.setPixel(x, y, invalidPixelColor); 795 } 796 } 797 } 798 799 // don't just hide failures 800 if (errorCount > errorFloodThreshold) 801 log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage; 802 803 // report result 804 if (invalidPixels) 805 { 806 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage; 807 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 808 << tcu::TestLog::Image("Result", "Result", surface) 809 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 810 << tcu::TestLog::EndImageSet; 811 812 return false; 813 } 814 else 815 { 816 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 817 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 818 << tcu::TestLog::Image("Result", "Result", surface) 819 << tcu::TestLog::EndImageSet; 820 821 return true; 822 } 823 } 824 825 bool verifyMultisampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 826 { 827 // Multisampled line == 2 triangles 828 829 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight()); 830 const float halfLineWidth = scene.lineWidth * 0.5f; 831 TriangleSceneSpec triangleScene; 832 833 triangleScene.triangles.resize(2 * scene.lines.size()); 834 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 835 { 836 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles 837 const tcu::Vec2 lineNormalizedDeviceSpace[2] = 838 { 839 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()), 840 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()), 841 }; 842 const tcu::Vec2 lineScreenSpace[2] = 843 { 844 (lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 845 (lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 846 }; 847 848 const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]); 849 const tcu::Vec2 lineNormalDir = tcu::Vec2(lineDir.y(), -lineDir.x()); 850 851 const tcu::Vec2 lineQuadScreenSpace[4] = 852 { 853 lineScreenSpace[0] + lineNormalDir * halfLineWidth, 854 lineScreenSpace[0] - lineNormalDir * halfLineWidth, 855 lineScreenSpace[1] - lineNormalDir * halfLineWidth, 856 lineScreenSpace[1] + lineNormalDir * halfLineWidth, 857 }; 858 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = 859 { 860 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 861 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 862 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 863 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 864 }; 865 866 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; 867 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; 868 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; 869 870 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; 871 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; 872 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; 873 } 874 875 return verifyTriangleGroupRasterization(surface, triangleScene, args, log); 876 } 877 878 bool verifyMultisampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 879 { 880 // Multisampled line == 2 triangles 881 882 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight()); 883 const float halfLineWidth = scene.lineWidth * 0.5f; 884 TriangleSceneSpec triangleScene; 885 886 triangleScene.triangles.resize(2 * scene.lines.size()); 887 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 888 { 889 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles 890 const tcu::Vec2 lineNormalizedDeviceSpace[2] = 891 { 892 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()), 893 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()), 894 }; 895 const tcu::Vec2 lineScreenSpace[2] = 896 { 897 (lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 898 (lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize, 899 }; 900 901 const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]); 902 const tcu::Vec2 lineNormalDir = tcu::Vec2(lineDir.y(), -lineDir.x()); 903 904 const tcu::Vec2 lineQuadScreenSpace[4] = 905 { 906 lineScreenSpace[0] + lineNormalDir * halfLineWidth, 907 lineScreenSpace[0] - lineNormalDir * halfLineWidth, 908 lineScreenSpace[1] - lineNormalDir * halfLineWidth, 909 lineScreenSpace[1] + lineNormalDir * halfLineWidth, 910 }; 911 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = 912 { 913 lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 914 lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 915 lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 916 lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 917 }; 918 919 triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); 920 triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f); 921 triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); 922 923 triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false; 924 triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false; 925 triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true; 926 927 triangleScene.triangles[lineNdx*2 + 0].colors[0] = scene.lines[lineNdx].colors[0]; 928 triangleScene.triangles[lineNdx*2 + 0].colors[1] = scene.lines[lineNdx].colors[0]; 929 triangleScene.triangles[lineNdx*2 + 0].colors[2] = scene.lines[lineNdx].colors[1]; 930 931 triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); 932 triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); 933 triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f); 934 935 triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true; 936 triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false; 937 triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false; 938 939 triangleScene.triangles[lineNdx*2 + 1].colors[0] = scene.lines[lineNdx].colors[0]; 940 triangleScene.triangles[lineNdx*2 + 1].colors[1] = scene.lines[lineNdx].colors[1]; 941 triangleScene.triangles[lineNdx*2 + 1].colors[2] = scene.lines[lineNdx].colors[1]; 942 } 943 944 return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, log, MultisampleLineInterpolator(scene)); 945 } 946 947 bool verifyMultisamplePointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 948 { 949 // Multisampled point == 2 triangles 950 951 const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight()); 952 TriangleSceneSpec triangleScene; 953 954 triangleScene.triangles.resize(2 * scene.points.size()); 955 for (int pointNdx = 0; pointNdx < (int)scene.points.size(); ++pointNdx) 956 { 957 // Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles 958 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()); 959 const tcu::Vec2 pointScreenSpace = (pointNormalizedDeviceSpace + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize; 960 const float offset = scene.points[pointNdx].pointSize * 0.5f; 961 const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] = 962 { 963 (pointScreenSpace + tcu::Vec2(-offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 964 (pointScreenSpace + tcu::Vec2(-offset, offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 965 (pointScreenSpace + tcu::Vec2( offset, offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 966 (pointScreenSpace + tcu::Vec2( offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f), 967 }; 968 969 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; 970 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; 971 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; 972 973 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; 974 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; 975 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; 976 } 977 978 return verifyTriangleGroupRasterization(surface, triangleScene, args, log); 979 } 980 981 bool verifySinglesampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 982 { 983 DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases 984 DE_ASSERT(scene.lines.size() < 255); // indices are stored as unsigned 8-bit ints 985 986 bool allOK = true; 987 bool overdrawInReference = false; 988 int referenceFragments = 0; 989 int resultFragments = 0; 990 int lineWidth = deFloorFloatToInt32(scene.lineWidth + 0.5f); 991 bool imageShown = false; 992 std::vector<bool> lineIsXMajor (scene.lines.size()); 993 994 // 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 995 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 996 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0)); 997 998 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 999 { 1000 rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight())); 1001 1002 const tcu::Vec2 lineNormalizedDeviceSpace[2] = 1003 { 1004 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()), 1005 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()), 1006 }; 1007 const tcu::Vec4 lineScreenSpace[2] = 1008 { 1009 tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f), 1010 tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f), 1011 }; 1012 1013 rasterizer.init(lineScreenSpace[0], lineScreenSpace[1], scene.lineWidth); 1014 1015 // calculate majority of later use 1016 lineIsXMajor[lineNdx] = de::abs(lineScreenSpace[1].x() - lineScreenSpace[0].x()) >= de::abs(lineScreenSpace[1].y() - lineScreenSpace[0].y()); 1017 1018 for (;;) 1019 { 1020 const int maxPackets = 32; 1021 int numRasterized = 0; 1022 rr::FragmentPacket packets[maxPackets]; 1023 1024 rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized); 1025 1026 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx) 1027 { 1028 for (int fragNdx = 0; fragNdx < 4; ++fragNdx) 1029 { 1030 if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx)) 1031 { 1032 const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2); 1033 1034 // Check for overdraw 1035 if (!overdrawInReference) 1036 overdrawInReference = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x() != 0; 1037 1038 // Output pixel 1039 referenceLineMap.getAccess().setPixel(tcu::IVec4(lineNdx + 1, 0, 0, 0), fragPos.x(), fragPos.y()); 1040 } 1041 } 1042 } 1043 1044 if (numRasterized != maxPackets) 1045 break; 1046 } 1047 } 1048 1049 // Requirement 1: The coordinates of a fragment produced by the algorithm may not deviate by more than one unit 1050 { 1051 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 1052 bool missingFragments = false; 1053 1054 tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 255, 0, 255)); 1055 1056 log << tcu::TestLog::Message << "Searching for deviating fragments." << tcu::TestLog::EndMessage; 1057 1058 for (int y = 0; y < referenceLineMap.getHeight(); ++y) 1059 for (int x = 0; x < referenceLineMap.getWidth(); ++x) 1060 { 1061 const bool reference = referenceLineMap.getAccess().getPixelInt(x, y).x() != 0; 1062 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits); 1063 1064 if (reference) 1065 ++referenceFragments; 1066 if (result) 1067 ++resultFragments; 1068 1069 if (reference == result) 1070 continue; 1071 1072 // Reference fragment here, matching result fragment must be nearby 1073 if (reference && !result) 1074 { 1075 bool foundFragment = false; 1076 1077 if (x == 0 || y == 0 || x == referenceLineMap.getWidth() - 1 || y == referenceLineMap.getHeight() -1) 1078 { 1079 // image boundary, missing fragment could be over the image edge 1080 foundFragment = true; 1081 } 1082 1083 // find nearby fragment 1084 for (int dy = -1; dy < 2 && !foundFragment; ++dy) 1085 for (int dx = -1; dx < 2 && !foundFragment; ++dx) 1086 { 1087 if (compareColors(surface.getPixel(x+dx, y+dy), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits)) 1088 foundFragment = true; 1089 } 1090 1091 if (!foundFragment) 1092 { 1093 missingFragments = true; 1094 errorMask.setPixel(x, y, tcu::RGBA::red); 1095 } 1096 } 1097 } 1098 1099 if (missingFragments) 1100 { 1101 log << tcu::TestLog::Message << "Invalid deviation(s) found." << tcu::TestLog::EndMessage; 1102 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1103 << tcu::TestLog::Image("Result", "Result", surface) 1104 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 1105 << tcu::TestLog::EndImageSet; 1106 1107 imageShown = true; 1108 allOK = false; 1109 } 1110 else 1111 { 1112 log << tcu::TestLog::Message << "No invalid deviations found." << tcu::TestLog::EndMessage; 1113 } 1114 } 1115 1116 // Requirement 2: The total number of fragments produced by the algorithm may differ from 1117 // that produced by the diamond-exit rule by no more than one. 1118 { 1119 // Check is not valid if the primitives intersect or otherwise share same fragments 1120 if (!overdrawInReference) 1121 { 1122 int allowedDeviation = (int)scene.lines.size() * lineWidth; // one pixel per primitive in the major direction 1123 1124 log << tcu::TestLog::Message << "Verifying fragment counts:\n" 1125 << "\tDiamond-exit rule: " << referenceFragments << " fragments.\n" 1126 << "\tResult image: " << resultFragments << " fragments.\n" 1127 << "\tAllowing deviation of " << allowedDeviation << " fragments.\n" 1128 << tcu::TestLog::EndMessage; 1129 1130 if (deAbs32(referenceFragments - resultFragments) > allowedDeviation) 1131 { 1132 tcu::Surface reference(surface.getWidth(), surface.getHeight()); 1133 1134 // show a helpful reference image 1135 tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255)); 1136 for (int y = 0; y < surface.getHeight(); ++y) 1137 for (int x = 0; x < surface.getWidth(); ++x) 1138 if (referenceLineMap.getAccess().getPixelInt(x, y).x()) 1139 reference.setPixel(x, y, tcu::RGBA::white); 1140 1141 log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage; 1142 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1143 << tcu::TestLog::Image("Reference", "Reference", reference) 1144 << tcu::TestLog::Image("Result", "Result", surface) 1145 << tcu::TestLog::EndImageSet; 1146 1147 allOK = false; 1148 imageShown = true; 1149 } 1150 else 1151 { 1152 log << tcu::TestLog::Message << "Fragment count is valid." << tcu::TestLog::EndMessage; 1153 } 1154 } 1155 else 1156 { 1157 log << tcu::TestLog::Message << "Overdraw in scene. Fragment count cannot be verified. Skipping fragment count checks." << tcu::TestLog::EndMessage; 1158 } 1159 } 1160 1161 // Requirement 3: Line width must be constant 1162 { 1163 bool invalidWidthFound = false; 1164 1165 log << tcu::TestLog::Message << "Verifying line widths of the x-major lines." << tcu::TestLog::EndMessage; 1166 for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y) 1167 { 1168 bool fullyVisibleLine = false; 1169 bool previousPixelUndefined = false; 1170 int currentLine = 0; 1171 int currentWidth = 1; 1172 1173 for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x) 1174 { 1175 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits); 1176 int lineID = 0; 1177 1178 // Which line does this fragment belong to? 1179 1180 if (result) 1181 { 1182 bool multipleNearbyLines = false; 1183 1184 for (int dy = -1; dy < 2; ++dy) 1185 for (int dx = -1; dx < 2; ++dx) 1186 { 1187 const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1188 if (nearbyID) 1189 { 1190 if (lineID && lineID != nearbyID) 1191 multipleNearbyLines = true; 1192 lineID = nearbyID; 1193 } 1194 } 1195 1196 if (multipleNearbyLines) 1197 { 1198 // Another line is too close, don't try to calculate width here 1199 previousPixelUndefined = true; 1200 continue; 1201 } 1202 } 1203 1204 // Only line with id of lineID is nearby 1205 1206 if (previousPixelUndefined) 1207 { 1208 // The line might have been overdrawn or not 1209 currentLine = lineID; 1210 currentWidth = 1; 1211 fullyVisibleLine = false; 1212 previousPixelUndefined = false; 1213 } 1214 else if (lineID == currentLine) 1215 { 1216 // Current line continues 1217 ++currentWidth; 1218 } 1219 else if (lineID > currentLine) 1220 { 1221 // Another line was drawn over or the line ends 1222 currentLine = lineID; 1223 currentWidth = 1; 1224 fullyVisibleLine = true; 1225 } 1226 else 1227 { 1228 // The line ends 1229 if (fullyVisibleLine && !lineIsXMajor[currentLine-1]) 1230 { 1231 // check width 1232 if (currentWidth != lineWidth) 1233 { 1234 log << tcu::TestLog::Message << "\tInvalid line width at (" << x - currentWidth << ", " << y << ") - (" << x - 1 << ", " << y << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage; 1235 invalidWidthFound = true; 1236 } 1237 } 1238 1239 currentLine = lineID; 1240 currentWidth = 1; 1241 fullyVisibleLine = false; 1242 } 1243 } 1244 } 1245 1246 log << tcu::TestLog::Message << "Verifying line widths of the y-major lines." << tcu::TestLog::EndMessage; 1247 for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x) 1248 { 1249 bool fullyVisibleLine = false; 1250 bool previousPixelUndefined = false; 1251 int currentLine = 0; 1252 int currentWidth = 1; 1253 1254 for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y) 1255 { 1256 const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white, args.redBits, args.greenBits, args.blueBits); 1257 int lineID = 0; 1258 1259 // Which line does this fragment belong to? 1260 1261 if (result) 1262 { 1263 bool multipleNearbyLines = false; 1264 1265 for (int dy = -1; dy < 2; ++dy) 1266 for (int dx = -1; dx < 2; ++dx) 1267 { 1268 const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1269 if (nearbyID) 1270 { 1271 if (lineID && lineID != nearbyID) 1272 multipleNearbyLines = true; 1273 lineID = nearbyID; 1274 } 1275 } 1276 1277 if (multipleNearbyLines) 1278 { 1279 // Another line is too close, don't try to calculate width here 1280 previousPixelUndefined = true; 1281 continue; 1282 } 1283 } 1284 1285 // Only line with id of lineID is nearby 1286 1287 if (previousPixelUndefined) 1288 { 1289 // The line might have been overdrawn or not 1290 currentLine = lineID; 1291 currentWidth = 1; 1292 fullyVisibleLine = false; 1293 previousPixelUndefined = false; 1294 } 1295 else if (lineID == currentLine) 1296 { 1297 // Current line continues 1298 ++currentWidth; 1299 } 1300 else if (lineID > currentLine) 1301 { 1302 // Another line was drawn over or the line ends 1303 currentLine = lineID; 1304 currentWidth = 1; 1305 fullyVisibleLine = true; 1306 } 1307 else 1308 { 1309 // The line ends 1310 if (fullyVisibleLine && lineIsXMajor[currentLine-1]) 1311 { 1312 // check width 1313 if (currentWidth != lineWidth) 1314 { 1315 log << tcu::TestLog::Message << "\tInvalid line width at (" << x << ", " << y - currentWidth << ") - (" << x << ", " << y - 1 << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage; 1316 invalidWidthFound = true; 1317 } 1318 } 1319 1320 currentLine = lineID; 1321 currentWidth = 1; 1322 fullyVisibleLine = false; 1323 } 1324 } 1325 } 1326 1327 if (invalidWidthFound) 1328 { 1329 log << tcu::TestLog::Message << "Invalid line width found, image is not valid." << tcu::TestLog::EndMessage; 1330 allOK = false; 1331 } 1332 else 1333 { 1334 log << tcu::TestLog::Message << "Line widths are valid." << tcu::TestLog::EndMessage; 1335 } 1336 } 1337 1338 //\todo [2013-10-24 jarkko]. 1339 //Requirement 4. If two line segments share a common endpoint, and both segments are either 1340 //x-major (both left-to-right or both right-to-left) or y-major (both bottom-totop 1341 //or both top-to-bottom), then rasterizing both segments may not produce 1342 //duplicate fragments, nor may any fragments be omitted so as to interrupt 1343 //continuity of the connected segments. 1344 1345 if (!imageShown) 1346 { 1347 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1348 << tcu::TestLog::Image("Result", "Result", surface) 1349 << tcu::TestLog::EndImageSet; 1350 } 1351 1352 return allOK; 1353 } 1354 1355 struct SingleSampleLineCoverageCandidate 1356 { 1357 int lineNdx; 1358 tcu::IVec3 colorMin; 1359 tcu::IVec3 colorMax; 1360 tcu::Vec3 colorMinF; 1361 tcu::Vec3 colorMaxF; 1362 tcu::Vec3 valueRangeMin; 1363 tcu::Vec3 valueRangeMax; 1364 }; 1365 1366 bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1367 { 1368 DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases 1369 DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints 1370 1371 const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255); 1372 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 1373 const int errorFloodThreshold = 4; 1374 int errorCount = 0; 1375 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 1376 int invalidPixels = 0; 1377 1378 // log format 1379 1380 log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage; 1381 if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8) 1382 log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage; 1383 1384 // Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield 1385 // The map is used to find lines with potential coverage to a given pixel 1386 tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 1387 tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0)); 1388 1389 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 1390 1391 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1392 { 1393 rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight())); 1394 1395 const tcu::Vec2 lineNormalizedDeviceSpace[2] = 1396 { 1397 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()), 1398 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()), 1399 }; 1400 const tcu::Vec4 lineScreenSpace[2] = 1401 { 1402 tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f), 1403 tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f), 1404 }; 1405 1406 rasterizer.init(lineScreenSpace[0], lineScreenSpace[1], scene.lineWidth); 1407 1408 // Calculate correct line coverage 1409 for (;;) 1410 { 1411 const int maxPackets = 32; 1412 int numRasterized = 0; 1413 rr::FragmentPacket packets[maxPackets]; 1414 1415 rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized); 1416 1417 for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx) 1418 { 1419 for (int fragNdx = 0; fragNdx < 4; ++fragNdx) 1420 { 1421 if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx)) 1422 { 1423 const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2); 1424 const int previousMask = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x(); 1425 const int newMask = (previousMask) | (1UL << lineNdx); 1426 1427 referenceLineMap.getAccess().setPixel(tcu::IVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y()); 1428 } 1429 } 1430 } 1431 1432 if (numRasterized != maxPackets) 1433 break; 1434 } 1435 } 1436 1437 // Find all possible lines with coverage, check pixel color matches one of them 1438 1439 for (int y = 1; y < surface.getHeight() - 1; ++y) 1440 for (int x = 1; x < surface.getWidth() - 1; ++x) 1441 { 1442 const tcu::RGBA color = surface.getPixel(x, y); 1443 const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565 1444 int lineCoverageSet = 0; // !< lines that may cover this fragment 1445 int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment 1446 bool matchFound = false; 1447 const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1); 1448 1449 std::vector<SingleSampleLineCoverageCandidate> candidates; 1450 1451 // Find lines with possible coverage 1452 1453 for (int dy = -1; dy < 2; ++dy) 1454 for (int dx = -1; dx < 2; ++dx) 1455 { 1456 const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x(); 1457 1458 lineCoverageSet |= coverage; 1459 lineSurroundingCoverage &= coverage; 1460 } 1461 1462 // background color is possible? 1463 if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black, args.redBits, args.greenBits, args.blueBits)) 1464 continue; 1465 1466 // Check those lines 1467 1468 for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx) 1469 { 1470 if (((lineCoverageSet >> lineNdx) & 0x01) != 0) 1471 { 1472 const LineInterpolationRange range = calcSingleSampleLineInterpolationRange(scene.lines[lineNdx].positions[0], 1473 scene.lines[lineNdx].positions[1], 1474 tcu::IVec2(x, y), 1475 viewportSize, 1476 args.subpixelBits); 1477 1478 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]; 1479 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]; 1480 1481 const tcu::Vec3 colorMinF (de::clamp(valueMin.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()), 1482 de::clamp(valueMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()), 1483 de::clamp(valueMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z())); 1484 const tcu::Vec3 colorMaxF (de::clamp(valueMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()), 1485 de::clamp(valueMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()), 1486 de::clamp(valueMax.z() * formatLimit.z(), 0.0f, (float)formatLimit.z())); 1487 const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()), 1488 (int)deFloatFloor(colorMinF.y()), 1489 (int)deFloatFloor(colorMinF.z())); 1490 const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()), 1491 (int)deFloatCeil (colorMaxF.y()), 1492 (int)deFloatCeil (colorMaxF.z())); 1493 1494 // Verify validity 1495 if (pixelNativeColor.x() < colorMin.x() || 1496 pixelNativeColor.y() < colorMin.y() || 1497 pixelNativeColor.z() < colorMin.z() || 1498 pixelNativeColor.x() > colorMax.x() || 1499 pixelNativeColor.y() > colorMax.y() || 1500 pixelNativeColor.z() > colorMax.z()) 1501 { 1502 if (errorCount < errorFloodThreshold) 1503 { 1504 // Store candidate information for logging 1505 SingleSampleLineCoverageCandidate candidate; 1506 1507 candidate.lineNdx = lineNdx; 1508 candidate.colorMin = colorMin; 1509 candidate.colorMax = colorMax; 1510 candidate.colorMinF = colorMinF; 1511 candidate.colorMaxF = colorMaxF; 1512 candidate.valueRangeMin = valueMin.swizzle(0, 1, 2); 1513 candidate.valueRangeMax = valueMax.swizzle(0, 1, 2); 1514 1515 candidates.push_back(candidate); 1516 } 1517 } 1518 else 1519 { 1520 matchFound = true; 1521 break; 1522 } 1523 } 1524 } 1525 1526 if (matchFound) 1527 continue; 1528 1529 // invalid fragment 1530 ++invalidPixels; 1531 errorMask.setPixel(x, y, invalidPixelColor); 1532 1533 ++errorCount; 1534 1535 // don't fill the logs with too much data 1536 if (errorCount < errorFloodThreshold) 1537 { 1538 log << tcu::TestLog::Message 1539 << "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n" 1540 << "\tPixel color:\t\t" << color << "\n" 1541 << "\tNative color:\t\t" << pixelNativeColor << "\n" 1542 << tcu::TestLog::EndMessage; 1543 1544 for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx) 1545 { 1546 const SingleSampleLineCoverageCandidate& candidate = candidates[candidateNdx]; 1547 1548 log << tcu::TestLog::Message << "\tCandidate (line " << candidate.lineNdx << "):\n" 1549 << "\t\tReference native color min: " << tcu::clamp(candidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n" 1550 << "\t\tReference native color max: " << tcu::clamp(candidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n" 1551 << "\t\tReference native float min: " << tcu::clamp(candidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 1552 << "\t\tReference native float max: " << tcu::clamp(candidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n" 1553 << "\t\tFmin:\t" << tcu::clamp(candidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 1554 << "\t\tFmax:\t" << tcu::clamp(candidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n" 1555 << tcu::TestLog::EndMessage; 1556 } 1557 } 1558 } 1559 1560 // don't just hide failures 1561 if (errorCount > errorFloodThreshold) 1562 log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage; 1563 1564 // report result 1565 if (invalidPixels) 1566 { 1567 log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage; 1568 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1569 << tcu::TestLog::Image("Result", "Result", surface) 1570 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 1571 << tcu::TestLog::EndImageSet; 1572 1573 return false; 1574 } 1575 else 1576 { 1577 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 1578 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1579 << tcu::TestLog::Image("Result", "Result", surface) 1580 << tcu::TestLog::EndImageSet; 1581 1582 return true; 1583 } 1584 } 1585 1586 } // anonymous 1587 1588 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) 1589 { 1590 typedef tcu::Vector<deInt64, 2> I64Vec2; 1591 1592 const deUint64 numSubPixels = ((deUint64)1) << subpixelBits; 1593 const deUint64 pixelHitBoxSize = (multisample) ? (numSubPixels) : (2+2); //!< allow 4 central (2x2) for non-multisample pixels. Rounding may move edges 1 subpixel to any direction. 1594 const bool order = isTriangleClockwise(p0, p1, p2); //!< clockwise / counter-clockwise 1595 const tcu::Vec4& orderedP0 = p0; //!< vertices of a clockwise triangle 1596 const tcu::Vec4& orderedP1 = (order) ? (p1) : (p2); 1597 const tcu::Vec4& orderedP2 = (order) ? (p2) : (p1); 1598 const tcu::Vec2 triangleNormalizedDeviceSpace[3] = 1599 { 1600 tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()), 1601 tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()), 1602 tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()), 1603 }; 1604 const tcu::Vec2 triangleScreenSpace[3] = 1605 { 1606 (triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 1607 (triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 1608 (triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()), 1609 }; 1610 1611 // Broad bounding box - pixel check 1612 { 1613 const float minX = de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x()); 1614 const float minY = de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y()); 1615 const float maxX = de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x()); 1616 const float maxY = de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y()); 1617 1618 if ((float)pixel.x() > maxX + 1 || 1619 (float)pixel.y() > maxY + 1 || 1620 (float)pixel.x() < minX - 1 || 1621 (float)pixel.y() < minY - 1) 1622 return COVERAGE_NONE; 1623 } 1624 1625 // Broad triangle - pixel area intersection 1626 { 1627 const I64Vec2 pixelCenterPosition = I64Vec2(pixel.x(), pixel.y()) * I64Vec2(numSubPixels, numSubPixels) + I64Vec2(numSubPixels / 2, numSubPixels / 2); 1628 const I64Vec2 triangleSubPixelSpaceRound[3] = 1629 { 1630 I64Vec2(deRoundFloatToInt32(triangleScreenSpace[0].x()*numSubPixels), deRoundFloatToInt32(triangleScreenSpace[0].y()*numSubPixels)), 1631 I64Vec2(deRoundFloatToInt32(triangleScreenSpace[1].x()*numSubPixels), deRoundFloatToInt32(triangleScreenSpace[1].y()*numSubPixels)), 1632 I64Vec2(deRoundFloatToInt32(triangleScreenSpace[2].x()*numSubPixels), deRoundFloatToInt32(triangleScreenSpace[2].y()*numSubPixels)), 1633 }; 1634 1635 // Check (using cross product) if pixel center is 1636 // a) too far from any edge 1637 // b) fully inside all edges 1638 bool insideAllEdges = true; 1639 for (int vtxNdx = 0; vtxNdx < 3; ++vtxNdx) 1640 { 1641 const int otherVtxNdx = (vtxNdx + 1) % 3; 1642 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 1643 const I64Vec2 edge = triangleSubPixelSpaceRound[otherVtxNdx] - triangleSubPixelSpaceRound[vtxNdx]; 1644 const I64Vec2 v = pixelCenterPosition - triangleSubPixelSpaceRound[vtxNdx]; 1645 const deInt64 crossProduct = (edge.x() * v.y() - edge.y() * v.x()); 1646 1647 // distance from edge: (edge x v) / |edge| 1648 // (edge x v) / |edge| > maxPixelDistance 1649 // ==> (edge x v)^2 / edge^2 > maxPixelDistance^2 | edge x v > 0 1650 // ==> (edge x v)^2 > maxPixelDistance^2 * edge^2 1651 if (crossProduct < 0 && crossProduct*crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(edge)) 1652 return COVERAGE_NONE; 1653 if (crossProduct < 0 || crossProduct*crossProduct < maxPixelDistanceSquared * tcu::lengthSquared(edge)) 1654 insideAllEdges = false; 1655 } 1656 1657 if (insideAllEdges) 1658 return COVERAGE_FULL; 1659 } 1660 1661 // Accurate intersection for edge pixels 1662 { 1663 // In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center. 1664 const I64Vec2 pixelCorners[4] = 1665 { 1666 I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+0) * numSubPixels), 1667 I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+0) * numSubPixels), 1668 I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+1) * numSubPixels), 1669 I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+1) * numSubPixels), 1670 }; 1671 const I64Vec2 pixelCenterCorners[4] = 1672 { 1673 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 0), 1674 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 0), 1675 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 1), 1676 I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 1), 1677 }; 1678 1679 // both rounding directions 1680 const I64Vec2 triangleSubPixelSpaceFloor[3] = 1681 { 1682 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x()*numSubPixels), deFloorFloatToInt32(triangleScreenSpace[0].y()*numSubPixels)), 1683 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x()*numSubPixels), deFloorFloatToInt32(triangleScreenSpace[1].y()*numSubPixels)), 1684 I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x()*numSubPixels), deFloorFloatToInt32(triangleScreenSpace[2].y()*numSubPixels)), 1685 }; 1686 const I64Vec2 triangleSubPixelSpaceCeil[3] = 1687 { 1688 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x()*numSubPixels), deCeilFloatToInt32(triangleScreenSpace[0].y()*numSubPixels)), 1689 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x()*numSubPixels), deCeilFloatToInt32(triangleScreenSpace[1].y()*numSubPixels)), 1690 I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x()*numSubPixels), deCeilFloatToInt32(triangleScreenSpace[2].y()*numSubPixels)), 1691 }; 1692 const I64Vec2* const corners = (multisample) ? (pixelCorners) : (pixelCenterCorners); 1693 1694 // Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside 1695 1696 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx) 1697 for (int startRounding = 0; startRounding < 4; ++startRounding) 1698 for (int endRounding = 0; endRounding < 4; ++endRounding) 1699 { 1700 const int nextEdgeNdx = (edgeNdx+1) % 3; 1701 const I64Vec2 startPos ((startRounding&0x01) ? (triangleSubPixelSpaceFloor[edgeNdx].x()) : (triangleSubPixelSpaceCeil[edgeNdx].x()), (startRounding&0x02) ? (triangleSubPixelSpaceFloor[edgeNdx].y()) : (triangleSubPixelSpaceCeil[edgeNdx].y())); 1702 const I64Vec2 endPos ((endRounding&0x01) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].x()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].x()), (endRounding&0x02) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].y()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].y())); 1703 const I64Vec2 edge = endPos - startPos; 1704 1705 for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx) 1706 { 1707 const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4; 1708 1709 if (lineLineIntersect(startPos, endPos, corners[pixelEdgeNdx], corners[pixelEdgeEnd])) 1710 return COVERAGE_PARTIAL; 1711 } 1712 } 1713 1714 // fully inside or outside 1715 for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx) 1716 { 1717 const int nextEdgeNdx = (edgeNdx+1) % 3; 1718 const I64Vec2& startPos = triangleSubPixelSpaceFloor[edgeNdx]; 1719 const I64Vec2& endPos = triangleSubPixelSpaceFloor[nextEdgeNdx]; 1720 const I64Vec2 edge = endPos - startPos; 1721 const I64Vec2 v = corners[0] - endPos; 1722 const deInt64 crossProduct = (edge.x() * v.y() - edge.y() * v.x()); 1723 1724 // a corner of the pixel is outside => "fully inside" option is impossible 1725 if (crossProduct < 0) 1726 return COVERAGE_NONE; 1727 } 1728 1729 return COVERAGE_FULL; 1730 } 1731 } 1732 1733 bool verifyTriangleGroupRasterization (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, VerificationMode mode) 1734 { 1735 DE_ASSERT(mode < VERIFICATIONMODE_LAST); 1736 1737 const tcu::RGBA backGroundColor = tcu::RGBA(0, 0, 0, 255); 1738 const tcu::RGBA triangleColor = tcu::RGBA(255, 255, 255, 255); 1739 const tcu::RGBA missingPixelColor = tcu::RGBA(255, 0, 255, 255); 1740 const tcu::RGBA unexpectedPixelColor = tcu::RGBA(255, 0, 0, 255); 1741 const tcu::RGBA partialPixelColor = tcu::RGBA(255, 255, 0, 255); 1742 const tcu::RGBA primitivePixelColor = tcu::RGBA(30, 30, 30, 255); 1743 const int weakVerificationThreshold = 10; 1744 const bool multisampled = (args.numSamples != 0); 1745 const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight()); 1746 int missingPixels = 0; 1747 int unexpectedPixels = 0; 1748 int subPixelBits = args.subpixelBits; 1749 tcu::TextureLevel coverageMap (tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight()); 1750 tcu::Surface errorMask (surface.getWidth(), surface.getHeight()); 1751 1752 // subpixel bits in in a valid range? 1753 1754 if (subPixelBits < 0) 1755 { 1756 log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage; 1757 subPixelBits = 0; 1758 } 1759 else if (subPixelBits > 16) 1760 { 1761 // At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict 1762 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; 1763 subPixelBits = 16; 1764 } 1765 1766 // generate coverage map 1767 1768 tcu::clear(coverageMap.getAccess(), tcu::IVec4(COVERAGE_NONE, 0, 0, 0)); 1769 1770 for (int triNdx = 0; triNdx < (int)scene.triangles.size(); ++triNdx) 1771 { 1772 const tcu::IVec4 aabb = getTriangleAABB(scene.triangles[triNdx], viewportSize); 1773 1774 for (int y = de::max(0, aabb.y()); y <= de::min(aabb.w(), coverageMap.getHeight() - 1); ++y) 1775 for (int x = de::max(0, aabb.x()); x <= de::min(aabb.z(), coverageMap.getWidth() - 1); ++x) 1776 { 1777 if (coverageMap.getAccess().getPixelUint(x, y).x() == COVERAGE_FULL) 1778 continue; 1779 1780 const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0], 1781 scene.triangles[triNdx].positions[1], 1782 scene.triangles[triNdx].positions[2], 1783 tcu::IVec2(x, y), 1784 viewportSize, 1785 subPixelBits, 1786 multisampled); 1787 1788 if (coverage == COVERAGE_FULL) 1789 { 1790 coverageMap.getAccess().setPixel(tcu::IVec4(COVERAGE_FULL, 0, 0, 0), x, y); 1791 } 1792 else if (coverage == COVERAGE_PARTIAL) 1793 { 1794 CoverageType resultCoverage = COVERAGE_PARTIAL; 1795 1796 // Sharing an edge with another triangle? 1797 // There should always be such a triangle, but the pixel in the other triangle might be 1798 // on multiple edges, some of which are not shared. In these cases the coverage cannot be determined. 1799 // Assume full coverage if the pixel is only on a shared edge in shared triangle too. 1800 if (pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[triNdx], viewportSize)) 1801 { 1802 bool friendFound = false; 1803 for (int friendTriNdx = 0; friendTriNdx < (int)scene.triangles.size(); ++friendTriNdx) 1804 { 1805 if (friendTriNdx != triNdx && pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[friendTriNdx], viewportSize)) 1806 { 1807 friendFound = true; 1808 break; 1809 } 1810 } 1811 1812 if (friendFound) 1813 resultCoverage = COVERAGE_FULL; 1814 } 1815 1816 coverageMap.getAccess().setPixel(tcu::IVec4(resultCoverage, 0, 0, 0), x, y); 1817 } 1818 } 1819 } 1820 1821 // check pixels 1822 1823 tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 1824 1825 for (int y = 0; y < surface.getHeight(); ++y) 1826 for (int x = 0; x < surface.getWidth(); ++x) 1827 { 1828 const tcu::RGBA color = surface.getPixel(x, y); 1829 const bool imageNoCoverage = compareColors(color, backGroundColor, args.redBits, args.greenBits, args.blueBits); 1830 const bool imageFullCoverage = compareColors(color, triangleColor, args.redBits, args.greenBits, args.blueBits); 1831 CoverageType referenceCoverage = (CoverageType)coverageMap.getAccess().getPixelUint(x, y).x(); 1832 1833 switch (referenceCoverage) 1834 { 1835 case COVERAGE_NONE: 1836 if (!imageNoCoverage) 1837 { 1838 // coverage where there should not be 1839 ++unexpectedPixels; 1840 errorMask.setPixel(x, y, unexpectedPixelColor); 1841 } 1842 break; 1843 1844 case COVERAGE_PARTIAL: 1845 // anything goes 1846 errorMask.setPixel(x, y, partialPixelColor); 1847 break; 1848 1849 case COVERAGE_FULL: 1850 if (!imageFullCoverage) 1851 { 1852 // no coverage where there should be 1853 ++missingPixels; 1854 errorMask.setPixel(x, y, missingPixelColor); 1855 } 1856 else 1857 { 1858 errorMask.setPixel(x, y, primitivePixelColor); 1859 } 1860 break; 1861 1862 default: 1863 DE_ASSERT(false); 1864 }; 1865 } 1866 1867 // Output results 1868 log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage; 1869 1870 if (((mode == VERIFICATIONMODE_STRICT) && (missingPixels + unexpectedPixels > 0)) || 1871 ((mode == VERIFICATIONMODE_WEAK) && (missingPixels + unexpectedPixels > weakVerificationThreshold))) 1872 { 1873 log << tcu::TestLog::Message << "Invalid pixels found:\n\t" 1874 << missingPixels << " missing pixels. (Marked with purple)\n\t" 1875 << unexpectedPixels << " incorrectly filled pixels. (Marked with red)\n\t" 1876 << "Unknown (subpixel on edge) pixels are marked with yellow." 1877 << tcu::TestLog::EndMessage; 1878 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1879 << tcu::TestLog::Image("Result", "Result", surface) 1880 << tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask) 1881 << tcu::TestLog::EndImageSet; 1882 1883 return false; 1884 } 1885 else 1886 { 1887 log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage; 1888 log << tcu::TestLog::ImageSet("Verification result", "Result of rendering") 1889 << tcu::TestLog::Image("Result", "Result", surface) 1890 << tcu::TestLog::EndImageSet; 1891 1892 return true; 1893 } 1894 } 1895 1896 bool verifyLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1897 { 1898 const bool multisampled = args.numSamples != 0; 1899 1900 if (multisampled) 1901 return verifyMultisampleLineGroupRasterization(surface, scene, args, log); 1902 else 1903 return verifySinglesampleLineGroupRasterization(surface, scene, args, log); 1904 } 1905 1906 bool verifyPointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1907 { 1908 // Splitting to triangles is a valid solution in multisampled cases and even in non-multisample cases too. 1909 return verifyMultisamplePointGroupRasterization(surface, scene, args, log); 1910 } 1911 1912 bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1913 { 1914 return verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, log, TriangleInterpolator(scene)); 1915 } 1916 1917 bool verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log) 1918 { 1919 const bool multisampled = args.numSamples != 0; 1920 1921 if (multisampled) 1922 return verifyMultisampleLineGroupInterpolation(surface, scene, args, log); 1923 else 1924 return verifySinglesampleLineGroupInterpolation(surface, scene, args, log); 1925 } 1926 1927 } // StateQueryUtil 1928 } // gls 1929 } // deqp 1930