/*------------------------------------------------------------------------- * 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 Buffer test utilities. *//*--------------------------------------------------------------------*/ #include "es2fBufferTestUtil.hpp" #include "tcuRandomValueIterator.hpp" #include "tcuSurface.hpp" #include "tcuImageCompare.hpp" #include "tcuVector.hpp" #include "tcuFormatUtil.hpp" #include "tcuTextureUtil.hpp" #include "tcuRenderTarget.hpp" #include "gluPixelTransfer.hpp" #include "gluRenderContext.hpp" #include "gluStrUtil.hpp" #include "gluShaderProgram.hpp" #include "deMemory.h" #include "deStringUtil.hpp" #include <algorithm> #include "glwEnums.hpp" #include "glwFunctions.hpp" namespace deqp { namespace gles2 { namespace Functional { namespace BufferTestUtil { enum { VERIFY_QUAD_SIZE = 8, //!< Quad size in VertexArrayVerifier MAX_LINES_PER_INDEX_ARRAY_DRAW = 128, //!< Maximum number of lines per one draw in IndexArrayVerifier INDEX_ARRAY_DRAW_VIEWPORT_WIDTH = 128, INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT = 128 }; static const bool LOG_VERIFIER_CALLS = false; //! \note Especially array verifier generates a lot of calls. using tcu::TestLog; using std::vector; using std::string; using std::set; // Helper functions. void fillWithRandomBytes (deUint8* ptr, int numBytes, deUint32 seed) { std::copy(tcu::RandomValueIterator<deUint8>::begin(seed, numBytes), tcu::RandomValueIterator<deUint8>::end(), ptr); } bool compareByteArrays (tcu::TestLog& log, const deUint8* resPtr, const deUint8* refPtr, int numBytes) { bool isOk = true; const int maxSpanLen = 8; const int maxDiffSpans = 4; int numDiffSpans = 0; int diffSpanStart = -1; int ndx = 0; log << TestLog::Section("Verify", "Verification result"); for (;ndx < numBytes; ndx++) { if (resPtr[ndx] != refPtr[ndx]) { if (diffSpanStart < 0) diffSpanStart = ndx; isOk = false; } else if (diffSpanStart >= 0) { if (numDiffSpans < maxDiffSpans) { int len = ndx-diffSpanStart; int printLen = de::min(len, maxSpanLen); log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n" << " expected " << tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n" << " got " << tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen)) << TestLog::EndMessage; } else log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage; numDiffSpans += 1; diffSpanStart = -1; } } if (diffSpanStart >= 0) { if (numDiffSpans < maxDiffSpans) { int len = ndx-diffSpanStart; int printLen = de::min(len, maxSpanLen); log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n" << " expected " << tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n" << " got " << tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen)) << TestLog::EndMessage; } else log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage; } log << TestLog::Message << (isOk ? "Verification passed." : "Verification FAILED!") << TestLog::EndMessage; log << TestLog::EndSection; return isOk; } const char* getBufferTargetName (deUint32 target) { switch (target) { case GL_ARRAY_BUFFER: return "array"; case GL_ELEMENT_ARRAY_BUFFER: return "element_array"; default: DE_ASSERT(false); return DE_NULL; } } const char* getUsageHintName (deUint32 hint) { switch (hint) { case GL_STREAM_DRAW: return "stream_draw"; case GL_STATIC_DRAW: return "static_draw"; case GL_DYNAMIC_DRAW: return "dynamic_draw"; default: DE_ASSERT(false); return DE_NULL; } } // BufferCase BufferCase::BufferCase (Context& context, const char* name, const char* description) : TestCase (context, name, description) , CallLogWrapper (context.getRenderContext().getFunctions(), m_context.getTestContext().getLog()) { } BufferCase::~BufferCase (void) { enableLogging(false); BufferCase::deinit(); } void BufferCase::init (void) { enableLogging(true); } void BufferCase::deinit (void) { for (set<deUint32>::const_iterator bufIter = m_allocatedBuffers.begin(); bufIter != m_allocatedBuffers.end(); bufIter++) glDeleteBuffers(1, &(*bufIter)); } deUint32 BufferCase::genBuffer (void) { deUint32 buf = 0; glGenBuffers(1, &buf); if (buf != 0) { try { m_allocatedBuffers.insert(buf); } catch (const std::exception&) { glDeleteBuffers(1, &buf); throw; } } return buf; } void BufferCase::deleteBuffer (deUint32 buffer) { glDeleteBuffers(1, &buffer); m_allocatedBuffers.erase(buffer); } void BufferCase::checkError (void) { glw::GLenum err = glGetError(); if (err != GL_NO_ERROR) throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString()); } // ReferenceBuffer void ReferenceBuffer::setSize (int numBytes) { m_data.resize(numBytes); } void ReferenceBuffer::setData (int numBytes, const deUint8* bytes) { m_data.resize(numBytes); std::copy(bytes, bytes+numBytes, m_data.begin()); } void ReferenceBuffer::setSubData (int offset, int numBytes, const deUint8* bytes) { DE_ASSERT(de::inBounds(offset, 0, (int)m_data.size()) && de::inRange(offset+numBytes, offset, (int)m_data.size())); std::copy(bytes, bytes+numBytes, m_data.begin()+offset); } // BufferVerifierBase BufferVerifierBase::BufferVerifierBase (Context& context) : CallLogWrapper (context.getRenderContext().getFunctions(), context.getTestContext().getLog()) , m_context (context) { enableLogging(LOG_VERIFIER_CALLS); } // BufferVerifier BufferVerifier::BufferVerifier (Context& context, VerifyType verifyType) : m_verifier(DE_NULL) { switch (verifyType) { case VERIFY_AS_VERTEX_ARRAY: m_verifier = new VertexArrayVerifier(context); break; case VERIFY_AS_INDEX_ARRAY: m_verifier = new IndexArrayVerifier (context); break; default: TCU_FAIL("Unsupported verifier"); } } BufferVerifier::~BufferVerifier (void) { delete m_verifier; } bool BufferVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes) { DE_ASSERT(numBytes >= getMinSize()); DE_ASSERT(offset%getAlignment() == 0); DE_ASSERT((offset+numBytes)%getAlignment() == 0); return m_verifier->verify(buffer, reference, offset, numBytes); } // VertexArrayVerifier VertexArrayVerifier::VertexArrayVerifier (Context& context) : BufferVerifierBase (context) , m_program (DE_NULL) , m_posLoc (0) , m_byteVecLoc (0) { m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources( "attribute highp vec2 a_position;\n" "attribute mediump vec3 a_byteVec;\n" "varying mediump vec3 v_byteVec;\n" "void main (void)\n" "{\n" " gl_Position = vec4(a_position, 0.0, 1.0);\n" " v_byteVec = a_byteVec;\n" "}\n", "varying mediump vec3 v_byteVec;\n" "void main (void)\n" "{\n" " gl_FragColor = vec4(v_byteVec, 1.0);\n" "}\n")); if (!m_program->isOk()) { m_context.getTestContext().getLog() << *m_program; delete m_program; TCU_FAIL("Compile failed"); } const glw::Functions& funcs = context.getRenderContext().getFunctions(); m_posLoc = funcs.getAttribLocation(m_program->getProgram(), "a_position"); m_byteVecLoc = funcs.getAttribLocation(m_program->getProgram(), "a_byteVec"); } VertexArrayVerifier::~VertexArrayVerifier (void) { delete m_program; } static void computePositions (vector<tcu::Vec2>& positions, int gridSizeX, int gridSizeY) { positions.resize(gridSizeX*gridSizeY*4); for (int y = 0; y < gridSizeY; y++) for (int x = 0; x < gridSizeX; x++) { float sx0 = (x+0) / (float)gridSizeX; float sy0 = (y+0) / (float)gridSizeY; float sx1 = (x+1) / (float)gridSizeX; float sy1 = (y+1) / (float)gridSizeY; float fx0 = 2.0f * sx0 - 1.0f; float fy0 = 2.0f * sy0 - 1.0f; float fx1 = 2.0f * sx1 - 1.0f; float fy1 = 2.0f * sy1 - 1.0f; int baseNdx = (y * gridSizeX + x)*4; positions[baseNdx+0] = tcu::Vec2(fx0, fy0); positions[baseNdx+1] = tcu::Vec2(fx0, fy1); positions[baseNdx+2] = tcu::Vec2(fx1, fy0); positions[baseNdx+3] = tcu::Vec2(fx1, fy1); } } static void computeIndices (vector<deUint16>& indices, int gridSizeX, int gridSizeY) { indices.resize(3 * 2 * gridSizeX * gridSizeY); for (int quadNdx = 0; quadNdx < gridSizeX*gridSizeY; quadNdx++) { int v00 = quadNdx*4 + 0; int v01 = quadNdx*4 + 1; int v10 = quadNdx*4 + 2; int v11 = quadNdx*4 + 3; DE_ASSERT(v11 < (1<<16)); indices[quadNdx*6 + 0] = (deUint16)v10; indices[quadNdx*6 + 1] = (deUint16)v00; indices[quadNdx*6 + 2] = (deUint16)v01; indices[quadNdx*6 + 3] = (deUint16)v10; indices[quadNdx*6 + 4] = (deUint16)v01; indices[quadNdx*6 + 5] = (deUint16)v11; } } static inline tcu::Vec4 fetchVtxColor (const deUint8* ptr, int vtxNdx) { return tcu::RGBA(*(ptr + vtxNdx*3 + 0), *(ptr + vtxNdx*3 + 1), *(ptr + vtxNdx*3 + 2), 255).toVec(); } static void renderQuadGridReference (tcu::Surface& dst, int numQuads, int rowLength, const deUint8* inPtr) { using tcu::Vec4; dst.setSize(rowLength*VERIFY_QUAD_SIZE, (numQuads/rowLength + (numQuads%rowLength != 0 ? 1 : 0))*VERIFY_QUAD_SIZE); tcu::PixelBufferAccess dstAccess = dst.getAccess(); tcu::clear(dstAccess, tcu::IVec4(0, 0, 0, 0xff)); for (int quadNdx = 0; quadNdx < numQuads; quadNdx++) { int x0 = (quadNdx%rowLength)*VERIFY_QUAD_SIZE; int y0 = (quadNdx/rowLength)*VERIFY_QUAD_SIZE; Vec4 v00 = fetchVtxColor(inPtr, quadNdx*4 + 0); Vec4 v10 = fetchVtxColor(inPtr, quadNdx*4 + 1); Vec4 v01 = fetchVtxColor(inPtr, quadNdx*4 + 2); Vec4 v11 = fetchVtxColor(inPtr, quadNdx*4 + 3); for (int y = 0; y < VERIFY_QUAD_SIZE; y++) for (int x = 0; x < VERIFY_QUAD_SIZE; x++) { float fx = (float)(x+0.5f) / (float)VERIFY_QUAD_SIZE; float fy = (float)(y+0.5f) / (float)VERIFY_QUAD_SIZE; bool tri = fx + fy <= 1.0f; float tx = tri ? fx : (1.0f-fx); float ty = tri ? fy : (1.0f-fy); const Vec4& t0 = tri ? v00 : v11; const Vec4& t1 = tri ? v01 : v10; const Vec4& t2 = tri ? v10 : v01; Vec4 color = t0 + (t1-t0)*tx + (t2-t0)*ty; dstAccess.setPixel(color, x0+x, y0+y); } } } bool VertexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes) { const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget(); const int numBytesInVtx = 3; const int numBytesInQuad = numBytesInVtx*4; int maxQuadsX = de::min(128, renderTarget.getWidth() / VERIFY_QUAD_SIZE); int maxQuadsY = de::min(128, renderTarget.getHeight() / VERIFY_QUAD_SIZE); int maxQuadsPerBatch = maxQuadsX*maxQuadsY; int numVerified = 0; deUint32 program = m_program->getProgram(); tcu::RGBA threshold = renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(3,3,3,3); bool isOk = true; vector<tcu::Vec2> positions; vector<deUint16> indices; tcu::Surface rendered; tcu::Surface reference; DE_ASSERT(numBytes >= numBytesInQuad); // Can't render full quad with smaller buffers. computePositions(positions, maxQuadsX, maxQuadsY); computeIndices(indices, maxQuadsX, maxQuadsY); // Reset buffer bindings. glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); glBindBuffer (GL_ARRAY_BUFFER, 0); // Setup rendering state. glViewport (0, 0, maxQuadsX*VERIFY_QUAD_SIZE, maxQuadsY*VERIFY_QUAD_SIZE); glClearColor (0.0f, 0.0f, 0.0f, 1.0f); glUseProgram (program); glEnableVertexAttribArray (m_posLoc); glVertexAttribPointer (m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, &positions[0]); glEnableVertexAttribArray (m_byteVecLoc); glBindBuffer (GL_ARRAY_BUFFER, buffer); while (numVerified < numBytes) { int numRemaining = numBytes-numVerified; bool isLeftoverBatch = numRemaining < numBytesInQuad; int numBytesToVerify = isLeftoverBatch ? numBytesInQuad : de::min(maxQuadsPerBatch*numBytesInQuad, numRemaining - numRemaining%numBytesInQuad); int curOffset = isLeftoverBatch ? (numBytes-numBytesInQuad) : numVerified; int numQuads = numBytesToVerify/numBytesInQuad; int numCols = de::min(maxQuadsX, numQuads); int numRows = numQuads/maxQuadsX + (numQuads%maxQuadsX != 0 ? 1 : 0); string imageSetDesc = string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1); DE_ASSERT(numBytesToVerify > 0 && numBytesToVerify%numBytesInQuad == 0); DE_ASSERT(de::inBounds(curOffset, 0, numBytes)); DE_ASSERT(de::inRange(curOffset+numBytesToVerify, curOffset, numBytes)); // Render batch. glClear (GL_COLOR_BUFFER_BIT); glVertexAttribPointer (m_byteVecLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, (const glw::GLvoid*)(deUintptr)(offset + curOffset)); glDrawElements (GL_TRIANGLES, numQuads*6, GL_UNSIGNED_SHORT, &indices[0]); renderQuadGridReference(reference, numQuads, numCols, refPtr + offset + curOffset); rendered.setSize(numCols*VERIFY_QUAD_SIZE, numRows*VERIFY_QUAD_SIZE); glu::readPixels(m_context.getRenderContext(), 0, 0, rendered.getAccess()); if (!tcu::pixelThresholdCompare(m_context.getTestContext().getLog(), "RenderResult", imageSetDesc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT)) { isOk = false; break; } numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify; } glDisableVertexAttribArray (m_posLoc); glDisableVertexAttribArray (m_byteVecLoc); return isOk; } // IndexArrayVerifier IndexArrayVerifier::IndexArrayVerifier (Context& context) : BufferVerifierBase (context) , m_program (DE_NULL) , m_posLoc (0) , m_colorLoc (0) { m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources( "attribute highp vec2 a_position;\n" "attribute mediump vec3 a_color;\n" "varying mediump vec3 v_color;\n" "void main (void)\n" "{\n" " gl_Position = vec4(a_position, 0.0, 1.0);\n" " v_color = a_color;\n" "}\n", "varying mediump vec3 v_color;\n" "void main (void)\n" "{\n" " gl_FragColor = vec4(v_color, 1.0);\n" "}\n")); if (!m_program->isOk()) { m_context.getTestContext().getLog() << *m_program; delete m_program; TCU_FAIL("Compile failed"); } const glw::Functions& funcs = context.getRenderContext().getFunctions(); m_posLoc = funcs.getAttribLocation(m_program->getProgram(), "a_position"); m_colorLoc = funcs.getAttribLocation(m_program->getProgram(), "a_color"); } IndexArrayVerifier::~IndexArrayVerifier (void) { delete m_program; } static void computeIndexVerifierPositions (std::vector<tcu::Vec2>& dst) { const int numPosX = 16; const int numPosY = 16; dst.resize(numPosX*numPosY); for (int y = 0; y < numPosY; y++) { for (int x = 0; x < numPosX; x++) { float xf = float(x) / float(numPosX-1); float yf = float(y) / float(numPosY-1); dst[y*numPosX + x] = tcu::Vec2(2.0f*xf - 1.0f, 2.0f*yf - 1.0f); } } } static void computeIndexVerifierColors (std::vector<tcu::Vec3>& dst) { const int numColors = 256; const float minVal = 0.1f; const float maxVal = 0.5f; de::Random rnd (0xabc231); dst.resize(numColors); for (std::vector<tcu::Vec3>::iterator i = dst.begin(); i != dst.end(); ++i) { i->x() = rnd.getFloat(minVal, maxVal); i->y() = rnd.getFloat(minVal, maxVal); i->z() = rnd.getFloat(minVal, maxVal); } } template<typename T> static void execVertexFetch (T* dst, const T* src, const deUint8* indices, int numIndices) { for (int i = 0; i < numIndices; ++i) dst[i] = src[indices[i]]; } bool IndexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes) { const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget(); const int viewportW = de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_WIDTH, renderTarget.getWidth()); const int viewportH = de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT, renderTarget.getHeight()); const int minBytesPerBatch = 2; const tcu::RGBA threshold (0,0,0,0); std::vector<tcu::Vec2> positions; std::vector<tcu::Vec3> colors; std::vector<tcu::Vec2> fetchedPos (MAX_LINES_PER_INDEX_ARRAY_DRAW+1); std::vector<tcu::Vec3> fetchedColor (MAX_LINES_PER_INDEX_ARRAY_DRAW+1); tcu::Surface indexBufferImg (viewportW, viewportH); tcu::Surface referenceImg (viewportW, viewportH); int numVerified = 0; bool isOk = true; DE_STATIC_ASSERT(sizeof(tcu::Vec2) == sizeof(float)*2); DE_STATIC_ASSERT(sizeof(tcu::Vec3) == sizeof(float)*3); computeIndexVerifierPositions(positions); computeIndexVerifierColors(colors); // Reset buffer bindings. glBindBuffer (GL_ARRAY_BUFFER, 0); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, buffer); // Setup rendering state. glViewport (0, 0, viewportW, viewportH); glClearColor (0.0f, 0.0f, 0.0f, 1.0f); glUseProgram (m_program->getProgram()); glEnableVertexAttribArray (m_posLoc); glEnableVertexAttribArray (m_colorLoc); glEnable (GL_BLEND); glBlendFunc (GL_ONE, GL_ONE); glBlendEquation (GL_FUNC_ADD); while (numVerified < numBytes) { int numRemaining = numBytes-numVerified; bool isLeftoverBatch = numRemaining < minBytesPerBatch; int numBytesToVerify = isLeftoverBatch ? minBytesPerBatch : de::min(MAX_LINES_PER_INDEX_ARRAY_DRAW+1, numRemaining); int curOffset = isLeftoverBatch ? (numBytes-minBytesPerBatch) : numVerified; string imageSetDesc = string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1); // Step 1: Render using index buffer. glClear (GL_COLOR_BUFFER_BIT); glVertexAttribPointer (m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, &positions[0]); glVertexAttribPointer (m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, &colors[0]); glDrawElements (GL_LINE_STRIP, numBytesToVerify, GL_UNSIGNED_BYTE, (void*)(deUintptr)(offset+curOffset)); glu::readPixels (m_context.getRenderContext(), 0, 0, indexBufferImg.getAccess()); // Step 2: Do manual fetch and render without index buffer. execVertexFetch(&fetchedPos[0], &positions[0], refPtr+offset+curOffset, numBytesToVerify); execVertexFetch(&fetchedColor[0], &colors[0], refPtr+offset+curOffset, numBytesToVerify); glClear (GL_COLOR_BUFFER_BIT); glVertexAttribPointer (m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, &fetchedPos[0]); glVertexAttribPointer (m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, &fetchedColor[0]); glDrawArrays (GL_LINE_STRIP, 0, numBytesToVerify); glu::readPixels (m_context.getRenderContext(), 0, 0, referenceImg.getAccess()); if (!tcu::pixelThresholdCompare(m_context.getTestContext().getLog(), "RenderResult", imageSetDesc.c_str(), referenceImg, indexBufferImg, threshold, tcu::COMPARE_LOG_RESULT)) { isOk = false; break; } numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify; } return isOk; } } // BufferTestUtil } // Functional } // gles2 } // deqp