/*------------------------------------------------------------------------- * 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 "tcuTexVerifierUtil.hpp" #include "tcuImageCompare.hpp" #include "deRandom.hpp" #include "deString.h" #include "deMath.h" #include #include #include #include "glw.h" using std::string; using std::vector; using tcu::IVec2; using tcu::IVec3; using tcu::IVec4; using tcu::Mat3; using tcu::TestLog; using tcu::Vec2; using tcu::Vec3; using tcu::Vec4; namespace deqp { using namespace gls::TextureTestUtil; using namespace glu::TextureTestUtil; using glu::TextureTestUtil::TEXTURETYPE_2D; using glu::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 static inline tcu::Vector safeCoords(const tcu::Vector &raw, const tcu::Vector &scale, const tcu::Vector &fraction) { tcu::Vector 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 struct TexTypeTcuClass; template <> struct TexTypeTcuClass { typedef tcu::Texture2D t; }; template <> struct TexTypeTcuClass { typedef tcu::TextureCube t; }; template struct TexTypeSizeDims; template <> struct TexTypeSizeDims { enum { V = 2 }; }; template <> struct TexTypeSizeDims { enum { V = 2 }; }; template struct TexTypeCoordDims; template <> struct TexTypeCoordDims { enum { V = 2 }; }; template <> struct TexTypeCoordDims { enum { V = 3 }; }; template struct TexTypeSizeIVec { typedef tcu::Vector::V> t; }; template struct TexTypeCoordVec { typedef tcu::Vector::V> t; }; template struct TexTypeCoordParams; template <> struct TexTypeCoordParams { Vec2 scale; Vec2 bias; TexTypeCoordParams(const Vec2 &scale_, const Vec2 &bias_) : scale(scale_), bias(bias_) { } }; template <> struct TexTypeCoordParams { 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 class PosTexCoordQuadGrid { private: enum { TEX_COORD_DIMS = TexTypeCoordDims::V }; typedef typename TexTypeCoordVec::t TexCoordVec; typedef typename TexTypeSizeIVec::t TexSizeIVec; typedef TexTypeCoordParams 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 uint16_t *getIndexPtr(void) const { return &m_indices[0]; } private: void initializeTexCoords(const TexSizeIVec &textureSize, const TexCoordParams &texCoordParams, bool useSafeTexCoords); const int m_gridSize; vector m_positions; vector m_texCoords; vector m_indices; }; template Vec4 PosTexCoordQuadGrid::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 const typename TexTypeCoordVec::t &PosTexCoordQuadGrid::getQuadTexCoord(int col, int row) const { return m_texCoords[(row * m_gridSize + col) * 4]; } template PosTexCoordQuadGrid::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::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(uint16_t(firstNdx + 0)); m_indices.push_back(uint16_t(firstNdx + 1)); m_indices.push_back(uint16_t(firstNdx + 2)); m_indices.push_back(uint16_t(firstNdx + 1)); m_indices.push_back(uint16_t(firstNdx + 3)); m_indices.push_back(uint16_t(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::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::initializeTexCoords(const IVec2 &textureSize, const TexCoordParams &texCoordParams, bool useSafeTexCoords) { DE_ASSERT(m_texCoords.empty()); const float gridSizeFloat = (float)m_gridSize; vector 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); } } } // namespace static inline bool isLevelNearest(uint32_t 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 static void setPixelColors(const vector &quadColors, const Rect ®ion, const PosTexCoordQuadGrid &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, tcu::RGBA(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 void computeReference(const typename TexTypeTcuClass::t &texture, float lod, const tcu::Sampler &sampler, const PosTexCoordQuadGrid &grid, tcu::Surface &dst, const Rect &dstRegion) { const int gridSize = grid.getSize(); vector 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::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, uint32_t minFilter, uint32_t magFilter, uint32_t wrapS, uint32_t wrapT); ~Vertex2DTextureCase(void); void init(void); void deinit(void); IterateResult iterate(void); private: typedef PosTexCoordQuadGrid 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 uint32_t m_minFilter; const uint32_t m_magFilter; const uint32_t m_wrapS; const uint32_t 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, uint32_t minFilter, uint32_t magFilter, uint32_t wrapS, uint32_t 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 uint32_t step = 0x00ffffff / numLevels; const uint32_t rgb = step * levelNdx; const uint32_t colorA = 0xff000000 | rgb; const uint32_t colorB = 0xff000000 | ~rgb; m_textures[1]->getRefTexture().allocLevel(levelNdx); tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, tcu::RGBA(colorA).toVec() * cScale + cBias, tcu::RGBA(colorB).toVec() * 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(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 uint32_t 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, uint32_t minFilter, uint32_t magFilter, uint32_t wrapS, uint32_t wrapT); ~VertexCubeTextureCase(void); void init(void); void deinit(void); IterateResult iterate(void); private: typedef PosTexCoordQuadGrid 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 uint32_t m_minFilter; const uint32_t m_magFilter; const uint32_t m_wrapS; const uint32_t m_wrapT; const glu::ShaderProgram *m_program; glu::TextureCube *m_textures[2]; // 2 textures, a gradient texture and a grid texture. bool m_isES3Capable; }; VertexCubeTextureCase::VertexCubeTextureCase(Context &testCtx, const char *name, const char *desc, uint32_t minFilter, uint32_t magFilter, uint32_t wrapS, uint32_t 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_isES3Capable(false) { 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"; m_isES3Capable = glu::IsES3Compatible(m_context.getRenderContext().getFunctions()); 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) != 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 uint32_t step = 0x00ffffff / (numLevels * tcu::CUBEFACE_LAST); const uint32_t rgb = step * levelNdx * face; const uint32_t colorA = 0xff000000 | rgb; const uint32_t colorB = 0xff000000 | ~rgb; m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx); tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, tcu::RGBA(colorA).toVec() * cScale + cBias, tcu::RGBA(colorB).toVec() * 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(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 uint32_t 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); sampler.seamlessCubeMap = m_isES3Capable; 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)); })) } } // namespace Functional } // namespace gles2 } // namespace deqp