/*------------------------------------------------------------------------- * 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