/*-------------------------------------------------------------------------
* drawElements Quality Program OpenGL ES 3.1 Module
* -------------------------------------------------
*
* 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 GLSL textureGather[Offset[s]] tests.
*//*--------------------------------------------------------------------*/
#include "es31fTextureGatherTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluTexture.hpp"
#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuTexLookupVerifier.hpp"
#include "tcuTexCompareVerifier.hpp"
#include "tcuCommandLine.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
using glu::ShaderProgram;
using tcu::ConstPixelBufferAccess;
using tcu::PixelBufferAccess;
using tcu::TestLog;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::UVec4;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using de::MovePtr;
using std::string;
using std::vector;
namespace deqp
{
using glu::TextureTestUtil::TextureType;
using glu::TextureTestUtil::TEXTURETYPE_2D;
using glu::TextureTestUtil::TEXTURETYPE_2D_ARRAY;
using glu::TextureTestUtil::TEXTURETYPE_CUBE;
namespace gles31
{
namespace Functional
{
namespace
{
// Round-to-zero int division, because pre-c++11 it's somewhat implementation-defined for negative values.
static inline int divRoundToZero (int a, int b)
{
return de::abs(a) / de::abs(b) * deSign32(a) * deSign32(b);
}
static void fillWithRandomColorTiles (const PixelBufferAccess& dst, const Vec4& minVal, const Vec4& maxVal, deUint32 seed)
{
const int numCols = dst.getWidth() >= 7 ? 7 : dst.getWidth();
const int numRows = dst.getHeight() >= 5 ? 5 : dst.getHeight();
de::Random rnd (seed);
for (int slice = 0; slice < dst.getDepth(); slice++)
for (int row = 0; row < numRows; row++)
for (int col = 0; col < numCols; col++)
{
const int yBegin = (row+0)*dst.getHeight()/numRows;
const int yEnd = (row+1)*dst.getHeight()/numRows;
const int xBegin = (col+0)*dst.getWidth()/numCols;
const int xEnd = (col+1)*dst.getWidth()/numCols;
Vec4 color;
for (int i = 0; i < 4; i++)
color[i] = rnd.getFloat(minVal[i], maxVal[i]);
tcu::clear(tcu::getSubregion(dst, xBegin, yBegin, slice, xEnd-xBegin, yEnd-yBegin, 1), color);
}
}
static inline bool isDepthFormat (const tcu::TextureFormat& fmt)
{
return fmt.order == tcu::TextureFormat::D || fmt.order == tcu::TextureFormat::DS;
}
static inline bool isUnormFormatType (tcu::TextureFormat::ChannelType type)
{
return type == tcu::TextureFormat::UNORM_INT8 ||
type == tcu::TextureFormat::UNORM_INT16 ||
type == tcu::TextureFormat::UNORM_INT32;
}
static inline bool isSIntFormatType (tcu::TextureFormat::ChannelType type)
{
return type == tcu::TextureFormat::SIGNED_INT8 ||
type == tcu::TextureFormat::SIGNED_INT16 ||
type == tcu::TextureFormat::SIGNED_INT32;
}
static inline bool isUIntFormatType (tcu::TextureFormat::ChannelType type)
{
return type == tcu::TextureFormat::UNSIGNED_INT8 ||
type == tcu::TextureFormat::UNSIGNED_INT16 ||
type == tcu::TextureFormat::UNSIGNED_INT32;
}
static tcu::TextureLevel getPixels (const glu::RenderContext& renderCtx, const IVec2& size, const tcu::TextureFormat& colorBufferFormat)
{
tcu::TextureLevel result(colorBufferFormat, size.x(), size.y());
// only a few pixel formats are guaranteed to be valid targets for readPixels, convert the rest
if (colorBufferFormat.order == tcu::TextureFormat::RGBA &&
(colorBufferFormat.type == tcu::TextureFormat::UNORM_INT8 ||
colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT32 ||
colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT32))
{
// valid as is
glu::readPixels(renderCtx, 0, 0, result.getAccess());
}
else if (colorBufferFormat.order == tcu::TextureFormat::RGBA &&
(isSIntFormatType(colorBufferFormat.type) ||
isUIntFormatType(colorBufferFormat.type)))
{
// signed and unsigned integers must be read using 32-bit values
const bool isSigned = isSIntFormatType(colorBufferFormat.type);
tcu::TextureLevel readResult (tcu::TextureFormat(tcu::TextureFormat::RGBA,
(isSigned) ? (tcu::TextureFormat::SIGNED_INT32) : (tcu::TextureFormat::UNSIGNED_INT32)),
size.x(),
size.y());
glu::readPixels(renderCtx, 0, 0, readResult.getAccess());
tcu::copy(result.getAccess(), readResult.getAccess());
}
else
{
// unreadable format
DE_ASSERT(false);
}
return result;
}
enum TextureSwizzleComponent
{
TEXTURESWIZZLECOMPONENT_R = 0,
TEXTURESWIZZLECOMPONENT_G,
TEXTURESWIZZLECOMPONENT_B,
TEXTURESWIZZLECOMPONENT_A,
TEXTURESWIZZLECOMPONENT_ZERO,
TEXTURESWIZZLECOMPONENT_ONE,
TEXTURESWIZZLECOMPONENT_LAST
};
static std::ostream& operator<< (std::ostream& stream, TextureSwizzleComponent comp)
{
switch (comp)
{
case TEXTURESWIZZLECOMPONENT_R: return stream << "RED";
case TEXTURESWIZZLECOMPONENT_G: return stream << "GREEN";
case TEXTURESWIZZLECOMPONENT_B: return stream << "BLUE";
case TEXTURESWIZZLECOMPONENT_A: return stream << "ALPHA";
case TEXTURESWIZZLECOMPONENT_ZERO: return stream << "ZERO";
case TEXTURESWIZZLECOMPONENT_ONE: return stream << "ONE";
default: DE_ASSERT(false); return stream;
}
}
struct MaybeTextureSwizzle
{
public:
static MaybeTextureSwizzle createNoneTextureSwizzle (void);
static MaybeTextureSwizzle createSomeTextureSwizzle (void);
bool isSome (void) const;
bool isNone (void) const;
bool isIdentitySwizzle (void) const;
tcu::Vector<TextureSwizzleComponent, 4>& getSwizzle (void);
const tcu::Vector<TextureSwizzleComponent, 4>& getSwizzle (void) const;
private:
MaybeTextureSwizzle (void);
tcu::Vector<TextureSwizzleComponent, 4> m_swizzle;
bool m_isSome;
};
static std::ostream& operator<< (std::ostream& stream, const MaybeTextureSwizzle& comp)
{
if (comp.isNone())
stream << "[default swizzle state]";
else
stream << "(" << comp.getSwizzle()[0]
<< ", " << comp.getSwizzle()[1]
<< ", " << comp.getSwizzle()[2]
<< ", " << comp.getSwizzle()[3]
<< ")";
return stream;
}
MaybeTextureSwizzle MaybeTextureSwizzle::createNoneTextureSwizzle (void)
{
MaybeTextureSwizzle swizzle;
swizzle.m_swizzle[0] = TEXTURESWIZZLECOMPONENT_LAST;
swizzle.m_swizzle[1] = TEXTURESWIZZLECOMPONENT_LAST;
swizzle.m_swizzle[2] = TEXTURESWIZZLECOMPONENT_LAST;
swizzle.m_swizzle[3] = TEXTURESWIZZLECOMPONENT_LAST;
swizzle.m_isSome = false;
return swizzle;
}
MaybeTextureSwizzle MaybeTextureSwizzle::createSomeTextureSwizzle (void)
{
MaybeTextureSwizzle swizzle;
swizzle.m_swizzle[0] = TEXTURESWIZZLECOMPONENT_R;
swizzle.m_swizzle[1] = TEXTURESWIZZLECOMPONENT_G;
swizzle.m_swizzle[2] = TEXTURESWIZZLECOMPONENT_B;
swizzle.m_swizzle[3] = TEXTURESWIZZLECOMPONENT_A;
swizzle.m_isSome = true;
return swizzle;
}
bool MaybeTextureSwizzle::isSome (void) const
{
return m_isSome;
}
bool MaybeTextureSwizzle::isNone (void) const
{
return !m_isSome;
}
bool MaybeTextureSwizzle::isIdentitySwizzle (void) const
{
return m_isSome &&
m_swizzle[0] == TEXTURESWIZZLECOMPONENT_R &&
m_swizzle[1] == TEXTURESWIZZLECOMPONENT_G &&
m_swizzle[2] == TEXTURESWIZZLECOMPONENT_B &&
m_swizzle[3] == TEXTURESWIZZLECOMPONENT_A;
}
tcu::Vector<TextureSwizzleComponent, 4>& MaybeTextureSwizzle::getSwizzle (void)
{
return m_swizzle;
}
const tcu::Vector<TextureSwizzleComponent, 4>& MaybeTextureSwizzle::getSwizzle (void) const
{
return m_swizzle;
}
MaybeTextureSwizzle::MaybeTextureSwizzle (void)
: m_swizzle (TEXTURESWIZZLECOMPONENT_LAST, TEXTURESWIZZLECOMPONENT_LAST, TEXTURESWIZZLECOMPONENT_LAST, TEXTURESWIZZLECOMPONENT_LAST)
, m_isSome (false)
{
}
static deUint32 getGLTextureSwizzleComponent (TextureSwizzleComponent c)
{
switch (c)
{
case TEXTURESWIZZLECOMPONENT_R: return GL_RED;
case TEXTURESWIZZLECOMPONENT_G: return GL_GREEN;
case TEXTURESWIZZLECOMPONENT_B: return GL_BLUE;
case TEXTURESWIZZLECOMPONENT_A: return GL_ALPHA;
case TEXTURESWIZZLECOMPONENT_ZERO: return GL_ZERO;
case TEXTURESWIZZLECOMPONENT_ONE: return GL_ONE;
default: DE_ASSERT(false); return (deUint32)-1;
}
}
template <typename T>
static inline T swizzleColorChannel (const tcu::Vector<T, 4>& src, TextureSwizzleComponent swizzle)
{
switch (swizzle)
{
case TEXTURESWIZZLECOMPONENT_R: return src[0];
case TEXTURESWIZZLECOMPONENT_G: return src[1];
case TEXTURESWIZZLECOMPONENT_B: return src[2];
case TEXTURESWIZZLECOMPONENT_A: return src[3];
case TEXTURESWIZZLECOMPONENT_ZERO: return (T)0;
case TEXTURESWIZZLECOMPONENT_ONE: return (T)1;
default: DE_ASSERT(false); return (T)-1;
}
}
template <typename T>
static inline tcu::Vector<T, 4> swizzleColor (const tcu::Vector<T, 4>& src, const MaybeTextureSwizzle& swizzle)
{
DE_ASSERT(swizzle.isSome());
tcu::Vector<T, 4> result;
for (int i = 0; i < 4; i++)
result[i] = swizzleColorChannel(src, swizzle.getSwizzle()[i]);
return result;
}
template <typename T>
static void swizzlePixels (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, const MaybeTextureSwizzle& swizzle)
{
DE_ASSERT(dst.getWidth() == src.getWidth() &&
dst.getHeight() == src.getHeight() &&
dst.getDepth() == src.getDepth());
for (int z = 0; z < src.getDepth(); z++)
for (int y = 0; y < src.getHeight(); y++)
for (int x = 0; x < src.getWidth(); x++)
dst.setPixel(swizzleColor(src.getPixelT<T>(x, y, z), swizzle), x, y, z);
}
static void swizzlePixels (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src, const MaybeTextureSwizzle& swizzle)
{
if (isDepthFormat(dst.getFormat()))
DE_ASSERT(swizzle.isNone() || swizzle.isIdentitySwizzle());
if (swizzle.isNone() || swizzle.isIdentitySwizzle())
tcu::copy(dst, src);
else if (isUnormFormatType(dst.getFormat().type))
swizzlePixels<float>(dst, src, swizzle);
else if (isUIntFormatType(dst.getFormat().type))
swizzlePixels<deUint32>(dst, src, swizzle);
else if (isSIntFormatType(dst.getFormat().type))
swizzlePixels<deInt32>(dst, src, swizzle);
else
DE_ASSERT(false);
}
static void swizzleTexture (tcu::Texture2D& dst, const tcu::Texture2D& src, const MaybeTextureSwizzle& swizzle)
{
dst = tcu::Texture2D(src.getFormat(), src.getWidth(), src.getHeight());
for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
{
if (src.isLevelEmpty(levelNdx))
continue;
dst.allocLevel(levelNdx);
swizzlePixels(dst.getLevel(levelNdx), src.getLevel(levelNdx), swizzle);
}
}
static void swizzleTexture (tcu::Texture2DArray& dst, const tcu::Texture2DArray& src, const MaybeTextureSwizzle& swizzle)
{
dst = tcu::Texture2DArray(src.getFormat(), src.getWidth(), src.getHeight(), src.getNumLayers());
for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
{
if (src.isLevelEmpty(levelNdx))
continue;
dst.allocLevel(levelNdx);
swizzlePixels(dst.getLevel(levelNdx), src.getLevel(levelNdx), swizzle);
}
}
static void swizzleTexture (tcu::TextureCube& dst, const tcu::TextureCube& src, const MaybeTextureSwizzle& swizzle)
{
dst = tcu::TextureCube(src.getFormat(), src.getSize());
for (int faceI = 0; faceI < tcu::CUBEFACE_LAST; faceI++)
{
const tcu::CubeFace face = (tcu::CubeFace)faceI;
for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
{
if (src.isLevelEmpty(face, levelNdx))
continue;
dst.allocLevel(face, levelNdx);
swizzlePixels(dst.getLevelFace(levelNdx, face), src.getLevelFace(levelNdx, face), swizzle);
}
}
}
static tcu::Texture2DView getOneLevelSubView (const tcu::Texture2DView& view, int level)
{
return tcu::Texture2DView(1, view.getLevels() + level);
}
static tcu::Texture2DArrayView getOneLevelSubView (const tcu::Texture2DArrayView& view, int level)
{
return tcu::Texture2DArrayView(1, view.getLevels() + level);
}
static tcu::TextureCubeView getOneLevelSubView (const tcu::TextureCubeView& view, int level)
{
const tcu::ConstPixelBufferAccess* levels[tcu::CUBEFACE_LAST];
for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
levels[face] = view.getFaceLevels((tcu::CubeFace)face) + level;
return tcu::TextureCubeView(1, levels);
}
class PixelOffsets
{
public:
virtual void operator() (const IVec2& pixCoord, IVec2 (&dst)[4]) const = 0;
virtual ~PixelOffsets (void) {}
};
class MultiplePixelOffsets : public PixelOffsets
{
public:
MultiplePixelOffsets (const IVec2& a,
const IVec2& b,
const IVec2& c,
const IVec2& d)
{
m_offsets[0] = a;
m_offsets[1] = b;
m_offsets[2] = c;
m_offsets[3] = d;
}
void operator() (const IVec2& /* pixCoord */, IVec2 (&dst)[4]) const
{
for (int i = 0; i < DE_LENGTH_OF_ARRAY(dst); i++)
dst[i] = m_offsets[i];
}
private:
IVec2 m_offsets[4];
};
class SinglePixelOffsets : public MultiplePixelOffsets
{
public:
SinglePixelOffsets (const IVec2& offset)
: MultiplePixelOffsets(offset + IVec2(0, 1),
offset + IVec2(1, 1),
offset + IVec2(1, 0),
offset + IVec2(0, 0))
{
}
};
class DynamicSinglePixelOffsets : public PixelOffsets
{
public:
DynamicSinglePixelOffsets (const IVec2& offsetRange) : m_offsetRange(offsetRange) {}
void operator() (const IVec2& pixCoord, IVec2 (&dst)[4]) const
{
const int offsetRangeSize = m_offsetRange.y() - m_offsetRange.x() + 1;
SinglePixelOffsets(tcu::mod(pixCoord.swizzle(1,0), IVec2(offsetRangeSize)) + m_offsetRange.x())(IVec2(), dst);
}
private:
IVec2 m_offsetRange;
};
template <typename T>
static inline T triQuadInterpolate (const T (&values)[4], float xFactor, float yFactor)
{
if (xFactor + yFactor < 1.0f)
return values[0] + (values[2]-values[0])*xFactor + (values[1]-values[0])*yFactor;
else
return values[3] + (values[1]-values[3])*(1.0f-xFactor) + (values[2]-values[3])*(1.0f-yFactor);
}
template <int N>
static inline void computeTexCoordVecs (const vector<float>& texCoords, tcu::Vector<float, N> (&dst)[4])
{
DE_ASSERT((int)texCoords.size() == 4*N);
for (int i = 0; i < 4; i++)
for (int j = 0; j < N; j++)
dst[i][j] = texCoords[i*N + j];
}
#if defined(DE_DEBUG)
// Whether offsets correspond to the sample offsets used with plain textureGather().
static inline bool isZeroOffsetOffsets (const IVec2 (&offsets)[4])
{
IVec2 ref[4];
SinglePixelOffsets(IVec2(0))(IVec2(), ref);
return std::equal(DE_ARRAY_BEGIN(offsets),
DE_ARRAY_END(offsets),
DE_ARRAY_BEGIN(ref));
}
#endif
template <typename ColorScalarType>
static tcu::Vector<ColorScalarType, 4> gatherOffsets (const tcu::Texture2DView& texture, const tcu::Sampler& sampler, const Vec2& coord, int componentNdx, const IVec2 (&offsets)[4])
{
return texture.gatherOffsets(sampler, coord.x(), coord.y(), componentNdx, offsets).cast<ColorScalarType>();
}
template <typename ColorScalarType>
static tcu::Vector<ColorScalarType, 4> gatherOffsets (const tcu::Texture2DArrayView& texture, const tcu::Sampler& sampler, const Vec3& coord, int componentNdx, const IVec2 (&offsets)[4])
{
return texture.gatherOffsets(sampler, coord.x(), coord.y(), coord.z(), componentNdx, offsets).cast<ColorScalarType>();
}
template <typename ColorScalarType>
static tcu::Vector<ColorScalarType, 4> gatherOffsets (const tcu::TextureCubeView& texture, const tcu::Sampler& sampler, const Vec3& coord, int componentNdx, const IVec2 (&offsets)[4])
{
DE_ASSERT(isZeroOffsetOffsets(offsets));
DE_UNREF(offsets);
return texture.gather(sampler, coord.x(), coord.y(), coord.z(), componentNdx).cast<ColorScalarType>();
}
static Vec4 gatherOffsetsCompare (const tcu::Texture2DView& texture, const tcu::Sampler& sampler, float refZ, const Vec2& coord, const IVec2 (&offsets)[4])
{
return texture.gatherOffsetsCompare(sampler, refZ, coord.x(), coord.y(), offsets);
}
static Vec4 gatherOffsetsCompare (const tcu::Texture2DArrayView& texture, const tcu::Sampler& sampler, float refZ, const Vec3& coord, const IVec2 (&offsets)[4])
{
return texture.gatherOffsetsCompare(sampler, refZ, coord.x(), coord.y(), coord.z(), offsets);
}
static Vec4 gatherOffsetsCompare (const tcu::TextureCubeView& texture, const tcu::Sampler& sampler, float refZ, const Vec3& coord, const IVec2 (&offsets)[4])
{
DE_ASSERT(isZeroOffsetOffsets(offsets));
DE_UNREF(offsets);
return texture.gatherCompare(sampler, refZ, coord.x(), coord.y(), coord.z());
}
template <typename PrecType, typename ColorScalarT>
static bool isGatherOffsetsResultValid (const tcu::TextureCubeView& texture,
const tcu::Sampler& sampler,
const PrecType& prec,
const Vec3& coord,
int componentNdx,
const IVec2 (&offsets)[4],
const tcu::Vector<ColorScalarT, 4>& result)
{
DE_ASSERT(isZeroOffsetOffsets(offsets));
DE_UNREF(offsets);
return tcu::isGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
}
static bool isGatherOffsetsCompareResultValid (const tcu::TextureCubeView& texture,
const tcu::Sampler& sampler,
const tcu::TexComparePrecision& prec,
const Vec3& coord,
const IVec2 (&offsets)[4],
float cmpReference,
const Vec4& result)
{
DE_ASSERT(isZeroOffsetOffsets(offsets));
DE_UNREF(offsets);
return tcu::isGatherCompareResultValid(texture, sampler, prec, coord, cmpReference, result);
}
template <typename ColorScalarType, typename PrecType, typename TexViewT, typename TexCoordT>
static bool verifyGatherOffsets (TestLog& log,
const ConstPixelBufferAccess& result,
const TexViewT& texture,
const TexCoordT (&texCoords)[4],
const tcu::Sampler& sampler,
const PrecType& lookupPrec,
int componentNdx,
const PixelOffsets& getPixelOffsets)
{
typedef tcu::Vector<ColorScalarType, 4> ColorVec;
const int width = result.getWidth();
const int height = result.getWidth();
tcu::TextureLevel ideal (result.getFormat(), width, height);
const PixelBufferAccess idealAccess = ideal.getAccess();
tcu::Surface errorMask (width, height);
bool success = true;
tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());
for (int py = 0; py < height; py++)
for (int px = 0; px < width; px++)
{
IVec2 offsets[4];
getPixelOffsets(IVec2(px, py), offsets);
const Vec2 viewportCoord = (Vec2((float)px, (float)py) + 0.5f) / Vec2((float)width, (float)height);
const TexCoordT texCoord = triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
const ColorVec resultPix = result.getPixelT<ColorScalarType>(px, py);
const ColorVec idealPix = gatherOffsets<ColorScalarType>(texture, sampler, texCoord, componentNdx, offsets);
idealAccess.setPixel(idealPix, px, py);
if (tcu::boolAny(tcu::logicalAnd(lookupPrec.colorMask,
tcu::greaterThan(tcu::absDiff(resultPix, idealPix),
lookupPrec.colorThreshold.template cast<ColorScalarType>()))))
{
if (!isGatherOffsetsResultValid(texture, sampler, lookupPrec, texCoord, componentNdx, offsets, resultPix))
{
errorMask.setPixel(px, py, tcu::RGBA::red());
success = false;
}
}
}
log << TestLog::ImageSet("VerifyResult", "Verification result")
<< TestLog::Image("Rendered", "Rendered image", result);
if (!success)
{
log << TestLog::Image("Reference", "Ideal reference image", ideal)
<< TestLog::Image("ErrorMask", "Error mask", errorMask);
}
log << TestLog::EndImageSet;
return success;
}
class PixelCompareRefZ
{
public:
virtual float operator() (const IVec2& pixCoord) const = 0;
};
class PixelCompareRefZDefault : public PixelCompareRefZ
{
public:
PixelCompareRefZDefault (const IVec2& renderSize) : m_renderSize(renderSize) {}
float operator() (const IVec2& pixCoord) const
{
return ((float)pixCoord.x() + 0.5f) / (float)m_renderSize.x();
}
private:
IVec2 m_renderSize;
};
template <typename TexViewT, typename TexCoordT>
static bool verifyGatherOffsetsCompare (TestLog& log,
const ConstPixelBufferAccess& result,
const TexViewT& texture,
const TexCoordT (&texCoords)[4],
const tcu::Sampler& sampler,
const tcu::TexComparePrecision& compPrec,
const PixelCompareRefZ& getPixelRefZ,
const PixelOffsets& getPixelOffsets)
{
const int width = result.getWidth();
const int height = result.getWidth();
tcu::Surface ideal (width, height);
const PixelBufferAccess idealAccess = ideal.getAccess();
tcu::Surface errorMask (width, height);
bool success = true;
tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());
for (int py = 0; py < height; py++)
for (int px = 0; px < width; px++)
{
IVec2 offsets[4];
getPixelOffsets(IVec2(px, py), offsets);
const Vec2 viewportCoord = (Vec2((float)px, (float)py) + 0.5f) / Vec2((float)width, (float)height);
const TexCoordT texCoord = triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
const float refZ = getPixelRefZ(IVec2(px, py));
const Vec4 resultPix = result.getPixel(px, py);
const Vec4 idealPix = gatherOffsetsCompare(texture, sampler, refZ, texCoord, offsets);
idealAccess.setPixel(idealPix, px, py);
if (!tcu::boolAll(tcu::equal(resultPix, idealPix)))
{
if (!isGatherOffsetsCompareResultValid(texture, sampler, compPrec, texCoord, offsets, refZ, resultPix))
{
errorMask.setPixel(px, py, tcu::RGBA::red());
success = false;
}
}
}
log << TestLog::ImageSet("VerifyResult", "Verification result")
<< TestLog::Image("Rendered", "Rendered image", result);
if (!success)
{
log << TestLog::Image("Reference", "Ideal reference image", ideal)
<< TestLog::Image("ErrorMask", "Error mask", errorMask);
}
log << TestLog::EndImageSet;
return success;
}
static bool verifySingleColored (TestLog& log, const ConstPixelBufferAccess& result, const Vec4& refColor)
{
const int width = result.getWidth();
const int height = result.getWidth();
tcu::Surface ideal (width, height);
const PixelBufferAccess idealAccess = ideal.getAccess();
tcu::Surface errorMask (width, height);
bool success = true;
tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());
tcu::clear(idealAccess, refColor);
for (int py = 0; py < height; py++)
for (int px = 0; px < width; px++)
{
if (result.getPixel(px, py) != refColor)
{
errorMask.setPixel(px, py, tcu::RGBA::red());
success = false;
}
}
log << TestLog::ImageSet("VerifyResult", "Verification result")
<< TestLog::Image("Rendered", "Rendered image", result);
if (!success)
{
log << TestLog::Image("Reference", "Ideal reference image", ideal)
<< TestLog::Image("ErrorMask", "Error mask", errorMask);
}
log << TestLog::EndImageSet;
return success;
}
enum GatherType
{
GATHERTYPE_BASIC = 0,
GATHERTYPE_OFFSET,
GATHERTYPE_OFFSET_DYNAMIC,
GATHERTYPE_OFFSETS,
GATHERTYPE_LAST
};
enum GatherCaseFlags
{
GATHERCASE_MIPMAP_INCOMPLETE = (1<<0), //!< Excercise special case of sampling mipmap-incomplete texture
GATHERCASE_DONT_SAMPLE_CUBE_CORNERS = (1<<1) //!< For cube map cases: do not sample cube corners
};
static inline const char* gatherTypeName (GatherType type)
{
switch (type)
{
case GATHERTYPE_BASIC: return "basic";
case GATHERTYPE_OFFSET: return "offset";
case GATHERTYPE_OFFSET_DYNAMIC: return "offset_dynamic";
case GATHERTYPE_OFFSETS: return "offsets";
default: DE_ASSERT(false); return DE_NULL;
}
}
static inline const char* gatherTypeDescription (GatherType type)
{
switch (type)
{
case GATHERTYPE_BASIC: return "textureGather";
case GATHERTYPE_OFFSET: return "textureGatherOffset";
case GATHERTYPE_OFFSET_DYNAMIC: return "textureGatherOffset with dynamic offsets";
case GATHERTYPE_OFFSETS: return "textureGatherOffsets";
default: DE_ASSERT(false); return DE_NULL;
}
}
static inline bool requireGpuShader5 (GatherType gatherType)
{
return gatherType == GATHERTYPE_OFFSET_DYNAMIC || gatherType == GATHERTYPE_OFFSETS;
}
struct GatherArgs
{
int componentNdx; // If negative, implicit component index 0 is used (i.e. the parameter is not given).
IVec2 offsets[4]; // \note Unless GATHERTYPE_OFFSETS is used, only offsets[0] is relevant; also, for GATHERTYPE_OFFSET_DYNAMIC, none are relevant.
GatherArgs (void)
: componentNdx(-1)
{
std::fill(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), IVec2());
}
GatherArgs (int comp,
const IVec2& off0 = IVec2(),
const IVec2& off1 = IVec2(),
const IVec2& off2 = IVec2(),
const IVec2& off3 = IVec2())
: componentNdx(comp)
{
offsets[0] = off0;
offsets[1] = off1;
offsets[2] = off2;
offsets[3] = off3;
}
};
static MovePtr<PixelOffsets> makePixelOffsetsFunctor (GatherType gatherType, const GatherArgs& gatherArgs, const IVec2& offsetRange)
{
if (gatherType == GATHERTYPE_BASIC || gatherType == GATHERTYPE_OFFSET)
{
const IVec2 offset = gatherType == GATHERTYPE_BASIC ? IVec2(0) : gatherArgs.offsets[0];
return MovePtr<PixelOffsets>(new SinglePixelOffsets(offset));
}
else if (gatherType == GATHERTYPE_OFFSET_DYNAMIC)
{
return MovePtr<PixelOffsets>(new DynamicSinglePixelOffsets(offsetRange));
}
else if (gatherType == GATHERTYPE_OFFSETS)
return MovePtr<PixelOffsets>(new MultiplePixelOffsets(gatherArgs.offsets[0],
gatherArgs.offsets[1],
gatherArgs.offsets[2],
gatherArgs.offsets[3]));
else
{
DE_ASSERT(false);
return MovePtr<PixelOffsets>(DE_NULL);
}
}
static inline glu::DataType getSamplerType (TextureType textureType, const tcu::TextureFormat& format)
{
if (isDepthFormat(format))
{
switch (textureType)
{
case TEXTURETYPE_2D: return glu::TYPE_SAMPLER_2D_SHADOW;
case TEXTURETYPE_2D_ARRAY: return glu::TYPE_SAMPLER_2D_ARRAY_SHADOW;
case TEXTURETYPE_CUBE: return glu::TYPE_SAMPLER_CUBE_SHADOW;
default: DE_ASSERT(false); return glu::TYPE_LAST;
}
}
else
{
switch (textureType)
{
case TEXTURETYPE_2D: return glu::getSampler2DType(format);
case TEXTURETYPE_2D_ARRAY: return glu::getSampler2DArrayType(format);
case TEXTURETYPE_CUBE: return glu::getSamplerCubeType(format);
default: DE_ASSERT(false); return glu::TYPE_LAST;
}
}
}
static inline glu::DataType getSamplerGatherResultType (glu::DataType samplerType)
{
switch (samplerType)
{
case glu::TYPE_SAMPLER_2D_SHADOW:
case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
case glu::TYPE_SAMPLER_CUBE_SHADOW:
case glu::TYPE_SAMPLER_2D:
case glu::TYPE_SAMPLER_2D_ARRAY:
case glu::TYPE_SAMPLER_CUBE:
return glu::TYPE_FLOAT_VEC4;
case glu::TYPE_INT_SAMPLER_2D:
case glu::TYPE_INT_SAMPLER_2D_ARRAY:
case glu::TYPE_INT_SAMPLER_CUBE:
return glu::TYPE_INT_VEC4;
case glu::TYPE_UINT_SAMPLER_2D:
case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
case glu::TYPE_UINT_SAMPLER_CUBE:
return glu::TYPE_UINT_VEC4;
default:
DE_ASSERT(false);
return glu::TYPE_LAST;
}
}
static inline int getNumTextureSamplingDimensions (TextureType type)
{
switch (type)
{
case TEXTURETYPE_2D: return 2;
case TEXTURETYPE_2D_ARRAY: return 3;
case TEXTURETYPE_CUBE: return 3;
default: DE_ASSERT(false); return -1;
}
}
static deUint32 getGLTextureType (TextureType type)
{
switch (type)
{
case TEXTURETYPE_2D: return GL_TEXTURE_2D;
case TEXTURETYPE_2D_ARRAY: return GL_TEXTURE_2D_ARRAY;
case TEXTURETYPE_CUBE: return GL_TEXTURE_CUBE_MAP;
default: DE_ASSERT(false); return (deUint32)-1;
}
}
enum OffsetSize
{
OFFSETSIZE_NONE = 0,
OFFSETSIZE_MINIMUM_REQUIRED,
OFFSETSIZE_IMPLEMENTATION_MAXIMUM,
OFFSETSIZE_LAST
};
static inline bool isMipmapFilter (tcu::Sampler::FilterMode filter)
{
switch (filter)
{
case tcu::Sampler::NEAREST:
case tcu::Sampler::LINEAR:
return false;
case tcu::Sampler::NEAREST_MIPMAP_NEAREST:
case tcu::Sampler::NEAREST_MIPMAP_LINEAR:
case tcu::Sampler::LINEAR_MIPMAP_NEAREST:
case tcu::Sampler::LINEAR_MIPMAP_LINEAR:
return true;
default:
DE_ASSERT(false);
return false;
}
}
class TextureGatherCase : public TestCase
{
public:
TextureGatherCase (Context& context,
const char* name,
const char* description,
TextureType textureType,
GatherType gatherType,
OffsetSize offsetSize,
tcu::TextureFormat textureFormat,
tcu::Sampler::CompareMode shadowCompareMode, //!< Should be COMPAREMODE_NONE iff textureFormat is a depth format.
tcu::Sampler::WrapMode wrapS,
tcu::Sampler::WrapMode wrapT,
const MaybeTextureSwizzle& texSwizzle,
// \note Filter modes have no effect on gather (except when it comes to
// texture completeness); these are supposed to test just that.
tcu::Sampler::FilterMode minFilter,
tcu::Sampler::FilterMode magFilter,
int baseLevel,
deUint32 flags);
void init (void);
void deinit (void);
IterateResult iterate (void);
protected:
IVec2 getOffsetRange (void) const;
template <typename TexViewT, typename TexCoordT>
bool verify (const ConstPixelBufferAccess& rendered,
const TexViewT& texture,
const TexCoordT (&bottomLeft)[4],
const GatherArgs& gatherArgs) const;
virtual void generateIterations (void) = 0;
virtual void createAndUploadTexture (void) = 0;
virtual int getNumIterations (void) const = 0;
virtual GatherArgs getGatherArgs (int iterationNdx) const = 0;
virtual vector<float> computeQuadTexCoord (int iterationNdx) const = 0;
virtual bool verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const = 0;
const GatherType m_gatherType;
const OffsetSize m_offsetSize;
const tcu::TextureFormat m_textureFormat;
const tcu::Sampler::CompareMode m_shadowCompareMode;
const tcu::Sampler::WrapMode m_wrapS;
const tcu::Sampler::WrapMode m_wrapT;
const MaybeTextureSwizzle m_textureSwizzle;
const tcu::Sampler::FilterMode m_minFilter;
const tcu::Sampler::FilterMode m_magFilter;
const int m_baseLevel;
const deUint32 m_flags;
private:
enum
{
SPEC_MAX_MIN_OFFSET = -8,
SPEC_MIN_MAX_OFFSET = 7
};
static const IVec2 RENDER_SIZE;
static glu::VertexSource genVertexShaderSource (bool requireGpuShader5, int numTexCoordComponents, bool useNormalizedCoordInput);
static glu::FragmentSource genFragmentShaderSource (bool requireGpuShader5, int numTexCoordComponents, glu::DataType samplerType, const string& funcCall, bool useNormalizedCoordInput, bool usePixCoord);
static string genGatherFuncCall (GatherType, const tcu::TextureFormat&, const GatherArgs&, const string& refZExpr, const IVec2& offsetRange, int indentationDepth);
static glu::ProgramSources genProgramSources (GatherType, TextureType, const tcu::TextureFormat&, const GatherArgs&, const string& refZExpr, const IVec2& offsetRange);
const TextureType m_textureType;
const tcu::TextureFormat m_colorBufferFormat;
MovePtr<glu::Renderbuffer> m_colorBuffer;
MovePtr<glu::Framebuffer> m_fbo;
int m_currentIteration;
MovePtr<ShaderProgram> m_program;
};
const IVec2 TextureGatherCase::RENDER_SIZE = IVec2(64, 64);
TextureGatherCase::TextureGatherCase (Context& context,
const char* name,
const char* description,
TextureType textureType,
GatherType gatherType,
OffsetSize offsetSize,
tcu::TextureFormat textureFormat,
tcu::Sampler::CompareMode shadowCompareMode, //!< Should be COMPAREMODE_NONE iff textureType == TEXTURETYPE_NORMAL.
tcu::Sampler::WrapMode wrapS,
tcu::Sampler::WrapMode wrapT,
const MaybeTextureSwizzle& textureSwizzle,
tcu::Sampler::FilterMode minFilter,
tcu::Sampler::FilterMode magFilter,
int baseLevel,
deUint32 flags)
: TestCase (context, name, description)
, m_gatherType (gatherType)
, m_offsetSize (offsetSize)
, m_textureFormat (textureFormat)
, m_shadowCompareMode (shadowCompareMode)
, m_wrapS (wrapS)
, m_wrapT (wrapT)
, m_textureSwizzle (textureSwizzle)
, m_minFilter (minFilter)
, m_magFilter (magFilter)
, m_baseLevel (baseLevel)
, m_flags (flags)
, m_textureType (textureType)
, m_colorBufferFormat (tcu::TextureFormat(tcu::TextureFormat::RGBA,
isDepthFormat(textureFormat) ? tcu::TextureFormat::UNORM_INT8 : textureFormat.type))
, m_currentIteration (0)
{
DE_ASSERT((m_gatherType == GATHERTYPE_BASIC) == (m_offsetSize == OFFSETSIZE_NONE));
DE_ASSERT((m_shadowCompareMode != tcu::Sampler::COMPAREMODE_NONE) == isDepthFormat(m_textureFormat));
DE_ASSERT(isUnormFormatType(m_colorBufferFormat.type) ||
m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT8 ||
m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT16 ||
m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT8 ||
m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT16);
DE_ASSERT(glu::isGLInternalColorFormatFilterable(glu::getInternalFormat(m_colorBufferFormat)) ||
(m_magFilter == tcu::Sampler::NEAREST && (m_minFilter == tcu::Sampler::NEAREST || m_minFilter == tcu::Sampler::NEAREST_MIPMAP_NEAREST)));
DE_ASSERT(isMipmapFilter(m_minFilter) || !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE));
DE_ASSERT(m_textureType == TEXTURETYPE_CUBE || !(m_flags & GATHERCASE_DONT_SAMPLE_CUBE_CORNERS));
DE_ASSERT(!((m_flags & GATHERCASE_MIPMAP_INCOMPLETE) && isDepthFormat(m_textureFormat))); // It's not clear what shadow textures should return when incomplete.
}
IVec2 TextureGatherCase::getOffsetRange (void) const
{
switch (m_offsetSize)
{
case OFFSETSIZE_NONE:
return IVec2(0);
break;
case OFFSETSIZE_MINIMUM_REQUIRED:
// \note Defined by spec.
return IVec2(SPEC_MAX_MIN_OFFSET,
SPEC_MIN_MAX_OFFSET);
break;
case OFFSETSIZE_IMPLEMENTATION_MAXIMUM:
return IVec2(m_context.getContextInfo().getInt(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET),
m_context.getContextInfo().getInt(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET));
break;
default:
DE_ASSERT(false);
return IVec2(-1);
}
}
glu::VertexSource TextureGatherCase::genVertexShaderSource (bool requireGpuShader5, int numTexCoordComponents, bool useNormalizedCoordInput)
{
DE_ASSERT(numTexCoordComponents == 2 || numTexCoordComponents == 3);
const string texCoordType = "vec" + de::toString(numTexCoordComponents);
return glu::VertexSource("#version 310 es\n"
+ string(requireGpuShader5 ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
"\n"
"in highp vec2 a_position;\n"
"in highp " + texCoordType + " a_texCoord;\n"
+ (useNormalizedCoordInput ? "in highp vec2 a_normalizedCoord; // (0,0) to (1,1)\n" : "") +
"\n"
"out highp " + texCoordType + " v_texCoord;\n"
+ (useNormalizedCoordInput ? "out highp vec2 v_normalizedCoord;\n" : "") +
"\n"
"void main (void)\n"
"{\n"
" gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);\n"
" v_texCoord = a_texCoord;\n"
+ (useNormalizedCoordInput ? "\tv_normalizedCoord = a_normalizedCoord;\n" : "") +
"}\n");
}
glu::FragmentSource TextureGatherCase::genFragmentShaderSource (bool requireGpuShader5,
int numTexCoordComponents,
glu::DataType samplerType,
const string& funcCall,
bool useNormalizedCoordInput,
bool usePixCoord)
{
DE_ASSERT(glu::isDataTypeSampler(samplerType));
DE_ASSERT(de::inRange(numTexCoordComponents, 2, 3));
DE_ASSERT(!usePixCoord || useNormalizedCoordInput);
const string texCoordType = "vec" + de::toString(numTexCoordComponents);
return glu::FragmentSource("#version 310 es\n"
+ string(requireGpuShader5 ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
"\n"
"layout (location = 0) out mediump " + glu::getDataTypeName(getSamplerGatherResultType(samplerType)) + " o_color;\n"
"\n"
"in highp " + texCoordType + " v_texCoord;\n"
+ (useNormalizedCoordInput ? "in highp vec2 v_normalizedCoord;\n" : "") +
"\n"
"uniform highp " + string(glu::getDataTypeName(samplerType)) + " u_sampler;\n"
+ (useNormalizedCoordInput ? "uniform highp vec2 u_viewportSize;\n" : "") +
"\n"
"void main(void)\n"
"{\n"
+ (usePixCoord ? "\tivec2 pixCoord = ivec2(v_normalizedCoord*u_viewportSize);\n" : "") +
" o_color = " + funcCall + ";\n"
"}\n");
}
string TextureGatherCase::genGatherFuncCall (GatherType gatherType, const tcu::TextureFormat& textureFormat, const GatherArgs& gatherArgs, const string& refZExpr, const IVec2& offsetRange, int indentationDepth)
{
string result;
switch (gatherType)
{
case GATHERTYPE_BASIC:
result += "textureGather";
break;
case GATHERTYPE_OFFSET: // \note Fallthrough.
case GATHERTYPE_OFFSET_DYNAMIC:
result += "textureGatherOffset";
break;
case GATHERTYPE_OFFSETS:
result += "textureGatherOffsets";
break;
default:
DE_ASSERT(false);
}
result += "(u_sampler, v_texCoord";
if (isDepthFormat(textureFormat))
{
DE_ASSERT(gatherArgs.componentNdx < 0);
result += ", " + refZExpr;
}
if (gatherType == GATHERTYPE_OFFSET ||
gatherType == GATHERTYPE_OFFSET_DYNAMIC ||
gatherType == GATHERTYPE_OFFSETS)
{
result += ", ";
switch (gatherType)
{
case GATHERTYPE_OFFSET:
result += "ivec2" + de::toString(gatherArgs.offsets[0]);
break;
case GATHERTYPE_OFFSET_DYNAMIC:
result += "pixCoord.yx % ivec2(" + de::toString(offsetRange.y() - offsetRange.x() + 1) + ") + " + de::toString(offsetRange.x());
break;
case GATHERTYPE_OFFSETS:
result += "ivec2[4](\n"
+ string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[0]) + ",\n"
+ string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[1]) + ",\n"
+ string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[2]) + ",\n"
+ string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[3]) + ")\n"
+ string(indentationDepth, '\t') + "\t";
break;
default:
DE_ASSERT(false);
}
}
if (gatherArgs.componentNdx >= 0)
{
DE_ASSERT(gatherArgs.componentNdx < 4);
result += ", " + de::toString(gatherArgs.componentNdx);
}
result += ")";
return result;
}
// \note If componentNdx for genProgramSources() is -1, component index is not specified.
glu::ProgramSources TextureGatherCase::genProgramSources (GatherType gatherType,
TextureType textureType,
const tcu::TextureFormat& textureFormat,
const GatherArgs& gatherArgs,
const string& refZExpr,
const IVec2& offsetRange)
{
const bool usePixCoord = gatherType == GATHERTYPE_OFFSET_DYNAMIC;
const bool useNormalizedCoord = usePixCoord || isDepthFormat(textureFormat);
const bool isDynamicOffset = gatherType == GATHERTYPE_OFFSET_DYNAMIC;
const bool isShadow = isDepthFormat(textureFormat);
const glu::DataType samplerType = getSamplerType(textureType, textureFormat);
const int numDims = getNumTextureSamplingDimensions(textureType);
const string funcCall = genGatherFuncCall(gatherType, textureFormat, gatherArgs, refZExpr, offsetRange, 1);
return glu::ProgramSources() << genVertexShaderSource(requireGpuShader5(gatherType), numDims, isDynamicOffset || isShadow)
<< genFragmentShaderSource(requireGpuShader5(gatherType), numDims, samplerType, funcCall, useNormalizedCoord, usePixCoord);
}
void TextureGatherCase::init (void)
{
TestLog& log = m_testCtx.getLog();
const glu::RenderContext& renderCtx = m_context.getRenderContext();
const glw::Functions& gl = renderCtx.getFunctions();
const deUint32 texTypeGL = getGLTextureType(m_textureType);
// Check prerequisites.
if (requireGpuShader5(m_gatherType) && !m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"))
throw tcu::NotSupportedError("GL_EXT_gpu_shader5 required");
// Log and check implementation offset limits, if appropriate.
if (m_offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM)
{
const IVec2 offsetRange = getOffsetRange();
log << TestLog::Integer("ImplementationMinTextureGatherOffset", "Implementation's value for GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET", "", QP_KEY_TAG_NONE, offsetRange[0])
<< TestLog::Integer("ImplementationMaxTextureGatherOffset", "Implementation's value for GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET", "", QP_KEY_TAG_NONE, offsetRange[1]);
TCU_CHECK_MSG(offsetRange[0] <= SPEC_MAX_MIN_OFFSET, ("GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET must be at most " + de::toString((int)SPEC_MAX_MIN_OFFSET)).c_str());
TCU_CHECK_MSG(offsetRange[1] >= SPEC_MIN_MAX_OFFSET, ("GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET must be at least " + de::toString((int)SPEC_MIN_MAX_OFFSET)).c_str());
}
// Create rbo and fbo.
m_colorBuffer = MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(renderCtx));
gl.bindRenderbuffer(GL_RENDERBUFFER, **m_colorBuffer);
gl.renderbufferStorage(GL_RENDERBUFFER, glu::getInternalFormat(m_colorBufferFormat), RENDER_SIZE.x(), RENDER_SIZE.y());
GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup renderbuffer object");
m_fbo = MovePtr<glu::Framebuffer>(new glu::Framebuffer(renderCtx));
gl.bindFramebuffer(GL_FRAMEBUFFER, **m_fbo);
gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_colorBuffer);
GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup framebuffer object");
log << TestLog::Message << "Using a framebuffer object with renderbuffer with format "
<< glu::getTextureFormatName(glu::getInternalFormat(m_colorBufferFormat))
<< " and size " << RENDER_SIZE << TestLog::EndMessage;
// Generate subclass-specific iterations.
generateIterations();
m_currentIteration = 0;
// Initialize texture.
createAndUploadTexture();
gl.texParameteri(texTypeGL, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(m_wrapS));
gl.texParameteri(texTypeGL, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(m_wrapT));
gl.texParameteri(texTypeGL, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(m_minFilter));
gl.texParameteri(texTypeGL, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(m_magFilter));
if (m_baseLevel != 0)
gl.texParameteri(texTypeGL, GL_TEXTURE_BASE_LEVEL, m_baseLevel);
if (isDepthFormat(m_textureFormat))
{
gl.texParameteri(texTypeGL, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
gl.texParameteri(texTypeGL, GL_TEXTURE_COMPARE_FUNC, glu::getGLCompareFunc(m_shadowCompareMode));
}
if (m_textureSwizzle.isSome())
{
const deUint32 swizzleNamesGL[4] =
{
GL_TEXTURE_SWIZZLE_R,
GL_TEXTURE_SWIZZLE_G,
GL_TEXTURE_SWIZZLE_B,
GL_TEXTURE_SWIZZLE_A
};
for (int i = 0; i < 4; i++)
{
const deUint32 curGLSwizzle = getGLTextureSwizzleComponent(m_textureSwizzle.getSwizzle()[i]);
gl.texParameteri(texTypeGL, swizzleNamesGL[i], curGLSwizzle);
}
}
GLU_EXPECT_NO_ERROR(gl.getError(), "Set texture parameters");
log << TestLog::Message << "Texture base level is " << m_baseLevel << TestLog::EndMessage
<< TestLog::Message << "s and t wrap modes are "
<< glu::getTextureWrapModeName(glu::getGLWrapMode(m_wrapS)) << " and "
<< glu::getTextureWrapModeName(glu::getGLWrapMode(m_wrapT)) << ", respectively" << TestLog::EndMessage
<< TestLog::Message << "Minification and magnification filter modes are "
<< glu::getTextureFilterName(glu::getGLFilterMode(m_minFilter)) << " and "
<< glu::getTextureFilterName(glu::getGLFilterMode(m_magFilter)) << ", respectively "
<< ((m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ?
"(note that they cause the texture to be incomplete)" :
"(note that they should have no effect on gather result)")
<< TestLog::EndMessage
<< TestLog::Message << "Using texture swizzle " << m_textureSwizzle << TestLog::EndMessage;
if (m_shadowCompareMode != tcu::Sampler::COMPAREMODE_NONE)
log << TestLog::Message << "Using texture compare func " << glu::getCompareFuncName(glu::getGLCompareFunc(m_shadowCompareMode)) << TestLog::EndMessage;
}
void TextureGatherCase::deinit (void)
{
m_program = MovePtr<ShaderProgram>(DE_NULL);
m_fbo = MovePtr<glu::Framebuffer>(DE_NULL);
m_colorBuffer = MovePtr<glu::Renderbuffer>(DE_NULL);
}
TextureGatherCase::IterateResult TextureGatherCase::iterate (void)
{
TestLog& log = m_testCtx.getLog();
const tcu::ScopedLogSection iterationSection (log, "Iteration" + de::toString(m_currentIteration), "Iteration " + de::toString(m_currentIteration));
const glu::RenderContext& renderCtx = m_context.getRenderContext();
const glw::Functions& gl = renderCtx.getFunctions();
const GatherArgs& gatherArgs = getGatherArgs(m_currentIteration);
const string refZExpr = "v_normalizedCoord.x";
const bool needPixelCoordInShader = m_gatherType == GATHERTYPE_OFFSET_DYNAMIC;
const bool needNormalizedCoordInShader = needPixelCoordInShader || isDepthFormat(m_textureFormat);
// Generate a program appropriate for this iteration.
m_program = MovePtr<ShaderProgram>(new ShaderProgram(renderCtx, genProgramSources(m_gatherType, m_textureType, m_textureFormat, gatherArgs, refZExpr, getOffsetRange())));
if (m_currentIteration == 0)
m_testCtx.getLog() << *m_program;
else
m_testCtx.getLog() << TestLog::Message << "Using a program similar to the previous one, except with a gather function call as follows:\n"
<< genGatherFuncCall(m_gatherType, m_textureFormat, gatherArgs, refZExpr, getOffsetRange(), 0)
<< TestLog::EndMessage;
if (!m_program->isOk())
{
if (m_currentIteration != 0)
m_testCtx.getLog() << *m_program;
TCU_FAIL("Failed to build program");
}
// Render.
gl.viewport(0, 0, RENDER_SIZE.x(), RENDER_SIZE.y());
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.clear(GL_COLOR_BUFFER_BIT);
{
const float position[4*2] =
{
-1.0f, -1.0f,
-1.0f, +1.0f,
+1.0f, -1.0f,
+1.0f, +1.0f,
};
const float normalizedCoord[4*2] =
{
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
const vector<float> texCoord = computeQuadTexCoord(m_currentIteration);
vector<glu::VertexArrayBinding> attrBindings;
attrBindings.push_back(glu::va::Float("a_position", 2, 4, 0, &position[0]));
attrBindings.push_back(glu::va::Float("a_texCoord", (int)texCoord.size()/4, 4, 0, &texCoord[0]));
if (needNormalizedCoordInShader)
attrBindings.push_back(glu::va::Float("a_normalizedCoord", 2, 4, 0, &normalizedCoord[0]));
const deUint16 indices[6] = { 0, 1, 2, 2, 1, 3 };
gl.useProgram(m_program->getProgram());
{
const int samplerUniformLocation = gl.getUniformLocation(m_program->getProgram(), "u_sampler");
TCU_CHECK(samplerUniformLocation >= 0);
gl.uniform1i(samplerUniformLocation, 0);
}
if (needPixelCoordInShader)
{
const int viewportSizeUniformLocation = gl.getUniformLocation(m_program->getProgram(), "u_viewportSize");
TCU_CHECK(viewportSizeUniformLocation >= 0);
gl.uniform2f(viewportSizeUniformLocation, (float)RENDER_SIZE.x(), (float)RENDER_SIZE.y());
}
if (texCoord.size() == 2*4)
{
Vec2 texCoordVec[4];
computeTexCoordVecs(texCoord, texCoordVec);
log << TestLog::Message << "Texture coordinates run from " << texCoordVec[0] << " to " << texCoordVec[3] << TestLog::EndMessage;
}
else if (texCoord.size() == 3*4)
{
Vec3 texCoordVec[4];
computeTexCoordVecs(texCoord, texCoordVec);
log << TestLog::Message << "Texture coordinates run from " << texCoordVec[0] << " to " << texCoordVec[3] << TestLog::EndMessage;
}
else
DE_ASSERT(false);
glu::draw(renderCtx, m_program->getProgram(), (int)attrBindings.size(), &attrBindings[0],
glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}
// Verify result.
{
const tcu::TextureLevel rendered = getPixels(renderCtx, RENDER_SIZE, m_colorBufferFormat);
if (!verify(m_currentIteration, rendered.getAccess()))
{
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
return STOP;
}
}
m_currentIteration++;
if (m_currentIteration == (int)getNumIterations())
{
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
return STOP;
}
else
return CONTINUE;
}
template <typename TexViewT, typename TexCoordT>
bool TextureGatherCase::verify (const ConstPixelBufferAccess& rendered,
const TexViewT& texture,
const TexCoordT (&texCoords)[4],
const GatherArgs& gatherArgs) const
{
TestLog& log = m_testCtx.getLog();
if (m_flags & GATHERCASE_MIPMAP_INCOMPLETE)
{
const int componentNdx = de::max(0, gatherArgs.componentNdx);
const Vec4 incompleteColor (0.0f, 0.0f, 0.0f, 1.0f);
const Vec4 refColor (incompleteColor[componentNdx]);
const bool isOk = verifySingleColored(log, rendered, refColor);
if (!isOk)
log << TestLog::Message << "Note: expected color " << refColor << " for all pixels; "
<< incompleteColor[componentNdx] << " is component at index " << componentNdx
<< " in the color " << incompleteColor << ", which is used for incomplete textures" << TestLog::EndMessage;
return isOk;
}
else
{
DE_ASSERT(m_colorBufferFormat.order == tcu::TextureFormat::RGBA);
DE_ASSERT(m_colorBufferFormat.type == tcu::TextureFormat::UNORM_INT8 ||
m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT8 ||
m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT8);
const MovePtr<PixelOffsets> pixelOffsets = makePixelOffsetsFunctor(m_gatherType, gatherArgs, getOffsetRange());
const tcu::PixelFormat pixelFormat = tcu::PixelFormat(8,8,8,8);
const IVec4 colorBits = tcu::max(glu::TextureTestUtil::getBitsVec(pixelFormat) - 1, tcu::IVec4(0));
const IVec3 coordBits = m_textureType == TEXTURETYPE_2D ? IVec3(20,20,0)
: m_textureType == TEXTURETYPE_CUBE ? IVec3(10,10,10)
: m_textureType == TEXTURETYPE_2D_ARRAY ? IVec3(20,20,20)
: IVec3(-1);
const IVec3 uvwBits = m_textureType == TEXTURETYPE_2D ? IVec3(7,7,0)
: m_textureType == TEXTURETYPE_CUBE ? IVec3(6,6,0)
: m_textureType == TEXTURETYPE_2D_ARRAY ? IVec3(7,7,7)
: IVec3(-1);
tcu::Sampler sampler;
sampler.wrapS = m_wrapS;
sampler.wrapT = m_wrapT;
sampler.compare = m_shadowCompareMode;
if (isDepthFormat(m_textureFormat))
{
tcu::TexComparePrecision comparePrec;
comparePrec.coordBits = coordBits;
comparePrec.uvwBits = uvwBits;
comparePrec.referenceBits = 16;
comparePrec.resultBits = pixelFormat.redBits-1;
return verifyGatherOffsetsCompare(log, rendered, texture, texCoords, sampler, comparePrec, PixelCompareRefZDefault(RENDER_SIZE), *pixelOffsets);
}
else
{
const int componentNdx = de::max(0, gatherArgs.componentNdx);
if (isUnormFormatType(m_textureFormat.type))
{
tcu::LookupPrecision lookupPrec;
lookupPrec.colorThreshold = tcu::computeFixedPointThreshold(colorBits);
lookupPrec.coordBits = coordBits;
lookupPrec.uvwBits = uvwBits;
lookupPrec.colorMask = glu::TextureTestUtil::getCompareMask(pixelFormat);
return verifyGatherOffsets<float>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx, *pixelOffsets);
}
else if (isUIntFormatType(m_textureFormat.type) || isSIntFormatType(m_textureFormat.type))
{
tcu::IntLookupPrecision lookupPrec;
lookupPrec.colorThreshold = UVec4(0);
lookupPrec.coordBits = coordBits;
lookupPrec.uvwBits = uvwBits;
lookupPrec.colorMask = glu::TextureTestUtil::getCompareMask(pixelFormat);
if (isUIntFormatType(m_textureFormat.type))
return verifyGatherOffsets<deUint32>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx, *pixelOffsets);
else if (isSIntFormatType(m_textureFormat.type))
return verifyGatherOffsets<deInt32>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx, *pixelOffsets);
else
{
DE_ASSERT(false);
return false;
}
}
else
{
DE_ASSERT(false);
return false;
}
}
}
}
vector<GatherArgs> generateBasic2DCaseIterations (GatherType gatherType, const tcu::TextureFormat& textureFormat, const IVec2& offsetRange)
{
const int numComponentCases = isDepthFormat(textureFormat) ? 1 : 4+1; // \note For non-depth textures, test explicit components 0 to 3 and implicit component 0.
vector<GatherArgs> result;
for (int componentCaseNdx = 0; componentCaseNdx < numComponentCases; componentCaseNdx++)
{
const int componentNdx = componentCaseNdx - 1;
switch (gatherType)
{
case GATHERTYPE_BASIC:
result.push_back(GatherArgs(componentNdx));
break;
case GATHERTYPE_OFFSET:
{
const int min = offsetRange.x();
const int max = offsetRange.y();
const int hmin = divRoundToZero(min, 2);
const int hmax = divRoundToZero(max, 2);
result.push_back(GatherArgs(componentNdx, IVec2(min, max)));
if (componentCaseNdx == 0) // Don't test all offsets variants for all color components (they should be pretty orthogonal).
{
result.push_back(GatherArgs(componentNdx, IVec2(min, min)));
result.push_back(GatherArgs(componentNdx, IVec2(max, min)));
result.push_back(GatherArgs(componentNdx, IVec2(max, max)));
result.push_back(GatherArgs(componentNdx, IVec2(0, hmax)));
result.push_back(GatherArgs(componentNdx, IVec2(hmin, 0)));
result.push_back(GatherArgs(componentNdx, IVec2(0, 0)));
}
break;
}
case GATHERTYPE_OFFSET_DYNAMIC:
result.push_back(GatherArgs(componentNdx));
break;
case GATHERTYPE_OFFSETS:
{
const int min = offsetRange.x();
const int max = offsetRange.y();
const int hmin = divRoundToZero(min, 2);
const int hmax = divRoundToZero(max, 2);
result.push_back(GatherArgs(componentNdx,
IVec2(min, min),
IVec2(min, max),
IVec2(max, min),
IVec2(max, max)));
if (componentCaseNdx == 0) // Don't test all offsets variants for all color components (they should be pretty orthogonal).
result.push_back(GatherArgs(componentNdx,
IVec2(min, hmax),
IVec2(hmin, max),
IVec2(0, hmax),
IVec2(hmax, 0)));
break;
}
default:
DE_ASSERT(false);
}
}
return result;
}
class TextureGather2DCase : public TextureGatherCase
{
public:
TextureGather2DCase (Context& context,
const char* name,
const char* description,
GatherType gatherType,
OffsetSize offsetSize,
tcu::TextureFormat textureFormat,
tcu::Sampler::CompareMode shadowCompareMode,
tcu::Sampler::WrapMode wrapS,
tcu::Sampler::WrapMode wrapT,
const MaybeTextureSwizzle& texSwizzle,
tcu::Sampler::FilterMode minFilter,
tcu::Sampler::FilterMode magFilter,
int baseLevel,
deUint32 flags,
const IVec2& textureSize)
: TextureGatherCase (context, name, description, TEXTURETYPE_2D, gatherType, offsetSize, textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags)
, m_textureSize (textureSize)
, m_swizzledTexture (tcu::TextureFormat(), 1, 1)
{
}
protected:
void generateIterations (void);
void createAndUploadTexture (void);
int getNumIterations (void) const { DE_ASSERT(!m_iterations.empty()); return (int)m_iterations.size(); }
GatherArgs getGatherArgs (int iterationNdx) const { return m_iterations[iterationNdx]; }
vector<float> computeQuadTexCoord (int iterationNdx) const;
bool verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const;
private:
const IVec2 m_textureSize;
MovePtr<glu::Texture2D> m_texture;
tcu::Texture2D m_swizzledTexture;
vector<GatherArgs> m_iterations;
};
vector<float> TextureGather2DCase::computeQuadTexCoord (int /* iterationNdx */) const
{
vector<float> res;
glu::TextureTestUtil::computeQuadTexCoord2D(res, Vec2(-0.3f, -0.4f), Vec2(1.5f, 1.6f));
return res;
}
void TextureGather2DCase::generateIterations (void)
{
DE_ASSERT(m_iterations.empty());
m_iterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
}
void TextureGather2DCase::createAndUploadTexture (void)
{
const glu::RenderContext& renderCtx = m_context.getRenderContext();
const glw::Functions& gl = renderCtx.getFunctions();
const tcu::TextureFormatInfo texFmtInfo = tcu::getTextureFormatInfo(m_textureFormat);
m_texture = MovePtr<glu::Texture2D>(new glu::Texture2D(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize.x(), m_textureSize.y()));
{
tcu::Texture2D& refTexture = m_texture->getRefTexture();
const int levelBegin = m_baseLevel;
const int levelEnd = isMipmapFilter(m_minFilter) && !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ? refTexture.getNumLevels() : m_baseLevel+1;
DE_ASSERT(m_baseLevel < refTexture.getNumLevels());
for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
{
refTexture.allocLevel(levelNdx);
const PixelBufferAccess& level = refTexture.getLevel(levelNdx);
fillWithRandomColorTiles(level, texFmtInfo.valueMin, texFmtInfo.valueMax, (deUint32)m_testCtx.getCommandLine().getBaseSeed());
m_testCtx.getLog() << TestLog::Image("InputTextureLevel" + de::toString(levelNdx), "Input texture, level " + de::toString(levelNdx), level)
<< TestLog::Message << "Note: texture level's size is " << IVec2(level.getWidth(), level.getHeight()) << TestLog::EndMessage;
}
swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
}
gl.activeTexture(GL_TEXTURE0);
m_texture->upload();
}
bool TextureGather2DCase::verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const
{
Vec2 texCoords[4];
computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::Texture2DView(m_swizzledTexture), m_baseLevel), texCoords, m_iterations[iterationNdx]);
}
class TextureGather2DArrayCase : public TextureGatherCase
{
public:
TextureGather2DArrayCase (Context& context,
const char* name,
const char* description,
GatherType gatherType,
OffsetSize offsetSize,
tcu::TextureFormat textureFormat,
tcu::Sampler::CompareMode shadowCompareMode,
tcu::Sampler::WrapMode wrapS,
tcu::Sampler::WrapMode wrapT,
const MaybeTextureSwizzle& texSwizzle,
tcu::Sampler::FilterMode minFilter,
tcu::Sampler::FilterMode magFilter,
int baseLevel,
deUint32 flags,
const IVec3& textureSize)
: TextureGatherCase (context, name, description, TEXTURETYPE_2D_ARRAY, gatherType, offsetSize, textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags)
, m_textureSize (textureSize)
, m_swizzledTexture (tcu::TextureFormat(), 1, 1, 1)
{
}
protected:
void generateIterations (void);
void createAndUploadTexture (void);
int getNumIterations (void) const { DE_ASSERT(!m_iterations.empty()); return (int)m_iterations.size(); }
GatherArgs getGatherArgs (int iterationNdx) const { return m_iterations[iterationNdx].gatherArgs; }
vector<float> computeQuadTexCoord (int iterationNdx) const;
bool verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const;
private:
struct Iteration
{
GatherArgs gatherArgs;
int layerNdx;
};
const IVec3 m_textureSize;
MovePtr<glu::Texture2DArray> m_texture;
tcu::Texture2DArray m_swizzledTexture;
vector<Iteration> m_iterations;
};
vector<float> TextureGather2DArrayCase::computeQuadTexCoord (int iterationNdx) const
{
vector<float> res;
glu::TextureTestUtil::computeQuadTexCoord2DArray(res, m_iterations[iterationNdx].layerNdx, Vec2(-0.3f, -0.4f), Vec2(1.5f, 1.6f));
return res;
}
void TextureGather2DArrayCase::generateIterations (void)
{
DE_ASSERT(m_iterations.empty());
const vector<GatherArgs> basicIterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
// \note Out-of-bounds layer indices are tested too.
for (int layerNdx = -1; layerNdx < m_textureSize.z()+1; layerNdx++)
{
// Don't duplicate all cases for all layers.
if (layerNdx == 0)
{
for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
{
m_iterations.push_back(Iteration());
m_iterations.back().gatherArgs = basicIterations[basicNdx];
m_iterations.back().layerNdx = layerNdx;
}
}
else
{
// For other layers than 0, only test one component and one set of offsets per layer.
for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
{
if (isDepthFormat(m_textureFormat) || basicIterations[basicNdx].componentNdx == (layerNdx + 2) % 4)
{
m_iterations.push_back(Iteration());
m_iterations.back().gatherArgs = basicIterations[basicNdx];
m_iterations.back().layerNdx = layerNdx;
break;
}
}
}
}
}
void TextureGather2DArrayCase::createAndUploadTexture (void)
{
TestLog& log = m_testCtx.getLog();
const glu::RenderContext& renderCtx = m_context.getRenderContext();
const glw::Functions& gl = renderCtx.getFunctions();
const tcu::TextureFormatInfo texFmtInfo = tcu::getTextureFormatInfo(m_textureFormat);
m_texture = MovePtr<glu::Texture2DArray>(new glu::Texture2DArray(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize.x(), m_textureSize.y(), m_textureSize.z()));
{
tcu::Texture2DArray& refTexture = m_texture->getRefTexture();
const int levelBegin = m_baseLevel;
const int levelEnd = isMipmapFilter(m_minFilter) && !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ? refTexture.getNumLevels() : m_baseLevel+1;
DE_ASSERT(m_baseLevel < refTexture.getNumLevels());
for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
{
refTexture.allocLevel(levelNdx);
const PixelBufferAccess& level = refTexture.getLevel(levelNdx);
fillWithRandomColorTiles(level, texFmtInfo.valueMin, texFmtInfo.valueMax, (deUint32)m_testCtx.getCommandLine().getBaseSeed());
log << TestLog::ImageSet("InputTextureLevel", "Input texture, level " + de::toString(levelNdx));
for (int layerNdx = 0; layerNdx < m_textureSize.z(); layerNdx++)
log << TestLog::Image("InputTextureLevel" + de::toString(layerNdx) + "Layer" + de::toString(layerNdx),
"Layer " + de::toString(layerNdx),
tcu::getSubregion(level, 0, 0, layerNdx, level.getWidth(), level.getHeight(), 1));
log << TestLog::EndImageSet
<< TestLog::Message << "Note: texture level's size is " << IVec3(level.getWidth(), level.getHeight(), level.getDepth()) << TestLog::EndMessage;
}
swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
}
gl.activeTexture(GL_TEXTURE0);
m_texture->upload();
}
bool TextureGather2DArrayCase::verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const
{
Vec3 texCoords[4];
computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::Texture2DArrayView(m_swizzledTexture), m_baseLevel), texCoords, m_iterations[iterationNdx].gatherArgs);
}
// \note Cube case always uses just basic textureGather(); offset versions are not defined for cube maps.
class TextureGatherCubeCase : public TextureGatherCase
{
public:
TextureGatherCubeCase (Context& context,
const char* name,
const char* description,
tcu::TextureFormat textureFormat,
tcu::Sampler::CompareMode shadowCompareMode,
tcu::Sampler::WrapMode wrapS,
tcu::Sampler::WrapMode wrapT,
const MaybeTextureSwizzle& texSwizzle,
tcu::Sampler::FilterMode minFilter,
tcu::Sampler::FilterMode magFilter,
int baseLevel,
deUint32 flags,
int textureSize)
: TextureGatherCase (context, name, description, TEXTURETYPE_CUBE, GATHERTYPE_BASIC, OFFSETSIZE_NONE, textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags)
, m_textureSize (textureSize)
, m_swizzledTexture (tcu::TextureFormat(), 1)
{
}
protected:
void generateIterations (void);
void createAndUploadTexture (void);
int getNumIterations (void) const { DE_ASSERT(!m_iterations.empty()); return (int)m_iterations.size(); }
GatherArgs getGatherArgs (int iterationNdx) const { return m_iterations[iterationNdx].gatherArgs; }
vector<float> computeQuadTexCoord (int iterationNdx) const;
bool verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const;
private:
struct Iteration
{
GatherArgs gatherArgs;
tcu::CubeFace face;
};
const int m_textureSize;
MovePtr<glu::TextureCube> m_texture;
tcu::TextureCube m_swizzledTexture;
vector<Iteration> m_iterations;
};
vector<float> TextureGatherCubeCase::computeQuadTexCoord (int iterationNdx) const
{
const bool corners = (m_flags & GATHERCASE_DONT_SAMPLE_CUBE_CORNERS) == 0;
const Vec2 minC = corners ? Vec2(-1.2f) : Vec2(-0.6f, -1.2f);
const Vec2 maxC = corners ? Vec2( 1.2f) : Vec2( 0.6f, 1.2f);
vector<float> res;
glu::TextureTestUtil::computeQuadTexCoordCube(res, m_iterations[iterationNdx].face, minC, maxC);
return res;
}
void TextureGatherCubeCase::generateIterations (void)
{
DE_ASSERT(m_iterations.empty());
const vector<GatherArgs> basicIterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
for (int cubeFaceI = 0; cubeFaceI < tcu::CUBEFACE_LAST; cubeFaceI++)
{
const tcu::CubeFace cubeFace = (tcu::CubeFace)cubeFaceI;
// Don't duplicate all cases for all faces.
if (cubeFaceI == 0)
{
for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
{
m_iterations.push_back(Iteration());
m_iterations.back().gatherArgs = basicIterations[basicNdx];
m_iterations.back().face = cubeFace;
}
}
else
{
// For other faces than first, only test one component per face.
for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
{
if (isDepthFormat(m_textureFormat) || basicIterations[basicNdx].componentNdx == cubeFaceI % 4)
{
m_iterations.push_back(Iteration());
m_iterations.back().gatherArgs = basicIterations[basicNdx];
m_iterations.back().face = cubeFace;
break;
}
}
}
}
}
void TextureGatherCubeCase::createAndUploadTexture (void)
{
TestLog& log = m_testCtx.getLog();
const glu::RenderContext& renderCtx = m_context.getRenderContext();
const glw::Functions& gl = renderCtx.getFunctions();
const tcu::TextureFormatInfo texFmtInfo = tcu::getTextureFormatInfo(m_textureFormat);
m_texture = MovePtr<glu::TextureCube>(new glu::TextureCube(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize));
{
tcu::TextureCube& refTexture = m_texture->getRefTexture();
const int levelBegin = m_baseLevel;
const int levelEnd = isMipmapFilter(m_minFilter) && !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ? refTexture.getNumLevels() : m_baseLevel+1;
DE_ASSERT(m_baseLevel < refTexture.getNumLevels());
for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
{
log << TestLog::ImageSet("InputTextureLevel" + de::toString(levelNdx), "Input texture, level " + de::toString(levelNdx));
for (int cubeFaceI = 0; cubeFaceI < tcu::CUBEFACE_LAST; cubeFaceI++)
{
const tcu::CubeFace cubeFace = (tcu::CubeFace)cubeFaceI;
refTexture.allocLevel(cubeFace, levelNdx);
const PixelBufferAccess& levelFace = refTexture.getLevelFace(levelNdx, cubeFace);
fillWithRandomColorTiles(levelFace, texFmtInfo.valueMin, texFmtInfo.valueMax, (deUint32)m_testCtx.getCommandLine().getBaseSeed() ^ (deUint32)cubeFaceI);
m_testCtx.getLog() << TestLog::Image("InputTextureLevel" + de::toString(levelNdx) + "Face" + de::toString((int)cubeFace),
de::toString(cubeFace),
levelFace);
}
log << TestLog::EndImageSet
<< TestLog::Message << "Note: texture level's size is " << refTexture.getLevelFace(levelNdx, tcu::CUBEFACE_NEGATIVE_X).getWidth() << TestLog::EndMessage;
}
swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
}
gl.activeTexture(GL_TEXTURE0);
m_texture->upload();
}
bool TextureGatherCubeCase::verify (int iterationNdx, const ConstPixelBufferAccess& rendered) const
{
Vec3 texCoords[4];
computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::TextureCubeView(m_swizzledTexture), m_baseLevel), texCoords, m_iterations[iterationNdx].gatherArgs);
}
static inline TextureGatherCase* makeTextureGatherCase (TextureType textureType,
Context& context,
const char* name,
const char* description,
GatherType gatherType,
OffsetSize offsetSize,
tcu::TextureFormat textureFormat,
tcu::Sampler::CompareMode shadowCompareMode,
tcu::Sampler::WrapMode wrapS,
tcu::Sampler::WrapMode wrapT,
const MaybeTextureSwizzle& texSwizzle,
tcu::Sampler::FilterMode minFilter,
tcu::Sampler::FilterMode magFilter,
int baseLevel,
const IVec3& textureSize,
deUint32 flags = 0)
{
switch (textureType)
{
case TEXTURETYPE_2D:
return new TextureGather2DCase(context, name, description, gatherType, offsetSize, textureFormat, shadowCompareMode,
wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags, textureSize.swizzle(0, 1));
case TEXTURETYPE_2D_ARRAY:
return new TextureGather2DArrayCase(context, name, description, gatherType, offsetSize, textureFormat, shadowCompareMode,
wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags, textureSize);
case TEXTURETYPE_CUBE:
DE_ASSERT(gatherType == GATHERTYPE_BASIC);
DE_ASSERT(offsetSize == OFFSETSIZE_NONE);
return new TextureGatherCubeCase(context, name, description, textureFormat, shadowCompareMode,
wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags, textureSize.x());
default:
DE_ASSERT(false);
return DE_NULL;
}
}
} // anonymous
TextureGatherTests::TextureGatherTests (Context& context)
: TestCaseGroup(context, "gather", "textureGather* tests")
{
}
static inline const char* compareModeName (tcu::Sampler::CompareMode mode)
{
switch (mode)
{
case tcu::Sampler::COMPAREMODE_LESS: return "less";
case tcu::Sampler::COMPAREMODE_LESS_OR_EQUAL: return "less_or_equal";
case tcu::Sampler::COMPAREMODE_GREATER: return "greater";
case tcu::Sampler::COMPAREMODE_GREATER_OR_EQUAL: return "greater_or_equal";
case tcu::Sampler::COMPAREMODE_EQUAL: return "equal";
case tcu::Sampler::COMPAREMODE_NOT_EQUAL: return "not_equal";
case tcu::Sampler::COMPAREMODE_ALWAYS: return "always";
case tcu::Sampler::COMPAREMODE_NEVER: return "never";
default: DE_ASSERT(false); return DE_NULL;
}
}
void TextureGatherTests::init (void)
{
const struct
{
const char* name;
TextureType type;
} textureTypes[] =
{
{ "2d", TEXTURETYPE_2D },
{ "2d_array", TEXTURETYPE_2D_ARRAY },
{ "cube", TEXTURETYPE_CUBE }
};
const struct
{
const char* name;
tcu::TextureFormat format;
} formats[] =
{
{ "rgba8", tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8) },
{ "rgba8ui", tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT8) },
{ "rgba8i", tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT8) },
{ "depth32f", tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT) }
};
const struct
{
const char* name;
IVec3 size;
} textureSizes[] =
{
{ "size_pot", IVec3(64, 64, 3) },
{ "size_npot", IVec3(17, 23, 3) }
};
const struct
{
const char* name;
tcu::Sampler::WrapMode mode;
} wrapModes[] =
{
{ "clamp_to_edge", tcu::Sampler::CLAMP_TO_EDGE },
{ "repeat", tcu::Sampler::REPEAT_GL },
{ "mirrored_repeat", tcu::Sampler::MIRRORED_REPEAT_GL }
};
for (int gatherTypeI = 0; gatherTypeI < GATHERTYPE_LAST; gatherTypeI++)
{
const GatherType gatherType = (GatherType)gatherTypeI;
TestCaseGroup* const gatherTypeGroup = new TestCaseGroup(m_context, gatherTypeName(gatherType), gatherTypeDescription(gatherType));
addChild(gatherTypeGroup);
for (int offsetSizeI = 0; offsetSizeI < OFFSETSIZE_LAST; offsetSizeI++)
{
const OffsetSize offsetSize = (OffsetSize)offsetSizeI;
if ((gatherType == GATHERTYPE_BASIC) != (offsetSize == OFFSETSIZE_NONE))
continue;
TestCaseGroup* const offsetSizeGroup = offsetSize == OFFSETSIZE_NONE ?
gatherTypeGroup :
new TestCaseGroup(m_context,
offsetSize == OFFSETSIZE_MINIMUM_REQUIRED ? "min_required_offset"
: offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM ? "implementation_offset"
: DE_NULL,
offsetSize == OFFSETSIZE_MINIMUM_REQUIRED ? "Use offsets within GL minimum required range"
: offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM ? "Use offsets within the implementation range"
: DE_NULL);
if (offsetSizeGroup != gatherTypeGroup)
gatherTypeGroup->addChild(offsetSizeGroup);
for (int textureTypeNdx = 0; textureTypeNdx < DE_LENGTH_OF_ARRAY(textureTypes); textureTypeNdx++)
{
const TextureType textureType = textureTypes[textureTypeNdx].type;
if (textureType == TEXTURETYPE_CUBE && gatherType != GATHERTYPE_BASIC)
continue;
TestCaseGroup* const textureTypeGroup = new TestCaseGroup(m_context, textureTypes[textureTypeNdx].name, "");
offsetSizeGroup->addChild(textureTypeGroup);
for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
{
const tcu::TextureFormat& format = formats[formatNdx].format;
TestCaseGroup* const formatGroup = new TestCaseGroup(m_context, formats[formatNdx].name, "");
textureTypeGroup->addChild(formatGroup);
for (int noCornersI = 0; noCornersI <= ((textureType == TEXTURETYPE_CUBE)?1:0); noCornersI++)
{
const bool noCorners = noCornersI!= 0;
TestCaseGroup* const cornersGroup = noCorners
? new TestCaseGroup(m_context, "no_corners", "Test case variants that don't sample around cube map corners")
: formatGroup;
if (formatGroup != cornersGroup)
formatGroup->addChild(cornersGroup);
for (int textureSizeNdx = 0; textureSizeNdx < DE_LENGTH_OF_ARRAY(textureSizes); textureSizeNdx++)
{
const IVec3& textureSize = textureSizes[textureSizeNdx].size;
TestCaseGroup* const textureSizeGroup = new TestCaseGroup(m_context, textureSizes[textureSizeNdx].name, "");
cornersGroup->addChild(textureSizeGroup);
for (int compareModeI = 0; compareModeI < tcu::Sampler::COMPAREMODE_LAST; compareModeI++)
{
const tcu::Sampler::CompareMode compareMode = (tcu::Sampler::CompareMode)compareModeI;
if ((compareMode != tcu::Sampler::COMPAREMODE_NONE) != isDepthFormat(format))
continue;
if (compareMode != tcu::Sampler::COMPAREMODE_NONE &&
compareMode != tcu::Sampler::COMPAREMODE_LESS &&
compareMode != tcu::Sampler::COMPAREMODE_GREATER)
continue;
TestCaseGroup* const compareModeGroup = compareMode == tcu::Sampler::COMPAREMODE_NONE ?
textureSizeGroup :
new TestCaseGroup(m_context,
(string() + "compare_" + compareModeName(compareMode)).c_str(),
"");
if (compareModeGroup != textureSizeGroup)
textureSizeGroup->addChild(compareModeGroup);
for (int wrapCaseNdx = 0; wrapCaseNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapCaseNdx++)
{
const int wrapSNdx = wrapCaseNdx;
const int wrapTNdx = (wrapCaseNdx + 1) % DE_LENGTH_OF_ARRAY(wrapModes);
const tcu::Sampler::WrapMode wrapS = wrapModes[wrapSNdx].mode;
const tcu::Sampler::WrapMode wrapT = wrapModes[wrapTNdx].mode;
const string caseName = string() + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name;
compareModeGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format, compareMode, wrapS, wrapT,
MaybeTextureSwizzle::createNoneTextureSwizzle(), tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0, textureSize,
noCorners ? GATHERCASE_DONT_SAMPLE_CUBE_CORNERS : 0));
}
}
}
}
if (offsetSize != OFFSETSIZE_MINIMUM_REQUIRED) // Don't test all features for both offset size types, as they should be rather orthogonal.
{
if (!isDepthFormat(format))
{
TestCaseGroup* const swizzleGroup = new TestCaseGroup(m_context, "texture_swizzle", "");
formatGroup->addChild(swizzleGroup);
DE_STATIC_ASSERT(TEXTURESWIZZLECOMPONENT_R == 0);
for (int swizzleCaseNdx = 0; swizzleCaseNdx < TEXTURESWIZZLECOMPONENT_LAST; swizzleCaseNdx++)
{
MaybeTextureSwizzle swizzle = MaybeTextureSwizzle::createSomeTextureSwizzle();
string caseName;
for (int i = 0; i < 4; i++)
{
swizzle.getSwizzle()[i] = (TextureSwizzleComponent)((swizzleCaseNdx + i) % (int)TEXTURESWIZZLECOMPONENT_LAST);
caseName += (i > 0 ? "_" : "") + de::toLower(de::toString(swizzle.getSwizzle()[i]));
}
swizzleGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
tcu::Sampler::COMPAREMODE_NONE, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
swizzle, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0, IVec3(64, 64, 3)));
}
}
{
TestCaseGroup* const filterModeGroup = new TestCaseGroup(m_context, "filter_mode", "Test that filter modes have no effect");
formatGroup->addChild(filterModeGroup);
const struct
{
const char* name;
tcu::Sampler::FilterMode filter;
} magFilters[] =
{
{ "linear", tcu::Sampler::LINEAR },
{ "nearest", tcu::Sampler::NEAREST }
};
const struct
{
const char* name;
tcu::Sampler::FilterMode filter;
} minFilters[] =
{
// \note Don't test NEAREST here, as it's covered by other cases.
{ "linear", tcu::Sampler::LINEAR },
{ "nearest_mipmap_nearest", tcu::Sampler::NEAREST_MIPMAP_NEAREST },
{ "nearest_mipmap_linear", tcu::Sampler::NEAREST_MIPMAP_LINEAR },
{ "linear_mipmap_nearest", tcu::Sampler::LINEAR_MIPMAP_NEAREST },
{ "linear_mipmap_linear", tcu::Sampler::LINEAR_MIPMAP_LINEAR },
};
for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilters); minFilterNdx++)
for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilters); magFilterNdx++)
{
const tcu::Sampler::FilterMode minFilter = minFilters[minFilterNdx].filter;
const tcu::Sampler::FilterMode magFilter = magFilters[magFilterNdx].filter;
const tcu::Sampler::CompareMode compareMode = isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
if ((isUnormFormatType(format.type) || isDepthFormat(format)) && magFilter == tcu::Sampler::NEAREST)
continue; // Covered by other cases.
if ((isUIntFormatType(format.type) || isSIntFormatType(format.type)) &&
(magFilter != tcu::Sampler::NEAREST || minFilter != tcu::Sampler::NEAREST_MIPMAP_NEAREST))
continue;
const string caseName = string() + "min_" + minFilters[minFilterNdx].name + "_mag_" + magFilters[magFilterNdx].name;
filterModeGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format, compareMode,
tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, MaybeTextureSwizzle::createNoneTextureSwizzle(),
minFilter, magFilter, 0, IVec3(64, 64, 3)));
}
}
{
TestCaseGroup* const baseLevelGroup = new TestCaseGroup(m_context, "base_level", "");
formatGroup->addChild(baseLevelGroup);
for (int baseLevel = 1; baseLevel <= 2; baseLevel++)
{
const string caseName = "level_" + de::toString(baseLevel);
const tcu::Sampler::CompareMode compareMode = isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
baseLevelGroup->addChild(makeTextureGatherCase(textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
MaybeTextureSwizzle::createNoneTextureSwizzle(), tcu::Sampler::NEAREST, tcu::Sampler::NEAREST,
baseLevel, IVec3(64, 64, 3)));
}
}
// What shadow textures should return for incomplete textures is unclear.
// Integer and unsigned integer lookups from incomplete textures return undefined values.
if (!isDepthFormat(format) && !isSIntFormatType(format.type) && !isUIntFormatType(format.type))
{
TestCaseGroup* const incompleteGroup = new TestCaseGroup(m_context, "incomplete", "Test that textureGather* takes components from (0,0,0,1) for incomplete textures");
formatGroup->addChild(incompleteGroup);
const tcu::Sampler::CompareMode compareMode = isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
incompleteGroup->addChild(makeTextureGatherCase(textureType, m_context, "mipmap_incomplete", "", gatherType, offsetSize, format,
compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
MaybeTextureSwizzle::createNoneTextureSwizzle(), tcu::Sampler::NEAREST_MIPMAP_NEAREST, tcu::Sampler::NEAREST,
0, IVec3(64, 64, 3), GATHERCASE_MIPMAP_INCOMPLETE));
}
}
}
}
}
}
}
} // Functional
} // gles31
} // deqp