/*------------------------------------------------------------------------- * 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 Default vertex attribute test *//*--------------------------------------------------------------------*/ #include "es2fDefaultVertexAttributeTests.hpp" #include "tcuVector.hpp" #include "tcuRenderTarget.hpp" #include "tcuSurface.hpp" #include "tcuTextureUtil.hpp" #include "gluRenderContext.hpp" #include "gluCallLogWrapper.hpp" #include "gluShaderProgram.hpp" #include "gluObjectWrapper.hpp" #include "gluPixelTransfer.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include "deMath.h" #include "deStringUtil.hpp" #include "deString.h" #include <limits> namespace deqp { namespace gles2 { namespace Functional { namespace { static const int s_valueRange = 10; static const char* const s_passThroughFragmentShaderSource = "varying mediump vec4 v_color;\n" "void main (void)\n" "{\n" " gl_FragColor = v_color;\n" "}\n"; template <typename T1, int S1, typename T2, int S2> tcu::Vector<T1, S1> convertToTypeVec (const tcu::Vector<T2, S2>& v) { tcu::Vector<T1, S1> retVal; for (int ndx = 0; ndx < S1; ++ndx) retVal[ndx] = T1(0); if (S1 == 4) retVal[3] = T1(1); for (int ndx = 0; ndx < de::min(S1, S2); ++ndx) retVal[ndx] = T1(v[ndx]); return retVal; } class FloatLoader { public: virtual ~FloatLoader (void) {}; // returns the value loaded virtual tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const = 0; }; #define GEN_DIRECT_FLOAT_LOADER(TYPE, COMPS, TYPECODE, CASENAME, VALUES) \ class LoaderVertexAttrib##COMPS##TYPECODE : public FloatLoader \ { \ public: \ enum \ { \ NORMALIZING = 0, \ }; \ enum \ { \ COMPONENTS = COMPS \ }; \ typedef TYPE Type; \ \ tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const \ { \ tcu::Vector<TYPE, COMPONENTS> value; \ value = convertToTypeVec<Type, COMPONENTS>(v); \ \ gl.glVertexAttrib ##COMPS ##TYPECODE VALUES; \ return convertToTypeVec<float, 4>(value); \ } \ \ static const char* getCaseName (void) \ { \ return CASENAME; \ } \ \ static const char* getName (void) \ { \ return "VertexAttrib" #COMPS #TYPECODE; \ } \ } #define GEN_INDIRECT_FLOAT_LOADER(TYPE, COMPS, TYPECODE, CASENAME) \ class LoaderVertexAttrib##COMPS##TYPECODE : public FloatLoader \ { \ public: \ enum \ { \ NORMALIZING = 0, \ }; \ enum \ { \ COMPONENTS = COMPS \ }; \ typedef TYPE Type; \ \ tcu::Vec4 load (glu::CallLogWrapper& gl, int index, const tcu::Vec4& v) const \ { \ tcu::Vector<TYPE, COMPONENTS> value; \ value = convertToTypeVec<Type, COMPONENTS>(v); \ \ gl.glVertexAttrib ##COMPS ##TYPECODE (index, value.getPtr()); \ return convertToTypeVec<float, 4>(value); \ } \ \ static const char* getCaseName (void) \ { \ return CASENAME; \ } \ \ static const char* getName (void) \ { \ return "VertexAttrib" #COMPS #TYPECODE; \ } \ } GEN_DIRECT_FLOAT_LOADER(float, 1, f, "vertex_attrib_1f", (index, value.x())); GEN_DIRECT_FLOAT_LOADER(float, 2, f, "vertex_attrib_2f", (index, value.x(), value.y())); GEN_DIRECT_FLOAT_LOADER(float, 3, f, "vertex_attrib_3f", (index, value.x(), value.y(), value.z())); GEN_DIRECT_FLOAT_LOADER(float, 4, f, "vertex_attrib_4f", (index, value.x(), value.y(), value.z(), value.w())); GEN_INDIRECT_FLOAT_LOADER(float, 1, fv, "vertex_attrib_1fv"); GEN_INDIRECT_FLOAT_LOADER(float, 2, fv, "vertex_attrib_2fv"); GEN_INDIRECT_FLOAT_LOADER(float, 3, fv, "vertex_attrib_3fv"); GEN_INDIRECT_FLOAT_LOADER(float, 4, fv, "vertex_attrib_4fv"); class AttributeCase : public TestCase { AttributeCase (Context& ctx, const char* name, const char* desc, const char* funcName, bool normalizing, bool useNegative, glu::DataType dataType); public: template<typename LoaderType> static AttributeCase* create (Context& ctx, glu::DataType dataType); ~AttributeCase (void); private: void init (void); void deinit (void); IterateResult iterate (void); glu::DataType getTargetType (void) const; std::string genVertexSource (void) const; bool renderWithValue (const tcu::Vec4& v); tcu::Vec4 computeColor (const tcu::Vec4& value); bool verifyUnicoloredBuffer (const tcu::Surface& scene, const tcu::Vec4& refValue); const bool m_normalizing; const bool m_useNegativeValues; const char* const m_funcName; const glu::DataType m_dataType; const FloatLoader* m_loader; glu::ShaderProgram* m_program; deUint32 m_bufID; bool m_allIterationsPassed; int m_iteration; enum { RENDER_SIZE = 32 }; }; AttributeCase::AttributeCase (Context& ctx, const char* name, const char* desc, const char* funcName, bool normalizing, bool useNegative, glu::DataType dataType) : TestCase (ctx, name, desc) , m_normalizing (normalizing) , m_useNegativeValues (useNegative) , m_funcName (funcName) , m_dataType (dataType) , m_loader (DE_NULL) , m_program (DE_NULL) , m_bufID (0) , m_allIterationsPassed (true) , m_iteration (0) { } template<typename LoaderType> AttributeCase* AttributeCase::create (Context& ctx, glu::DataType dataType) { AttributeCase* retVal = new AttributeCase(ctx, LoaderType::getCaseName(), (std::string("Test ") + LoaderType::getName()).c_str(), LoaderType::getName(), LoaderType::NORMALIZING != 0, std::numeric_limits<typename LoaderType::Type>::is_signed, dataType); retVal->m_loader = new LoaderType(); return retVal; } AttributeCase::~AttributeCase (void) { deinit(); } void AttributeCase::init (void) { if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE) throw tcu::NotSupportedError("Render target must be at least " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE)); // log test info { const float maxRange = (m_normalizing) ? (1.0f) : (s_valueRange); const float minRange = (m_useNegativeValues) ? (-maxRange) : (0.0f); m_testCtx.getLog() << tcu::TestLog::Message << "Loading attribute values using " << m_funcName << "\n" << "Attribute type: " << glu::getDataTypeName(m_dataType) << "\n" << "Attribute value range: [" << minRange << ", " << maxRange << "]" << tcu::TestLog::EndMessage; } // gen shader and base quad m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(s_passThroughFragmentShaderSource)); m_testCtx.getLog() << *m_program; if (!m_program->isOk()) throw tcu::TestError("could not build program"); { const tcu::Vec4 fullscreenQuad[] = { tcu::Vec4( 1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f), tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f), }; const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.genBuffers(1, &m_bufID); gl.bindBuffer(GL_ARRAY_BUFFER, m_bufID); gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "fill buffer"); } } void AttributeCase::deinit (void) { delete m_loader; m_loader = DE_NULL; delete m_program; m_program = DE_NULL; if (m_bufID) { m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufID); m_bufID = 0; } } AttributeCase::IterateResult AttributeCase::iterate (void) { static const tcu::Vec4 testValues[] = { tcu::Vec4(0.0f, 0.5f, 0.2f, 1.0f), tcu::Vec4(0.1f, 0.7f, 1.0f, 0.6f), tcu::Vec4(0.4f, 0.2f, 0.0f, 0.5f), tcu::Vec4(0.5f, 0.0f, 0.9f, 0.1f), tcu::Vec4(0.6f, 0.2f, 0.2f, 0.9f), tcu::Vec4(0.9f, 1.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 0.5f, 0.3f, 0.8f), }; const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration", "Iteration " + de::toString(m_iteration+1) + "/" + de::toString(DE_LENGTH_OF_ARRAY(testValues))); // Test normalizing transfers with whole range, non-normalizing with up to s_valueRange const tcu::Vec4 testValue = ((m_useNegativeValues) ? (testValues[m_iteration] * 2.0f - tcu::Vec4(1.0f)) : (testValues[m_iteration])) * ((m_normalizing) ? (1.0f) : ((float)s_valueRange)); if (!renderWithValue(testValue)) m_allIterationsPassed = false; // continue if (++m_iteration < DE_LENGTH_OF_ARRAY(testValues)) return CONTINUE; if (m_allIterationsPassed) m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); else m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected values"); return STOP; } std::string AttributeCase::genVertexSource (void) const { const int vectorSize = (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::isDataTypeVector(m_dataType)) ? (glu::getDataTypeScalarSize(m_dataType)) : (-1); const char* const vectorType = glu::getDataTypeName((glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeVector(glu::TYPE_FLOAT, vectorSize)) : (glu::isDataTypeVector(m_dataType)) ? (glu::getDataTypeVector(glu::TYPE_FLOAT, vectorSize)) : (glu::TYPE_FLOAT)); const int components = (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::getDataTypeScalarSize(m_dataType)); std::ostringstream buf; buf << "attribute highp vec4 a_position;\n" "attribute highp " << glu::getDataTypeName(m_dataType) << " a_value;\n" "varying highp vec4 v_color;\n" "void main (void)\n" "{\n" " gl_Position = a_position;\n" "\n"; if (m_normalizing) buf << " highp " << vectorType << " normalizedValue = " << ((glu::getDataTypeScalarType(m_dataType) == glu::TYPE_FLOAT) ? ("") : (vectorType)) << "(a_value" << ((glu::isDataTypeMatrix(m_dataType)) ? ("[1]") : ("")) << ");\n"; else buf << " highp " << vectorType << " normalizedValue = " << ((glu::getDataTypeScalarType(m_dataType) == glu::TYPE_FLOAT) ? ("") : (vectorType)) << "(a_value" << ((glu::isDataTypeMatrix(m_dataType)) ? ("[1]") : ("")) << ") / float(" << s_valueRange << ");\n"; if (m_useNegativeValues) buf << " highp " << vectorType << " positiveNormalizedValue = (normalizedValue + " << vectorType << "(1.0)) / 2.0;\n"; else buf << " highp " << vectorType << " positiveNormalizedValue = normalizedValue;\n"; if (components == 1) buf << " v_color = vec4(positiveNormalizedValue, 0.0, 0.0, 1.0);\n"; else if (components == 2) buf << " v_color = vec4(positiveNormalizedValue.xy, 0.0, 1.0);\n"; else if (components == 3) buf << " v_color = vec4(positiveNormalizedValue.xyz, 1.0);\n"; else if (components == 4) buf << " v_color = vec4((positiveNormalizedValue.xy + positiveNormalizedValue.zz) / 2.0, positiveNormalizedValue.w, 1.0);\n"; else DE_ASSERT(DE_FALSE); buf << "}\n"; return buf.str(); } bool AttributeCase::renderWithValue (const tcu::Vec4& v) { glu::CallLogWrapper gl (m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); gl.enableLogging(true); const int positionIndex = gl.glGetAttribLocation(m_program->getProgram(), "a_position"); const int valueIndex = gl.glGetAttribLocation(m_program->getProgram(), "a_value"); tcu::Surface dest (RENDER_SIZE, RENDER_SIZE); tcu::Vec4 loadedValue; gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear(GL_COLOR_BUFFER_BIT); gl.glViewport(0, 0, RENDER_SIZE, RENDER_SIZE); GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup"); gl.glBindBuffer(GL_ARRAY_BUFFER, m_bufID); gl.glVertexAttribPointer(positionIndex, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); gl.glEnableVertexAttribArray(positionIndex); GLU_EXPECT_NO_ERROR(gl.glGetError(), "position va"); // transfer test value. Load to the second column in the matrix case loadedValue = m_loader->load(gl, (glu::isDataTypeMatrix(m_dataType)) ? (valueIndex + 1) : (valueIndex), v); GLU_EXPECT_NO_ERROR(gl.glGetError(), "default va"); gl.glUseProgram(m_program->getProgram()); gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); gl.glUseProgram(0); GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw"); glu::readPixels(m_context.getRenderContext(), 0, 0, dest.getAccess()); // check whole result is colored correctly return verifyUnicoloredBuffer(dest, computeColor(loadedValue)); } tcu::Vec4 AttributeCase::computeColor (const tcu::Vec4& value) { const tcu::Vec4 normalizedValue = value / ((m_normalizing) ? (1.0f) : ((float)s_valueRange)); const tcu::Vec4 positiveNormalizedValue = ((m_useNegativeValues) ? ((normalizedValue + tcu::Vec4(1.0f)) / 2.0f) : (normalizedValue)); const int components = (glu::isDataTypeMatrix(m_dataType)) ? (glu::getDataTypeMatrixNumRows(m_dataType)) : (glu::getDataTypeScalarSize(m_dataType)); if (components == 1) return tcu::Vec4(positiveNormalizedValue.x(), 0.0f, 0.0f, 1.0f); else if (components == 2) return tcu::Vec4(positiveNormalizedValue.x(), positiveNormalizedValue.y(), 0.0f, 1.0f); else if (components == 3) return tcu::Vec4(positiveNormalizedValue.x(), positiveNormalizedValue.y(), positiveNormalizedValue.z(), 1.0f); else if (components == 4) return tcu::Vec4((positiveNormalizedValue.x() + positiveNormalizedValue.z()) / 2.0f, (positiveNormalizedValue.y() + positiveNormalizedValue.z()) / 2.0f, positiveNormalizedValue.w(), 1.0f); else DE_ASSERT(DE_FALSE); return tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f); } bool AttributeCase::verifyUnicoloredBuffer (const tcu::Surface& scene, const tcu::Vec4& refValue) { tcu::Surface errorMask (RENDER_SIZE, RENDER_SIZE); const tcu::RGBA refColor (refValue); const int resultThreshold = 2; const tcu::RGBA colorThreshold = m_context.getRenderTarget().getPixelFormat().getColorThreshold() * resultThreshold; bool error = false; tcu::RGBA exampleColor; tcu::IVec2 examplePos; tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toIVec()); m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered image. Expecting color " << refColor << ", threshold " << colorThreshold << tcu::TestLog::EndMessage; for (int y = 0; y < RENDER_SIZE; ++y) for (int x = 0; x < RENDER_SIZE; ++x) { const tcu::RGBA color = scene.getPixel(x, y); if (de::abs(color.getRed() - refColor.getRed()) > colorThreshold.getRed() || de::abs(color.getGreen() - refColor.getGreen()) > colorThreshold.getGreen() || de::abs(color.getBlue() - refColor.getBlue()) > colorThreshold.getBlue()) { // first error if (!error) { exampleColor = color; examplePos = tcu::IVec2(x, y); } error = true; errorMask.setPixel(x, y, tcu::RGBA::red); } } if (!error) m_testCtx.getLog() << tcu::TestLog::Message << "Rendered image is valid." << tcu::TestLog::EndMessage; else { m_testCtx.getLog() << tcu::TestLog::Message << "Found invalid pixel(s).\n" << "Pixel at (" << examplePos.x() << ", " << examplePos.y() << ") color: " << exampleColor << tcu::TestLog::EndMessage << tcu::TestLog::ImageSet("Result", "Render result") << tcu::TestLog::Image("Result", "Result", scene) << tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask) << tcu::TestLog::EndImageSet; } return !error; } } // anonymous DefaultVertexAttributeTests::DefaultVertexAttributeTests (Context& context) : TestCaseGroup(context, "default_vertex_attrib", "Test default vertex attributes") { } DefaultVertexAttributeTests::~DefaultVertexAttributeTests (void) { } void DefaultVertexAttributeTests::init (void) { struct Target { const char* name; glu::DataType dataType; bool reducedTestSets; // !< use reduced coverage }; static const Target floatTargets[] = { { "float", glu::TYPE_FLOAT, false }, { "vec2", glu::TYPE_FLOAT_VEC2, true }, { "vec3", glu::TYPE_FLOAT_VEC3, true }, { "vec4", glu::TYPE_FLOAT_VEC4, false }, { "mat2", glu::TYPE_FLOAT_MAT2, true }, { "mat3", glu::TYPE_FLOAT_MAT3, true }, { "mat4", glu::TYPE_FLOAT_MAT4, false }, }; // float targets for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(floatTargets); ++targetNdx) { tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, floatTargets[targetNdx].name, (std::string("test with ") + floatTargets[targetNdx].name).c_str()); const bool fullSet = !floatTargets[targetNdx].reducedTestSets; #define ADD_CASE(X) group->addChild(AttributeCase::create<X>(m_context, floatTargets[targetNdx].dataType)) #define ADD_REDUCED_CASE(X) if (fullSet) ADD_CASE(X) ADD_CASE (LoaderVertexAttrib1f); ADD_REDUCED_CASE(LoaderVertexAttrib2f); ADD_REDUCED_CASE(LoaderVertexAttrib3f); ADD_CASE (LoaderVertexAttrib4f); ADD_CASE (LoaderVertexAttrib1fv); ADD_REDUCED_CASE(LoaderVertexAttrib2fv); ADD_REDUCED_CASE(LoaderVertexAttrib3fv); ADD_CASE (LoaderVertexAttrib4fv); #undef ADD_CASE #undef ADD_REDUCED_CASE addChild(group); } } } // Functional } // gles2 } // deqp