/*------------------------------------------------------------------------ * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2016 The Khronos Group Inc. * Copyright (c) 2016 Samsung Electronics Co., Ltd. * Copyright (c) 2016 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 Shader derivate function tests. * * \todo [2013-06-25 pyry] Missing features: * - lines and points * - projected coordinates * - continous non-trivial functions (sin, exp) * - non-continous functions (step) *//*--------------------------------------------------------------------*/ #include "vktShaderRenderDerivateTests.hpp" #include "vktShaderRender.hpp" #include "vkImageUtil.hpp" #include "vkQueryUtil.hpp" #include "gluTextureUtil.hpp" #include "tcuStringTemplate.hpp" #include "tcuSurface.hpp" #include "tcuTestLog.hpp" #include "tcuVectorUtil.hpp" #include "tcuTextureUtil.hpp" #include "tcuRGBA.hpp" #include "tcuFloat.hpp" #include "tcuInterval.hpp" #include "deUniquePtr.hpp" #include "glwEnums.hpp" #include #include namespace vkt { namespace sr { namespace { using namespace vk; using std::vector; using std::string; using std::map; using tcu::TestLog; using std::ostringstream; enum { VIEWPORT_WIDTH = 99, VIEWPORT_HEIGHT = 133, MAX_FAILED_MESSAGES = 10 }; enum DerivateFunc { DERIVATE_DFDX = 0, DERIVATE_DFDXFINE, DERIVATE_DFDXCOARSE, DERIVATE_DFDY, DERIVATE_DFDYFINE, DERIVATE_DFDYCOARSE, DERIVATE_FWIDTH, DERIVATE_FWIDTHFINE, DERIVATE_FWIDTHCOARSE, DERIVATE_LAST }; enum SurfaceType { SURFACETYPE_UNORM_FBO = 0, SURFACETYPE_FLOAT_FBO, // \note Uses RGBA32UI fbo actually, since FP rendertargets are not in core spec. SURFACETYPE_LAST }; // Utilities static const char* getDerivateFuncName (DerivateFunc func) { switch (func) { case DERIVATE_DFDX: return "dFdx"; case DERIVATE_DFDXFINE: return "dFdxFine"; case DERIVATE_DFDXCOARSE: return "dFdxCoarse"; case DERIVATE_DFDY: return "dFdy"; case DERIVATE_DFDYFINE: return "dFdyFine"; case DERIVATE_DFDYCOARSE: return "dFdyCoarse"; case DERIVATE_FWIDTH: return "fwidth"; case DERIVATE_FWIDTHFINE: return "fwidthFine"; case DERIVATE_FWIDTHCOARSE: return "fwidthCoarse"; default: DE_ASSERT(false); return DE_NULL; } } static const char* getDerivateFuncCaseName (DerivateFunc func) { switch (func) { case DERIVATE_DFDX: return "dfdx"; case DERIVATE_DFDXFINE: return "dfdxfine"; case DERIVATE_DFDXCOARSE: return "dfdxcoarse"; case DERIVATE_DFDY: return "dfdy"; case DERIVATE_DFDYFINE: return "dfdyfine"; case DERIVATE_DFDYCOARSE: return "dfdycoarse"; case DERIVATE_FWIDTH: return "fwidth"; case DERIVATE_FWIDTHFINE: return "fwidthfine"; case DERIVATE_FWIDTHCOARSE: return "fwidthcoarse"; default: DE_ASSERT(false); return DE_NULL; } } static inline bool isDfdxFunc (DerivateFunc func) { return func == DERIVATE_DFDX || func == DERIVATE_DFDXFINE || func == DERIVATE_DFDXCOARSE; } static inline bool isDfdyFunc (DerivateFunc func) { return func == DERIVATE_DFDY || func == DERIVATE_DFDYFINE || func == DERIVATE_DFDYCOARSE; } static inline bool isFwidthFunc (DerivateFunc func) { return func == DERIVATE_FWIDTH || func == DERIVATE_FWIDTHFINE || func == DERIVATE_FWIDTHCOARSE; } static inline tcu::BVec4 getDerivateMask (glu::DataType type) { switch (type) { case glu::TYPE_FLOAT: return tcu::BVec4(true, false, false, false); case glu::TYPE_FLOAT_VEC2: return tcu::BVec4(true, true, false, false); case glu::TYPE_FLOAT_VEC3: return tcu::BVec4(true, true, true, false); case glu::TYPE_FLOAT_VEC4: return tcu::BVec4(true, true, true, true); default: DE_ASSERT(false); return tcu::BVec4(true); } } static inline tcu::Vec4 readDerivate (const tcu::ConstPixelBufferAccess& surface, const tcu::Vec4& derivScale, const tcu::Vec4& derivBias, int x, int y) { return (surface.getPixel(x, y) - derivBias) / derivScale; } static inline tcu::UVec4 getCompExpBits (const tcu::Vec4& v) { return tcu::UVec4(tcu::Float32(v[0]).exponentBits(), tcu::Float32(v[1]).exponentBits(), tcu::Float32(v[2]).exponentBits(), tcu::Float32(v[3]).exponentBits()); } float computeFloatingPointError (const float value, const int numAccurateBits) { const int numGarbageBits = 23-numAccurateBits; const deUint32 mask = (1u< 0) { DE_ASSERT(numMantissaBits <= 23); const int ulpBitNdx = 23-numMantissaBits; return tcu::Float32::construct(+1, exp, (1<<23) | (1 << ulpBitNdx)).asFloat() - tcu::Float32::construct(+1, exp, (1<<23)).asFloat(); } else { DE_ASSERT(numMantissaBits == 0); return tcu::Float32::construct(+1, exp, (1<<23)).asFloat(); } } static float getSingleULPForValue (float value, int numMantissaBits) { const int exp = tcu::Float32(value).exponent(); return getSingleULPForExponent(exp, numMantissaBits); } static float convertFloatFlushToZeroRtn (float value, int minExponent, int numAccurateBits) { if (value == 0.0f) { return 0.0f; } else { const tcu::Float32 inputFloat = tcu::Float32(value); const int numTruncatedBits = 23-numAccurateBits; const deUint32 truncMask = (1u< 0.0f) { if (value > 0.0f && tcu::Float32(value).exponent() < minExponent) { // flush to zero if possible return 0.0f; } else { // just mask away non-representable bits return tcu::Float32::construct(+1, inputFloat.exponent(), inputFloat.mantissa() & ~truncMask).asFloat(); } } else { if (inputFloat.mantissa() & truncMask) { // decrement one ulp if truncated bits are non-zero (i.e. if value is not representable) return tcu::Float32::construct(-1, inputFloat.exponent(), inputFloat.mantissa() & ~truncMask).asFloat() - getSingleULPForExponent(inputFloat.exponent(), numAccurateBits); } else { // value is representable, no need to do anything return value; } } } } static float convertFloatFlushToZeroRtp (float value, int minExponent, int numAccurateBits) { return -convertFloatFlushToZeroRtn(-value, minExponent, numAccurateBits); } static float addErrorUlp (float value, float numUlps, int numMantissaBits) { return value + numUlps * getSingleULPForValue(value, numMantissaBits); } enum { INTERPOLATION_LOST_BITS = 3, // number mantissa of bits allowed to be lost in varying interpolation }; static inline tcu::Vec4 getDerivateThreshold (const glu::Precision precision, const tcu::Vec4& valueMin, const tcu::Vec4& valueMax, const tcu::Vec4& expectedDerivate) { const int baseBits = getNumMantissaBits(precision); const tcu::UVec4 derivExp = getCompExpBits(expectedDerivate); const tcu::UVec4 maxValueExp = max(getCompExpBits(valueMin), getCompExpBits(valueMax)); const tcu::UVec4 numBitsLost = maxValueExp - min(maxValueExp, derivExp); const tcu::IVec4 numAccurateBits = max(baseBits - numBitsLost.asInt() - (int)INTERPOLATION_LOST_BITS, tcu::IVec4(0)); return tcu::Vec4(computeFloatingPointError(expectedDerivate[0], numAccurateBits[0]), computeFloatingPointError(expectedDerivate[1], numAccurateBits[1]), computeFloatingPointError(expectedDerivate[2], numAccurateBits[2]), computeFloatingPointError(expectedDerivate[3], numAccurateBits[3])); } struct LogVecComps { const tcu::Vec4& v; int numComps; LogVecComps (const tcu::Vec4& v_, int numComps_) : v (v_) , numComps (numComps_) { } }; std::ostream& operator<< (std::ostream& str, const LogVecComps& v) { DE_ASSERT(de::inRange(v.numComps, 1, 4)); if (v.numComps == 1) return str << v.v[0]; else if (v.numComps == 2) return str << v.v.toWidth<2>(); else if (v.numComps == 3) return str << v.v.toWidth<3>(); else return str << v.v; } enum VerificationLogging { LOG_ALL = 0, LOG_NOTHING }; static bool verifyConstantDerivate (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask, glu::DataType dataType, const tcu::Vec4& reference, const tcu::Vec4& threshold, const tcu::Vec4& scale, const tcu::Vec4& bias, VerificationLogging logPolicy = LOG_ALL) { const int numComps = glu::getDataTypeFloatScalars(dataType); const tcu::BVec4 mask = tcu::logicalNot(getDerivateMask(dataType)); int numFailedPixels = 0; if (logPolicy == LOG_ALL) log << TestLog::Message << "Expecting " << LogVecComps(reference, numComps) << " with threshold " << LogVecComps(threshold, numComps) << TestLog::EndMessage; for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) { const tcu::Vec4 resDerivate = readDerivate(result, scale, bias, x, y); const bool isOk = tcu::allEqual(tcu::logicalOr(tcu::lessThanEqual(tcu::abs(reference - resDerivate), threshold), mask), tcu::BVec4(true)); if (!isOk) { if (numFailedPixels < MAX_FAILED_MESSAGES && logPolicy == LOG_ALL) log << TestLog::Message << "FAIL: got " << LogVecComps(resDerivate, numComps) << ", diff = " << LogVecComps(tcu::abs(reference - resDerivate), numComps) << ", at x = " << x << ", y = " << y << TestLog::EndMessage; numFailedPixels += 1; errorMask.setPixel(tcu::RGBA::red().toVec(), x, y); } } } if (numFailedPixels >= MAX_FAILED_MESSAGES && logPolicy == LOG_ALL) log << TestLog::Message << "..." << TestLog::EndMessage; if (numFailedPixels > 0 && logPolicy == LOG_ALL) log << TestLog::Message << "FAIL: found " << numFailedPixels << " failed pixels" << TestLog::EndMessage; return numFailedPixels == 0; } struct Linear2DFunctionEvaluator { tcu::Matrix matrix; // .-----. // | s_x | // M x | s_y | // | 1.0 | // '-----' tcu::Vec4 evaluateAt (float screenX, float screenY) const; }; tcu::Vec4 Linear2DFunctionEvaluator::evaluateAt (float screenX, float screenY) const { const tcu::Vec3 position(screenX, screenY, 1.0f); return matrix * position; } static bool reverifyConstantDerivateWithFlushRelaxations (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask, glu::DataType dataType, glu::Precision precision, const tcu::Vec4& derivScale, const tcu::Vec4& derivBias, const tcu::Vec4& surfaceThreshold, DerivateFunc derivateFunc, const Linear2DFunctionEvaluator& function) { DE_ASSERT(result.getWidth() == errorMask.getWidth()); DE_ASSERT(result.getHeight() == errorMask.getHeight()); DE_ASSERT(isDfdxFunc(derivateFunc) || isDfdyFunc(derivateFunc)); const tcu::IVec4 red (255, 0, 0, 255); const tcu::IVec4 green (0, 255, 0, 255); const float divisionErrorUlps = 2.5f; const int numComponents = glu::getDataTypeFloatScalars(dataType); const int numBits = getNumMantissaBits(precision); const int minExponent = getMinExponent(precision); const int numVaryingSampleBits = numBits - INTERPOLATION_LOST_BITS; int numFailedPixels = 0; tcu::clear(errorMask, green); // search for failed pixels for (int y = 0; y < result.getHeight(); ++y) for (int x = 0; x < result.getWidth(); ++x) { // flushToZero?(f2z?(functionValueCurrent) - f2z?(functionValueBefore)) // flushToZero? ( ------------------------------------------------------------------------ +- 2.5 ULP ) // dx const tcu::Vec4 resultDerivative = readDerivate(result, derivScale, derivBias, x, y); // sample at the front of the back pixel and the back of the front pixel to cover the whole area of // legal sample positions. In general case this is NOT OK, but we know that the target funtion is // (mostly*) linear which allows us to take the sample points at arbitrary points. This gets us the // maximum difference possible in exponents which are used in error bound calculations. // * non-linearity may happen around zero or with very high function values due to subnorms not // behaving well. const tcu::Vec4 functionValueForward = (isDfdxFunc(derivateFunc)) ? (function.evaluateAt((float)x + 2.0f, (float)y + 0.5f)) : (function.evaluateAt((float)x + 0.5f, (float)y + 2.0f)); const tcu::Vec4 functionValueBackward = (isDfdyFunc(derivateFunc)) ? (function.evaluateAt((float)x - 1.0f, (float)y + 0.5f)) : (function.evaluateAt((float)x + 0.5f, (float)y - 1.0f)); bool anyComponentFailed = false; // check components separately for (int c = 0; c < numComponents; ++c) { // Simulate interpolation. Add allowed interpolation error and round to target precision. Allow one half ULP (i.e. correct rounding) const tcu::Interval forwardComponent (convertFloatFlushToZeroRtn(addErrorUlp((float)functionValueForward[c], -0.5f, numVaryingSampleBits), minExponent, numBits), convertFloatFlushToZeroRtp(addErrorUlp((float)functionValueForward[c], +0.5f, numVaryingSampleBits), minExponent, numBits)); const tcu::Interval backwardComponent (convertFloatFlushToZeroRtn(addErrorUlp((float)functionValueBackward[c], -0.5f, numVaryingSampleBits), minExponent, numBits), convertFloatFlushToZeroRtp(addErrorUlp((float)functionValueBackward[c], +0.5f, numVaryingSampleBits), minExponent, numBits)); const int maxValueExp = de::max(de::max(tcu::Float32(forwardComponent.lo()).exponent(), tcu::Float32(forwardComponent.hi()).exponent()), de::max(tcu::Float32(backwardComponent.lo()).exponent(), tcu::Float32(backwardComponent.hi()).exponent())); // subtraction in numerator will likely cause a cancellation of the most // significant bits. Apply error bounds. const tcu::Interval numerator (forwardComponent - backwardComponent); const int numeratorLoExp = tcu::Float32(numerator.lo()).exponent(); const int numeratorHiExp = tcu::Float32(numerator.hi()).exponent(); const int numeratorLoBitsLost = de::max(0, maxValueExp - numeratorLoExp); //!< must clamp to zero since if forward and backward components have different const int numeratorHiBitsLost = de::max(0, maxValueExp - numeratorHiExp); //!< sign, numerator might have larger exponent than its operands. const int numeratorLoBits = de::max(0, numBits - numeratorLoBitsLost); const int numeratorHiBits = de::max(0, numBits - numeratorHiBitsLost); const tcu::Interval numeratorRange (convertFloatFlushToZeroRtn((float)numerator.lo(), minExponent, numeratorLoBits), convertFloatFlushToZeroRtp((float)numerator.hi(), minExponent, numeratorHiBits)); const tcu::Interval divisionRange = numeratorRange / 3.0f; // legal sample area is anywhere within this and neighboring pixels (i.e. size = 3) const tcu::Interval divisionResultRange (convertFloatFlushToZeroRtn(addErrorUlp((float)divisionRange.lo(), -divisionErrorUlps, numBits), minExponent, numBits), convertFloatFlushToZeroRtp(addErrorUlp((float)divisionRange.hi(), +divisionErrorUlps, numBits), minExponent, numBits)); const tcu::Interval finalResultRange (divisionResultRange.lo() - surfaceThreshold[c], divisionResultRange.hi() + surfaceThreshold[c]); if (resultDerivative[c] >= finalResultRange.lo() && resultDerivative[c] <= finalResultRange.hi()) { // value ok } else { if (numFailedPixels < MAX_FAILED_MESSAGES) log << tcu::TestLog::Message << "Error in pixel at " << x << ", " << y << " with component " << c << " (channel " << ("rgba"[c]) << ")\n" << "\tGot pixel value " << result.getPixelInt(x, y) << "\n" << "\t\tdFd" << ((isDfdxFunc(derivateFunc)) ? ('x') : ('y')) << " ~= " << resultDerivative[c] << "\n" << "\t\tdifference to a valid range: " << ((resultDerivative[c] < finalResultRange.lo()) ? ("-") : ("+")) << ((resultDerivative[c] < finalResultRange.lo()) ? (finalResultRange.lo() - resultDerivative[c]) : (resultDerivative[c] - finalResultRange.hi())) << "\n" << "\tDerivative value range:\n" << "\t\tMin: " << finalResultRange.lo() << "\n" << "\t\tMax: " << finalResultRange.hi() << "\n" << tcu::TestLog::EndMessage; ++numFailedPixels; anyComponentFailed = true; } } if (anyComponentFailed) errorMask.setPixel(red, x, y); } if (numFailedPixels >= MAX_FAILED_MESSAGES) log << TestLog::Message << "..." << TestLog::EndMessage; if (numFailedPixels > 0) log << TestLog::Message << "FAIL: found " << numFailedPixels << " failed pixels" << TestLog::EndMessage; return numFailedPixels == 0; } // TestCase utils struct DerivateCaseDefinition { DerivateCaseDefinition (void) { func = DERIVATE_LAST; dataType = glu::TYPE_LAST; precision = glu::PRECISION_LAST; inNonUniformControlFlow = false; coordDataType = glu::TYPE_LAST; coordPrecision = glu::PRECISION_LAST; surfaceType = SURFACETYPE_UNORM_FBO; numSamples = 0; } DerivateFunc func; glu::DataType dataType; glu::Precision precision; bool inNonUniformControlFlow; glu::DataType coordDataType; glu::Precision coordPrecision; SurfaceType surfaceType; int numSamples; }; struct DerivateCaseValues { tcu::Vec4 coordMin; tcu::Vec4 coordMax; tcu::Vec4 derivScale; tcu::Vec4 derivBias; }; struct TextureCaseValues { tcu::Vec4 texValueMin; tcu::Vec4 texValueMax; }; class DerivateUniformSetup : public UniformSetup { public: DerivateUniformSetup (bool useSampler); virtual ~DerivateUniformSetup (void); virtual void setup (ShaderRenderCaseInstance& instance, const tcu::Vec4&) const; private: const bool m_useSampler; }; DerivateUniformSetup::DerivateUniformSetup (bool useSampler) : m_useSampler(useSampler) { } DerivateUniformSetup::~DerivateUniformSetup (void) { } // TriangleDerivateCaseInstance class TriangleDerivateCaseInstance : public ShaderRenderCaseInstance { public: TriangleDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values); virtual ~TriangleDerivateCaseInstance (void); virtual tcu::TestStatus iterate (void); DerivateCaseDefinition getDerivateCaseDefinition (void) { return m_definitions; } DerivateCaseValues getDerivateCaseValues (void) { return m_values; } protected: virtual bool verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask) = 0; tcu::Vec4 getSurfaceThreshold (void) const; virtual void setupDefaultInputs (void); const DerivateCaseDefinition& m_definitions; const DerivateCaseValues& m_values; }; static VkSampleCountFlagBits getVkSampleCount (int numSamples) { switch (numSamples) { case 0: return VK_SAMPLE_COUNT_1_BIT; case 2: return VK_SAMPLE_COUNT_2_BIT; case 4: return VK_SAMPLE_COUNT_4_BIT; default: DE_ASSERT(false); return (VkSampleCountFlagBits)0; } } TriangleDerivateCaseInstance::TriangleDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values) : ShaderRenderCaseInstance (context, true, DE_NULL, uniformSetup, DE_NULL) , m_definitions (definitions) , m_values (values) { m_renderSize = tcu::UVec2(VIEWPORT_WIDTH, VIEWPORT_HEIGHT); m_colorFormat = vk::mapTextureFormat(glu::mapGLInternalFormat(m_definitions.surfaceType == SURFACETYPE_FLOAT_FBO ? GL_RGBA32UI : GL_RGBA8)); setSampleCount(getVkSampleCount(definitions.numSamples)); } TriangleDerivateCaseInstance::~TriangleDerivateCaseInstance (void) { } tcu::Vec4 TriangleDerivateCaseInstance::getSurfaceThreshold (void) const { switch (m_definitions.surfaceType) { case SURFACETYPE_UNORM_FBO: return tcu::IVec4(1).asFloat() / 255.0f; case SURFACETYPE_FLOAT_FBO: return tcu::Vec4(0.0f); default: DE_ASSERT(false); return tcu::Vec4(0.0f); } } void TriangleDerivateCaseInstance::setupDefaultInputs (void) { const int numVertices = 4; const float positions[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f }; const float coords[] = { m_values.coordMin.x(), m_values.coordMin.y(), m_values.coordMin.z(), m_values.coordMax.w(), m_values.coordMin.x(), m_values.coordMax.y(), (m_values.coordMin.z()+m_values.coordMax.z())*0.5f, (m_values.coordMin.w()+m_values.coordMax.w())*0.5f, m_values.coordMax.x(), m_values.coordMin.y(), (m_values.coordMin.z()+m_values.coordMax.z())*0.5f, (m_values.coordMin.w()+m_values.coordMax.w())*0.5f, m_values.coordMax.x(), m_values.coordMax.y(), m_values.coordMax.z(), m_values.coordMin.w() }; addAttribute(0u, vk::VK_FORMAT_R32G32B32A32_SFLOAT, 4 * (deUint32)sizeof(float), numVertices, positions); if (m_definitions.coordDataType != glu::TYPE_LAST) addAttribute(1u, vk::VK_FORMAT_R32G32B32A32_SFLOAT, 4 * (deUint32)sizeof(float), numVertices, coords); } tcu::TestStatus TriangleDerivateCaseInstance::iterate (void) { tcu::TestLog& log = m_context.getTestContext().getLog(); const deUint32 numVertices = 4; const deUint32 numTriangles = 2; const deUint16 indices[] = { 0, 2, 1, 2, 3, 1 }; tcu::TextureLevel resultImage; if (m_definitions.inNonUniformControlFlow) { if (!m_context.contextSupports(vk::ApiVersion(0, 1, 1, 0))) throw tcu::NotSupportedError("Derivatives in dynamic control flow requires Vulkan 1.1"); vk::VkPhysicalDeviceSubgroupProperties subgroupProperties; deMemset(&subgroupProperties, 0, sizeof(subgroupProperties)); subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES; vk::VkPhysicalDeviceProperties2 properties2; deMemset(&properties2, 0, sizeof(properties2)); properties2.sType = vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; properties2.pNext = &subgroupProperties; m_context.getInstanceInterface().getPhysicalDeviceProperties2(m_context.getPhysicalDevice(), &properties2); if (subgroupProperties.subgroupSize < 4) throw tcu::NotSupportedError("Derivatives in dynamic control flow requires subgroupSize >= 4"); if ((subgroupProperties.supportedOperations & VK_SUBGROUP_FEATURE_BALLOT_BIT) == 0) throw tcu::NotSupportedError("Derivative dynamic control flow tests require VK_SUBGROUP_FEATURE_BALLOT_BIT"); if ((subgroupProperties.supportedStages & VK_SHADER_STAGE_FRAGMENT_BIT) == 0) throw tcu::NotSupportedError("Derivative dynamic control flow tests require subgroup supported stage including VK_SHADER_STAGE_FRAGMENT_BIT"); } setup(); render(numVertices, numTriangles, indices); { const tcu::TextureLevel& renderedImage = getResultImage(); if (m_definitions.surfaceType == SURFACETYPE_FLOAT_FBO) { const tcu::TextureFormat dataFormat (tcu::TextureFormat::RGBA, tcu::TextureFormat::FLOAT); resultImage.setStorage(dataFormat, renderedImage.getWidth(), renderedImage.getHeight()); tcu::copy(resultImage.getAccess(), tcu::ConstPixelBufferAccess(dataFormat, renderedImage.getSize(), renderedImage.getAccess().getDataPtr())); } else { resultImage = renderedImage; } } // Verify { tcu::Surface errorMask(resultImage.getWidth(), resultImage.getHeight()); tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec()); const bool isOk = verify(resultImage.getAccess(), errorMask.getAccess()); log << TestLog::ImageSet("Result", "Result images") << TestLog::Image("Rendered", "Rendered image", resultImage); if (!isOk) log << TestLog::Image("ErrorMask", "Error mask", errorMask); log << TestLog::EndImageSet; if (isOk) return tcu::TestStatus::pass("Pass"); else return tcu::TestStatus::fail("Image comparison failed"); } } void DerivateUniformSetup::setup (ShaderRenderCaseInstance& instance, const tcu::Vec4&) const { DerivateCaseDefinition definitions = dynamic_cast(instance).getDerivateCaseDefinition(); DerivateCaseValues values = dynamic_cast(instance).getDerivateCaseValues(); DE_ASSERT(glu::isDataTypeFloatOrVec(definitions.dataType)); instance.addUniform(0u, vk::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, glu::getDataTypeScalarSize(definitions.dataType) * sizeof(float), values.derivScale.getPtr()); instance.addUniform(1u, vk::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, glu::getDataTypeScalarSize(definitions.dataType) * sizeof(float), values.derivBias.getPtr()); if (m_useSampler) instance.useSampler(2u, 0u); // To the uniform binding location 2 bind the texture 0 } // TriangleDerivateCase class TriangleDerivateCase : public ShaderRenderCase { public: TriangleDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const UniformSetup* uniformSetup); virtual ~TriangleDerivateCase (void); protected: mutable DerivateCaseDefinition m_definitions; mutable DerivateCaseValues m_values; }; TriangleDerivateCase::TriangleDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const UniformSetup* uniformSetup) : ShaderRenderCase (testCtx, name, description, false, (ShaderEvaluator*)DE_NULL, uniformSetup, DE_NULL) , m_definitions () { } TriangleDerivateCase::~TriangleDerivateCase (void) { } static std::string genVertexSource (glu::DataType coordType, glu::Precision precision) { DE_ASSERT(coordType == glu::TYPE_LAST || glu::isDataTypeFloatOrVec(coordType)); const std::string vertexTmpl = "#version 450\n" "layout(location = 0) in highp vec4 a_position;\n" + string(coordType != glu::TYPE_LAST ? "layout(location = 1) in ${PRECISION} ${DATATYPE} a_coord;\n" "layout(location = 0) out ${PRECISION} ${DATATYPE} v_coord;\n" : "") + "out gl_PerVertex {\n" " vec4 gl_Position;\n" "};\n" "void main (void)\n" "{\n" " gl_Position = a_position;\n" + string(coordType != glu::TYPE_LAST ? " v_coord = a_coord;\n" : "") + "}\n"; map vertexParams; if (coordType != glu::TYPE_LAST) { vertexParams["PRECISION"] = glu::getPrecisionName(precision); vertexParams["DATATYPE"] = glu::getDataTypeName(coordType); } return tcu::StringTemplate(vertexTmpl).specialize(vertexParams); } // ConstantDerivateCaseInstance class ConstantDerivateCaseInstance : public TriangleDerivateCaseInstance { public: ConstantDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values); virtual ~ConstantDerivateCaseInstance (void); virtual bool verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask); }; ConstantDerivateCaseInstance::ConstantDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values) : TriangleDerivateCaseInstance (context, uniformSetup, definitions, values) { } ConstantDerivateCaseInstance::~ConstantDerivateCaseInstance (void) { } bool ConstantDerivateCaseInstance::verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask) { const tcu::Vec4 reference (0.0f); // Derivate of constant argument should always be 0 const tcu::Vec4 threshold = getSurfaceThreshold() / abs(m_values.derivScale); return verifyConstantDerivate(m_context.getTestContext().getLog(), result, errorMask, m_definitions.dataType, reference, threshold, m_values.derivScale, m_values.derivBias); } // ConstantDerivateCase class ConstantDerivateCase : public TriangleDerivateCase { public: ConstantDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, DerivateFunc func, glu::DataType type); virtual ~ConstantDerivateCase (void); virtual void initPrograms (vk::SourceCollections& programCollection) const; virtual TestInstance* createInstance (Context& context) const; }; ConstantDerivateCase::ConstantDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, DerivateFunc func, glu::DataType type) : TriangleDerivateCase (testCtx, name, description, new DerivateUniformSetup(false)) { m_definitions.func = func; m_definitions.dataType = type; m_definitions.precision = glu::PRECISION_HIGHP; } ConstantDerivateCase::~ConstantDerivateCase (void) { } TestInstance* ConstantDerivateCase::createInstance (Context& context) const { DE_ASSERT(m_uniformSetup != DE_NULL); return new ConstantDerivateCaseInstance(context, *m_uniformSetup, m_definitions, m_values); } void ConstantDerivateCase::initPrograms (vk::SourceCollections& programCollection) const { const char* fragmentTmpl = "#version 450\n" "layout(location = 0) out mediump vec4 o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; }; \n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res = ${FUNC}(${VALUE}) * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n"; map fragmentParams; fragmentParams["PRECISION"] = glu::getPrecisionName(m_definitions.precision); fragmentParams["DATATYPE"] = glu::getDataTypeName(m_definitions.dataType); fragmentParams["FUNC"] = getDerivateFuncName(m_definitions.func); fragmentParams["VALUE"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "vec4(1.0, 7.2, -1e5, 0.0)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? "vec3(1e2, 8.0, 0.01)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? "vec2(-0.0, 2.7)" : /* TYPE_FLOAT */ "7.7"; fragmentParams["CAST_TO_OUTPUT"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "res" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? "vec4(res, 1.0)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? "vec4(res, 0.0, 1.0)" : /* TYPE_FLOAT */ "vec4(res, 0.0, 0.0, 1.0)"; std::string fragmentSrc = tcu::StringTemplate(fragmentTmpl).specialize(fragmentParams); programCollection.glslSources.add("vert") << glu::VertexSource(genVertexSource(m_definitions.coordDataType, m_definitions.coordPrecision)); programCollection.glslSources.add("frag") << glu::FragmentSource(fragmentSrc); m_values.derivScale = tcu::Vec4(1e3f, 1e3f, 1e3f, 1e3f); m_values.derivBias = tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f); } // Linear cases class LinearDerivateUniformSetup : public DerivateUniformSetup { public: LinearDerivateUniformSetup (bool useSampler, BaseUniformType usedDefaultUniform); virtual ~LinearDerivateUniformSetup (void); virtual void setup (ShaderRenderCaseInstance& instance, const tcu::Vec4& constCoords) const; private: const BaseUniformType m_usedDefaultUniform; }; LinearDerivateUniformSetup::LinearDerivateUniformSetup (bool useSampler, BaseUniformType usedDefaultUniform) : DerivateUniformSetup (useSampler) , m_usedDefaultUniform (usedDefaultUniform) { } LinearDerivateUniformSetup::~LinearDerivateUniformSetup (void) { } void LinearDerivateUniformSetup::setup (ShaderRenderCaseInstance& instance, const tcu::Vec4& constCoords) const { DerivateUniformSetup::setup(instance, constCoords); if (m_usedDefaultUniform != U_LAST) switch (m_usedDefaultUniform) { case UB_TRUE: case UI_ONE: case UI_TWO: instance.useUniform(2u, m_usedDefaultUniform); break; default: DE_ASSERT(false); break; } } class LinearDerivateCaseInstance : public TriangleDerivateCaseInstance { public: LinearDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values); virtual ~LinearDerivateCaseInstance (void); virtual bool verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask); }; LinearDerivateCaseInstance::LinearDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values) : TriangleDerivateCaseInstance (context, uniformSetup, definitions, values) { } LinearDerivateCaseInstance::~LinearDerivateCaseInstance (void) { } bool LinearDerivateCaseInstance::verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask) { const tcu::Vec4 xScale = tcu::Vec4(1.0f, 0.0f, 0.5f, -0.5f); const tcu::Vec4 yScale = tcu::Vec4(0.0f, 1.0f, 0.5f, -0.5f); const tcu::Vec4 surfaceThreshold = getSurfaceThreshold() / abs(m_values.derivScale); if (isDfdxFunc(m_definitions.func) || isDfdyFunc(m_definitions.func)) { const bool isX = isDfdxFunc(m_definitions.func); const float div = isX ? float(result.getWidth()) : float(result.getHeight()); const tcu::Vec4 scale = isX ? xScale : yScale; tcu::Vec4 reference = ((m_values.coordMax - m_values.coordMin) / div); const tcu::Vec4 opThreshold = getDerivateThreshold(m_definitions.precision, m_values.coordMin, m_values.coordMax, reference); const tcu::Vec4 threshold = max(surfaceThreshold, opThreshold); const int numComps = glu::getDataTypeFloatScalars(m_definitions.dataType); /* adjust the reference value for the correct dfdx or dfdy sample adjacency */ reference = reference * scale; m_context.getTestContext().getLog() << tcu::TestLog::Message << "Verifying result image.\n" << "\tValid derivative is " << LogVecComps(reference, numComps) << " with threshold " << LogVecComps(threshold, numComps) << tcu::TestLog::EndMessage; // short circuit if result is strictly within the normal value error bounds. // This improves performance significantly. if (verifyConstantDerivate(m_context.getTestContext().getLog(), result, errorMask, m_definitions.dataType, reference, threshold, m_values.derivScale, m_values.derivBias, LOG_NOTHING)) { m_context.getTestContext().getLog() << tcu::TestLog::Message << "No incorrect derivatives found, result valid." << tcu::TestLog::EndMessage; return true; } // some pixels exceed error bounds calculated for normal values. Verify that these // potentially invalid pixels are in fact valid due to (for example) subnorm flushing. m_context.getTestContext().getLog() << tcu::TestLog::Message << "Initial verification failed, verifying image by calculating accurate error bounds for each result pixel.\n" << "\tVerifying each result derivative is within its range of legal result values." << tcu::TestLog::EndMessage; { const tcu::UVec2 viewportSize (VIEWPORT_WIDTH, VIEWPORT_HEIGHT); const float w = float(viewportSize.x()); const float h = float(viewportSize.y()); const tcu::Vec4 valueRamp = (m_values.coordMax - m_values.coordMin); Linear2DFunctionEvaluator function; function.matrix.setRow(0, tcu::Vec3(valueRamp.x() / w, 0.0f, m_values.coordMin.x())); function.matrix.setRow(1, tcu::Vec3(0.0f, valueRamp.y() / h, m_values.coordMin.y())); function.matrix.setRow(2, tcu::Vec3(valueRamp.z() / w, valueRamp.z() / h, m_values.coordMin.z() + m_values.coordMin.z()) / 2.0f); function.matrix.setRow(3, tcu::Vec3(-valueRamp.w() / w, -valueRamp.w() / h, m_values.coordMax.w() + m_values.coordMax.w()) / 2.0f); return reverifyConstantDerivateWithFlushRelaxations(m_context.getTestContext().getLog(), result, errorMask, m_definitions.dataType, m_definitions.precision, m_values.derivScale, m_values.derivBias, surfaceThreshold, m_definitions.func, function); } } else { DE_ASSERT(isFwidthFunc(m_definitions.func)); const float w = float(result.getWidth()); const float h = float(result.getHeight()); const tcu::Vec4 dx = ((m_values.coordMax - m_values.coordMin) / w) * xScale; const tcu::Vec4 dy = ((m_values.coordMax - m_values.coordMin) / h) * yScale; const tcu::Vec4 reference = tcu::abs(dx) + tcu::abs(dy); const tcu::Vec4 dxThreshold = getDerivateThreshold(m_definitions.precision, m_values.coordMin*xScale, m_values.coordMax*xScale, dx); const tcu::Vec4 dyThreshold = getDerivateThreshold(m_definitions.precision, m_values.coordMin*yScale, m_values.coordMax*yScale, dy); const tcu::Vec4 threshold = max(surfaceThreshold, max(dxThreshold, dyThreshold)); return verifyConstantDerivate(m_context.getTestContext().getLog(), result, errorMask, m_definitions.dataType, reference, threshold, m_values.derivScale, m_values.derivBias); } } // LinearDerivateCase class LinearDerivateCase : public TriangleDerivateCase { public: LinearDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, DerivateFunc func, glu::DataType type, glu::Precision precision, bool inNonUniformControlFlow, SurfaceType surfaceType, int numSamples, const std::string& fragmentSrcTmpl, BaseUniformType usedDefaultUniform); virtual ~LinearDerivateCase (void); virtual void initPrograms (vk::SourceCollections& programCollection) const; virtual TestInstance* createInstance (Context& context) const; private: const std::string m_fragmentTmpl; }; LinearDerivateCase::LinearDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, DerivateFunc func, glu::DataType type, glu::Precision precision, bool inNonUniformControlFlow, SurfaceType surfaceType, int numSamples, const std::string& fragmentSrcTmpl, BaseUniformType usedDefaultUniform) : TriangleDerivateCase (testCtx, name, description, new LinearDerivateUniformSetup(false, usedDefaultUniform)) , m_fragmentTmpl (fragmentSrcTmpl) { m_definitions.func = func; m_definitions.dataType = type; m_definitions.precision = precision; m_definitions.inNonUniformControlFlow = inNonUniformControlFlow; m_definitions.coordDataType = m_definitions.dataType; m_definitions.coordPrecision = m_definitions.precision; m_definitions.surfaceType = surfaceType; m_definitions.numSamples = numSamples; } LinearDerivateCase::~LinearDerivateCase (void) { } TestInstance* LinearDerivateCase::createInstance (Context& context) const { DE_ASSERT(m_uniformSetup != DE_NULL); return new LinearDerivateCaseInstance(context, *m_uniformSetup, m_definitions, m_values); } void LinearDerivateCase::initPrograms (vk::SourceCollections& programCollection) const { const SpirvVersion spirvVersion = m_definitions.inNonUniformControlFlow ? vk::SPIRV_VERSION_1_3 : vk::SPIRV_VERSION_1_0; const vk::ShaderBuildOptions buildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u); const tcu::UVec2 viewportSize (VIEWPORT_WIDTH, VIEWPORT_HEIGHT); const float w = float(viewportSize.x()); const float h = float(viewportSize.y()); const bool packToInt = m_definitions.surfaceType == SURFACETYPE_FLOAT_FBO; map fragmentParams; fragmentParams["OUTPUT_TYPE"] = glu::getDataTypeName(packToInt ? glu::TYPE_UINT_VEC4 : glu::TYPE_FLOAT_VEC4); fragmentParams["OUTPUT_PREC"] = glu::getPrecisionName(packToInt ? glu::PRECISION_HIGHP : m_definitions.precision); fragmentParams["PRECISION"] = glu::getPrecisionName(m_definitions.precision); fragmentParams["DATATYPE"] = glu::getDataTypeName(m_definitions.dataType); fragmentParams["FUNC"] = getDerivateFuncName(m_definitions.func); if (packToInt) { fragmentParams["CAST_TO_OUTPUT"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "floatBitsToUint(res)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? "floatBitsToUint(vec4(res, 1.0))" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? "floatBitsToUint(vec4(res, 0.0, 1.0))" : /* TYPE_FLOAT */ "floatBitsToUint(vec4(res, 0.0, 0.0, 1.0))"; } else { fragmentParams["CAST_TO_OUTPUT"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "res" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? "vec4(res, 1.0)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? "vec4(res, 0.0, 1.0)" : /* TYPE_FLOAT */ "vec4(res, 0.0, 0.0, 1.0)"; } std::string fragmentSrc = tcu::StringTemplate(m_fragmentTmpl).specialize(fragmentParams); programCollection.glslSources.add("vert") << glu::VertexSource(genVertexSource(m_definitions.coordDataType, m_definitions.coordPrecision)); programCollection.glslSources.add("frag") << glu::FragmentSource(fragmentSrc) << buildOptions; switch (m_definitions.precision) { case glu::PRECISION_HIGHP: m_values.coordMin = tcu::Vec4(-97.f, 0.2f, 71.f, 74.f); m_values.coordMax = tcu::Vec4(-13.2f, -77.f, 44.f, 76.f); break; case glu::PRECISION_MEDIUMP: m_values.coordMin = tcu::Vec4(-37.0f, 47.f, -7.f, 0.0f); m_values.coordMax = tcu::Vec4(-1.0f, 12.f, 7.f, 19.f); break; case glu::PRECISION_LOWP: m_values.coordMin = tcu::Vec4(0.0f, -1.0f, 0.0f, 1.0f); m_values.coordMax = tcu::Vec4(1.0f, 1.0f, -1.0f, -1.0f); break; default: DE_ASSERT(false); } if (m_definitions.surfaceType == SURFACETYPE_FLOAT_FBO) { // No scale or bias used for accuracy. m_values.derivScale = tcu::Vec4(1.0f); m_values.derivBias = tcu::Vec4(0.0f); } else { // Compute scale - bias that normalizes to 0..1 range. const tcu::Vec4 dx = (m_values.coordMax - m_values.coordMin) / tcu::Vec4(w, w, w*0.5f, -w*0.5f); const tcu::Vec4 dy = (m_values.coordMax - m_values.coordMin) / tcu::Vec4(h, h, h*0.5f, -h*0.5f); if (isDfdxFunc(m_definitions.func)) m_values.derivScale = 0.5f / dx; else if (isDfdyFunc(m_definitions.func)) m_values.derivScale = 0.5f / dy; else if (isFwidthFunc(m_definitions.func)) m_values.derivScale = 0.5f / (tcu::abs(dx) + tcu::abs(dy)); else DE_ASSERT(false); m_values.derivBias = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f); } } // TextureDerivateCaseInstance class TextureDerivateCaseInstance : public TriangleDerivateCaseInstance { public: TextureDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values, const TextureCaseValues& textureValues); virtual ~TextureDerivateCaseInstance (void); virtual bool verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask); private: const TextureCaseValues& m_textureValues; }; TextureDerivateCaseInstance::TextureDerivateCaseInstance (Context& context, const UniformSetup& uniformSetup, const DerivateCaseDefinition& definitions, const DerivateCaseValues& values, const TextureCaseValues& textureValues) : TriangleDerivateCaseInstance (context, uniformSetup, definitions, values) , m_textureValues (textureValues) { de::MovePtr texture; // Lowp and mediump cases use RGBA16F format, while highp uses RGBA32F. { const tcu::UVec2 viewportSize (VIEWPORT_WIDTH, VIEWPORT_HEIGHT); const tcu::TextureFormat format = glu::mapGLInternalFormat(m_definitions.precision == glu::PRECISION_HIGHP ? GL_RGBA32F : GL_RGBA16F); texture = de::MovePtr(new tcu::Texture2D(format, viewportSize.x(), viewportSize.y())); texture->allocLevel(0); } // Fill with gradients. { const tcu::PixelBufferAccess level0 = texture->getLevel(0); for (int y = 0; y < level0.getHeight(); y++) { for (int x = 0; x < level0.getWidth(); x++) { const float xf = (float(x)+0.5f) / float(level0.getWidth()); const float yf = (float(y)+0.5f) / float(level0.getHeight()); const tcu::Vec4 s = tcu::Vec4(xf, yf, (xf+yf)/2.0f, 1.0f - (xf+yf)/2.0f); level0.setPixel(m_textureValues.texValueMin + (m_textureValues.texValueMax - m_textureValues.texValueMin)*s, x, y); } } } de::SharedPtr testTexture (new TextureBinding(texture.release(), tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0.0f, true, tcu::Sampler::COMPAREMODE_NONE, 0, tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), true))); m_textures.push_back(testTexture); } TextureDerivateCaseInstance::~TextureDerivateCaseInstance (void) { } bool TextureDerivateCaseInstance::verify (const tcu::ConstPixelBufferAccess& result, const tcu::PixelBufferAccess& errorMask) { // \note Edges are ignored in comparison if (result.getWidth() < 2 || result.getHeight() < 2) throw tcu::NotSupportedError("Too small viewport"); tcu::ConstPixelBufferAccess compareArea = tcu::getSubregion(result, 1, 1, result.getWidth()-2, result.getHeight()-2); tcu::PixelBufferAccess maskArea = tcu::getSubregion(errorMask, 1, 1, errorMask.getWidth()-2, errorMask.getHeight()-2); const tcu::Vec4 xScale = tcu::Vec4(1.0f, 0.0f, 0.5f, -0.5f); const tcu::Vec4 yScale = tcu::Vec4(0.0f, 1.0f, 0.5f, -0.5f); const float w = float(result.getWidth()); const float h = float(result.getHeight()); const tcu::Vec4 surfaceThreshold = getSurfaceThreshold() / abs(m_values.derivScale); if (isDfdxFunc(m_definitions.func) || isDfdyFunc(m_definitions.func)) { const bool isX = isDfdxFunc(m_definitions.func); const float div = isX ? w : h; const tcu::Vec4 scale = isX ? xScale : yScale; tcu::Vec4 reference = ((m_textureValues.texValueMax - m_textureValues.texValueMin) / div); const tcu::Vec4 opThreshold = getDerivateThreshold(m_definitions.precision, m_textureValues.texValueMin, m_textureValues.texValueMax, reference); const tcu::Vec4 threshold = max(surfaceThreshold, opThreshold); const int numComps = glu::getDataTypeFloatScalars(m_definitions.dataType); /* adjust the reference value for the correct dfdx or dfdy sample adjacency */ reference = reference * scale; m_context.getTestContext().getLog() << tcu::TestLog::Message << "Verifying result image.\n" << "\tValid derivative is " << LogVecComps(reference, numComps) << " with threshold " << LogVecComps(threshold, numComps) << tcu::TestLog::EndMessage; // short circuit if result is strictly within the normal value error bounds. // This improves performance significantly. if (verifyConstantDerivate(m_context.getTestContext().getLog(), compareArea, maskArea, m_definitions.dataType, reference, threshold, m_values.derivScale, m_values.derivBias, LOG_NOTHING)) { m_context.getTestContext().getLog() << tcu::TestLog::Message << "No incorrect derivatives found, result valid." << tcu::TestLog::EndMessage; return true; } // some pixels exceed error bounds calculated for normal values. Verify that these // potentially invalid pixels are in fact valid due to (for example) subnorm flushing. m_context.getTestContext().getLog() << tcu::TestLog::Message << "Initial verification failed, verifying image by calculating accurate error bounds for each result pixel.\n" << "\tVerifying each result derivative is within its range of legal result values." << tcu::TestLog::EndMessage; { const tcu::Vec4 valueRamp = (m_textureValues.texValueMax - m_textureValues.texValueMin); Linear2DFunctionEvaluator function; function.matrix.setRow(0, tcu::Vec3(valueRamp.x() / w, 0.0f, m_textureValues.texValueMin.x())); function.matrix.setRow(1, tcu::Vec3(0.0f, valueRamp.y() / h, m_textureValues.texValueMin.y())); function.matrix.setRow(2, tcu::Vec3(valueRamp.z() / w, valueRamp.z() / h, m_textureValues.texValueMin.z() + m_textureValues.texValueMin.z()) / 2.0f); function.matrix.setRow(3, tcu::Vec3(-valueRamp.w() / w, -valueRamp.w() / h, m_textureValues.texValueMax.w() + m_textureValues.texValueMax.w()) / 2.0f); return reverifyConstantDerivateWithFlushRelaxations(m_context.getTestContext().getLog(), compareArea, maskArea, m_definitions.dataType, m_definitions.precision, m_values.derivScale, m_values.derivBias, surfaceThreshold, m_definitions.func, function); } } else { DE_ASSERT(isFwidthFunc(m_definitions.func)); const tcu::Vec4 dx = ((m_textureValues.texValueMax - m_textureValues.texValueMin) / w) * xScale; const tcu::Vec4 dy = ((m_textureValues.texValueMax - m_textureValues.texValueMin) / h) * yScale; const tcu::Vec4 reference = tcu::abs(dx) + tcu::abs(dy); const tcu::Vec4 dxThreshold = getDerivateThreshold(m_definitions.precision, m_textureValues.texValueMin*xScale, m_textureValues.texValueMax*xScale, dx); const tcu::Vec4 dyThreshold = getDerivateThreshold(m_definitions.precision, m_textureValues.texValueMin*yScale, m_textureValues.texValueMax*yScale, dy); const tcu::Vec4 threshold = max(surfaceThreshold, max(dxThreshold, dyThreshold)); return verifyConstantDerivate(m_context.getTestContext().getLog(), compareArea, maskArea, m_definitions.dataType, reference, threshold, m_values.derivScale, m_values.derivBias); } } // TextureDerivateCase class TextureDerivateCase : public TriangleDerivateCase { public: TextureDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, DerivateFunc func, glu::DataType type, glu::Precision precision, SurfaceType surfaceType, int numSamples); virtual ~TextureDerivateCase (void); virtual void initPrograms (vk::SourceCollections& programCollection) const; virtual TestInstance* createInstance (Context& context) const; private: mutable TextureCaseValues m_textureValues; }; TextureDerivateCase::TextureDerivateCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, DerivateFunc func, glu::DataType type, glu::Precision precision, SurfaceType surfaceType, int numSamples) : TriangleDerivateCase (testCtx, name, description, new DerivateUniformSetup(true)) { m_definitions.dataType = type; m_definitions.func = func; m_definitions.precision = precision; m_definitions.coordDataType = glu::TYPE_FLOAT_VEC2; m_definitions.coordPrecision = glu::PRECISION_HIGHP; m_definitions.surfaceType = surfaceType; m_definitions.numSamples = numSamples; } TextureDerivateCase::~TextureDerivateCase (void) { } TestInstance* TextureDerivateCase::createInstance (Context& context) const { DE_ASSERT(m_uniformSetup != DE_NULL); return new TextureDerivateCaseInstance(context, *m_uniformSetup, m_definitions, m_values, m_textureValues); } void TextureDerivateCase::initPrograms (vk::SourceCollections& programCollection) const { // Generate shader { const char* fragmentTmpl = "#version 450\n" "layout(location = 0) in highp vec2 v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2) uniform ${PRECISION} sampler2D u_sampler;\n" "void main (void)\n" "{\n" " ${PRECISION} vec4 tex = texture(u_sampler, v_coord);\n" " ${PRECISION} ${DATATYPE} res = ${FUNC}(tex${SWIZZLE}) * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n"; const bool packToInt = m_definitions.surfaceType == SURFACETYPE_FLOAT_FBO; map fragmentParams; fragmentParams["OUTPUT_TYPE"] = glu::getDataTypeName(packToInt ? glu::TYPE_UINT_VEC4 : glu::TYPE_FLOAT_VEC4); fragmentParams["OUTPUT_PREC"] = glu::getPrecisionName(packToInt ? glu::PRECISION_HIGHP : m_definitions.precision); fragmentParams["PRECISION"] = glu::getPrecisionName(m_definitions.precision); fragmentParams["DATATYPE"] = glu::getDataTypeName(m_definitions.dataType); fragmentParams["FUNC"] = getDerivateFuncName(m_definitions.func); fragmentParams["SWIZZLE"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? ".xyz" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? ".xy" : /* TYPE_FLOAT */ ".x"; if (packToInt) { fragmentParams["CAST_TO_OUTPUT"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "floatBitsToUint(res)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? "floatBitsToUint(vec4(res, 1.0))" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? "floatBitsToUint(vec4(res, 0.0, 1.0))" : /* TYPE_FLOAT */ "floatBitsToUint(vec4(res, 0.0, 0.0, 1.0))"; } else { fragmentParams["CAST_TO_OUTPUT"] = m_definitions.dataType == glu::TYPE_FLOAT_VEC4 ? "res" : m_definitions.dataType == glu::TYPE_FLOAT_VEC3 ? "vec4(res, 1.0)" : m_definitions.dataType == glu::TYPE_FLOAT_VEC2 ? "vec4(res, 0.0, 1.0)" : /* TYPE_FLOAT */ "vec4(res, 0.0, 0.0, 1.0)"; } std::string fragmentSrc = tcu::StringTemplate(fragmentTmpl).specialize(fragmentParams); programCollection.glslSources.add("vert") << glu::VertexSource(genVertexSource(m_definitions.coordDataType, m_definitions.coordPrecision)); programCollection.glslSources.add("frag") << glu::FragmentSource(fragmentSrc); } // Texture size matches viewport and nearest sampling is used. Thus texture sampling // is equal to just interpolating the texture value range. // Determine value range for texture. switch (m_definitions.precision) { case glu::PRECISION_HIGHP: m_textureValues.texValueMin = tcu::Vec4(-97.f, 0.2f, 71.f, 74.f); m_textureValues.texValueMax = tcu::Vec4(-13.2f, -77.f, 44.f, 76.f); break; case glu::PRECISION_MEDIUMP: m_textureValues.texValueMin = tcu::Vec4(-37.0f, 47.f, -7.f, 0.0f); m_textureValues.texValueMax = tcu::Vec4(-1.0f, 12.f, 7.f, 19.f); break; case glu::PRECISION_LOWP: m_textureValues.texValueMin = tcu::Vec4(0.0f, -1.0f, 0.0f, 1.0f); m_textureValues.texValueMax = tcu::Vec4(1.0f, 1.0f, -1.0f, -1.0f); break; default: DE_ASSERT(false); } // Texture coordinates m_values.coordMin = tcu::Vec4(0.0f); m_values.coordMax = tcu::Vec4(1.0f); if (m_definitions.surfaceType == SURFACETYPE_FLOAT_FBO) { // No scale or bias used for accuracy. m_values.derivScale = tcu::Vec4(1.0f); m_values.derivBias = tcu::Vec4(0.0f); } else { // Compute scale - bias that normalizes to 0..1 range. const tcu::UVec2 viewportSize (VIEWPORT_WIDTH, VIEWPORT_HEIGHT); const float w = float(viewportSize.x()); const float h = float(viewportSize.y()); const tcu::Vec4 dx = (m_textureValues.texValueMax - m_textureValues.texValueMin) / tcu::Vec4(w, w, w*0.5f, -w*0.5f); const tcu::Vec4 dy = (m_textureValues.texValueMax - m_textureValues.texValueMin) / tcu::Vec4(h, h, h*0.5f, -h*0.5f); if (isDfdxFunc(m_definitions.func)) m_values.derivScale = 0.5f / dx; else if (isDfdyFunc(m_definitions.func)) m_values.derivScale = 0.5f / dy; else if (isFwidthFunc(m_definitions.func)) m_values.derivScale = 0.5f / (tcu::abs(dx) + tcu::abs(dy)); else DE_ASSERT(false); m_values.derivBias = tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f); } } // ShaderDerivateTests class ShaderDerivateTests : public tcu::TestCaseGroup { public: ShaderDerivateTests (tcu::TestContext& testCtx); virtual ~ShaderDerivateTests (void); virtual void init (void); private: ShaderDerivateTests (const ShaderDerivateTests&); // not allowed! ShaderDerivateTests& operator= (const ShaderDerivateTests&); // not allowed! }; ShaderDerivateTests::ShaderDerivateTests (tcu::TestContext& testCtx) : TestCaseGroup(testCtx, "derivate", "Derivate Function Tests") { } ShaderDerivateTests::~ShaderDerivateTests (void) { } struct FunctionSpec { std::string name; DerivateFunc function; glu::DataType dataType; glu::Precision precision; FunctionSpec (const std::string& name_, DerivateFunc function_, glu::DataType dataType_, glu::Precision precision_) : name (name_) , function (function_) , dataType (dataType_) , precision (precision_) { } }; void ShaderDerivateTests::init (void) { static const struct { const char* name; const char* description; const char* source; BaseUniformType usedDefaultUniform; bool inNonUniformControlFlow; } s_linearDerivateCases[] = { { "linear", "Basic derivate of linearly interpolated argument", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res = ${FUNC}(v_coord) * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", U_LAST, false }, { "in_function", "Derivate of linear function argument", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "\n" "${PRECISION} ${DATATYPE} computeRes (${PRECISION} ${DATATYPE} value)\n" "{\n" " return ${FUNC}(v_coord) * u_scale + u_bias;\n" "}\n" "\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res = computeRes(v_coord);\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", U_LAST, false }, { "static_if", "Derivate of linearly interpolated value in static if", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res;\n" " if (false)\n" " res = ${FUNC}(-v_coord) * u_scale + u_bias;\n" " else\n" " res = ${FUNC}(v_coord) * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", U_LAST, false }, { "static_loop", "Derivate of linearly interpolated value in static loop", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res = ${DATATYPE}(0.0);\n" " for (int i = 0; i < 2; i++)\n" " res += ${FUNC}(v_coord * float(i));\n" " res = res * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", U_LAST, false }, { "static_switch", "Derivate of linearly interpolated value in static switch", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res;\n" " switch (1)\n" " {\n" " case 0: res = ${FUNC}(-v_coord) * u_scale + u_bias; break;\n" " case 1: res = ${FUNC}(v_coord) * u_scale + u_bias; break;\n" " }\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", U_LAST, false }, { "uniform_if", "Derivate of linearly interpolated value in uniform if", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2, std140) uniform Ui_true { bool ub_true; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res;\n" " if (ub_true)" " res = ${FUNC}(v_coord) * u_scale + u_bias;\n" " else\n" " res = ${FUNC}(-v_coord) * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", UB_TRUE, false }, { "uniform_loop", "Derivate of linearly interpolated value in uniform loop", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2, std140) uniform Ui_two { int ui_two; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res = ${DATATYPE}(0.0);\n" " for (int i = 0; i < ui_two; i++)\n" " res += ${FUNC}(v_coord * float(i));\n" " res = res * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", UI_TWO, false }, { "uniform_switch", "Derivate of linearly interpolated value in uniform switch", "#version 450\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2, std140) uniform Ui_one { int ui_one; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res;\n" " switch (ui_one)\n" " {\n" " case 0: res = ${FUNC}(-v_coord) * u_scale + u_bias; break;\n" " case 1: res = ${FUNC}(v_coord) * u_scale + u_bias; break;\n" " }\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", UI_ONE, false }, { "dynamic_if", "Derivate of linearly interpolated value in static if", "#version 450\n" "#extension GL_KHR_shader_subgroup_ballot : require\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2, std140) uniform Ui_one { int ui_one; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res;\n" " bool non_uniform = ((uint(gl_FragCoord.x * 0.4) + uint(gl_FragCoord.y * 0.3)) & 2) != 0;\n" " uvec4 quad_ballot = uvec4(0);\n" " quad_ballot[gl_SubgroupInvocationID >> 5] = 0xf << (gl_SubgroupInvocationID & 0x1c);\n" " bool quad_uniform = (subgroupBallot(non_uniform) & quad_ballot) == quad_ballot;\n" " if (quad_uniform)\n" " res = ${FUNC}(v_coord) * u_scale + u_bias;\n" " else\n" " res = ${FUNC}(v_coord * float(ui_one)) * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", UI_ONE, true }, { "dynamic_loop", "Derivate of linearly interpolated value in uniform loop", "#version 450\n" "#extension GL_KHR_shader_subgroup_ballot : require\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2, std140) uniform Ui_one { int ui_one; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res = ${DATATYPE}(0.0);\n" " bool non_uniform = ((uint(gl_FragCoord.x * 0.4) + uint(gl_FragCoord.y * 0.3)) & 2) != 0;\n" " uvec4 quad_ballot = uvec4(0);\n" " quad_ballot[gl_SubgroupInvocationID >> 5] = 0xf << (gl_SubgroupInvocationID & 0x1c);\n" " bool quad_uniform = (subgroupBallot(non_uniform) & quad_ballot) == quad_ballot;\n" " for (int i = 0; i < ui_one + int(quad_uniform); i++)\n" " res = ${FUNC}(v_coord * float(i - int(quad_uniform) + 1));\n" " res = res * u_scale + u_bias;\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", UI_ONE, true }, { "dynamic_switch", "Derivate of linearly interpolated value in uniform switch", "#version 450\n" "#extension GL_KHR_shader_subgroup_ballot : require\n" "layout(location = 0) in ${PRECISION} ${DATATYPE} v_coord;\n" "layout(location = 0) out ${OUTPUT_PREC} ${OUTPUT_TYPE} o_color;\n" "layout(binding = 0, std140) uniform Scale { ${PRECISION} ${DATATYPE} u_scale; };\n" "layout(binding = 1, std140) uniform Bias { ${PRECISION} ${DATATYPE} u_bias; };\n" "layout(binding = 2, std140) uniform Ui_one { int ui_one; };\n" "void main (void)\n" "{\n" " ${PRECISION} ${DATATYPE} res;\n" " bool non_uniform = ((uint(gl_FragCoord.x * 0.4) + uint(gl_FragCoord.y * 0.3)) & 2) != 0;\n" " uvec4 quad_ballot = uvec4(0);\n" " quad_ballot[gl_SubgroupInvocationID >> 5] = 0xf << (gl_SubgroupInvocationID & 0x1c);\n" " bool quad_uniform = (subgroupBallot(non_uniform) & quad_ballot) == quad_ballot;\n" " switch (int(quad_uniform))\n" " {\n" " case 0: res = ${FUNC}(v_coord) * u_scale + u_bias; break;\n" " case 1: res = ${FUNC}(v_coord * float(ui_one)) * u_scale + u_bias; break;\n" " }\n" " o_color = ${CAST_TO_OUTPUT};\n" "}\n", UI_ONE, true }, }; static const struct { const char* name; SurfaceType surfaceType; int numSamples; } s_fboConfigs[] = { { "fbo", SURFACETYPE_UNORM_FBO, 0 }, { "fbo_msaa2", SURFACETYPE_UNORM_FBO, 2 }, { "fbo_msaa4", SURFACETYPE_UNORM_FBO, 4 }, { "fbo_float", SURFACETYPE_FLOAT_FBO, 0 }, }; static const struct { const char* name; SurfaceType surfaceType; int numSamples; } s_textureConfigs[] = { { "basic", SURFACETYPE_UNORM_FBO, 0 }, { "msaa4", SURFACETYPE_UNORM_FBO, 4 }, { "float", SURFACETYPE_FLOAT_FBO, 0 }, }; // .dfdx[fine|coarse], .dfdy[fine|coarse], .fwidth[fine|coarse] for (int funcNdx = 0; funcNdx < DERIVATE_LAST; funcNdx++) { const DerivateFunc function = DerivateFunc(funcNdx); de::MovePtr functionGroup (new tcu::TestCaseGroup(m_testCtx, getDerivateFuncCaseName(function), getDerivateFuncName(function))); // .constant - no precision variants, checks that derivate of constant arguments is 0 { de::MovePtr constantGroup (new tcu::TestCaseGroup(m_testCtx, "constant", "Derivate of constant argument")); for (int vecSize = 1; vecSize <= 4; vecSize++) { const glu::DataType dataType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT; constantGroup->addChild(new ConstantDerivateCase(m_testCtx, glu::getDataTypeName(dataType), "", function, dataType)); } functionGroup->addChild(constantGroup.release()); } // Cases based on LinearDerivateCase for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_linearDerivateCases); caseNdx++) { de::MovePtr linearCaseGroup (new tcu::TestCaseGroup(m_testCtx, s_linearDerivateCases[caseNdx].name, s_linearDerivateCases[caseNdx].description)); const char* source = s_linearDerivateCases[caseNdx].source; for (int vecSize = 1; vecSize <= 4; vecSize++) { for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++) { const glu::DataType dataType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT; const glu::Precision precision = glu::Precision(precNdx); const SurfaceType surfaceType = SURFACETYPE_UNORM_FBO; const int numSamples = 0; std::ostringstream caseName; if (caseNdx != 0 && precision == glu::PRECISION_LOWP) continue; // Skip as lowp doesn't actually produce any bits when rendered to default FB. caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision); linearCaseGroup->addChild(new LinearDerivateCase(m_testCtx, caseName.str(), "", function, dataType, precision, s_linearDerivateCases[caseNdx].inNonUniformControlFlow, surfaceType, numSamples, source, s_linearDerivateCases[caseNdx].usedDefaultUniform)); } } functionGroup->addChild(linearCaseGroup.release()); } // Fbo cases for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_fboConfigs); caseNdx++) { de::MovePtr fboGroup (new tcu::TestCaseGroup(m_testCtx, s_fboConfigs[caseNdx].name, "Derivate usage when rendering into FBO")); const char* source = s_linearDerivateCases[0].source; // use source from .linear group const SurfaceType surfaceType = s_fboConfigs[caseNdx].surfaceType; const int numSamples = s_fboConfigs[caseNdx].numSamples; for (int vecSize = 1; vecSize <= 4; vecSize++) { for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++) { const glu::DataType dataType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT; const glu::Precision precision = glu::Precision(precNdx); std::ostringstream caseName; if (surfaceType != SURFACETYPE_FLOAT_FBO && precision == glu::PRECISION_LOWP) continue; // Skip as lowp doesn't actually produce any bits when rendered to U8 RT. caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision); fboGroup->addChild(new LinearDerivateCase(m_testCtx, caseName.str(), "", function, dataType, precision, false, surfaceType, numSamples, source, U_LAST)); } } functionGroup->addChild(fboGroup.release()); } // .texture { de::MovePtr textureGroup (new tcu::TestCaseGroup(m_testCtx, "texture", "Derivate of texture lookup result")); for (int texCaseNdx = 0; texCaseNdx < DE_LENGTH_OF_ARRAY(s_textureConfigs); texCaseNdx++) { de::MovePtr caseGroup (new tcu::TestCaseGroup(m_testCtx, s_textureConfigs[texCaseNdx].name, "")); const SurfaceType surfaceType = s_textureConfigs[texCaseNdx].surfaceType; const int numSamples = s_textureConfigs[texCaseNdx].numSamples; for (int vecSize = 1; vecSize <= 4; vecSize++) { for (int precNdx = 0; precNdx < glu::PRECISION_LAST; precNdx++) { const glu::DataType dataType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT; const glu::Precision precision = glu::Precision(precNdx); std::ostringstream caseName; if (surfaceType != SURFACETYPE_FLOAT_FBO && precision == glu::PRECISION_LOWP) continue; // Skip as lowp doesn't actually produce any bits when rendered to U8 RT. caseName << glu::getDataTypeName(dataType) << "_" << glu::getPrecisionName(precision); caseGroup->addChild(new TextureDerivateCase(m_testCtx, caseName.str(), "", function, dataType, precision, surfaceType, numSamples)); } } textureGroup->addChild(caseGroup.release()); } functionGroup->addChild(textureGroup.release()); } addChild(functionGroup.release()); } } } // anonymous tcu::TestCaseGroup* createDerivateTests (tcu::TestContext& testCtx) { return new ShaderDerivateTests(testCtx); } } // sr } // vkt