2999 lines
127 KiB
2999 lines
127 KiB
/*-------------------------------------------------------------------------
|
|
* drawElements Quality Program Tester Core
|
|
* ----------------------------------------
|
|
*
|
|
* Copyright 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*//*!
|
|
* \file
|
|
* \brief Rasterization verifier utils.
|
|
*//*--------------------------------------------------------------------*/
|
|
|
|
#include "tcuRasterizationVerifier.hpp"
|
|
#include "tcuVector.hpp"
|
|
#include "tcuSurface.hpp"
|
|
#include "tcuTestLog.hpp"
|
|
#include "tcuTextureUtil.hpp"
|
|
#include "tcuVectorUtil.hpp"
|
|
#include "tcuFloat.hpp"
|
|
|
|
#include "deMath.h"
|
|
#include "deStringUtil.hpp"
|
|
|
|
#include "rrRasterizer.hpp"
|
|
|
|
#include <limits>
|
|
|
|
namespace tcu
|
|
{
|
|
namespace
|
|
{
|
|
|
|
bool verifyLineGroupInterpolationWithProjectedWeights (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
|
|
|
|
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)
|
|
{
|
|
typedef tcu::Vector<deInt64, 2> I64Vec2;
|
|
|
|
// Lines do not intersect if the other line's endpoints are on the same side
|
|
// otherwise, the do intersect
|
|
|
|
// Test line 0
|
|
{
|
|
const I64Vec2 line = line0End - line0Beg;
|
|
const I64Vec2 v0 = line1Beg - line0Beg;
|
|
const I64Vec2 v1 = line1End - line0Beg;
|
|
const deInt64 crossProduct0 = (line.x() * v0.y() - line.y() * v0.x());
|
|
const deInt64 crossProduct1 = (line.x() * v1.y() - line.y() * v1.x());
|
|
|
|
// check signs
|
|
if ((crossProduct0 < 0 && crossProduct1 < 0) ||
|
|
(crossProduct0 > 0 && crossProduct1 > 0))
|
|
return false;
|
|
}
|
|
|
|
// Test line 1
|
|
{
|
|
const I64Vec2 line = line1End - line1Beg;
|
|
const I64Vec2 v0 = line0Beg - line1Beg;
|
|
const I64Vec2 v1 = line0End - line1Beg;
|
|
const deInt64 crossProduct0 = (line.x() * v0.y() - line.y() * v0.x());
|
|
const deInt64 crossProduct1 = (line.x() * v1.y() - line.y() * v1.x());
|
|
|
|
// check signs
|
|
if ((crossProduct0 < 0 && crossProduct1 < 0) ||
|
|
(crossProduct0 > 0 && crossProduct1 > 0))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isTriangleClockwise (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2)
|
|
{
|
|
const tcu::Vec2 u (p1.x() / p1.w() - p0.x() / p0.w(), p1.y() / p1.w() - p0.y() / p0.w());
|
|
const tcu::Vec2 v (p2.x() / p2.w() - p0.x() / p0.w(), p2.y() / p2.w() - p0.y() / p0.w());
|
|
const float crossProduct = (u.x() * v.y() - u.y() * v.x());
|
|
|
|
return crossProduct > 0.0f;
|
|
}
|
|
|
|
bool compareColors (const tcu::RGBA& colorA, const tcu::RGBA& colorB, int redBits, int greenBits, int blueBits)
|
|
{
|
|
const int thresholdRed = 1 << (8 - redBits);
|
|
const int thresholdGreen = 1 << (8 - greenBits);
|
|
const int thresholdBlue = 1 << (8 - blueBits);
|
|
|
|
return deAbs32(colorA.getRed() - colorB.getRed()) <= thresholdRed &&
|
|
deAbs32(colorA.getGreen() - colorB.getGreen()) <= thresholdGreen &&
|
|
deAbs32(colorA.getBlue() - colorB.getBlue()) <= thresholdBlue;
|
|
}
|
|
|
|
bool pixelNearLineSegment (const tcu::IVec2& pixel, const tcu::Vec2& p0, const tcu::Vec2& p1)
|
|
{
|
|
const tcu::Vec2 pixelCenterPosition = tcu::Vec2((float)pixel.x() + 0.5f, (float)pixel.y() + 0.5f);
|
|
|
|
// "Near" = Distance from the line to the pixel is less than 2 * pixel_max_radius. (pixel_max_radius = sqrt(2) / 2)
|
|
const float maxPixelDistance = 1.414f;
|
|
const float maxPixelDistanceSquared = 2.0f;
|
|
|
|
// Near the line
|
|
{
|
|
const tcu::Vec2 line = p1 - p0;
|
|
const tcu::Vec2 v = pixelCenterPosition - p0;
|
|
const float crossProduct = (line.x() * v.y() - line.y() * v.x());
|
|
|
|
// distance to line: (line x v) / |line|
|
|
// |(line x v) / |line|| > maxPixelDistance
|
|
// ==> (line x v)^2 / |line|^2 > maxPixelDistance^2
|
|
// ==> (line x v)^2 > maxPixelDistance^2 * |line|^2
|
|
|
|
if (crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(line))
|
|
return false;
|
|
}
|
|
|
|
// Between the endpoints
|
|
{
|
|
// distance from line endpoint 1 to pixel is less than line length + maxPixelDistance
|
|
const float maxDistance = tcu::length(p1 - p0) + maxPixelDistance;
|
|
|
|
if (tcu::length(pixelCenterPosition - p0) > maxDistance)
|
|
return false;
|
|
if (tcu::length(pixelCenterPosition - p1) > maxDistance)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pixelOnlyOnASharedEdge (const tcu::IVec2& pixel, const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize)
|
|
{
|
|
if (triangle.sharedEdge[0] || triangle.sharedEdge[1] || triangle.sharedEdge[2])
|
|
{
|
|
const tcu::Vec2 triangleNormalizedDeviceSpace[3] =
|
|
{
|
|
tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()),
|
|
tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()),
|
|
tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()),
|
|
};
|
|
const tcu::Vec2 triangleScreenSpace[3] =
|
|
{
|
|
(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
};
|
|
|
|
const bool pixelOnEdge0 = pixelNearLineSegment(pixel, triangleScreenSpace[0], triangleScreenSpace[1]);
|
|
const bool pixelOnEdge1 = pixelNearLineSegment(pixel, triangleScreenSpace[1], triangleScreenSpace[2]);
|
|
const bool pixelOnEdge2 = pixelNearLineSegment(pixel, triangleScreenSpace[2], triangleScreenSpace[0]);
|
|
|
|
// If the pixel is on a multiple edges return false
|
|
|
|
if (pixelOnEdge0 && !pixelOnEdge1 && !pixelOnEdge2)
|
|
return triangle.sharedEdge[0];
|
|
if (!pixelOnEdge0 && pixelOnEdge1 && !pixelOnEdge2)
|
|
return triangle.sharedEdge[1];
|
|
if (!pixelOnEdge0 && !pixelOnEdge1 && pixelOnEdge2)
|
|
return triangle.sharedEdge[2];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float triangleArea (const tcu::Vec2& s0, const tcu::Vec2& s1, const tcu::Vec2& s2)
|
|
{
|
|
const tcu::Vec2 u (s1.x() - s0.x(), s1.y() - s0.y());
|
|
const tcu::Vec2 v (s2.x() - s0.x(), s2.y() - s0.y());
|
|
const float crossProduct = (u.x() * v.y() - u.y() * v.x());
|
|
|
|
return crossProduct / 2.0f;
|
|
}
|
|
|
|
tcu::IVec4 getTriangleAABB (const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize)
|
|
{
|
|
const tcu::Vec2 normalizedDeviceSpace[3] =
|
|
{
|
|
tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()),
|
|
tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()),
|
|
tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()),
|
|
};
|
|
const tcu::Vec2 screenSpace[3] =
|
|
{
|
|
(normalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(normalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(normalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
};
|
|
|
|
tcu::IVec4 aabb;
|
|
|
|
aabb.x() = (int)deFloatFloor(de::min(de::min(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
|
|
aabb.y() = (int)deFloatFloor(de::min(de::min(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
|
|
aabb.z() = (int)deFloatCeil (de::max(de::max(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
|
|
aabb.w() = (int)deFloatCeil (de::max(de::max(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
|
|
|
|
return aabb;
|
|
}
|
|
|
|
float getExponentEpsilonFromULP (int valueExponent, deUint32 ulp)
|
|
{
|
|
DE_ASSERT(ulp < (1u<<10));
|
|
|
|
// assume mediump precision, using ulp as ulps in a 10 bit mantissa
|
|
return tcu::Float32::construct(+1, valueExponent, (1u<<23) + (ulp << (23 - 10))).asFloat() - tcu::Float32::construct(+1, valueExponent, (1u<<23)).asFloat();
|
|
}
|
|
|
|
float getValueEpsilonFromULP (float value, deUint32 ulp)
|
|
{
|
|
DE_ASSERT(value != std::numeric_limits<float>::infinity() && value != -std::numeric_limits<float>::infinity());
|
|
|
|
const int exponent = tcu::Float32(value).exponent();
|
|
return getExponentEpsilonFromULP(exponent, ulp);
|
|
}
|
|
|
|
float getMaxValueWithinError (float value, deUint32 ulp)
|
|
{
|
|
if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
|
|
return value;
|
|
|
|
return value + getValueEpsilonFromULP(value, ulp);
|
|
}
|
|
|
|
float getMinValueWithinError (float value, deUint32 ulp)
|
|
{
|
|
if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
|
|
return value;
|
|
|
|
return value - getValueEpsilonFromULP(value, ulp);
|
|
}
|
|
|
|
float getMinFlushToZero (float value)
|
|
{
|
|
// flush to zero if that decreases the value
|
|
// assume mediump precision
|
|
if (value > 0.0f && value < tcu::Float32::construct(+1, -14, 1u<<23).asFloat())
|
|
return 0.0f;
|
|
return value;
|
|
}
|
|
|
|
float getMaxFlushToZero (float value)
|
|
{
|
|
// flush to zero if that increases the value
|
|
// assume mediump precision
|
|
if (value < 0.0f && value > tcu::Float32::construct(-1, -14, 1u<<23).asFloat())
|
|
return 0.0f;
|
|
return value;
|
|
}
|
|
|
|
tcu::IVec3 convertRGB8ToNativeFormat (const tcu::RGBA& color, const RasterizationArguments& args)
|
|
{
|
|
tcu::IVec3 pixelNativeColor;
|
|
|
|
for (int channelNdx = 0; channelNdx < 3; ++channelNdx)
|
|
{
|
|
const int channelBitCount = (channelNdx == 0) ? (args.redBits) : (channelNdx == 1) ? (args.greenBits) : (args.blueBits);
|
|
const int channelPixelValue = (channelNdx == 0) ? (color.getRed()) : (channelNdx == 1) ? (color.getGreen()) : (color.getBlue());
|
|
|
|
if (channelBitCount <= 8)
|
|
pixelNativeColor[channelNdx] = channelPixelValue >> (8 - channelBitCount);
|
|
else if (channelBitCount == 8)
|
|
pixelNativeColor[channelNdx] = channelPixelValue;
|
|
else
|
|
{
|
|
// just in case someone comes up with 8+ bits framebuffers pixel formats. But as
|
|
// we can only read in rgba8, we have to guess the trailing bits. Guessing 0.
|
|
pixelNativeColor[channelNdx] = channelPixelValue << (channelBitCount - 8);
|
|
}
|
|
}
|
|
|
|
return pixelNativeColor;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*//*!
|
|
* Returns the maximum value of x / y, where x c [minDividend, maxDividend]
|
|
* and y c [minDivisor, maxDivisor]
|
|
*//*--------------------------------------------------------------------*/
|
|
float maximalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor)
|
|
{
|
|
DE_ASSERT(minDividend <= maxDividend);
|
|
DE_ASSERT(minDivisor <= maxDivisor);
|
|
|
|
// special cases
|
|
if (minDividend == 0.0f && maxDividend == 0.0f)
|
|
return 0.0f;
|
|
if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
|
|
return std::numeric_limits<float>::infinity();
|
|
|
|
return de::max(de::max(minDividend / minDivisor, minDividend / maxDivisor), de::max(maxDividend / minDivisor, maxDividend / maxDivisor));
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*//*!
|
|
* Returns the minimum value of x / y, where x c [minDividend, maxDividend]
|
|
* and y c [minDivisor, maxDivisor]
|
|
*//*--------------------------------------------------------------------*/
|
|
float minimalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor)
|
|
{
|
|
DE_ASSERT(minDividend <= maxDividend);
|
|
DE_ASSERT(minDivisor <= maxDivisor);
|
|
|
|
// special cases
|
|
if (minDividend == 0.0f && maxDividend == 0.0f)
|
|
return 0.0f;
|
|
if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
|
|
return -std::numeric_limits<float>::infinity();
|
|
|
|
return de::min(de::min(minDividend / minDivisor, minDividend / maxDivisor), de::min(maxDividend / minDivisor, maxDividend / maxDivisor));
|
|
}
|
|
|
|
static bool isLineXMajor (const tcu::Vec2& lineScreenSpaceP0, const tcu::Vec2& lineScreenSpaceP1)
|
|
{
|
|
return de::abs(lineScreenSpaceP1.x() - lineScreenSpaceP0.x()) >= de::abs(lineScreenSpaceP1.y() - lineScreenSpaceP0.y());
|
|
}
|
|
|
|
static bool isPackedSSLineXMajor (const tcu::Vec4& packedLine)
|
|
{
|
|
const tcu::Vec2 lineScreenSpaceP0 = packedLine.swizzle(0, 1);
|
|
const tcu::Vec2 lineScreenSpaceP1 = packedLine.swizzle(2, 3);
|
|
|
|
return isLineXMajor(lineScreenSpaceP0, lineScreenSpaceP1);
|
|
}
|
|
|
|
struct InterpolationRange
|
|
{
|
|
tcu::Vec3 max;
|
|
tcu::Vec3 min;
|
|
};
|
|
|
|
struct LineInterpolationRange
|
|
{
|
|
tcu::Vec2 max;
|
|
tcu::Vec2 min;
|
|
};
|
|
|
|
InterpolationRange calcTriangleInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::Vec2& ndpixel)
|
|
{
|
|
const int roundError = 1;
|
|
const int barycentricError = 3;
|
|
const int divError = 8;
|
|
|
|
const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
|
|
const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
|
|
const tcu::Vec2 nd2 = p2.swizzle(0, 1) / p2.w();
|
|
|
|
const float ka = triangleArea(ndpixel, nd1, nd2);
|
|
const float kb = triangleArea(ndpixel, nd2, nd0);
|
|
const float kc = triangleArea(ndpixel, nd0, nd1);
|
|
|
|
const float kaMax = getMaxFlushToZero(getMaxValueWithinError(ka, barycentricError));
|
|
const float kbMax = getMaxFlushToZero(getMaxValueWithinError(kb, barycentricError));
|
|
const float kcMax = getMaxFlushToZero(getMaxValueWithinError(kc, barycentricError));
|
|
const float kaMin = getMinFlushToZero(getMinValueWithinError(ka, barycentricError));
|
|
const float kbMin = getMinFlushToZero(getMinValueWithinError(kb, barycentricError));
|
|
const float kcMin = getMinFlushToZero(getMinValueWithinError(kc, barycentricError));
|
|
DE_ASSERT(kaMin <= kaMax);
|
|
DE_ASSERT(kbMin <= kbMax);
|
|
DE_ASSERT(kcMin <= kcMax);
|
|
|
|
// calculate weights: vec3(ka / p0.w, kb / p1.w, kc / p2.w) / (ka / p0.w + kb / p1.w + kc / p2.w)
|
|
const float maxPreDivisionValues[3] =
|
|
{
|
|
getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kaMax / p0.w()), divError)),
|
|
getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kbMax / p1.w()), divError)),
|
|
getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kcMax / p2.w()), divError)),
|
|
};
|
|
const float minPreDivisionValues[3] =
|
|
{
|
|
getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kaMin / p0.w()), divError)),
|
|
getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kbMin / p1.w()), divError)),
|
|
getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kcMin / p2.w()), divError)),
|
|
};
|
|
DE_ASSERT(minPreDivisionValues[0] <= maxPreDivisionValues[0]);
|
|
DE_ASSERT(minPreDivisionValues[1] <= maxPreDivisionValues[1]);
|
|
DE_ASSERT(minPreDivisionValues[2] <= maxPreDivisionValues[2]);
|
|
|
|
const float maxDivisor = getMaxFlushToZero(getMaxValueWithinError(maxPreDivisionValues[0] + maxPreDivisionValues[1] + maxPreDivisionValues[2], 2*roundError));
|
|
const float minDivisor = getMinFlushToZero(getMinValueWithinError(minPreDivisionValues[0] + minPreDivisionValues[1] + minPreDivisionValues[2], 2*roundError));
|
|
DE_ASSERT(minDivisor <= maxDivisor);
|
|
|
|
InterpolationRange returnValue;
|
|
|
|
returnValue.max.x() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError));
|
|
returnValue.max.y() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError));
|
|
returnValue.max.z() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError));
|
|
returnValue.min.x() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError));
|
|
returnValue.min.y() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError));
|
|
returnValue.min.z() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError));
|
|
|
|
DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
|
|
DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
|
|
DE_ASSERT(returnValue.min.z() <= returnValue.max.z());
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
LineInterpolationRange calcLineInterpolationWeights (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr)
|
|
{
|
|
const int roundError = 1;
|
|
const int divError = 3;
|
|
|
|
// calc weights:
|
|
// (1-t) / wa t / wb
|
|
// ------------------- , -------------------
|
|
// (1-t) / wa + t / wb (1-t) / wa + t / wb
|
|
|
|
// Allow 1 ULP
|
|
const float dividend = tcu::dot(pr - pa, pb - pa);
|
|
const float dividendMax = getMaxValueWithinError(dividend, 1);
|
|
const float dividendMin = getMinValueWithinError(dividend, 1);
|
|
DE_ASSERT(dividendMin <= dividendMax);
|
|
|
|
// Assuming lengthSquared will not be implemented as sqrt(x)^2, allow 1 ULP
|
|
const float divisor = tcu::lengthSquared(pb - pa);
|
|
const float divisorMax = getMaxValueWithinError(divisor, 1);
|
|
const float divisorMin = getMinValueWithinError(divisor, 1);
|
|
DE_ASSERT(divisorMin <= divisorMax);
|
|
|
|
// Allow 3 ULP precision for division
|
|
const float tMax = getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
|
|
const float tMin = getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
|
|
DE_ASSERT(tMin <= tMax);
|
|
|
|
const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError);
|
|
const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError);
|
|
DE_ASSERT(perspectiveTMin <= perspectiveTMax);
|
|
|
|
const float perspectiveInvTMax = getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
|
|
const float perspectiveInvTMin = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
|
|
DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);
|
|
|
|
const float perspectiveDivisorMax = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
|
|
const float perspectiveDivisorMin = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
|
|
DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
|
|
|
|
LineInterpolationRange returnValue;
|
|
returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
|
|
DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
|
|
DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
LineInterpolationRange calcLineInterpolationWeightsAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr)
|
|
{
|
|
const int roundError = 1;
|
|
const int divError = 3;
|
|
const bool isXMajor = isLineXMajor(pa, pb);
|
|
const int majorAxisNdx = (isXMajor) ? (0) : (1);
|
|
|
|
// calc weights:
|
|
// (1-t) / wa t / wb
|
|
// ------------------- , -------------------
|
|
// (1-t) / wa + t / wb (1-t) / wa + t / wb
|
|
|
|
// Use axis projected (inaccurate) method, i.e. for X-major lines:
|
|
// (xd - xa) * (xb - xa) xd - xa
|
|
// t = --------------------- == -------
|
|
// ( xb - xa ) ^ 2 xb - xa
|
|
|
|
// Allow 1 ULP
|
|
const float dividend = (pr[majorAxisNdx] - pa[majorAxisNdx]);
|
|
const float dividendMax = getMaxValueWithinError(dividend, 1);
|
|
const float dividendMin = getMinValueWithinError(dividend, 1);
|
|
DE_ASSERT(dividendMin <= dividendMax);
|
|
|
|
// Allow 1 ULP
|
|
const float divisor = (pb[majorAxisNdx] - pa[majorAxisNdx]);
|
|
const float divisorMax = getMaxValueWithinError(divisor, 1);
|
|
const float divisorMin = getMinValueWithinError(divisor, 1);
|
|
DE_ASSERT(divisorMin <= divisorMax);
|
|
|
|
// Allow 3 ULP precision for division
|
|
const float tMax = getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
|
|
const float tMin = getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
|
|
DE_ASSERT(tMin <= tMax);
|
|
|
|
const float perspectiveTMax = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError);
|
|
const float perspectiveTMin = getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError);
|
|
DE_ASSERT(perspectiveTMin <= perspectiveTMax);
|
|
|
|
const float perspectiveInvTMax = getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
|
|
const float perspectiveInvTMin = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
|
|
DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);
|
|
|
|
const float perspectiveDivisorMax = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
|
|
const float perspectiveDivisorMin = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
|
|
DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
|
|
|
|
LineInterpolationRange returnValue;
|
|
returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin, perspectiveInvTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin, perspectiveTMax, perspectiveDivisorMin, perspectiveDivisorMax), divError);
|
|
|
|
DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
|
|
DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
template <typename WeightEquation>
|
|
LineInterpolationRange calcSingleSampleLineInterpolationRangeWithWeightEquation (const tcu::Vec2& pa,
|
|
float wa,
|
|
const tcu::Vec2& pb,
|
|
float wb,
|
|
const tcu::IVec2& pixel,
|
|
int subpixelBits,
|
|
WeightEquation weightEquation)
|
|
{
|
|
// allow interpolation weights anywhere in the central subpixels
|
|
const float testSquareSize = (2.0f / (float)(1UL << subpixelBits));
|
|
const float testSquarePos = (0.5f - testSquareSize / 2);
|
|
|
|
const tcu::Vec2 corners[4] =
|
|
{
|
|
tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f, (float)pixel.y() + testSquarePos + 0.0f),
|
|
tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f, (float)pixel.y() + testSquarePos + testSquareSize),
|
|
tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize, (float)pixel.y() + testSquarePos + testSquareSize),
|
|
tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize, (float)pixel.y() + testSquarePos + 0.0f),
|
|
};
|
|
|
|
// calculate interpolation as a line
|
|
const LineInterpolationRange weights[4] =
|
|
{
|
|
weightEquation(pa, wa, pb, wb, corners[0]),
|
|
weightEquation(pa, wa, pb, wb, corners[1]),
|
|
weightEquation(pa, wa, pb, wb, corners[2]),
|
|
weightEquation(pa, wa, pb, wb, corners[3]),
|
|
};
|
|
|
|
const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
|
|
const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
|
|
|
|
LineInterpolationRange result;
|
|
result.min = minWeights;
|
|
result.max = maxWeights;
|
|
return result;
|
|
}
|
|
|
|
LineInterpolationRange calcSingleSampleLineInterpolationRange (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::IVec2& pixel, int subpixelBits)
|
|
{
|
|
return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits, calcLineInterpolationWeights);
|
|
}
|
|
|
|
LineInterpolationRange calcSingleSampleLineInterpolationRangeAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::IVec2& pixel, int subpixelBits)
|
|
{
|
|
return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits, calcLineInterpolationWeightsAxisProjected);
|
|
}
|
|
|
|
struct TriangleInterpolator
|
|
{
|
|
const TriangleSceneSpec& scene;
|
|
|
|
TriangleInterpolator (const TriangleSceneSpec& scene_)
|
|
: scene(scene_)
|
|
{
|
|
}
|
|
|
|
InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const
|
|
{
|
|
// allow anywhere in the pixel area in multisample
|
|
// allow only in the center subpixels (4 subpixels) in singlesample
|
|
const float testSquareSize = (multisample) ? (1.0f) : (2.0f / (float)(1UL << subpixelBits));
|
|
const float testSquarePos = (multisample) ? (0.0f) : (0.5f - testSquareSize / 2);
|
|
const tcu::Vec2 corners[4] =
|
|
{
|
|
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),
|
|
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),
|
|
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),
|
|
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),
|
|
};
|
|
const InterpolationRange weights[4] =
|
|
{
|
|
calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[0]),
|
|
calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[1]),
|
|
calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[2]),
|
|
calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[3]),
|
|
};
|
|
|
|
InterpolationRange result;
|
|
result.min = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
|
|
result.max = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
|
|
return result;
|
|
}
|
|
};
|
|
|
|
/*--------------------------------------------------------------------*//*!
|
|
* Used only by verifyMultisampleLineGroupInterpolation to calculate
|
|
* correct line interpolations for the triangulated lines.
|
|
*//*--------------------------------------------------------------------*/
|
|
struct MultisampleLineInterpolator
|
|
{
|
|
const LineSceneSpec& scene;
|
|
|
|
MultisampleLineInterpolator (const LineSceneSpec& scene_)
|
|
: scene(scene_)
|
|
{
|
|
}
|
|
|
|
InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const
|
|
{
|
|
DE_UNREF(multisample);
|
|
DE_UNREF(subpixelBits);
|
|
|
|
// in triangulation, one line emits two triangles
|
|
const int lineNdx = primitiveNdx / 2;
|
|
|
|
// allow interpolation weights anywhere in the pixel
|
|
const tcu::Vec2 corners[4] =
|
|
{
|
|
tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 0.0f),
|
|
tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 1.0f),
|
|
tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 1.0f),
|
|
tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 0.0f),
|
|
};
|
|
|
|
const float wa = scene.lines[lineNdx].positions[0].w();
|
|
const float wb = scene.lines[lineNdx].positions[1].w();
|
|
const tcu::Vec2 pa = tcu::Vec2((scene.lines[lineNdx].positions[0].x() / wa + 1.0f) * 0.5f * (float)viewportSize.x(),
|
|
(scene.lines[lineNdx].positions[0].y() / wa + 1.0f) * 0.5f * (float)viewportSize.y());
|
|
const tcu::Vec2 pb = tcu::Vec2((scene.lines[lineNdx].positions[1].x() / wb + 1.0f) * 0.5f * (float)viewportSize.x(),
|
|
(scene.lines[lineNdx].positions[1].y() / wb + 1.0f) * 0.5f * (float)viewportSize.y());
|
|
|
|
// calculate interpolation as a line
|
|
const LineInterpolationRange weights[4] =
|
|
{
|
|
calcLineInterpolationWeights(pa, wa, pb, wb, corners[0]),
|
|
calcLineInterpolationWeights(pa, wa, pb, wb, corners[1]),
|
|
calcLineInterpolationWeights(pa, wa, pb, wb, corners[2]),
|
|
calcLineInterpolationWeights(pa, wa, pb, wb, corners[3]),
|
|
};
|
|
|
|
const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
|
|
const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
|
|
|
|
// 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
|
|
InterpolationRange result;
|
|
result.min = tcu::Vec3(minWeights.x(), 0.0f, minWeights.y());
|
|
result.max = tcu::Vec3(maxWeights.x(), 0.0f, maxWeights.y());
|
|
return result;
|
|
}
|
|
};
|
|
|
|
template <typename Interpolator>
|
|
bool verifyTriangleGroupInterpolationWithInterpolator (const tcu::Surface& surface,
|
|
const TriangleSceneSpec& scene,
|
|
const RasterizationArguments& args,
|
|
VerifyTriangleGroupInterpolationLogStash& logStash,
|
|
const Interpolator& interpolator)
|
|
{
|
|
const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255);
|
|
const bool multisampled = (args.numSamples != 0);
|
|
const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
|
|
const int errorFloodThreshold = 4;
|
|
int errorCount = 0;
|
|
int invalidPixels = 0;
|
|
int subPixelBits = args.subpixelBits;
|
|
tcu::Surface errorMask (surface.getWidth(), surface.getHeight());
|
|
|
|
tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
|
|
|
// log format
|
|
|
|
logStash.messages.push_back(std::string("Verifying rasterization result. Native format is RGB" + de::toString(args.redBits) + de::toString(args.greenBits) + de::toString(args.blueBits)));
|
|
if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
|
|
logStash.messages.push_back(std::string("Warning! More than 8 bits in a color channel, this may produce false negatives."));
|
|
|
|
// subpixel bits in a valid range?
|
|
|
|
if (subPixelBits < 0)
|
|
{
|
|
logStash.messages.push_back(std::string("Invalid subpixel count (" + de::toString(subPixelBits) + "), assuming 0"));
|
|
subPixelBits = 0;
|
|
}
|
|
else if (subPixelBits > 16)
|
|
{
|
|
// At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
|
|
logStash.messages.push_back(std::string("Subpixel count is greater than 16 (" + de::toString(subPixelBits) + ")."
|
|
" Checking results using less strict 16 bit requirements. This may produce false positives."));
|
|
subPixelBits = 16;
|
|
}
|
|
|
|
// check pixels
|
|
|
|
for (int y = 0; y < surface.getHeight(); ++y)
|
|
for (int x = 0; x < surface.getWidth(); ++x)
|
|
{
|
|
const tcu::RGBA color = surface.getPixel(x, y);
|
|
bool stackBottomFound = false;
|
|
int stackSize = 0;
|
|
tcu::Vec4 colorStackMin;
|
|
tcu::Vec4 colorStackMax;
|
|
|
|
// Iterate triangle coverage front to back, find the stack of pontentially contributing fragments
|
|
for (int triNdx = (int)scene.triangles.size() - 1; triNdx >= 0; --triNdx)
|
|
{
|
|
const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0],
|
|
scene.triangles[triNdx].positions[1],
|
|
scene.triangles[triNdx].positions[2],
|
|
tcu::IVec2(x, y),
|
|
viewportSize,
|
|
subPixelBits,
|
|
multisampled);
|
|
|
|
if (coverage == COVERAGE_FULL || coverage == COVERAGE_PARTIAL)
|
|
{
|
|
// potentially contributes to the result fragment's value
|
|
const InterpolationRange weights = interpolator.interpolate(triNdx, tcu::IVec2(x, y), viewportSize, multisampled, subPixelBits);
|
|
|
|
const tcu::Vec4 fragmentColorMax = de::clamp(weights.max.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
|
|
de::clamp(weights.max.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
|
|
de::clamp(weights.max.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
|
|
const tcu::Vec4 fragmentColorMin = de::clamp(weights.min.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
|
|
de::clamp(weights.min.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
|
|
de::clamp(weights.min.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
|
|
|
|
if (stackSize++ == 0)
|
|
{
|
|
// first triangle, set the values properly
|
|
colorStackMin = fragmentColorMin;
|
|
colorStackMax = fragmentColorMax;
|
|
}
|
|
else
|
|
{
|
|
// contributing triangle
|
|
colorStackMin = tcu::min(colorStackMin, fragmentColorMin);
|
|
colorStackMax = tcu::max(colorStackMax, fragmentColorMax);
|
|
}
|
|
|
|
if (coverage == COVERAGE_FULL)
|
|
{
|
|
// loop terminates, this is the bottommost fragment
|
|
stackBottomFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Partial coverage == background may be visible
|
|
if (stackSize != 0 && !stackBottomFound)
|
|
{
|
|
stackSize++;
|
|
colorStackMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
|
}
|
|
|
|
// Is the result image color in the valid range.
|
|
if (stackSize == 0)
|
|
{
|
|
// No coverage, allow only background (black, value=0)
|
|
const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args);
|
|
const int threshold = 1;
|
|
|
|
if (pixelNativeColor.x() > threshold ||
|
|
pixelNativeColor.y() > threshold ||
|
|
pixelNativeColor.z() > threshold)
|
|
{
|
|
++errorCount;
|
|
|
|
// don't fill the logs with too much data
|
|
if (errorCount < errorFloodThreshold)
|
|
{
|
|
std::ostringstream str;
|
|
|
|
str << "Found an invalid pixel at (" << x << "," << y << ")\n"
|
|
<< "\tPixel color:\t\t" << color << "\n"
|
|
<< "\tExpected background color.\n";
|
|
|
|
logStash.messages.push_back(str.str());
|
|
}
|
|
|
|
++invalidPixels;
|
|
errorMask.setPixel(x, y, invalidPixelColor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DE_ASSERT(stackSize);
|
|
|
|
// Each additional step in the stack may cause conversion error of 1 bit due to undefined rounding direction
|
|
const int thresholdRed = stackSize - 1;
|
|
const int thresholdGreen = stackSize - 1;
|
|
const int thresholdBlue = stackSize - 1;
|
|
|
|
const tcu::Vec3 valueRangeMin = tcu::Vec3(colorStackMin.xyz());
|
|
const tcu::Vec3 valueRangeMax = tcu::Vec3(colorStackMax.xyz());
|
|
|
|
const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
|
|
const tcu::Vec3 colorMinF (de::clamp(valueRangeMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
|
|
de::clamp(valueRangeMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
|
|
de::clamp(valueRangeMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
|
|
const tcu::Vec3 colorMaxF (de::clamp(valueRangeMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
|
|
de::clamp(valueRangeMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
|
|
de::clamp(valueRangeMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
|
|
const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()),
|
|
(int)deFloatFloor(colorMinF.y()),
|
|
(int)deFloatFloor(colorMinF.z()));
|
|
const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()),
|
|
(int)deFloatCeil (colorMaxF.y()),
|
|
(int)deFloatCeil (colorMaxF.z()));
|
|
|
|
// Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
|
|
const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args);
|
|
|
|
// Validity check
|
|
if (pixelNativeColor.x() < colorMin.x() - thresholdRed ||
|
|
pixelNativeColor.y() < colorMin.y() - thresholdGreen ||
|
|
pixelNativeColor.z() < colorMin.z() - thresholdBlue ||
|
|
pixelNativeColor.x() > colorMax.x() + thresholdRed ||
|
|
pixelNativeColor.y() > colorMax.y() + thresholdGreen ||
|
|
pixelNativeColor.z() > colorMax.z() + thresholdBlue)
|
|
{
|
|
++errorCount;
|
|
|
|
// don't fill the logs with too much data
|
|
if (errorCount <= errorFloodThreshold)
|
|
{
|
|
std::ostringstream str;
|
|
|
|
str << "Found an invalid pixel at (" << x << "," << y << ")\n"
|
|
<< "\tPixel color:\t\t" << color << "\n"
|
|
<< "\tNative color:\t\t" << pixelNativeColor << "\n"
|
|
<< "\tAllowed error:\t\t" << tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue) << "\n"
|
|
<< "\tReference native color min: " << tcu::clamp(colorMin - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n"
|
|
<< "\tReference native color max: " << tcu::clamp(colorMax + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n"
|
|
<< "\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"
|
|
<< "\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"
|
|
<< "\tFmin:\t" << tcu::clamp(valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
|
|
<< "\tFmax:\t" << tcu::clamp(valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n";
|
|
logStash.messages.push_back(str.str());
|
|
}
|
|
|
|
++invalidPixels;
|
|
errorMask.setPixel(x, y, invalidPixelColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't just hide failures
|
|
if (errorCount > errorFloodThreshold)
|
|
logStash.messages.push_back(std::string("Omitted " + de::toString(errorCount - errorFloodThreshold) + " pixel error description(s)."));
|
|
|
|
logStash.success = (invalidPixels == 0);
|
|
logStash.invalidPixels = invalidPixels;
|
|
|
|
// report result
|
|
if (!logStash.success)
|
|
logStash.errorMask = errorMask;
|
|
|
|
return logStash.success;
|
|
}
|
|
|
|
|
|
float calculateIntersectionParameter (const tcu::Vec2 line[2], float w, int componentNdx)
|
|
{
|
|
DE_ASSERT(componentNdx < 2);
|
|
if (line[1][componentNdx] == line[0][componentNdx])
|
|
return -1.0f;
|
|
|
|
return (w - line[0][componentNdx]) / (line[1][componentNdx] - line[0][componentNdx]);
|
|
}
|
|
|
|
// Clips the given line with a ((-w, -w), (-w, w), (w, w), (w, -w)) rectangle
|
|
void applyClippingBox (tcu::Vec2 line[2], float w)
|
|
{
|
|
for (int side = 0; side < 4; ++side)
|
|
{
|
|
const int sign = ((side / 2) * -2) + 1;
|
|
const int component = side % 2;
|
|
const float t = calculateIntersectionParameter(line, w * (float)sign, component);
|
|
|
|
if ((t > 0) && (t < 1))
|
|
{
|
|
const float newCoord = t * line[1][1 - component] + (1 - t) * line[0][1 - component];
|
|
|
|
if (line[1][component] > (w * (float)sign))
|
|
{
|
|
line[1 - side / 2][component] = w * (float)sign;
|
|
line[1 - side / 2][1 - component] = newCoord;
|
|
}
|
|
else
|
|
{
|
|
line[side / 2][component] = w * (float)sign;
|
|
line[side / 2][1 - component] = newCoord;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ClipMode
|
|
{
|
|
CLIPMODE_NO_CLIPPING = 0,
|
|
CLIPMODE_USE_CLIPPING_BOX,
|
|
|
|
CLIPMODE_LAST
|
|
};
|
|
|
|
bool verifyMultisampleLineGroupRasterization (const tcu::Surface& surface,
|
|
const LineSceneSpec& scene,
|
|
const RasterizationArguments& args,
|
|
tcu::TestLog& log,
|
|
ClipMode clipMode,
|
|
VerifyTriangleGroupRasterizationLogStash* logStash,
|
|
const bool vulkanLinesTest,
|
|
const bool strictMode)
|
|
{
|
|
// Multisampled line == 2 triangles
|
|
|
|
const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
|
|
const float halfLineWidth = scene.lineWidth * 0.5f;
|
|
TriangleSceneSpec triangleScene;
|
|
|
|
deUint32 stippleCounter = 0;
|
|
float leftoverPhase = 0.0f;
|
|
|
|
triangleScene.triangles.resize(2 * scene.lines.size());
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
|
|
if (!scene.isStrip)
|
|
{
|
|
// reset stipple at the start of each line segment
|
|
stippleCounter = 0;
|
|
leftoverPhase = 0;
|
|
}
|
|
|
|
// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
|
|
tcu::Vec2 lineNormalizedDeviceSpace[2] =
|
|
{
|
|
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()),
|
|
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()),
|
|
};
|
|
|
|
if (clipMode == CLIPMODE_USE_CLIPPING_BOX)
|
|
{
|
|
applyClippingBox(lineNormalizedDeviceSpace, 1.0f);
|
|
}
|
|
|
|
const tcu::Vec2 lineScreenSpace[2] =
|
|
{
|
|
(lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
|
|
(lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
|
|
};
|
|
|
|
const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
|
|
const tcu::Vec2 lineNormalDir = strictMode ? tcu::Vec2(lineDir.y(), -lineDir.x())
|
|
: isLineXMajor(lineScreenSpace[0], lineScreenSpace[1]) ? tcu::Vec2(0.0f, 1.0f)
|
|
: tcu::Vec2(1.0f, 0.0f);
|
|
|
|
if (scene.stippleEnable)
|
|
{
|
|
float lineLength = tcu::distance(lineScreenSpace[0], lineScreenSpace[1]);
|
|
float lineOffset = 0.0f;
|
|
|
|
while (lineOffset < lineLength)
|
|
{
|
|
float d0 = (float)lineOffset;
|
|
float d1 = d0 + 1.0f;
|
|
|
|
// "leftoverPhase" carries over a fractional stipple phase that was "unused"
|
|
// by the last line segment in the strip, if it wasn't an integer length.
|
|
if (leftoverPhase > lineLength)
|
|
{
|
|
DE_ASSERT(d0 == 0.0f);
|
|
d1 = lineLength;
|
|
leftoverPhase -= lineLength;
|
|
}
|
|
else if (leftoverPhase != 0.0f)
|
|
{
|
|
DE_ASSERT(d0 == 0.0f);
|
|
d1 = leftoverPhase;
|
|
leftoverPhase = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
if (d0 + 1.0f > lineLength)
|
|
{
|
|
d1 = lineLength;
|
|
leftoverPhase = d0 + 1.0f - lineLength;
|
|
}
|
|
else
|
|
d1 = d0 + 1.0f;
|
|
}
|
|
|
|
// set offset for next iteration
|
|
lineOffset = d1;
|
|
|
|
int stippleBit = (stippleCounter / scene.stippleFactor) % 16;
|
|
bool stipplePass = (scene.stipplePattern & (1 << stippleBit)) != 0;
|
|
|
|
if (leftoverPhase == 0)
|
|
stippleCounter++;
|
|
|
|
if (!stipplePass)
|
|
continue;
|
|
|
|
d0 /= lineLength;
|
|
d1 /= lineLength;
|
|
|
|
tcu::Vec2 l0 = mix(lineScreenSpace[0], lineScreenSpace[1], d0);
|
|
tcu::Vec2 l1 = mix(lineScreenSpace[0], lineScreenSpace[1], d1);
|
|
|
|
const tcu::Vec2 lineQuadScreenSpace[4] =
|
|
{
|
|
l0 + lineNormalDir * halfLineWidth,
|
|
l0 - lineNormalDir * halfLineWidth,
|
|
l1 - lineNormalDir * halfLineWidth,
|
|
l1 + lineNormalDir * halfLineWidth,
|
|
};
|
|
const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
|
|
{
|
|
lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
};
|
|
|
|
TriangleSceneSpec::SceneTriangle tri;
|
|
|
|
tri.positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); tri.sharedEdge[0] = (d0 != 0.0f);
|
|
tri.positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f); tri.sharedEdge[1] = false;
|
|
tri.positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); tri.sharedEdge[2] = true;
|
|
|
|
triangleScene.triangles.push_back(tri);
|
|
|
|
tri.positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f); tri.sharedEdge[0] = true;
|
|
tri.positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f); tri.sharedEdge[1] = (d1 != 1.0f);
|
|
tri.positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f); tri.sharedEdge[2] = false;
|
|
|
|
triangleScene.triangles.push_back(tri);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const tcu::Vec2 lineQuadScreenSpace[4] =
|
|
{
|
|
lineScreenSpace[0] + lineNormalDir * halfLineWidth,
|
|
lineScreenSpace[0] - lineNormalDir * halfLineWidth,
|
|
lineScreenSpace[1] - lineNormalDir * halfLineWidth,
|
|
lineScreenSpace[1] + lineNormalDir * halfLineWidth,
|
|
};
|
|
const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
|
|
{
|
|
lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
};
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (logStash != DE_NULL)
|
|
{
|
|
logStash->messages.push_back("Rasterization clipping mode: " + std::string(clipMode == CLIPMODE_USE_CLIPPING_BOX ? "CLIPMODE_USE_CLIPPING_BOX" : "CLIPMODE_NO_CLIPPING") + ".");
|
|
logStash->messages.push_back("Rasterization line draw strictness mode: " + std::string(strictMode ? "strict" : "non-strict") + ".");
|
|
}
|
|
|
|
return verifyTriangleGroupRasterization(surface, triangleScene, args, log, scene.verificationMode, logStash, vulkanLinesTest);
|
|
}
|
|
|
|
static bool verifyMultisampleLineGroupInterpolationInternal (const tcu::Surface& surface,
|
|
const LineSceneSpec& scene,
|
|
const RasterizationArguments& args,
|
|
VerifyTriangleGroupInterpolationLogStash& logStash,
|
|
const bool strictMode)
|
|
{
|
|
// Multisampled line == 2 triangles
|
|
|
|
const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
|
|
const float halfLineWidth = scene.lineWidth * 0.5f;
|
|
TriangleSceneSpec triangleScene;
|
|
|
|
triangleScene.triangles.resize(2 * scene.lines.size());
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
// Need the w-coordinates a couple of times
|
|
const float wa = scene.lines[lineNdx].positions[0].w();
|
|
const float wb = scene.lines[lineNdx].positions[1].w();
|
|
|
|
// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
|
|
const tcu::Vec2 lineNormalizedDeviceSpace[2] =
|
|
{
|
|
tcu::Vec2(scene.lines[lineNdx].positions[0].x() / wa, scene.lines[lineNdx].positions[0].y() / wa),
|
|
tcu::Vec2(scene.lines[lineNdx].positions[1].x() / wb, scene.lines[lineNdx].positions[1].y() / wb),
|
|
};
|
|
const tcu::Vec2 lineScreenSpace[2] =
|
|
{
|
|
(lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
|
|
(lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
|
|
};
|
|
|
|
const tcu::Vec2 lineDir = tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
|
|
const tcu::Vec2 lineNormalDir = strictMode ? tcu::Vec2(lineDir.y(), -lineDir.x())
|
|
: isLineXMajor(lineScreenSpace[0], lineScreenSpace[1]) ? tcu::Vec2(0.0f, 1.0f)
|
|
: tcu::Vec2(1.0f, 0.0f);
|
|
|
|
const tcu::Vec2 lineQuadScreenSpace[4] =
|
|
{
|
|
lineScreenSpace[0] + lineNormalDir * halfLineWidth,
|
|
lineScreenSpace[0] - lineNormalDir * halfLineWidth,
|
|
lineScreenSpace[1] - lineNormalDir * halfLineWidth,
|
|
lineScreenSpace[1] + lineNormalDir * halfLineWidth,
|
|
};
|
|
const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
|
|
{
|
|
lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
};
|
|
|
|
// Re-construct un-projected geometry using the quantised positions
|
|
const tcu::Vec4 lineQuadUnprojected[4] =
|
|
{
|
|
tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x() * wa, lineQuadNormalizedDeviceSpace[0].y() * wa, 0.0f, wa),
|
|
tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x() * wa, lineQuadNormalizedDeviceSpace[1].y() * wa, 0.0f, wa),
|
|
tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x() * wb, lineQuadNormalizedDeviceSpace[2].y() * wb, 0.0f, wb),
|
|
tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x() * wb, lineQuadNormalizedDeviceSpace[3].y() * wb, 0.0f, wb),
|
|
};
|
|
|
|
triangleScene.triangles[lineNdx*2 + 0].positions[0] = lineQuadUnprojected[0];
|
|
triangleScene.triangles[lineNdx*2 + 0].positions[1] = lineQuadUnprojected[1];
|
|
triangleScene.triangles[lineNdx*2 + 0].positions[2] = lineQuadUnprojected[2];
|
|
|
|
triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false;
|
|
triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false;
|
|
triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true;
|
|
|
|
triangleScene.triangles[lineNdx*2 + 0].colors[0] = scene.lines[lineNdx].colors[0];
|
|
triangleScene.triangles[lineNdx*2 + 0].colors[1] = scene.lines[lineNdx].colors[0];
|
|
triangleScene.triangles[lineNdx*2 + 0].colors[2] = scene.lines[lineNdx].colors[1];
|
|
|
|
triangleScene.triangles[lineNdx*2 + 1].positions[0] = lineQuadUnprojected[0];
|
|
triangleScene.triangles[lineNdx*2 + 1].positions[1] = lineQuadUnprojected[2];
|
|
triangleScene.triangles[lineNdx*2 + 1].positions[2] = lineQuadUnprojected[3];
|
|
|
|
triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true;
|
|
triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false;
|
|
triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false;
|
|
|
|
triangleScene.triangles[lineNdx*2 + 1].colors[0] = scene.lines[lineNdx].colors[0];
|
|
triangleScene.triangles[lineNdx*2 + 1].colors[1] = scene.lines[lineNdx].colors[1];
|
|
triangleScene.triangles[lineNdx*2 + 1].colors[2] = scene.lines[lineNdx].colors[1];
|
|
}
|
|
|
|
if (strictMode)
|
|
{
|
|
// Strict mode interpolation should be purely in the direction of the line-segment
|
|
logStash.messages.push_back("Verify using line interpolator");
|
|
return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, logStash, MultisampleLineInterpolator(scene));
|
|
}
|
|
else
|
|
{
|
|
// For non-strict lines some allowance needs to be inplace for a few different styles of implementation.
|
|
//
|
|
// Some implementations duplicate the attributes at the endpoints to the corners of the triangle
|
|
// deconstruted parallelogram. Gradients along the line will be seen to travel in the major axis,
|
|
// with values effectively duplicated in the minor axis direction. In other cases, implementations
|
|
// will use the original parameters of the line to calculate attribute interpolation so it will
|
|
// follow the direction of the line-segment.
|
|
logStash.messages.push_back("Verify using triangle interpolator");
|
|
if (!verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, logStash, TriangleInterpolator(triangleScene)))
|
|
{
|
|
logStash.messages.push_back("Verify using line interpolator");
|
|
return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, logStash, MultisampleLineInterpolator(scene));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static void logTriangleGroupnterpolationStash (const tcu::Surface& surface, tcu::TestLog& log, VerifyTriangleGroupInterpolationLogStash& logStash)
|
|
{
|
|
// Output results
|
|
log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage;
|
|
|
|
for (size_t msgNdx = 0; msgNdx < logStash.messages.size(); ++msgNdx)
|
|
log << tcu::TestLog::Message << logStash.messages[msgNdx] << tcu::TestLog::EndMessage;
|
|
|
|
// report result
|
|
if (!logStash.success)
|
|
{
|
|
log << tcu::TestLog::Message << logStash.invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::Image("ErrorMask", "ErrorMask", logStash.errorMask)
|
|
<< tcu::TestLog::EndImageSet;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::EndImageSet;
|
|
}
|
|
}
|
|
|
|
static bool verifyMultisampleLineGroupInterpolation (const tcu::Surface& surface,
|
|
const LineSceneSpec& scene,
|
|
const RasterizationArguments& args,
|
|
tcu::TestLog& log,
|
|
const bool strictMode = true,
|
|
const bool allowBresenhamForNonStrictLines = false)
|
|
{
|
|
bool result = false;
|
|
VerifyTriangleGroupInterpolationLogStash nonStrictModeLogStash;
|
|
VerifyTriangleGroupInterpolationLogStash strictModeLogStash;
|
|
|
|
nonStrictModeLogStash.messages.push_back("Non-strict line draw mode.");
|
|
strictModeLogStash.messages.push_back("Strict mode line draw mode.");
|
|
|
|
if (strictMode)
|
|
{
|
|
result = verifyMultisampleLineGroupInterpolationInternal(surface,scene, args, strictModeLogStash, strictMode);
|
|
|
|
logTriangleGroupnterpolationStash(surface, log, strictModeLogStash);
|
|
}
|
|
else
|
|
{
|
|
if (verifyMultisampleLineGroupInterpolationInternal(surface,scene, args, nonStrictModeLogStash, false))
|
|
{
|
|
logTriangleGroupnterpolationStash(surface, log, nonStrictModeLogStash);
|
|
|
|
result = true;
|
|
}
|
|
else if (verifyMultisampleLineGroupInterpolationInternal(surface,scene, args, strictModeLogStash, true))
|
|
{
|
|
logTriangleGroupnterpolationStash(surface, log, strictModeLogStash);
|
|
|
|
result = true;
|
|
}
|
|
else
|
|
{
|
|
logTriangleGroupnterpolationStash(surface, log, nonStrictModeLogStash);
|
|
logTriangleGroupnterpolationStash(surface, log, strictModeLogStash);
|
|
}
|
|
|
|
// In the non-strict line case, bresenham is also permissable, though not specified. This is due
|
|
// to a change in how lines are specified in Vulkan versus GLES; in GLES bresenham lines using the
|
|
// diamond-exit rule were the preferred way to draw single pixel non-antialiased lines, and not all
|
|
// GLES implementations are able to disable this behaviour.
|
|
if (result == false)
|
|
{
|
|
log << tcu::TestLog::Message << "Checking line rasterisation using verifySinglesampleNarrowLineGroupInterpolation for nonStrict lines" << tcu::TestLog::EndMessage;
|
|
if (args.numSamples <= 1 &&
|
|
allowBresenhamForNonStrictLines &&
|
|
verifyLineGroupInterpolationWithProjectedWeights(surface, scene, args, log))
|
|
{
|
|
log << tcu::TestLog::Message << "verifySinglesampleNarrowLineGroupInterpolation for nonStrict lines Passed" << tcu::TestLog::EndMessage;
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool verifyMultisamplePointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
// Multisampled point == 2 triangles
|
|
|
|
const tcu::Vec2 viewportSize = tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
|
|
TriangleSceneSpec triangleScene;
|
|
|
|
triangleScene.triangles.resize(2 * scene.points.size());
|
|
for (int pointNdx = 0; pointNdx < (int)scene.points.size(); ++pointNdx)
|
|
{
|
|
// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
|
|
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());
|
|
const tcu::Vec2 pointScreenSpace = (pointNormalizedDeviceSpace + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize;
|
|
const float offset = scene.points[pointNdx].pointSize * 0.5f;
|
|
const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
|
|
{
|
|
(pointScreenSpace + tcu::Vec2(-offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
(pointScreenSpace + tcu::Vec2(-offset, offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
(pointScreenSpace + tcu::Vec2( offset, offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
(pointScreenSpace + tcu::Vec2( offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
|
|
};
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
}
|
|
|
|
return verifyTriangleGroupRasterization(surface, triangleScene, args, log);
|
|
}
|
|
|
|
void genScreenSpaceLines (std::vector<tcu::Vec4>& screenspaceLines, const std::vector<LineSceneSpec::SceneLine>& lines, const tcu::IVec2& viewportSize)
|
|
{
|
|
DE_ASSERT(screenspaceLines.size() == lines.size());
|
|
|
|
for (int lineNdx = 0; lineNdx < (int)lines.size(); ++lineNdx)
|
|
{
|
|
const tcu::Vec2 lineNormalizedDeviceSpace[2] =
|
|
{
|
|
tcu::Vec2(lines[lineNdx].positions[0].x() / lines[lineNdx].positions[0].w(), lines[lineNdx].positions[0].y() / lines[lineNdx].positions[0].w()),
|
|
tcu::Vec2(lines[lineNdx].positions[1].x() / lines[lineNdx].positions[1].w(), lines[lineNdx].positions[1].y() / lines[lineNdx].positions[1].w()),
|
|
};
|
|
const tcu::Vec4 lineScreenSpace[2] =
|
|
{
|
|
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),
|
|
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),
|
|
};
|
|
|
|
screenspaceLines[lineNdx] = tcu::Vec4(lineScreenSpace[0].x(), lineScreenSpace[0].y(), lineScreenSpace[1].x(), lineScreenSpace[1].y());
|
|
}
|
|
}
|
|
|
|
bool verifySinglesampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases
|
|
DE_ASSERT(scene.lines.size() < 255); // indices are stored as unsigned 8-bit ints
|
|
|
|
bool allOK = true;
|
|
bool overdrawInReference = false;
|
|
int referenceFragments = 0;
|
|
int resultFragments = 0;
|
|
int lineWidth = deFloorFloatToInt32(scene.lineWidth + 0.5f);
|
|
std::vector<bool> lineIsXMajor (scene.lines.size());
|
|
std::vector<tcu::Vec4> screenspaceLines(scene.lines.size());
|
|
|
|
// 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
|
|
tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
|
|
tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
|
|
|
|
genScreenSpaceLines(screenspaceLines, scene.lines, tcu::IVec2(surface.getWidth(), surface.getHeight()));
|
|
|
|
rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight()), args.subpixelBits);
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
rasterizer.init(tcu::Vec4(screenspaceLines[lineNdx][0],
|
|
screenspaceLines[lineNdx][1],
|
|
0.0f,
|
|
1.0f),
|
|
tcu::Vec4(screenspaceLines[lineNdx][2],
|
|
screenspaceLines[lineNdx][3],
|
|
0.0f,
|
|
1.0f),
|
|
scene.lineWidth,
|
|
scene.stippleFactor,
|
|
scene.stipplePattern);
|
|
|
|
if (!scene.isStrip)
|
|
rasterizer.resetStipple();
|
|
|
|
// calculate majority of later use
|
|
lineIsXMajor[lineNdx] = isPackedSSLineXMajor(screenspaceLines[lineNdx]);
|
|
|
|
for (;;)
|
|
{
|
|
const int maxPackets = 32;
|
|
int numRasterized = 0;
|
|
rr::FragmentPacket packets[maxPackets];
|
|
|
|
rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized);
|
|
|
|
for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
|
|
{
|
|
for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
|
|
{
|
|
if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx))
|
|
{
|
|
const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2);
|
|
|
|
// Check for overdraw
|
|
if (!overdrawInReference)
|
|
overdrawInReference = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x() != 0;
|
|
|
|
// Output pixel
|
|
referenceLineMap.getAccess().setPixel(tcu::IVec4(lineNdx + 1, 0, 0, 0), fragPos.x(), fragPos.y());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numRasterized != maxPackets)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Requirement 1: The coordinates of a fragment produced by the algorithm may not deviate by more than one unit
|
|
{
|
|
tcu::Surface errorMask (surface.getWidth(), surface.getHeight());
|
|
bool missingFragments = false;
|
|
|
|
tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 255, 0, 255));
|
|
|
|
log << tcu::TestLog::Message << "Searching for deviating fragments." << tcu::TestLog::EndMessage;
|
|
|
|
for (int y = 0; y < referenceLineMap.getHeight(); ++y)
|
|
for (int x = 0; x < referenceLineMap.getWidth(); ++x)
|
|
{
|
|
const bool reference = referenceLineMap.getAccess().getPixelInt(x, y).x() != 0;
|
|
const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits);
|
|
|
|
if (reference)
|
|
++referenceFragments;
|
|
if (result)
|
|
++resultFragments;
|
|
|
|
if (reference == result)
|
|
continue;
|
|
|
|
// Reference fragment here, matching result fragment must be nearby
|
|
if (reference && !result)
|
|
{
|
|
bool foundFragment = false;
|
|
|
|
if (x == 0 || y == 0 || x == referenceLineMap.getWidth() - 1 || y == referenceLineMap.getHeight() -1)
|
|
{
|
|
// image boundary, missing fragment could be over the image edge
|
|
foundFragment = true;
|
|
}
|
|
|
|
// find nearby fragment
|
|
for (int dy = -1; dy < 2 && !foundFragment; ++dy)
|
|
for (int dx = -1; dx < 2 && !foundFragment; ++dx)
|
|
{
|
|
if (compareColors(surface.getPixel(x+dx, y+dy), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits))
|
|
foundFragment = true;
|
|
}
|
|
|
|
if (!foundFragment)
|
|
{
|
|
missingFragments = true;
|
|
errorMask.setPixel(x, y, tcu::RGBA::red());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missingFragments)
|
|
{
|
|
|
|
allOK = false;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "No invalid deviations found." << tcu::TestLog::EndMessage;
|
|
}
|
|
}
|
|
|
|
// Requirement 2: The total number of fragments produced by the algorithm may differ from
|
|
// that produced by the diamond-exit rule by no more than one.
|
|
{
|
|
// Check is not valid if the primitives intersect or otherwise share same fragments
|
|
if (!overdrawInReference)
|
|
{
|
|
int allowedDeviation = (int)scene.lines.size() * lineWidth; // one pixel per primitive in the major direction
|
|
|
|
log << tcu::TestLog::Message << "Verifying fragment counts:\n"
|
|
<< "\tDiamond-exit rule: " << referenceFragments << " fragments.\n"
|
|
<< "\tResult image: " << resultFragments << " fragments.\n"
|
|
<< "\tAllowing deviation of " << allowedDeviation << " fragments.\n"
|
|
<< tcu::TestLog::EndMessage;
|
|
|
|
if (deAbs32(referenceFragments - resultFragments) > allowedDeviation)
|
|
{
|
|
tcu::Surface reference(surface.getWidth(), surface.getHeight());
|
|
|
|
// show a helpful reference image
|
|
tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255));
|
|
for (int y = 0; y < surface.getHeight(); ++y)
|
|
for (int x = 0; x < surface.getWidth(); ++x)
|
|
if (referenceLineMap.getAccess().getPixelInt(x, y).x())
|
|
reference.setPixel(x, y, tcu::RGBA::white());
|
|
|
|
log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Reference", "Reference", reference)
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::EndImageSet;
|
|
|
|
allOK = false;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "Fragment count is valid." << tcu::TestLog::EndMessage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "Overdraw in scene. Fragment count cannot be verified. Skipping fragment count checks." << tcu::TestLog::EndMessage;
|
|
}
|
|
}
|
|
|
|
// Requirement 3: Line width must be constant
|
|
{
|
|
bool invalidWidthFound = false;
|
|
|
|
log << tcu::TestLog::Message << "Verifying line widths of the x-major lines." << tcu::TestLog::EndMessage;
|
|
for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
|
|
{
|
|
bool fullyVisibleLine = false;
|
|
bool previousPixelUndefined = false;
|
|
int currentLine = 0;
|
|
int currentWidth = 1;
|
|
|
|
for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
|
|
{
|
|
const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits);
|
|
int lineID = 0;
|
|
|
|
// Which line does this fragment belong to?
|
|
|
|
if (result)
|
|
{
|
|
bool multipleNearbyLines = false;
|
|
bool renderAtSurfaceEdge = false;
|
|
|
|
renderAtSurfaceEdge = (x == 1) || (x == referenceLineMap.getWidth() - 2);
|
|
|
|
for (int dy = -1; dy < 2; ++dy)
|
|
for (int dx = -1; dx < 2; ++dx)
|
|
{
|
|
const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
|
|
if (nearbyID)
|
|
{
|
|
if (lineID && lineID != nearbyID)
|
|
multipleNearbyLines = true;
|
|
}
|
|
}
|
|
|
|
if (multipleNearbyLines || renderAtSurfaceEdge)
|
|
{
|
|
// Another line is too close, don't try to calculate width here
|
|
// Or the render result is outside of surface range
|
|
previousPixelUndefined = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Only line with id of lineID is nearby
|
|
|
|
if (previousPixelUndefined)
|
|
{
|
|
// The line might have been overdrawn or not
|
|
currentLine = lineID;
|
|
currentWidth = 1;
|
|
fullyVisibleLine = false;
|
|
previousPixelUndefined = false;
|
|
}
|
|
else if (lineID == currentLine)
|
|
{
|
|
// Current line continues
|
|
++currentWidth;
|
|
}
|
|
else if (lineID > currentLine)
|
|
{
|
|
// Another line was drawn over or the line ends
|
|
currentLine = lineID;
|
|
currentWidth = 1;
|
|
fullyVisibleLine = true;
|
|
}
|
|
else
|
|
{
|
|
// The line ends
|
|
if (fullyVisibleLine && !lineIsXMajor[currentLine-1])
|
|
{
|
|
// check width
|
|
if (currentWidth != lineWidth)
|
|
{
|
|
log << tcu::TestLog::Message << "\tInvalid line width at (" << x - currentWidth << ", " << y << ") - (" << x - 1 << ", " << y << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage;
|
|
invalidWidthFound = true;
|
|
}
|
|
}
|
|
|
|
currentLine = lineID;
|
|
currentWidth = 1;
|
|
fullyVisibleLine = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
log << tcu::TestLog::Message << "Verifying line widths of the y-major lines." << tcu::TestLog::EndMessage;
|
|
for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
|
|
{
|
|
bool fullyVisibleLine = false;
|
|
bool previousPixelUndefined = false;
|
|
int currentLine = 0;
|
|
int currentWidth = 1;
|
|
|
|
for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
|
|
{
|
|
const bool result = compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits);
|
|
int lineID = 0;
|
|
|
|
// Which line does this fragment belong to?
|
|
|
|
if (result)
|
|
{
|
|
bool multipleNearbyLines = false;
|
|
bool renderAtSurfaceEdge = false;
|
|
|
|
renderAtSurfaceEdge = (y == 1) || (y == referenceLineMap.getWidth() - 2);
|
|
|
|
for (int dy = -1; dy < 2; ++dy)
|
|
for (int dx = -1; dx < 2; ++dx)
|
|
{
|
|
const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
|
|
if (nearbyID)
|
|
{
|
|
if (lineID && lineID != nearbyID)
|
|
multipleNearbyLines = true;
|
|
lineID = nearbyID;
|
|
}
|
|
}
|
|
|
|
if (multipleNearbyLines || renderAtSurfaceEdge)
|
|
{
|
|
// Another line is too close, don't try to calculate width here
|
|
// Or the render result is outside of surface range
|
|
previousPixelUndefined = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Only line with id of lineID is nearby
|
|
|
|
if (previousPixelUndefined)
|
|
{
|
|
// The line might have been overdrawn or not
|
|
currentLine = lineID;
|
|
currentWidth = 1;
|
|
fullyVisibleLine = false;
|
|
previousPixelUndefined = false;
|
|
}
|
|
else if (lineID == currentLine)
|
|
{
|
|
// Current line continues
|
|
++currentWidth;
|
|
}
|
|
else if (lineID > currentLine)
|
|
{
|
|
// Another line was drawn over or the line ends
|
|
currentLine = lineID;
|
|
currentWidth = 1;
|
|
fullyVisibleLine = true;
|
|
}
|
|
else
|
|
{
|
|
// The line ends
|
|
if (fullyVisibleLine && lineIsXMajor[currentLine-1])
|
|
{
|
|
// check width
|
|
if (currentWidth != lineWidth)
|
|
{
|
|
log << tcu::TestLog::Message << "\tInvalid line width at (" << x << ", " << y - currentWidth << ") - (" << x << ", " << y - 1 << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage;
|
|
invalidWidthFound = true;
|
|
}
|
|
}
|
|
|
|
currentLine = lineID;
|
|
currentWidth = 1;
|
|
fullyVisibleLine = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (invalidWidthFound)
|
|
{
|
|
log << tcu::TestLog::Message << "Invalid line width found, image is not valid." << tcu::TestLog::EndMessage;
|
|
allOK = false;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "Line widths are valid." << tcu::TestLog::EndMessage;
|
|
}
|
|
}
|
|
|
|
//\todo [2013-10-24 jarkko].
|
|
//Requirement 4. If two line segments share a common endpoint, and both segments are either
|
|
//x-major (both left-to-right or both right-to-left) or y-major (both bottom-totop
|
|
//or both top-to-bottom), then rasterizing both segments may not produce
|
|
//duplicate fragments, nor may any fragments be omitted so as to interrupt
|
|
//continuity of the connected segments.
|
|
|
|
{
|
|
tcu::Surface reference(surface.getWidth(), surface.getHeight());
|
|
tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255));
|
|
for (int y = 0; y < surface.getHeight(); ++y)
|
|
for (int x = 0; x < surface.getWidth(); ++x)
|
|
if (referenceLineMap.getAccess().getPixelInt(x, y).x())
|
|
reference.setPixel(x, y, tcu::RGBA::white());
|
|
log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Reference", "Reference", reference)
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::EndImageSet;
|
|
}
|
|
|
|
return allOK;
|
|
}
|
|
|
|
struct SingleSampleNarrowLineCandidate
|
|
{
|
|
int lineNdx;
|
|
tcu::IVec3 colorMin;
|
|
tcu::IVec3 colorMax;
|
|
tcu::Vec3 colorMinF;
|
|
tcu::Vec3 colorMaxF;
|
|
tcu::Vec3 valueRangeMin;
|
|
tcu::Vec3 valueRangeMax;
|
|
};
|
|
|
|
void setMaskMapCoverageBitForLine (int bitNdx, const tcu::Vec2& screenSpaceP0, const tcu::Vec2& screenSpaceP1, float lineWidth, tcu::PixelBufferAccess maskMap, const int subpixelBits)
|
|
{
|
|
enum
|
|
{
|
|
MAX_PACKETS = 32,
|
|
};
|
|
|
|
rr::SingleSampleLineRasterizer rasterizer (tcu::IVec4(0, 0, maskMap.getWidth(), maskMap.getHeight()), subpixelBits);
|
|
int numRasterized = MAX_PACKETS;
|
|
rr::FragmentPacket packets[MAX_PACKETS];
|
|
|
|
rasterizer.init(tcu::Vec4(screenSpaceP0.x(), screenSpaceP0.y(), 0.0f, 1.0f),
|
|
tcu::Vec4(screenSpaceP1.x(), screenSpaceP1.y(), 0.0f, 1.0f),
|
|
lineWidth,
|
|
1, 0xFFFF);
|
|
|
|
while (numRasterized == MAX_PACKETS)
|
|
{
|
|
rasterizer.rasterize(packets, DE_NULL, MAX_PACKETS, numRasterized);
|
|
|
|
for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
|
|
{
|
|
for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
|
|
{
|
|
if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx))
|
|
{
|
|
const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2);
|
|
|
|
DE_ASSERT(deInBounds32(fragPos.x(), 0, maskMap.getWidth()));
|
|
DE_ASSERT(deInBounds32(fragPos.y(), 0, maskMap.getHeight()));
|
|
|
|
const deUint32 previousMask = maskMap.getPixelUint(fragPos.x(), fragPos.y()).x();
|
|
const deUint32 newMask = (previousMask) | ((deUint32)1u << bitNdx);
|
|
|
|
maskMap.setPixel(tcu::UVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void setMaskMapCoverageBitForLines (const std::vector<tcu::Vec4>& screenspaceLines, float lineWidth, tcu::PixelBufferAccess maskMap, const int subpixelBits)
|
|
{
|
|
for (int lineNdx = 0; lineNdx < (int)screenspaceLines.size(); ++lineNdx)
|
|
{
|
|
const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1);
|
|
const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3);
|
|
|
|
setMaskMapCoverageBitForLine(lineNdx, pa, pb, lineWidth, maskMap, subpixelBits);
|
|
}
|
|
}
|
|
|
|
// verify line interpolation assuming line pixels are interpolated independently depending only on screen space location
|
|
bool verifyLineGroupPixelIndependentInterpolation (const tcu::Surface& surface,
|
|
const LineSceneSpec& scene,
|
|
const RasterizationArguments& args,
|
|
tcu::TestLog& log,
|
|
LineInterpolationMethod interpolationMethod)
|
|
{
|
|
DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints
|
|
DE_ASSERT(interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT || interpolationMethod == LINEINTERPOLATION_PROJECTED);
|
|
|
|
const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255);
|
|
const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
|
|
const int errorFloodThreshold = 4;
|
|
int errorCount = 0;
|
|
tcu::Surface errorMask (surface.getWidth(), surface.getHeight());
|
|
int invalidPixels = 0;
|
|
std::vector<tcu::Vec4> screenspaceLines (scene.lines.size()); //!< packed (x0, y0, x1, y1)
|
|
|
|
// Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
|
|
// The map is used to find lines with potential coverage to a given pixel
|
|
tcu::TextureLevel referenceLineMap (tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
|
|
|
|
tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
|
|
tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
|
|
|
// log format
|
|
|
|
log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
|
|
if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
|
|
log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;
|
|
|
|
// prepare lookup map
|
|
|
|
genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize);
|
|
setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess(), args.subpixelBits);
|
|
|
|
// Find all possible lines with coverage, check pixel color matches one of them
|
|
|
|
for (int y = 1; y < surface.getHeight() - 1; ++y)
|
|
for (int x = 1; x < surface.getWidth() - 1; ++x)
|
|
{
|
|
const tcu::RGBA color = surface.getPixel(x, y);
|
|
const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
|
|
int lineCoverageSet = 0; // !< lines that may cover this fragment
|
|
int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment
|
|
bool matchFound = false;
|
|
const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
|
|
|
|
std::vector<SingleSampleNarrowLineCandidate> candidates;
|
|
|
|
// Find lines with possible coverage
|
|
|
|
for (int dy = -1; dy < 2; ++dy)
|
|
for (int dx = -1; dx < 2; ++dx)
|
|
{
|
|
const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
|
|
|
|
lineCoverageSet |= coverage;
|
|
lineSurroundingCoverage &= coverage;
|
|
}
|
|
|
|
// background color is possible?
|
|
if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits))
|
|
continue;
|
|
|
|
// Check those lines
|
|
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
|
|
{
|
|
const float wa = scene.lines[lineNdx].positions[0].w();
|
|
const float wb = scene.lines[lineNdx].positions[1].w();
|
|
const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1);
|
|
const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3);
|
|
|
|
const LineInterpolationRange range = (interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT)
|
|
? (calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits))
|
|
: (calcSingleSampleLineInterpolationRangeAxisProjected(pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits));
|
|
|
|
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];
|
|
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];
|
|
|
|
const tcu::Vec3 colorMinF (de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
|
|
de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
|
|
de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
|
|
const tcu::Vec3 colorMaxF (de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
|
|
de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
|
|
de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
|
|
const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()),
|
|
(int)deFloatFloor(colorMinF.y()),
|
|
(int)deFloatFloor(colorMinF.z()));
|
|
const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()),
|
|
(int)deFloatCeil (colorMaxF.y()),
|
|
(int)deFloatCeil (colorMaxF.z()));
|
|
|
|
// Verify validity
|
|
if (pixelNativeColor.x() < colorMin.x() ||
|
|
pixelNativeColor.y() < colorMin.y() ||
|
|
pixelNativeColor.z() < colorMin.z() ||
|
|
pixelNativeColor.x() > colorMax.x() ||
|
|
pixelNativeColor.y() > colorMax.y() ||
|
|
pixelNativeColor.z() > colorMax.z())
|
|
{
|
|
if (errorCount < errorFloodThreshold)
|
|
{
|
|
// Store candidate information for logging
|
|
SingleSampleNarrowLineCandidate candidate;
|
|
|
|
candidate.lineNdx = lineNdx;
|
|
candidate.colorMin = colorMin;
|
|
candidate.colorMax = colorMax;
|
|
candidate.colorMinF = colorMinF;
|
|
candidate.colorMaxF = colorMaxF;
|
|
candidate.valueRangeMin = valueMin.swizzle(0, 1, 2);
|
|
candidate.valueRangeMax = valueMax.swizzle(0, 1, 2);
|
|
|
|
candidates.push_back(candidate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
matchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matchFound)
|
|
continue;
|
|
|
|
// invalid fragment
|
|
++invalidPixels;
|
|
errorMask.setPixel(x, y, invalidPixelColor);
|
|
|
|
++errorCount;
|
|
|
|
// don't fill the logs with too much data
|
|
if (errorCount < errorFloodThreshold)
|
|
{
|
|
log << tcu::TestLog::Message
|
|
<< "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n"
|
|
<< "\tPixel color:\t\t" << color << "\n"
|
|
<< "\tNative color:\t\t" << pixelNativeColor << "\n"
|
|
<< tcu::TestLog::EndMessage;
|
|
|
|
for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx)
|
|
{
|
|
const SingleSampleNarrowLineCandidate& candidate = candidates[candidateNdx];
|
|
|
|
log << tcu::TestLog::Message << "\tCandidate (line " << candidate.lineNdx << "):\n"
|
|
<< "\t\tReference native color min: " << tcu::clamp(candidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n"
|
|
<< "\t\tReference native color max: " << tcu::clamp(candidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n"
|
|
<< "\t\tReference native float min: " << tcu::clamp(candidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
|
|
<< "\t\tReference native float max: " << tcu::clamp(candidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
|
|
<< "\t\tFmin:\t" << tcu::clamp(candidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
|
|
<< "\t\tFmax:\t" << tcu::clamp(candidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
|
|
<< tcu::TestLog::EndMessage;
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't just hide failures
|
|
if (errorCount > errorFloodThreshold)
|
|
log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;
|
|
|
|
// report result
|
|
if (invalidPixels)
|
|
{
|
|
log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask)
|
|
<< tcu::TestLog::EndImageSet;
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::EndImageSet;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool verifySinglesampleNarrowLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
DE_ASSERT(scene.lineWidth == 1.0f);
|
|
return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_STRICTLY_CORRECT);
|
|
}
|
|
|
|
bool verifyLineGroupInterpolationWithProjectedWeights (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_PROJECTED);
|
|
}
|
|
|
|
struct SingleSampleWideLineCandidate
|
|
{
|
|
struct InterpolationPointCandidate
|
|
{
|
|
tcu::IVec2 interpolationPoint;
|
|
tcu::IVec3 colorMin;
|
|
tcu::IVec3 colorMax;
|
|
tcu::Vec3 colorMinF;
|
|
tcu::Vec3 colorMaxF;
|
|
tcu::Vec3 valueRangeMin;
|
|
tcu::Vec3 valueRangeMax;
|
|
};
|
|
|
|
int lineNdx;
|
|
int numCandidates;
|
|
InterpolationPointCandidate interpolationCandidates[3];
|
|
};
|
|
|
|
// return point on line at a given position on a given axis
|
|
tcu::Vec2 getLineCoordAtAxisCoord (const tcu::Vec2& pa, const tcu::Vec2& pb, bool isXAxis, float axisCoord)
|
|
{
|
|
const int fixedCoordNdx = (isXAxis) ? (0) : (1);
|
|
const int varyingCoordNdx = (isXAxis) ? (1) : (0);
|
|
|
|
const float fixedDifference = pb[fixedCoordNdx] - pa[fixedCoordNdx];
|
|
const float varyingDifference = pb[varyingCoordNdx] - pa[varyingCoordNdx];
|
|
|
|
DE_ASSERT(fixedDifference != 0.0f);
|
|
|
|
const float resultFixedCoord = axisCoord;
|
|
const float resultVaryingCoord = pa[varyingCoordNdx] + (axisCoord - pa[fixedCoordNdx]) * (varyingDifference / fixedDifference);
|
|
|
|
return (isXAxis) ? (tcu::Vec2(resultFixedCoord, resultVaryingCoord))
|
|
: (tcu::Vec2(resultVaryingCoord, resultFixedCoord));
|
|
}
|
|
|
|
bool isBlack (const tcu::RGBA& c)
|
|
{
|
|
return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
|
|
}
|
|
|
|
bool verifySinglesampleWideLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases
|
|
DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints
|
|
|
|
enum
|
|
{
|
|
FLAG_ROOT_NOT_SET = (1u << 16)
|
|
};
|
|
|
|
const tcu::RGBA invalidPixelColor = tcu::RGBA(255, 0, 0, 255);
|
|
const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
|
|
const int errorFloodThreshold = 4;
|
|
int errorCount = 0;
|
|
tcu::Surface errorMask (surface.getWidth(), surface.getHeight());
|
|
int invalidPixels = 0;
|
|
std::vector<tcu::Vec4> effectiveLines (scene.lines.size()); //!< packed (x0, y0, x1, y1)
|
|
std::vector<bool> lineIsXMajor (scene.lines.size());
|
|
|
|
// for each line, for every distinct major direction fragment, store root pixel location (along
|
|
// minor direction);
|
|
std::vector<std::vector<deUint32> > rootPixelLocation (scene.lines.size()); //!< packed [16b - flags] [16b - coordinate]
|
|
|
|
// log format
|
|
|
|
log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
|
|
if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
|
|
log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;
|
|
|
|
// Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
|
|
// The map is used to find lines with potential coverage to a given pixel
|
|
tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
|
|
tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
|
|
|
|
tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
|
|
|
// calculate mask and effective line coordinates
|
|
{
|
|
std::vector<tcu::Vec4> screenspaceLines(scene.lines.size());
|
|
|
|
genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize);
|
|
setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess(), args.subpixelBits);
|
|
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
const tcu::Vec2 lineScreenSpaceP0 = screenspaceLines[lineNdx].swizzle(0, 1);
|
|
const tcu::Vec2 lineScreenSpaceP1 = screenspaceLines[lineNdx].swizzle(2, 3);
|
|
const bool isXMajor = isPackedSSLineXMajor(screenspaceLines[lineNdx]);
|
|
|
|
lineIsXMajor[lineNdx] = isXMajor;
|
|
|
|
// wide line interpolations are calculated for a line moved in minor direction
|
|
{
|
|
const float offsetLength = (scene.lineWidth - 1.0f) / 2.0f;
|
|
const tcu::Vec2 offsetDirection = (isXMajor) ? (tcu::Vec2(0.0f, -1.0f)) : (tcu::Vec2(-1.0f, 0.0f));
|
|
const tcu::Vec2 offset = offsetDirection * offsetLength;
|
|
|
|
effectiveLines[lineNdx] = tcu::Vec4(lineScreenSpaceP0.x() + offset.x(),
|
|
lineScreenSpaceP0.y() + offset.y(),
|
|
lineScreenSpaceP1.x() + offset.x(),
|
|
lineScreenSpaceP1.y() + offset.y());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
// Calculate root pixel lookup table for this line. Since the implementation's fragment
|
|
// major coordinate range might not be a subset of the correct line range (they are allowed
|
|
// to vary by one pixel), we must extend the domain to cover whole viewport along major
|
|
// dimension.
|
|
//
|
|
// Expanding line strip to (effectively) infinite line might result in exit-diamnod set
|
|
// that is not a superset of the exit-diamond set of the line strip. In practice, this
|
|
// won't be an issue, since the allow-one-pixel-variation rule should tolerate this even
|
|
// if the original and extended line would resolve differently a diamond the line just
|
|
// touches (precision lost in expansion changes enter/exit status).
|
|
|
|
{
|
|
const bool isXMajor = lineIsXMajor[lineNdx];
|
|
const int majorSize = (isXMajor) ? (surface.getWidth()) : (surface.getHeight());
|
|
rr::LineExitDiamondGenerator diamondGenerator (args.subpixelBits);
|
|
rr::LineExitDiamond diamonds[32];
|
|
int numRasterized = DE_LENGTH_OF_ARRAY(diamonds);
|
|
|
|
// Expand to effectively infinite line (endpoints are just one pixel over viewport boundaries)
|
|
const tcu::Vec2 expandedP0 = getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, -1.0f);
|
|
const tcu::Vec2 expandedP1 = getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, (float)majorSize + 1.0f);
|
|
|
|
diamondGenerator.init(tcu::Vec4(expandedP0.x(), expandedP0.y(), 0.0f, 1.0f),
|
|
tcu::Vec4(expandedP1.x(), expandedP1.y(), 0.0f, 1.0f));
|
|
|
|
rootPixelLocation[lineNdx].resize(majorSize, FLAG_ROOT_NOT_SET);
|
|
|
|
while (numRasterized == DE_LENGTH_OF_ARRAY(diamonds))
|
|
{
|
|
diamondGenerator.rasterize(diamonds, DE_LENGTH_OF_ARRAY(diamonds), numRasterized);
|
|
|
|
for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
|
|
{
|
|
const tcu::IVec2 fragPos = diamonds[packetNdx].position;
|
|
const int majorPos = (isXMajor) ? (fragPos.x()) : (fragPos.y());
|
|
const int rootPos = (isXMajor) ? (fragPos.y()) : (fragPos.x());
|
|
const deUint32 packed = (deUint32)((deUint16)((deInt16)rootPos));
|
|
|
|
// infinite line will generate some diamonds outside the viewport
|
|
if (deInBounds32(majorPos, 0, majorSize))
|
|
{
|
|
DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) != 0u);
|
|
rootPixelLocation[lineNdx][majorPos] = packed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filled whole lookup table
|
|
for (int majorPos = 0; majorPos < majorSize; ++majorPos)
|
|
DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) == 0u);
|
|
}
|
|
}
|
|
|
|
// Find all possible lines with coverage, check pixel color matches one of them
|
|
|
|
for (int y = 1; y < surface.getHeight() - 1; ++y)
|
|
for (int x = 1; x < surface.getWidth() - 1; ++x)
|
|
{
|
|
const tcu::RGBA color = surface.getPixel(x, y);
|
|
const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args); // Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
|
|
int lineCoverageSet = 0; // !< lines that may cover this fragment
|
|
int lineSurroundingCoverage = 0xFFFF; // !< lines that will cover this fragment
|
|
bool matchFound = false;
|
|
const tcu::IVec3 formatLimit ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
|
|
|
|
std::vector<SingleSampleWideLineCandidate> candidates;
|
|
|
|
// Find lines with possible coverage
|
|
|
|
for (int dy = -1; dy < 2; ++dy)
|
|
for (int dx = -1; dx < 2; ++dx)
|
|
{
|
|
const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
|
|
|
|
lineCoverageSet |= coverage;
|
|
lineSurroundingCoverage &= coverage;
|
|
}
|
|
|
|
// background color is possible?
|
|
if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits))
|
|
continue;
|
|
|
|
// Check those lines
|
|
|
|
for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
|
|
{
|
|
if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
|
|
{
|
|
const float wa = scene.lines[lineNdx].positions[0].w();
|
|
const float wb = scene.lines[lineNdx].positions[1].w();
|
|
const tcu::Vec2 pa = effectiveLines[lineNdx].swizzle(0, 1);
|
|
const tcu::Vec2 pb = effectiveLines[lineNdx].swizzle(2, 3);
|
|
|
|
// \note Wide line fragments are generated by replicating the root fragment for each
|
|
// fragment column (row for y-major). Calculate interpolation at the root
|
|
// fragment.
|
|
const bool isXMajor = lineIsXMajor[lineNdx];
|
|
const int majorPosition = (isXMajor) ? (x) : (y);
|
|
const deUint32 minorInfoPacked = rootPixelLocation[lineNdx][majorPosition];
|
|
const int minorPosition = (int)((deInt16)((deUint16)(minorInfoPacked & 0xFFFFu)));
|
|
const tcu::IVec2 idealRootPos = (isXMajor) ? (tcu::IVec2(majorPosition, minorPosition)) : (tcu::IVec2(minorPosition, majorPosition));
|
|
const tcu::IVec2 minorDirection = (isXMajor) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));
|
|
|
|
SingleSampleWideLineCandidate candidate;
|
|
|
|
candidate.lineNdx = lineNdx;
|
|
candidate.numCandidates = 0;
|
|
DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates) == 3);
|
|
|
|
// Interpolation happens at the root fragment, which is then replicated in minor
|
|
// direction. Search for implementation's root position near accurate root.
|
|
for (int minorOffset = -1; minorOffset < 2; ++minorOffset)
|
|
{
|
|
const tcu::IVec2 rootPosition = idealRootPos + minorOffset * minorDirection;
|
|
|
|
// A fragment can be root fragment only if it exists
|
|
// \note root fragment can "exist" outside viewport
|
|
// \note no pixel format theshold since in this case allowing only black is more conservative
|
|
if (deInBounds32(rootPosition.x(), 0, surface.getWidth()) &&
|
|
deInBounds32(rootPosition.y(), 0, surface.getHeight()) &&
|
|
isBlack(surface.getPixel(rootPosition.x(), rootPosition.y())))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const LineInterpolationRange range = calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, rootPosition, args.subpixelBits);
|
|
|
|
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];
|
|
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];
|
|
|
|
const tcu::Vec3 colorMinF (de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
|
|
de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
|
|
de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
|
|
const tcu::Vec3 colorMaxF (de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
|
|
de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
|
|
de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
|
|
const tcu::IVec3 colorMin ((int)deFloatFloor(colorMinF.x()),
|
|
(int)deFloatFloor(colorMinF.y()),
|
|
(int)deFloatFloor(colorMinF.z()));
|
|
const tcu::IVec3 colorMax ((int)deFloatCeil (colorMaxF.x()),
|
|
(int)deFloatCeil (colorMaxF.y()),
|
|
(int)deFloatCeil (colorMaxF.z()));
|
|
|
|
// Verify validity
|
|
if (pixelNativeColor.x() < colorMin.x() ||
|
|
pixelNativeColor.y() < colorMin.y() ||
|
|
pixelNativeColor.z() < colorMin.z() ||
|
|
pixelNativeColor.x() > colorMax.x() ||
|
|
pixelNativeColor.y() > colorMax.y() ||
|
|
pixelNativeColor.z() > colorMax.z())
|
|
{
|
|
if (errorCount < errorFloodThreshold)
|
|
{
|
|
// Store candidate information for logging
|
|
SingleSampleWideLineCandidate::InterpolationPointCandidate& interpolationCandidate = candidate.interpolationCandidates[candidate.numCandidates++];
|
|
DE_ASSERT(candidate.numCandidates <= DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates));
|
|
|
|
interpolationCandidate.interpolationPoint = rootPosition;
|
|
interpolationCandidate.colorMin = colorMin;
|
|
interpolationCandidate.colorMax = colorMax;
|
|
interpolationCandidate.colorMinF = colorMinF;
|
|
interpolationCandidate.colorMaxF = colorMaxF;
|
|
interpolationCandidate.valueRangeMin = valueMin.swizzle(0, 1, 2);
|
|
interpolationCandidate.valueRangeMax = valueMax.swizzle(0, 1, 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
matchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!matchFound)
|
|
{
|
|
// store info for logging
|
|
if (errorCount < errorFloodThreshold && candidate.numCandidates > 0)
|
|
candidates.push_back(candidate);
|
|
}
|
|
else
|
|
{
|
|
// no need to check other lines
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matchFound)
|
|
continue;
|
|
|
|
// invalid fragment
|
|
++invalidPixels;
|
|
errorMask.setPixel(x, y, invalidPixelColor);
|
|
|
|
++errorCount;
|
|
|
|
// don't fill the logs with too much data
|
|
if (errorCount < errorFloodThreshold)
|
|
{
|
|
tcu::MessageBuilder msg(&log);
|
|
|
|
msg << "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n"
|
|
<< "\tPixel color:\t\t" << color << "\n"
|
|
<< "\tNative color:\t\t" << pixelNativeColor << "\n";
|
|
|
|
for (int lineCandidateNdx = 0; lineCandidateNdx < (int)candidates.size(); ++lineCandidateNdx)
|
|
{
|
|
const SingleSampleWideLineCandidate& candidate = candidates[lineCandidateNdx];
|
|
|
|
msg << "\tCandidate line (line " << candidate.lineNdx << "):\n";
|
|
|
|
for (int interpolationCandidateNdx = 0; interpolationCandidateNdx < candidate.numCandidates; ++interpolationCandidateNdx)
|
|
{
|
|
const SingleSampleWideLineCandidate::InterpolationPointCandidate& interpolationCandidate = candidate.interpolationCandidates[interpolationCandidateNdx];
|
|
|
|
msg << "\t\tCandidate interpolation point (index " << interpolationCandidateNdx << "):\n"
|
|
<< "\t\t\tRoot fragment position (non-replicated fragment): " << interpolationCandidate.interpolationPoint << ":\n"
|
|
<< "\t\t\tReference native color min: " << tcu::clamp(interpolationCandidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n"
|
|
<< "\t\t\tReference native color max: " << tcu::clamp(interpolationCandidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n"
|
|
<< "\t\t\tReference native float min: " << tcu::clamp(interpolationCandidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
|
|
<< "\t\t\tReference native float max: " << tcu::clamp(interpolationCandidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
|
|
<< "\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"
|
|
<< "\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";
|
|
}
|
|
}
|
|
|
|
msg << tcu::TestLog::EndMessage;
|
|
}
|
|
}
|
|
|
|
// don't just hide failures
|
|
if (errorCount > errorFloodThreshold)
|
|
log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;
|
|
|
|
// report result
|
|
if (invalidPixels)
|
|
{
|
|
log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::Image("ErrorMask", "ErrorMask", errorMask)
|
|
<< tcu::TestLog::EndImageSet;
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::EndImageSet;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
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)
|
|
{
|
|
typedef tcu::Vector<deInt64, 2> I64Vec2;
|
|
|
|
const deUint64 numSubPixels = ((deUint64)1) << subpixelBits;
|
|
const deUint64 pixelHitBoxSize = (multisample) ? (numSubPixels) : 5; //!< 5 = ceil(6 * sqrt(2) / 2) to account for a 3 subpixel fuzz around pixel center
|
|
const bool order = isTriangleClockwise(p0, p1, p2); //!< clockwise / counter-clockwise
|
|
const tcu::Vec4& orderedP0 = p0; //!< vertices of a clockwise triangle
|
|
const tcu::Vec4& orderedP1 = (order) ? (p1) : (p2);
|
|
const tcu::Vec4& orderedP2 = (order) ? (p2) : (p1);
|
|
const tcu::Vec2 triangleNormalizedDeviceSpace[3] =
|
|
{
|
|
tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()),
|
|
tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()),
|
|
tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()),
|
|
};
|
|
const tcu::Vec2 triangleScreenSpace[3] =
|
|
{
|
|
(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
};
|
|
|
|
// Broad bounding box - pixel check
|
|
{
|
|
const float minX = de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
|
|
const float minY = de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
|
|
const float maxX = de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
|
|
const float maxY = de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
|
|
|
|
if ((float)pixel.x() > maxX + 1 ||
|
|
(float)pixel.y() > maxY + 1 ||
|
|
(float)pixel.x() < minX - 1 ||
|
|
(float)pixel.y() < minY - 1)
|
|
return COVERAGE_NONE;
|
|
}
|
|
|
|
// Broad triangle - pixel area intersection
|
|
{
|
|
const DVec2 pixelCenterPosition = DVec2((double)pixel.x(), (double)pixel.y()) * DVec2((double)numSubPixels, (double)numSubPixels) +
|
|
DVec2((double)numSubPixels / 2, (double)numSubPixels / 2);
|
|
const DVec2 triangleSubPixelSpace[3] =
|
|
{
|
|
DVec2(triangleScreenSpace[0].x() * (double)numSubPixels, triangleScreenSpace[0].y() * (double)numSubPixels),
|
|
DVec2(triangleScreenSpace[1].x() * (double)numSubPixels, triangleScreenSpace[1].y() * (double)numSubPixels),
|
|
DVec2(triangleScreenSpace[2].x() * (double)numSubPixels, triangleScreenSpace[2].y() * (double)numSubPixels),
|
|
};
|
|
|
|
// Check (using cross product) if pixel center is
|
|
// a) too far from any edge
|
|
// b) fully inside all edges
|
|
bool insideAllEdges = true;
|
|
for (int vtxNdx = 0; vtxNdx < 3; ++vtxNdx)
|
|
{
|
|
const int otherVtxNdx = (vtxNdx + 1) % 3;
|
|
const double maxPixelDistanceSquared = (double)(pixelHitBoxSize * pixelHitBoxSize); // Max distance from the pixel center from within the pixel is (sqrt(2) * boxWidth/2). Use 2x value for rounding tolerance
|
|
const DVec2 edge = triangleSubPixelSpace[otherVtxNdx] - triangleSubPixelSpace[vtxNdx];
|
|
const DVec2 v = pixelCenterPosition - triangleSubPixelSpace[vtxNdx];
|
|
const double crossProduct = (edge.x() * v.y() - edge.y() * v.x());
|
|
|
|
// distance from edge: (edge x v) / |edge|
|
|
// (edge x v) / |edge| > maxPixelDistance
|
|
// ==> (edge x v)^2 / edge^2 > maxPixelDistance^2 | edge x v > 0
|
|
// ==> (edge x v)^2 > maxPixelDistance^2 * edge^2
|
|
if (crossProduct < 0 && crossProduct*crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(edge))
|
|
return COVERAGE_NONE;
|
|
if (crossProduct < 0 || crossProduct*crossProduct < maxPixelDistanceSquared * tcu::lengthSquared(edge))
|
|
insideAllEdges = false;
|
|
}
|
|
|
|
if (insideAllEdges)
|
|
return COVERAGE_FULL;
|
|
}
|
|
|
|
// Accurate intersection for edge pixels
|
|
{
|
|
// In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center.
|
|
const I64Vec2 pixelCorners[4] =
|
|
{
|
|
I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+0) * numSubPixels),
|
|
I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+0) * numSubPixels),
|
|
I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+1) * numSubPixels),
|
|
I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+1) * numSubPixels),
|
|
};
|
|
|
|
// 3 subpixel tolerance around pixel center to account for accumulated errors during various line rasterization methods
|
|
const I64Vec2 pixelCenterCorners[4] =
|
|
{
|
|
I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 - 3, pixel.y() * numSubPixels + numSubPixels/2 - 3),
|
|
I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 3, pixel.y() * numSubPixels + numSubPixels/2 - 3),
|
|
I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 3, pixel.y() * numSubPixels + numSubPixels/2 + 3),
|
|
I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 - 3, pixel.y() * numSubPixels + numSubPixels/2 + 3),
|
|
};
|
|
|
|
// both rounding directions
|
|
const I64Vec2 triangleSubPixelSpaceFloor[3] =
|
|
{
|
|
I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
|
|
I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
|
|
I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
|
|
};
|
|
const I64Vec2 triangleSubPixelSpaceCeil[3] =
|
|
{
|
|
I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
|
|
I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
|
|
I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
|
|
};
|
|
const I64Vec2* const corners = (multisample) ? (pixelCorners) : (pixelCenterCorners);
|
|
|
|
// Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside
|
|
|
|
for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
|
|
for (int startRounding = 0; startRounding < 4; ++startRounding)
|
|
for (int endRounding = 0; endRounding < 4; ++endRounding)
|
|
{
|
|
const int nextEdgeNdx = (edgeNdx+1) % 3;
|
|
const I64Vec2 startPos ((startRounding&0x01) ? (triangleSubPixelSpaceFloor[edgeNdx].x()) : (triangleSubPixelSpaceCeil[edgeNdx].x()), (startRounding&0x02) ? (triangleSubPixelSpaceFloor[edgeNdx].y()) : (triangleSubPixelSpaceCeil[edgeNdx].y()));
|
|
const I64Vec2 endPos ((endRounding&0x01) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].x()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].x()), (endRounding&0x02) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].y()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].y()));
|
|
|
|
for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx)
|
|
{
|
|
const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4;
|
|
|
|
if (lineLineIntersect(startPos, endPos, corners[pixelEdgeNdx], corners[pixelEdgeEnd]))
|
|
return COVERAGE_PARTIAL;
|
|
}
|
|
}
|
|
|
|
// fully inside or outside
|
|
for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
|
|
{
|
|
const int nextEdgeNdx = (edgeNdx+1) % 3;
|
|
const I64Vec2& startPos = triangleSubPixelSpaceFloor[edgeNdx];
|
|
const I64Vec2& endPos = triangleSubPixelSpaceFloor[nextEdgeNdx];
|
|
const I64Vec2 edge = endPos - startPos;
|
|
const I64Vec2 v = corners[0] - endPos;
|
|
const deInt64 crossProduct = (edge.x() * v.y() - edge.y() * v.x());
|
|
|
|
// a corner of the pixel is outside => "fully inside" option is impossible
|
|
if (crossProduct < 0)
|
|
return COVERAGE_NONE;
|
|
}
|
|
|
|
return COVERAGE_FULL;
|
|
}
|
|
}
|
|
|
|
CoverageType calculateUnderestimateLineCoverage (const tcu::Vec4& p0, const tcu::Vec4& p1, const float lineWidth, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize)
|
|
{
|
|
DE_ASSERT(viewportSize.x() == viewportSize.y() && viewportSize.x() > 0);
|
|
DE_ASSERT(p0.w() == 1.0f && p1.w() == 1.0f);
|
|
|
|
const Vec2 p = Vec2(p0.x(), p0.y());
|
|
const Vec2 q = Vec2(p1.x(), p1.y());
|
|
const Vec2 pq = Vec2(p1.x() - p0.x(), p1.y() - p0.y());
|
|
const Vec2 pqn = normalize(pq);
|
|
const Vec2 lw = 0.5f * lineWidth * pqn;
|
|
const Vec2 n = Vec2(lw.y(), -lw.x());
|
|
const Vec2 vp = Vec2(float(viewportSize.x()), float(viewportSize.y()));
|
|
const Vec2 a = 0.5f * (p + Vec2(1.0f, 1.0f)) * vp + n;
|
|
const Vec2 b = 0.5f * (p + Vec2(1.0f, 1.0f)) * vp - n;
|
|
const Vec2 c = 0.5f * (q + Vec2(1.0f, 1.0f)) * vp - n;
|
|
const Vec2 ba = b - a;
|
|
const Vec2 bc = b - c;
|
|
const float det = ba.x() * bc.y() - ba.y() * bc.x();
|
|
int within = 0;
|
|
|
|
if (det != 0.0f)
|
|
{
|
|
for (int cornerNdx = 0; cornerNdx < 4; ++cornerNdx)
|
|
{
|
|
const int pixelCornerOffsetX = ((cornerNdx & 1) ? 1 : 0);
|
|
const int pixelCornerOffsetY = ((cornerNdx & 2) ? 1 : 0);
|
|
const Vec2 f = Vec2(float(pixel.x() + pixelCornerOffsetX), float(pixel.y() + pixelCornerOffsetY));
|
|
const Vec2 bf = b - f;
|
|
const float alpha = (bf.x() * bc.y() - bc.x() * bf.y()) / det;
|
|
const float beta = (ba.x() * bf.y() - bf.x() * ba.y()) / det;
|
|
bool cornerWithin = de::inRange(alpha, 0.0f, 1.0f) && de::inRange(beta, 0.0f, 1.0f);
|
|
|
|
if (cornerWithin)
|
|
within++;
|
|
}
|
|
}
|
|
|
|
if (within == 0)
|
|
return COVERAGE_NONE;
|
|
else if (within == 4)
|
|
return COVERAGE_FULL;
|
|
else
|
|
return COVERAGE_PARTIAL;
|
|
}
|
|
|
|
CoverageType calculateUnderestimateTriangleCoverage (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::IVec2& pixel, int subpixelBits, const tcu::IVec2& viewportSize)
|
|
{
|
|
typedef tcu::Vector<deInt64, 2> I64Vec2;
|
|
|
|
const deUint64 numSubPixels = ((deUint64)1) << subpixelBits;
|
|
const bool order = isTriangleClockwise(p0, p1, p2); //!< clockwise / counter-clockwise
|
|
const tcu::Vec4& orderedP0 = p0; //!< vertices of a clockwise triangle
|
|
const tcu::Vec4& orderedP1 = (order) ? (p1) : (p2);
|
|
const tcu::Vec4& orderedP2 = (order) ? (p2) : (p1);
|
|
const tcu::Vec2 triangleNormalizedDeviceSpace[3] =
|
|
{
|
|
tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()),
|
|
tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()),
|
|
tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()),
|
|
};
|
|
const tcu::Vec2 triangleScreenSpace[3] =
|
|
{
|
|
(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
|
|
};
|
|
|
|
// Broad bounding box - pixel check
|
|
{
|
|
const float minX = de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
|
|
const float minY = de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
|
|
const float maxX = de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
|
|
const float maxY = de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
|
|
|
|
if ((float)pixel.x() > maxX + 1 ||
|
|
(float)pixel.y() > maxY + 1 ||
|
|
(float)pixel.x() < minX - 1 ||
|
|
(float)pixel.y() < minY - 1)
|
|
return COVERAGE_NONE;
|
|
}
|
|
|
|
// Accurate intersection for edge pixels
|
|
{
|
|
// In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center.
|
|
const I64Vec2 pixelCorners[4] =
|
|
{
|
|
I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+0) * numSubPixels),
|
|
I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+0) * numSubPixels),
|
|
I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+1) * numSubPixels),
|
|
I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+1) * numSubPixels),
|
|
};
|
|
// both rounding directions
|
|
const I64Vec2 triangleSubPixelSpaceFloor[3] =
|
|
{
|
|
I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
|
|
I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
|
|
I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
|
|
};
|
|
const I64Vec2 triangleSubPixelSpaceCeil[3] =
|
|
{
|
|
I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
|
|
I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
|
|
I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
|
|
};
|
|
|
|
// Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside
|
|
|
|
for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
|
|
for (int startRounding = 0; startRounding < 4; ++startRounding)
|
|
for (int endRounding = 0; endRounding < 4; ++endRounding)
|
|
{
|
|
const int nextEdgeNdx = (edgeNdx+1) % 3;
|
|
const I64Vec2 startPos ((startRounding&0x01) ? (triangleSubPixelSpaceFloor[edgeNdx].x()) : (triangleSubPixelSpaceCeil[edgeNdx].x()), (startRounding&0x02) ? (triangleSubPixelSpaceFloor[edgeNdx].y()) : (triangleSubPixelSpaceCeil[edgeNdx].y()));
|
|
const I64Vec2 endPos ((endRounding&0x01) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].x()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].x()), (endRounding&0x02) ? (triangleSubPixelSpaceFloor[nextEdgeNdx].y()) : (triangleSubPixelSpaceCeil[nextEdgeNdx].y()));
|
|
|
|
for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx)
|
|
{
|
|
const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4;
|
|
|
|
if (lineLineIntersect(startPos, endPos, pixelCorners[pixelEdgeNdx], pixelCorners[pixelEdgeEnd]))
|
|
return COVERAGE_PARTIAL;
|
|
}
|
|
}
|
|
|
|
// fully inside or outside
|
|
for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
|
|
{
|
|
const int nextEdgeNdx = (edgeNdx+1) % 3;
|
|
const I64Vec2& startPos = triangleSubPixelSpaceFloor[edgeNdx];
|
|
const I64Vec2& endPos = triangleSubPixelSpaceFloor[nextEdgeNdx];
|
|
const I64Vec2 edge = endPos - startPos;
|
|
const I64Vec2 v = pixelCorners[0] - endPos;
|
|
const deInt64 crossProduct = (edge.x() * v.y() - edge.y() * v.x());
|
|
|
|
// a corner of the pixel is outside => "fully inside" option is impossible
|
|
if (crossProduct < 0)
|
|
return COVERAGE_NONE;
|
|
}
|
|
|
|
return COVERAGE_FULL;
|
|
}
|
|
}
|
|
|
|
static void logTriangleGroupRasterizationStash (const tcu::Surface& surface, tcu::TestLog& log, VerifyTriangleGroupRasterizationLogStash& logStash)
|
|
{
|
|
// Output results
|
|
log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage;
|
|
|
|
for (size_t msgNdx = 0; msgNdx < logStash.messages.size(); ++msgNdx)
|
|
log << tcu::TestLog::Message << logStash.messages[msgNdx] << tcu::TestLog::EndMessage;
|
|
|
|
if (!logStash.result)
|
|
{
|
|
log << tcu::TestLog::Message << "Invalid pixels found:\n\t"
|
|
<< logStash.missingPixels << " missing pixels. (Marked with purple)\n\t"
|
|
<< logStash.unexpectedPixels << " incorrectly filled pixels. (Marked with red)\n\t"
|
|
<< "Unknown (subpixel on edge) pixels are marked with yellow."
|
|
<< tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::Image("ErrorMask", "ErrorMask", logStash.errorMask)
|
|
<< tcu::TestLog::EndImageSet;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
|
|
log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
|
|
<< tcu::TestLog::Image("Result", "Result", surface)
|
|
<< tcu::TestLog::EndImageSet;
|
|
}
|
|
}
|
|
|
|
bool verifyTriangleGroupRasterization (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, VerificationMode mode, VerifyTriangleGroupRasterizationLogStash* logStash, const bool vulkanLinesTest)
|
|
{
|
|
DE_ASSERT(mode < VERIFICATIONMODE_LAST);
|
|
|
|
const tcu::RGBA backGroundColor = tcu::RGBA(0, 0, 0, 255);
|
|
const tcu::RGBA triangleColor = tcu::RGBA(255, 255, 255, 255);
|
|
const tcu::RGBA missingPixelColor = tcu::RGBA(255, 0, 255, 255);
|
|
const tcu::RGBA unexpectedPixelColor = tcu::RGBA(255, 0, 0, 255);
|
|
const tcu::RGBA partialPixelColor = tcu::RGBA(255, 255, 0, 255);
|
|
const tcu::RGBA primitivePixelColor = tcu::RGBA(30, 30, 30, 255);
|
|
const int weakVerificationThreshold = 10;
|
|
const int weakerVerificationThreshold = 25;
|
|
const bool multisampled = (args.numSamples != 0);
|
|
const tcu::IVec2 viewportSize = tcu::IVec2(surface.getWidth(), surface.getHeight());
|
|
int missingPixels = 0;
|
|
int unexpectedPixels = 0;
|
|
int subPixelBits = args.subpixelBits;
|
|
tcu::TextureLevel coverageMap (tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
|
|
tcu::Surface errorMask (surface.getWidth(), surface.getHeight());
|
|
bool result = false;
|
|
|
|
// subpixel bits in a valid range?
|
|
|
|
if (subPixelBits < 0)
|
|
{
|
|
log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage;
|
|
subPixelBits = 0;
|
|
}
|
|
else if (subPixelBits > 16)
|
|
{
|
|
// At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
|
|
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;
|
|
subPixelBits = 16;
|
|
}
|
|
|
|
// generate coverage map
|
|
|
|
tcu::clear(coverageMap.getAccess(), tcu::IVec4(COVERAGE_NONE, 0, 0, 0));
|
|
|
|
for (int triNdx = 0; triNdx < (int)scene.triangles.size(); ++triNdx)
|
|
{
|
|
const tcu::IVec4 aabb = getTriangleAABB(scene.triangles[triNdx], viewportSize);
|
|
|
|
for (int y = de::max(0, aabb.y()); y <= de::min(aabb.w(), coverageMap.getHeight() - 1); ++y)
|
|
for (int x = de::max(0, aabb.x()); x <= de::min(aabb.z(), coverageMap.getWidth() - 1); ++x)
|
|
{
|
|
if (coverageMap.getAccess().getPixelUint(x, y).x() == COVERAGE_FULL)
|
|
continue;
|
|
|
|
const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0],
|
|
scene.triangles[triNdx].positions[1],
|
|
scene.triangles[triNdx].positions[2],
|
|
tcu::IVec2(x, y),
|
|
viewportSize,
|
|
subPixelBits,
|
|
multisampled);
|
|
|
|
if (coverage == COVERAGE_FULL)
|
|
{
|
|
coverageMap.getAccess().setPixel(tcu::IVec4(COVERAGE_FULL, 0, 0, 0), x, y);
|
|
}
|
|
else if (coverage == COVERAGE_PARTIAL)
|
|
{
|
|
CoverageType resultCoverage = COVERAGE_PARTIAL;
|
|
|
|
// Sharing an edge with another triangle?
|
|
// There should always be such a triangle, but the pixel in the other triangle might be
|
|
// on multiple edges, some of which are not shared. In these cases the coverage cannot be determined.
|
|
// Assume full coverage if the pixel is only on a shared edge in shared triangle too.
|
|
if (pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[triNdx], viewportSize))
|
|
{
|
|
bool friendFound = false;
|
|
for (int friendTriNdx = 0; friendTriNdx < (int)scene.triangles.size(); ++friendTriNdx)
|
|
{
|
|
if (friendTriNdx == triNdx)
|
|
continue;
|
|
|
|
const CoverageType friendCoverage = calculateTriangleCoverage(scene.triangles[friendTriNdx].positions[0],
|
|
scene.triangles[friendTriNdx].positions[1],
|
|
scene.triangles[friendTriNdx].positions[2],
|
|
tcu::IVec2(x, y),
|
|
viewportSize,
|
|
subPixelBits,
|
|
multisampled);
|
|
|
|
if (friendCoverage != COVERAGE_NONE && pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[friendTriNdx], viewportSize))
|
|
{
|
|
friendFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (friendFound)
|
|
resultCoverage = COVERAGE_FULL;
|
|
}
|
|
|
|
coverageMap.getAccess().setPixel(tcu::IVec4(resultCoverage, 0, 0, 0), x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check pixels
|
|
|
|
tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
|
|
|
// Use these to sanity check there is something drawn when a test expects something else than an empty picture.
|
|
bool referenceEmpty = true;
|
|
bool resultEmpty = true;
|
|
|
|
for (int y = 0; y < surface.getHeight(); ++y)
|
|
for (int x = 0; x < surface.getWidth(); ++x)
|
|
{
|
|
const tcu::RGBA color = surface.getPixel(x, y);
|
|
const bool imageNoCoverage = compareColors(color, backGroundColor, args.redBits, args.greenBits, args.blueBits);
|
|
const bool imageFullCoverage = compareColors(color, triangleColor, args.redBits, args.greenBits, args.blueBits);
|
|
CoverageType referenceCoverage = (CoverageType)coverageMap.getAccess().getPixelUint(x, y).x();
|
|
|
|
if (!imageNoCoverage)
|
|
resultEmpty = false;
|
|
|
|
switch (referenceCoverage)
|
|
{
|
|
case COVERAGE_NONE:
|
|
if (!imageNoCoverage)
|
|
{
|
|
// coverage where there should not be
|
|
++unexpectedPixels;
|
|
errorMask.setPixel(x, y, unexpectedPixelColor);
|
|
}
|
|
break;
|
|
|
|
case COVERAGE_PARTIAL:
|
|
{
|
|
referenceEmpty = false;
|
|
bool foundFragment = false;
|
|
if (vulkanLinesTest == true)
|
|
{
|
|
for (int dy = -1; dy < 2 && !foundFragment; ++dy)
|
|
for (int dx = -1; dx < 2 && !foundFragment; ++dx)
|
|
{
|
|
if (x + dx >= 0 && x + dx != surface.getWidth() && y + dy >= 0 && y + dy != surface.getHeight()
|
|
&& (CoverageType)coverageMap.getAccess().getPixelUint(x + dx, y + dy).x() != COVERAGE_NONE)
|
|
{
|
|
const tcu::RGBA color2 = surface.getPixel(x + dx , y + dy);
|
|
if (compareColors(color2, triangleColor, args.redBits, args.greenBits, args.blueBits))
|
|
foundFragment = true;
|
|
}
|
|
}
|
|
}
|
|
// anything goes
|
|
if (foundFragment == false)
|
|
{
|
|
errorMask.setPixel(x, y, partialPixelColor);
|
|
if (vulkanLinesTest == true)
|
|
++missingPixels;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COVERAGE_FULL:
|
|
referenceEmpty = false;
|
|
if (!imageFullCoverage)
|
|
{
|
|
// no coverage where there should be
|
|
++missingPixels;
|
|
errorMask.setPixel(x, y, missingPixelColor);
|
|
}
|
|
else
|
|
{
|
|
errorMask.setPixel(x, y, primitivePixelColor);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DE_ASSERT(false);
|
|
};
|
|
}
|
|
|
|
if (((mode == VERIFICATIONMODE_STRICT) && (missingPixels + unexpectedPixels > 0)) ||
|
|
((mode == VERIFICATIONMODE_WEAK) && (missingPixels + unexpectedPixels > weakVerificationThreshold)) ||
|
|
((mode == VERIFICATIONMODE_WEAKER) && (missingPixels + unexpectedPixels > weakerVerificationThreshold)) ||
|
|
((mode == VERIFICATIONMODE_SMOOTH) && (missingPixels > weakVerificationThreshold)) ||
|
|
referenceEmpty != resultEmpty)
|
|
{
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
result = true;
|
|
}
|
|
|
|
// Output or stash results
|
|
{
|
|
VerifyTriangleGroupRasterizationLogStash* tempLogStash = (logStash == DE_NULL) ? new VerifyTriangleGroupRasterizationLogStash : logStash;
|
|
|
|
tempLogStash->result = result;
|
|
tempLogStash->missingPixels = missingPixels;
|
|
tempLogStash->unexpectedPixels = unexpectedPixels;
|
|
tempLogStash->errorMask = errorMask;
|
|
|
|
if (logStash == DE_NULL)
|
|
{
|
|
logTriangleGroupRasterizationStash(surface, log, *tempLogStash);
|
|
delete tempLogStash;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool verifyLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
const bool multisampled = args.numSamples != 0;
|
|
|
|
if (multisampled)
|
|
return verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING, DE_NULL, false, true);
|
|
else
|
|
return verifySinglesampleLineGroupRasterization(surface, scene, args, log);
|
|
}
|
|
|
|
bool verifyClippedTriangulatedLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
return verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX, DE_NULL, false, true);
|
|
}
|
|
|
|
bool verifyRelaxedLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, const bool vulkanLinesTest, const bool strict)
|
|
{
|
|
VerifyTriangleGroupRasterizationLogStash useClippingLogStash;
|
|
VerifyTriangleGroupRasterizationLogStash noClippingLogStash;
|
|
VerifyTriangleGroupRasterizationLogStash useClippingForcedStrictLogStash;
|
|
VerifyTriangleGroupRasterizationLogStash noClippingForcedStrictLogStash;
|
|
|
|
if (verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX, &useClippingLogStash, vulkanLinesTest, strict))
|
|
{
|
|
logTriangleGroupRasterizationStash(surface, log, useClippingLogStash);
|
|
|
|
return true;
|
|
}
|
|
else if (verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING, &noClippingLogStash, vulkanLinesTest, strict))
|
|
{
|
|
logTriangleGroupRasterizationStash(surface, log, noClippingLogStash);
|
|
|
|
return true;
|
|
}
|
|
else if (strict == false && verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_USE_CLIPPING_BOX, &useClippingForcedStrictLogStash, vulkanLinesTest, true))
|
|
{
|
|
logTriangleGroupRasterizationStash(surface, log, useClippingForcedStrictLogStash);
|
|
|
|
return true;
|
|
}
|
|
else if (strict == false && verifyMultisampleLineGroupRasterization(surface, scene, args, log, CLIPMODE_NO_CLIPPING, &noClippingForcedStrictLogStash, vulkanLinesTest, true))
|
|
{
|
|
logTriangleGroupRasterizationStash(surface, log, noClippingForcedStrictLogStash);
|
|
|
|
return true;
|
|
}
|
|
else if (strict == false && args.numSamples == 0 && verifyLineGroupRasterization(surface, scene, args, log))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
log << tcu::TestLog::Message << "Relaxed rasterization failed, details follow." << tcu::TestLog::EndMessage;
|
|
|
|
logTriangleGroupRasterizationStash(surface, log, useClippingLogStash);
|
|
logTriangleGroupRasterizationStash(surface, log, noClippingLogStash);
|
|
|
|
if (strict == false)
|
|
{
|
|
logTriangleGroupRasterizationStash(surface, log, useClippingForcedStrictLogStash);
|
|
logTriangleGroupRasterizationStash(surface, log, noClippingForcedStrictLogStash);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool verifyPointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
// Splitting to triangles is a valid solution in multisampled cases and even in non-multisample cases too.
|
|
return verifyMultisamplePointGroupRasterization(surface, scene, args, log);
|
|
}
|
|
|
|
bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
VerifyTriangleGroupInterpolationLogStash logStash;
|
|
const bool result = verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, logStash, TriangleInterpolator(scene));
|
|
|
|
logTriangleGroupnterpolationStash(surface, log, logStash);
|
|
|
|
return result;
|
|
}
|
|
|
|
LineInterpolationMethod verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
|
|
{
|
|
const bool multisampled = args.numSamples != 0;
|
|
|
|
if (multisampled)
|
|
{
|
|
if (verifyMultisampleLineGroupInterpolation(surface, scene, args, log))
|
|
return LINEINTERPOLATION_STRICTLY_CORRECT;
|
|
return LINEINTERPOLATION_INCORRECT;
|
|
}
|
|
else
|
|
{
|
|
const bool isNarrow = (scene.lineWidth == 1.0f);
|
|
|
|
// accurate interpolation
|
|
if (isNarrow)
|
|
{
|
|
if (verifySinglesampleNarrowLineGroupInterpolation(surface, scene, args, log))
|
|
return LINEINTERPOLATION_STRICTLY_CORRECT;
|
|
}
|
|
else
|
|
{
|
|
if (verifySinglesampleWideLineGroupInterpolation(surface, scene, args, log))
|
|
return LINEINTERPOLATION_STRICTLY_CORRECT;
|
|
}
|
|
|
|
// check with projected (inaccurate) interpolation
|
|
log << tcu::TestLog::Message << "Accurate verification failed, checking with projected weights (inaccurate equation)." << tcu::TestLog::EndMessage;
|
|
if (verifyLineGroupInterpolationWithProjectedWeights(surface, scene, args, log))
|
|
return LINEINTERPOLATION_PROJECTED;
|
|
|
|
return LINEINTERPOLATION_INCORRECT;
|
|
}
|
|
}
|
|
|
|
bool verifyTriangulatedLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, const bool strictMode, const bool allowBresenhamForNonStrictLines)
|
|
{
|
|
return verifyMultisampleLineGroupInterpolation(surface, scene, args, log, strictMode, allowBresenhamForNonStrictLines);
|
|
}
|
|
|
|
} // tcu
|