/*-------------------------------------------------------------------------
* drawElements Quality Program OpenGL ES 2.0 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 Vertex texture tests.
*//*--------------------------------------------------------------------*/
#include "es2fVertexTextureTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluTexture.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "tcuVector.hpp"
#include "tcuMatrix.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "deRandom.hpp"
#include "deString.h"
#include <string>
#include <vector>
#include <limits>
#include "glw.h"
using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::Mat3;
using std::string;
using std::vector;
namespace deqp
{
using namespace gls::TextureTestUtil;
using gls::TextureTestUtil::TEXTURETYPE_2D;
using gls::TextureTestUtil::TEXTURETYPE_CUBE;
namespace gles2
{
namespace Functional
{
// The 2D case draws four images.
static const int MAX_2D_RENDER_WIDTH = 128*2;
static const int MAX_2D_RENDER_HEIGHT = 128*2;
// The cube map case draws four 3-by-2 image groups.
static const int MAX_CUBE_RENDER_WIDTH = 28*2*3;
static const int MAX_CUBE_RENDER_HEIGHT = 28*2*2;
static const int GRID_SIZE_2D = 127;
static const int GRID_SIZE_CUBE = 63;
// Helpers for making texture coordinates "safe", i.e. move them further from coordinate bounary.
// Moves x towards the closest K+targetFraction, where K is an integer.
// E.g. moveTowardsFraction(x, 0.5f) moves x away from integer boundaries.
static inline float moveTowardsFraction (float x, float targetFraction)
{
const float strictness = 0.5f;
DE_ASSERT(0.0f < strictness && strictness <= 1.0f);
DE_ASSERT(de::inBounds(targetFraction, 0.0f, 1.0f));
const float y = x + 0.5f - targetFraction;
return deFloatFloor(y) + deFloatFrac(y)*(1.0f-strictness) + strictness*0.5f - 0.5f + targetFraction;
}
static inline float safeCoord (float raw, int scale, float fraction)
{
const float scaleFloat = (float)scale;
return moveTowardsFraction(raw*scaleFloat, fraction) / scaleFloat;
}
template <int Size>
static inline tcu::Vector<float, Size> safeCoords (const tcu::Vector<float, Size>& raw, const tcu::Vector<int, Size>& scale, const tcu::Vector<float, Size>& fraction)
{
tcu::Vector<float, Size> result;
for (int i = 0; i < Size; i++)
result[i] = safeCoord(raw[i], scale[i], fraction[i]);
return result;
}
static inline Vec2 safe2DTexCoords (const Vec2& raw, const IVec2& textureSize)
{
return safeCoords(raw, textureSize, Vec2(0.5f));
}
namespace
{
struct Rect
{
Rect (int x_, int y_, int w_, int h_) : x(x_), y(y_), w(w_), h(h_) {}
IVec2 pos (void) const { return IVec2(x, y); }
IVec2 size (void) const { return IVec2(w, h); }
int x;
int y;
int w;
int h;
};
template <TextureType> struct TexTypeTcuClass;
template <> struct TexTypeTcuClass<TEXTURETYPE_2D> { typedef tcu::Texture2D t; };
template <> struct TexTypeTcuClass<TEXTURETYPE_CUBE> { typedef tcu::TextureCube t; };
template <TextureType> struct TexTypeSizeDims;
template <> struct TexTypeSizeDims<TEXTURETYPE_2D> { enum { V = 2 }; };
template <> struct TexTypeSizeDims<TEXTURETYPE_CUBE> { enum { V = 2 }; };
template <TextureType> struct TexTypeCoordDims;
template <> struct TexTypeCoordDims<TEXTURETYPE_2D> { enum { V = 2 }; };
template <> struct TexTypeCoordDims<TEXTURETYPE_CUBE> { enum { V = 3 }; };
template <TextureType TexType> struct TexTypeSizeIVec { typedef tcu::Vector<int, TexTypeSizeDims<TexType>::V> t; };
template <TextureType TexType> struct TexTypeCoordVec { typedef tcu::Vector<float, TexTypeCoordDims<TexType>::V> t; };
template <TextureType> struct TexTypeCoordParams;
template <> struct
TexTypeCoordParams<TEXTURETYPE_2D>
{
Vec2 scale;
Vec2 bias;
TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_) : scale(scale_), bias(bias_) {}
};
template <> struct
TexTypeCoordParams<TEXTURETYPE_CUBE>
{
Vec2 scale;
Vec2 bias;
tcu::CubeFace face;
TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_, tcu::CubeFace face_) : scale(scale_), bias(bias_), face(face_) {}
};
/*--------------------------------------------------------------------*//*!
* \brief Quad grid class containing position and texture coordinate data.
*
* A quad grid of size S means a grid consisting of S*S quads (S rows and
* S columns). The quads are rectangles with main axis aligned sides, and
* each consists of two triangles. Note that although there are only
* (S+1)*(S+1) distinct vertex positions, there are S*S*4 distinct vertices
* because we want texture coordinates to be constant across the vertices
* of a quad (to avoid interpolation issues), and thus each quad needs its
* own 4 vertices.
*
* Pointers returned by get*Ptr() are suitable for gl calls such as
* glVertexAttribPointer() (for position and tex coord) or glDrawElements()
* (for indices).
*//*--------------------------------------------------------------------*/
template <TextureType TexType>
class PosTexCoordQuadGrid
{
private:
enum { TEX_COORD_DIMS = TexTypeCoordDims <TexType>::V };
typedef typename TexTypeCoordVec<TexType>::t TexCoordVec;
typedef typename TexTypeSizeIVec<TexType>::t TexSizeIVec;
typedef TexTypeCoordParams<TexType> TexCoordParams;
public:
PosTexCoordQuadGrid (int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);
int getSize (void) const { return m_gridSize; }
Vec4 getQuadLDRU (int col, int row) const; //!< Vec4(leftX, downY, rightX, upY)
const TexCoordVec& getQuadTexCoord (int col, int row) const;
int getNumIndices (void) const { return m_gridSize*m_gridSize*3*2; }
const float* getPositionPtr (void) const { DE_STATIC_ASSERT(sizeof(Vec2) == 2*sizeof(float)); return (float*)&m_positions[0]; }
const float* getTexCoordPtr (void) const { DE_STATIC_ASSERT(sizeof(TexCoordVec) == TEX_COORD_DIMS*(int)sizeof(float)); return (float*)&m_texCoords[0]; }
const deUint16* getIndexPtr (void) const { return &m_indices[0]; }
private:
void initializeTexCoords (const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);
const int m_gridSize;
vector<Vec2> m_positions;
vector<TexCoordVec> m_texCoords;
vector<deUint16> m_indices;
};
template <TextureType TexType>
Vec4 PosTexCoordQuadGrid<TexType>::getQuadLDRU (int col, int row) const
{
int ndx00 = (row*m_gridSize + col) * 4;
int ndx11 = ndx00 + 3;
return Vec4(m_positions[ndx00].x(),
m_positions[ndx00].y(),
m_positions[ndx11].x(),
m_positions[ndx11].y());
}
template <TextureType TexType>
const typename TexTypeCoordVec<TexType>::t& PosTexCoordQuadGrid<TexType>::getQuadTexCoord (int col, int row) const
{
return m_texCoords[(row*m_gridSize + col) * 4];
}
template <TextureType TexType>
PosTexCoordQuadGrid<TexType>::PosTexCoordQuadGrid (int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
: m_gridSize(gridSize)
{
DE_ASSERT(m_gridSize > 0 && m_gridSize*m_gridSize <= (int)std::numeric_limits<deUint16>::max() + 1);
const float gridSizeFloat = (float)m_gridSize;
m_positions.reserve(m_gridSize*m_gridSize*4);
m_indices.reserve(m_gridSize*m_gridSize*3*2);
for (int y = 0; y < m_gridSize; y++)
for (int x = 0; x < m_gridSize; x++)
{
float fx0 = (float)(x+0) / gridSizeFloat;
float fx1 = (float)(x+1) / gridSizeFloat;
float fy0 = (float)(y+0) / gridSizeFloat;
float fy1 = (float)(y+1) / gridSizeFloat;
Vec2 quadVertices[4] = { Vec2(fx0, fy0), Vec2(fx1, fy0), Vec2(fx0, fy1), Vec2(fx1, fy1) };
int firstNdx = (int)m_positions.size();
for (int i = 0; i < DE_LENGTH_OF_ARRAY(quadVertices); i++)
m_positions.push_back(safeCoords(quadVertices[i], renderSize, Vec2(0.0f)) * 2.0f - 1.0f);
m_indices.push_back(firstNdx + 0);
m_indices.push_back(firstNdx + 1);
m_indices.push_back(firstNdx + 2);
m_indices.push_back(firstNdx + 1);
m_indices.push_back(firstNdx + 3);
m_indices.push_back(firstNdx + 2);
}
m_texCoords.reserve(m_gridSize*m_gridSize*4);
initializeTexCoords(textureSize, texCoordParams, useSafeTexCoords);
DE_ASSERT((int)m_positions.size() == m_gridSize*m_gridSize*4);
DE_ASSERT((int)m_indices.size() == m_gridSize*m_gridSize*3*2);
DE_ASSERT((int)m_texCoords.size() == m_gridSize*m_gridSize*4);
}
template <>
void PosTexCoordQuadGrid<TEXTURETYPE_2D>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
{
DE_ASSERT(m_texCoords.empty());
const float gridSizeFloat = (float)m_gridSize;
for (int y = 0; y < m_gridSize; y++)
for (int x = 0; x < m_gridSize; x++)
{
Vec2 rawCoord = Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) * texCoordParams.scale + texCoordParams.bias;
for (int i = 0; i < 4; i++)
m_texCoords.push_back(useSafeTexCoords ? safe2DTexCoords(rawCoord, textureSize) : rawCoord);
}
}
template <>
void PosTexCoordQuadGrid<TEXTURETYPE_CUBE>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
{
DE_ASSERT(m_texCoords.empty());
const float gridSizeFloat = (float)m_gridSize;
vector<float> texBoundaries;
computeQuadTexCoordCube(texBoundaries, texCoordParams.face);
const Vec3 coordA = Vec3(texBoundaries[0], texBoundaries[1], texBoundaries[2]);
const Vec3 coordB = Vec3(texBoundaries[3], texBoundaries[4], texBoundaries[5]);
const Vec3 coordC = Vec3(texBoundaries[6], texBoundaries[7], texBoundaries[8]);
const Vec3 coordAB = coordB - coordA;
const Vec3 coordAC = coordC - coordA;
for (int y = 0; y < m_gridSize; y++)
for (int x = 0; x < m_gridSize; x++)
{
const Vec2 rawFaceCoord = texCoordParams.scale * Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) + texCoordParams.bias;
const Vec2 safeFaceCoord = useSafeTexCoords ? safe2DTexCoords(rawFaceCoord, textureSize) : rawFaceCoord;
const Vec3 texCoord = coordA + coordAC*safeFaceCoord.x() + coordAB*safeFaceCoord.y();
for (int i = 0; i < 4; i++)
m_texCoords.push_back(texCoord);
}
}
} // anonymous
static inline bool isLevelNearest (deUint32 filter)
{
return filter == GL_NEAREST || filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_NEAREST_MIPMAP_LINEAR;
}
static inline IVec2 getTextureSize (const glu::Texture2D& tex)
{
const tcu::Texture2D& ref = tex.getRefTexture();
return IVec2(ref.getWidth(), ref.getHeight());
}
static inline IVec2 getTextureSize (const glu::TextureCube& tex)
{
const tcu::TextureCube& ref = tex.getRefTexture();
return IVec2(ref.getSize(), ref.getSize());
}
template <TextureType TexType>
static void setPixelColors (const vector<Vec4>& quadColors, const Rect& region, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst)
{
const int gridSize = grid.getSize();
for (int y = 0; y < gridSize; y++)
for (int x = 0; x < gridSize; x++)
{
const Vec4 color = quadColors[y*gridSize + x];
const Vec4 ldru = grid.getQuadLDRU(x, y) * 0.5f + 0.5f; // [-1, 1] -> [0, 1]
const int ix0 = deCeilFloatToInt32(ldru.x() * (float)region.w - 0.5f);
const int ix1 = deCeilFloatToInt32(ldru.z() * (float)region.w - 0.5f);
const int iy0 = deCeilFloatToInt32(ldru.y() * (float)region.h - 0.5f);
const int iy1 = deCeilFloatToInt32(ldru.w() * (float)region.h - 0.5f);
for (int iy = iy0; iy < iy1; iy++)
for (int ix = ix0; ix < ix1; ix++)
{
DE_ASSERT(deInBounds32(ix + region.x, 0, dst.getWidth()));
DE_ASSERT(deInBounds32(iy + region.y, 0, dst.getHeight()));
dst.setPixel(ix + region.x, iy + region.y, toRGBA(color));
}
}
}
static inline Vec4 sample (const tcu::Texture2D& tex, const Vec2& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), lod); }
static inline Vec4 sample (const tcu::TextureCube& tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
template <TextureType TexType>
void computeReference (const typename TexTypeTcuClass<TexType>::t& texture, float lod, const tcu::Sampler& sampler, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst, const Rect& dstRegion)
{
const int gridSize = grid.getSize();
vector<Vec4> quadColors (gridSize*gridSize);
for (int y = 0; y < gridSize; y++)
for (int x = 0; x < gridSize; x++)
{
const int ndx = y*gridSize + x;
const typename TexTypeCoordVec<TexType>::t& coord = grid.getQuadTexCoord(x, y);
quadColors[ndx] = sample(texture, coord, lod, sampler);
}
setPixelColors(quadColors, dstRegion, grid, dst);
}
static bool compareImages (const glu::RenderContext& renderCtx, tcu::TestLog& log, const tcu::Surface& ref, const tcu::Surface& res)
{
DE_ASSERT(renderCtx.getRenderTarget().getNumSamples() == 0);
const tcu::RGBA threshold = renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(15,15,15,15);
return tcu::pixelThresholdCompare(log, "Result", "Image compare result", ref, res, threshold, tcu::COMPARE_LOG_RESULT);
}
class Vertex2DTextureCase : public TestCase
{
public:
Vertex2DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
~Vertex2DTextureCase (void);
void init (void);
void deinit (void);
IterateResult iterate (void);
private:
typedef PosTexCoordQuadGrid<TEXTURETYPE_2D> Grid;
Vertex2DTextureCase (const Vertex2DTextureCase& other);
Vertex2DTextureCase& operator= (const Vertex2DTextureCase& other);
float calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
void setupShaderInputs (int textureNdx, float lod, const Grid& grid) const;
void renderCell (int textureNdx, float lod, const Grid& grid) const;
void computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
const deUint32 m_minFilter;
const deUint32 m_magFilter;
const deUint32 m_wrapS;
const deUint32 m_wrapT;
const glu::ShaderProgram* m_program;
glu::Texture2D* m_textures[2]; // 2 textures, a gradient texture and a grid texture.
};
Vertex2DTextureCase::Vertex2DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
: TestCase (testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
, m_minFilter (minFilter)
, m_magFilter (magFilter)
, m_wrapS (wrapS)
, m_wrapT (wrapT)
, m_program (DE_NULL)
{
m_textures[0] = DE_NULL;
m_textures[1] = DE_NULL;
}
Vertex2DTextureCase::~Vertex2DTextureCase(void)
{
Vertex2DTextureCase::deinit();
}
void Vertex2DTextureCase::init (void)
{
const char* const vertexShader =
"attribute highp vec2 a_position;\n"
"attribute highp vec2 a_texCoord;\n"
"uniform highp sampler2D u_texture;\n"
"uniform highp float u_lod;\n"
"varying mediump vec4 v_color;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = vec4(a_position, 0.0, 1.0);\n"
" v_color = texture2DLod(u_texture, a_texCoord, u_lod);\n"
"}\n";
const char* const fragmentShader =
"varying mediump vec4 v_color;\n"
"\n"
"void main()\n"
"{\n"
" gl_FragColor = v_color;\n"
"}\n";
if (m_context.getRenderTarget().getNumSamples() != 0)
throw tcu::NotSupportedError("MSAA config not supported by this test");
DE_ASSERT(!m_program);
m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));
if(!m_program->isOk())
{
m_testCtx.getLog() << *m_program;
GLint maxVertexTextures;
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
if (maxVertexTextures < 1)
throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
else
TCU_FAIL("Failed to compile shader");
}
// Make the textures.
try
{
// Compute suitable power-of-two sizes (for mipmaps).
const int texWidth = 1 << deLog2Ceil32(MAX_2D_RENDER_WIDTH / 2);
const int texHeight = 1 << deLog2Ceil32(MAX_2D_RENDER_HEIGHT / 2);
for (int i = 0; i < 2; i++)
{
DE_ASSERT(!m_textures[i]);
m_textures[i] = new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
}
const bool mipmaps = (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight));
const int numLevels = mipmaps ? deLog2Floor32(de::max(texWidth, texHeight))+1 : 1;
const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
const Vec4 cBias = fmtInfo.valueMin;
const Vec4 cScale = fmtInfo.valueMax-fmtInfo.valueMin;
// Fill first with gradient texture.
for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
{
const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
const Vec4 gMax = Vec4( 1.0f, 1.0f, 1.0f, 0.0f)*cScale + cBias;
m_textures[0]->getRefTexture().allocLevel(levelNdx);
tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
}
// Fill second with grid texture.
for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
{
const deUint32 step = 0x00ffffff / numLevels;
const deUint32 rgb = step*levelNdx;
const deUint32 colorA = 0xff000000 | rgb;
const deUint32 colorB = 0xff000000 | ~rgb;
m_textures[1]->getRefTexture().allocLevel(levelNdx);
tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
}
// Upload.
for (int i = 0; i < 2; i++)
m_textures[i]->upload();
}
catch (const std::exception&)
{
// Clean up to save memory.
Vertex2DTextureCase::deinit();
throw;
}
}
void Vertex2DTextureCase::deinit (void)
{
for (int i = 0; i < 2; i++)
{
delete m_textures[i];
m_textures[i] = DE_NULL;
}
delete m_program;
m_program = DE_NULL;
}
float Vertex2DTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
{
const tcu::Texture2D& refTexture = m_textures[textureNdx]->getRefTexture();
const Vec2 srcSize = Vec2((float)refTexture.getWidth(), (float)refTexture.getHeight());
const Vec2 sizeRatio = texScale*srcSize / dstSize;
// \note In this particular case dv/dx and du/dy are zero, simplifying the expression.
return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
}
Vertex2DTextureCase::IterateResult Vertex2DTextureCase::iterate (void)
{
const int viewportWidth = deMin32(m_context.getRenderTarget().getWidth(), MAX_2D_RENDER_WIDTH);
const int viewportHeight = deMin32(m_context.getRenderTarget().getHeight(), MAX_2D_RENDER_HEIGHT);
const int viewportXOffsetMax = m_context.getRenderTarget().getWidth() - viewportWidth;
const int viewportYOffsetMax = m_context.getRenderTarget().getHeight() - viewportHeight;
de::Random rnd (deStringHash(getName()));
const int viewportXOffset = rnd.getInt(0, viewportXOffsetMax);
const int viewportYOffset = rnd.getInt(0, viewportYOffsetMax);
glUseProgram(m_program->getProgram());
// Divide viewport into 4 cells.
const int leftWidth = viewportWidth / 2;
const int rightWidth = viewportWidth - leftWidth;
const int bottomHeight = viewportHeight / 2;
const int topHeight = viewportHeight - bottomHeight;
// Clear.
glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Texture scaling and offsetting vectors.
const Vec2 texMinScale (+1.8f, +1.8f);
const Vec2 texMinOffset (-0.3f, -0.2f);
const Vec2 texMagScale (+0.3f, +0.3f);
const Vec2 texMagOffset (+0.9f, +0.8f);
// Surface for the reference image.
tcu::Surface refImage(viewportWidth, viewportHeight);
{
const struct Render
{
const Rect region;
int textureNdx;
const Vec2 texCoordScale;
const Vec2 texCoordOffset;
Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
} renders[] =
{
Render(Rect(0, 0, leftWidth, bottomHeight), 0, texMinScale, texMinOffset),
Render(Rect(leftWidth, 0, rightWidth, bottomHeight), 0, texMagScale, texMagOffset),
Render(Rect(0, bottomHeight, leftWidth, topHeight), 1, texMinScale, texMinOffset),
Render(Rect(leftWidth, bottomHeight, rightWidth, topHeight), 1, texMagScale, texMagOffset)
};
for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
{
const Render& rend = renders[renderNdx];
const float lod = calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
const bool useSafeTexCoords = isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
const Grid grid (GRID_SIZE_2D, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
TexTypeCoordParams<TEXTURETYPE_2D>(rend.texCoordScale, rend.texCoordOffset), useSafeTexCoords);
glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
renderCell (rend.textureNdx, lod, grid);
computeReferenceCell (rend.textureNdx, lod, grid, refImage, rend.region);
}
}
// Read back rendered results.
tcu::Surface resImage(viewportWidth, viewportHeight);
glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
glUseProgram(0);
// Compare and log.
{
const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
isOk ? "Pass" : "Image comparison failed");
}
return STOP;
}
void Vertex2DTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
{
const deUint32 programID = m_program->getProgram();
// SETUP ATTRIBUTES.
{
const int positionLoc = glGetAttribLocation(programID, "a_position");
if (positionLoc != -1)
{
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
}
}
{
const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
if (texCoordLoc != -1)
{
glEnableVertexAttribArray(texCoordLoc);
glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
}
}
// SETUP UNIFORMS.
{
const int lodLoc = glGetUniformLocation(programID, "u_lod");
if (lodLoc != -1)
glUniform1f(lodLoc, lod);
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_textures[textureNdx]->getGLTexture());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrapT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_magFilter);
{
const int texLoc = glGetUniformLocation(programID, "u_texture");
if (texLoc != -1)
glUniform1i(texLoc, 0);
}
}
// Renders one sub-image with given parameters.
void Vertex2DTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
{
setupShaderInputs(textureNdx, lod, grid);
glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
}
void Vertex2DTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
{
computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter), grid, dst, dstRegion);
}
class VertexCubeTextureCase : public TestCase
{
public:
VertexCubeTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
~VertexCubeTextureCase (void);
void init (void);
void deinit (void);
IterateResult iterate (void);
private:
typedef PosTexCoordQuadGrid<TEXTURETYPE_CUBE> Grid;
VertexCubeTextureCase (const VertexCubeTextureCase& other);
VertexCubeTextureCase& operator= (const VertexCubeTextureCase& other);
float calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
void setupShaderInputs (int textureNdx, float lod, const Grid& grid) const;
void renderCell (int textureNdx, float lod, const Grid& grid) const;
void computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;
const deUint32 m_minFilter;
const deUint32 m_magFilter;
const deUint32 m_wrapS;
const deUint32 m_wrapT;
const glu::ShaderProgram* m_program;
glu::TextureCube* m_textures[2]; // 2 textures, a gradient texture and a grid texture.
};
VertexCubeTextureCase::VertexCubeTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
: TestCase (testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
, m_minFilter (minFilter)
, m_magFilter (magFilter)
, m_wrapS (wrapS)
, m_wrapT (wrapT)
, m_program (DE_NULL)
{
m_textures[0] = DE_NULL;
m_textures[1] = DE_NULL;
}
VertexCubeTextureCase::~VertexCubeTextureCase(void)
{
VertexCubeTextureCase::deinit();
}
void VertexCubeTextureCase::init (void)
{
const char* const vertexShader =
"attribute highp vec2 a_position;\n"
"attribute highp vec3 a_texCoord;\n"
"uniform highp samplerCube u_texture;\n"
"uniform highp float u_lod;\n"
"varying mediump vec4 v_color;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = vec4(a_position, 0.0, 1.0);\n"
" v_color = textureCubeLod(u_texture, a_texCoord, u_lod);\n"
"}\n";
const char* const fragmentShader =
"varying mediump vec4 v_color;\n"
"\n"
"void main()\n"
"{\n"
" gl_FragColor = v_color;\n"
"}\n";
if (m_context.getRenderTarget().getNumSamples() != 0)
throw tcu::NotSupportedError("MSAA config not supported by this test");
DE_ASSERT(!m_program);
m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));
if(!m_program->isOk())
{
m_testCtx.getLog() << *m_program;
GLint maxVertexTextures;
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);
if (maxVertexTextures < 1)
throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
else
TCU_FAIL("Failed to compile shader");
}
// Make the textures.
try
{
// Compute suitable power-of-two sizes (for mipmaps).
const int texWidth = 1 << deLog2Ceil32(MAX_CUBE_RENDER_WIDTH / 3 / 2);
const int texHeight = 1 << deLog2Ceil32(MAX_CUBE_RENDER_HEIGHT / 2 / 2);
DE_ASSERT(texWidth == texHeight);
DE_UNREF(texHeight);
for (int i = 0; i < 2; i++)
{
DE_ASSERT(!m_textures[i]);
m_textures[i] = new glu::TextureCube(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth);
}
const bool mipmaps = deIsPowerOfTwo32(texWidth) != DE_FALSE;
const int numLevels = mipmaps ? deLog2Floor32(texWidth)+1 : 1;
const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
const Vec4 cBias = fmtInfo.valueMin;
const Vec4 cScale = fmtInfo.valueMax-fmtInfo.valueMin;
// Fill first with gradient texture.
static const Vec4 gradients[tcu::CUBEFACE_LAST][2] =
{
{ Vec4(-1.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
{ Vec4( 0.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
{ Vec4(-1.0f, 0.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
{ Vec4(-1.0f, -1.0f, 0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
{ Vec4(-1.0f, -1.0f, -1.0f, 0.0f), Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
{ Vec4( 0.0f, 0.0f, 0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) } // positive z
};
for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
{
for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
{
m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
}
}
// Fill second with grid texture.
for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
{
for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
{
const deUint32 step = 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
const deUint32 rgb = step*levelNdx*face;
const deUint32 colorA = 0xff000000 | rgb;
const deUint32 colorB = 0xff000000 | ~rgb;
m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
}
}
// Upload.
for (int i = 0; i < 2; i++)
m_textures[i]->upload();
}
catch (const std::exception&)
{
// Clean up to save memory.
VertexCubeTextureCase::deinit();
throw;
}
}
void VertexCubeTextureCase::deinit (void)
{
for (int i = 0; i < 2; i++)
{
delete m_textures[i];
m_textures[i] = DE_NULL;
}
delete m_program;
m_program = DE_NULL;
}
float VertexCubeTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
{
const tcu::TextureCube& refTexture = m_textures[textureNdx]->getRefTexture();
const Vec2 srcSize = Vec2((float)refTexture.getSize(), (float)refTexture.getSize());
const Vec2 sizeRatio = texScale*srcSize / dstSize;
// \note In this particular case, dv/dx and du/dy are zero, simplifying the expression.
return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
}
VertexCubeTextureCase::IterateResult VertexCubeTextureCase::iterate (void)
{
const int viewportWidth = deMin32(m_context.getRenderTarget().getWidth(), MAX_CUBE_RENDER_WIDTH);
const int viewportHeight = deMin32(m_context.getRenderTarget().getHeight(), MAX_CUBE_RENDER_HEIGHT);
const int viewportXOffsetMax = m_context.getRenderTarget().getWidth() - viewportWidth;
const int viewportYOffsetMax = m_context.getRenderTarget().getHeight() - viewportHeight;
de::Random rnd (deStringHash(getName()));
const int viewportXOffset = rnd.getInt(0, viewportXOffsetMax);
const int viewportYOffset = rnd.getInt(0, viewportYOffsetMax);
glUseProgram(m_program->getProgram());
// Divide viewport into 4 areas.
const int leftWidth = viewportWidth / 2;
const int rightWidth = viewportWidth - leftWidth;
const int bottomHeight = viewportHeight / 2;
const int topHeight = viewportHeight - bottomHeight;
// Clear.
glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Texture scaling and offsetting vectors.
const Vec2 texMinScale (1.0f, 1.0f);
const Vec2 texMinOffset (0.0f, 0.0f);
const Vec2 texMagScale (0.3f, 0.3f);
const Vec2 texMagOffset (0.5f, 0.3f);
// Surface for the reference image.
tcu::Surface refImage(viewportWidth, viewportHeight);
// Each of the four areas is divided into 6 cells.
const int defCellWidth = viewportWidth / 2 / 3;
const int defCellHeight = viewportHeight / 2 / 2;
for (int i = 0; i < tcu::CUBEFACE_LAST; i++)
{
const int cellOffsetX = defCellWidth * (i % 3);
const int cellOffsetY = defCellHeight * (i / 3);
const bool isRightmostCell = i == 2 || i == 5;
const bool isTopCell = i >= 3;
const int leftCellWidth = isRightmostCell ? leftWidth - cellOffsetX : defCellWidth;
const int rightCellWidth = isRightmostCell ? rightWidth - cellOffsetX : defCellWidth;
const int bottomCellHeight = isTopCell ? bottomHeight - cellOffsetY : defCellHeight;
const int topCellHeight = isTopCell ? topHeight - cellOffsetY : defCellHeight;
const struct Render
{
const Rect region;
int textureNdx;
const Vec2 texCoordScale;
const Vec2 texCoordOffset;
Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
} renders[] =
{
Render(Rect(cellOffsetX + 0, cellOffsetY + 0, leftCellWidth, bottomCellHeight), 0, texMinScale, texMinOffset),
Render(Rect(cellOffsetX + leftWidth, cellOffsetY + 0, rightCellWidth, bottomCellHeight), 0, texMagScale, texMagOffset),
Render(Rect(cellOffsetX + 0, cellOffsetY + bottomHeight, leftCellWidth, topCellHeight), 1, texMinScale, texMinOffset),
Render(Rect(cellOffsetX + leftWidth, cellOffsetY + bottomHeight, rightCellWidth, topCellHeight), 1, texMagScale, texMagOffset)
};
for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
{
const Render& rend = renders[renderNdx];
const float lod = calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
const bool useSafeTexCoords = isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
const Grid grid (GRID_SIZE_CUBE, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
TexTypeCoordParams<TEXTURETYPE_CUBE>(rend.texCoordScale, rend.texCoordOffset, (tcu::CubeFace)i), useSafeTexCoords);
glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
renderCell (rend.textureNdx, lod, grid);
computeReferenceCell (rend.textureNdx, lod, grid, refImage, rend.region);
}
}
// Read back rendered results.
tcu::Surface resImage(viewportWidth, viewportHeight);
glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());
glUseProgram(0);
// Compare and log.
{
const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);
m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
isOk ? "Pass" : "Image comparison failed");
}
return STOP;
}
void VertexCubeTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
{
const deUint32 programID = m_program->getProgram();
// SETUP ATTRIBUTES.
{
const int positionLoc = glGetAttribLocation(programID, "a_position");
if (positionLoc != -1)
{
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
}
}
{
const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
if (texCoordLoc != -1)
{
glEnableVertexAttribArray(texCoordLoc);
glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
}
}
// SETUP UNIFORMS.
{
const int lodLoc = glGetUniformLocation(programID, "u_lod");
if (lodLoc != -1)
glUniform1f(lodLoc, lod);
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, m_textures[textureNdx]->getGLTexture());
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, m_wrapS);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, m_wrapT);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, m_minFilter);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, m_magFilter);
{
const int texLoc = glGetUniformLocation(programID, "u_texture");
if (texLoc != -1)
glUniform1i(texLoc, 0);
}
}
// Renders one cube face with given parameters.
void VertexCubeTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
{
setupShaderInputs(textureNdx, lod, grid);
glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
}
// Computes reference for one cube face with given parameters.
void VertexCubeTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
{
tcu::Sampler sampler = glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
computeReference(m_textures[textureNdx]->getRefTexture(), lod, sampler, grid, dst, dstRegion);
}
VertexTextureTests::VertexTextureTests (Context& context)
: TestCaseGroup(context, "vertex", "Vertex Texture Tests")
{
}
VertexTextureTests::~VertexTextureTests(void)
{
}
void VertexTextureTests::init (void)
{
// 2D and cube map groups, and their filtering and wrap sub-groups.
TestCaseGroup* const group2D = new TestCaseGroup(m_context, "2d", "2D Vertex Texture Tests");
TestCaseGroup* const groupCube = new TestCaseGroup(m_context, "cube", "Cube Map Vertex Texture Tests");
TestCaseGroup* const filteringGroup2D = new TestCaseGroup(m_context, "filtering", "2D Vertex Texture Filtering Tests");
TestCaseGroup* const wrapGroup2D = new TestCaseGroup(m_context, "wrap", "2D Vertex Texture Wrap Tests");
TestCaseGroup* const filteringGroupCube = new TestCaseGroup(m_context, "filtering", "Cube Map Vertex Texture Filtering Tests");
TestCaseGroup* const wrapGroupCube = new TestCaseGroup(m_context, "wrap", "Cube Map Vertex Texture Wrap Tests");
group2D->addChild(filteringGroup2D);
group2D->addChild(wrapGroup2D);
groupCube->addChild(filteringGroupCube);
groupCube->addChild(wrapGroupCube);
addChild(group2D);
addChild(groupCube);
static const struct
{
const char* name;
GLenum mode;
} wrapModes[] =
{
{ "clamp", GL_CLAMP_TO_EDGE },
{ "repeat", GL_REPEAT },
{ "mirror", GL_MIRRORED_REPEAT }
};
static const struct
{
const char* name;
GLenum mode;
} minFilterModes[] =
{
{ "nearest", GL_NEAREST },
{ "linear", GL_LINEAR },
{ "nearest_mipmap_nearest", GL_NEAREST_MIPMAP_NEAREST },
{ "linear_mipmap_nearest", GL_LINEAR_MIPMAP_NEAREST },
{ "nearest_mipmap_linear", GL_NEAREST_MIPMAP_LINEAR },
{ "linear_mipmap_linear", GL_LINEAR_MIPMAP_LINEAR }
};
static const struct
{
const char* name;
GLenum mode;
} magFilterModes[] =
{
{ "nearest", GL_NEAREST },
{ "linear", GL_LINEAR }
};
#define FOR_EACH(ITERATOR, ARRAY, BODY) \
for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++) \
BODY
// 2D cases.
FOR_EACH(minFilter, minFilterModes,
FOR_EACH(magFilter, magFilterModes,
FOR_EACH(wrapMode, wrapModes,
{
const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
filteringGroup2D->addChild(new Vertex2DTextureCase(m_context,
name.c_str(), "",
minFilterModes[minFilter].mode,
magFilterModes[magFilter].mode,
wrapModes[wrapMode].mode,
wrapModes[wrapMode].mode));
})));
FOR_EACH(wrapSMode, wrapModes,
FOR_EACH(wrapTMode, wrapModes,
{
const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
wrapGroup2D->addChild(new Vertex2DTextureCase(m_context,
name.c_str(), "",
GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR,
wrapModes[wrapSMode].mode,
wrapModes[wrapTMode].mode));
}));
// Cube map cases.
FOR_EACH(minFilter, minFilterModes,
FOR_EACH(magFilter, magFilterModes,
FOR_EACH(wrapMode, wrapModes,
{
const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;
filteringGroupCube->addChild(new VertexCubeTextureCase(m_context,
name.c_str(), "",
minFilterModes[minFilter].mode,
magFilterModes[magFilter].mode,
wrapModes[wrapMode].mode,
wrapModes[wrapMode].mode));
})));
FOR_EACH(wrapSMode, wrapModes,
FOR_EACH(wrapTMode, wrapModes,
{
const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;
wrapGroupCube->addChild(new VertexCubeTextureCase(m_context,
name.c_str(), "",
GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR,
wrapModes[wrapSMode].mode,
wrapModes[wrapTMode].mode));
}));
}
} // Functional
} // gles2
} // deqp