/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL (ES) 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 Compiler test case. *//*--------------------------------------------------------------------*/ #include "glsShaderLibraryCase.hpp" #include "tcuTestLog.hpp" #include "tcuRenderTarget.hpp" #include "tcuTextureUtil.hpp" #include "tcuSurface.hpp" #include "tcuStringTemplate.hpp" #include "gluShaderProgram.hpp" #include "gluPixelTransfer.hpp" #include "gluDrawUtil.hpp" #include "gluContextInfo.hpp" #include "gluStrUtil.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "deRandom.hpp" #include "deInt32.h" #include "deMath.h" #include "deString.h" #include "deStringUtil.hpp" #include "deSharedPtr.hpp" #include #include #include #include namespace deqp { namespace gls { using namespace tcu; using namespace glu; using namespace glu::sl; using std::vector; using std::string; using std::ostringstream; using std::map; using std::pair; using de::SharedPtr; // OpenGL-specific specialization utils static vector checkAndSpecializeExtensions (const vector& src, const ContextInfo& ctxInfo) { vector specialized; for (size_t extNdx = 0; extNdx < src.size(); ++extNdx) { const RequiredExtension& extension = src[extNdx]; int supportedAltNdx = -1; for (size_t alternativeNdx = 0; alternativeNdx < extension.alternatives.size(); ++alternativeNdx) { if (ctxInfo.isExtensionSupported(extension.alternatives[alternativeNdx].c_str())) { supportedAltNdx = (int)alternativeNdx; break; } } if (supportedAltNdx >= 0) { specialized.push_back(RequiredExtension(extension.alternatives[supportedAltNdx], extension.effectiveStages)); } else { // no extension(s). Make a nice output std::ostringstream extensionList; for (size_t ndx = 0; ndx < extension.alternatives.size(); ++ndx) { if (!extensionList.str().empty()) extensionList << ", "; extensionList << extension.alternatives[ndx]; } if (extension.alternatives.size() == 1) throw tcu::NotSupportedError("Test requires extension " + extensionList.str()); else throw tcu::NotSupportedError("Test requires any extension of " + extensionList.str()); } } return specialized; } static void checkImplementationLimits (const vector& requiredCaps, const ContextInfo& ctxInfo) { for (size_t capNdx = 0; capNdx < requiredCaps.size(); ++capNdx) { const RequiredCapability& capability = requiredCaps[capNdx]; if (capability.type != CAPABILITY_LIMIT) continue; const deUint32 pname = capability.enumName; const int requiredValue = capability.referenceValue; const int supportedValue = ctxInfo.getInt((int)pname); if (supportedValue <= requiredValue) throw tcu::NotSupportedError("Test requires " + de::toString(glu::getGettableStateStr(pname)) + " (" + de::toString(supportedValue) + ") >= " + de::toString(requiredValue)); } } // Shader source specialization // This functions builds a matching vertex shader for a 'both' case, when // the fragment shader is being tested. // We need to build attributes and varyings for each 'input'. static string genVertexShader (const ShaderCaseSpecification& spec) { ostringstream res; const bool usesInout = glslVersionUsesInOutQualifiers(spec.targetVersion); const char* const vtxIn = usesInout ? "in" : "attribute"; const char* const vtxOut = usesInout ? "out" : "varying"; res << glu::getGLSLVersionDeclaration(spec.targetVersion) << "\n"; // Declarations (position + attribute/varying for each input). res << "precision highp float;\n"; res << "precision highp int;\n"; res << "\n"; res << vtxIn << " highp vec4 dEQP_Position;\n"; for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++) { const Value& val = spec.values.inputs[ndx]; const DataType basicType = val.type.getBasicType(); const DataType floatType = getDataTypeFloatScalars(basicType); const char* const typeStr = getDataTypeName(floatType); res << vtxIn << " " << typeStr << " a_" << val.name << ";\n"; if (getDataTypeScalarType(basicType) == TYPE_FLOAT) res << vtxOut << " " << typeStr << " " << val.name << ";\n"; else res << vtxOut << " " << typeStr << " v_" << val.name << ";\n"; } res << "\n"; // Main function. // - gl_Position = dEQP_Position; // - for each input: write attribute directly to varying res << "void main()\n"; res << "{\n"; res << " gl_Position = dEQP_Position;\n"; for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++) { const Value& val = spec.values.inputs[ndx]; const string& name = val.name; if (getDataTypeScalarType(val.type.getBasicType()) == TYPE_FLOAT) res << " " << name << " = a_" << name << ";\n"; else res << " v_" << name << " = a_" << name << ";\n"; } res << "}\n"; return res.str(); } static void genCompareOp (ostringstream& output, const char* dstVec4Var, const ValueBlock& valueBlock, const char* nonFloatNamePrefix, const char* checkVarName) { bool isFirstOutput = true; for (size_t ndx = 0; ndx < valueBlock.outputs.size(); ndx++) { const Value& val = valueBlock.outputs[ndx]; // Check if we're only interested in one variable (then skip if not the right one). if (checkVarName && val.name != checkVarName) continue; // Prefix. if (isFirstOutput) { output << "bool RES = "; isFirstOutput = false; } else output << "RES = RES && "; // Generate actual comparison. if (getDataTypeScalarType(val.type.getBasicType()) == TYPE_FLOAT) output << "isOk(" << val.name << ", ref_" << val.name << ", 0.05);\n"; else output << "isOk(" << nonFloatNamePrefix << val.name << ", ref_" << val.name << ");\n"; } if (isFirstOutput) output << dstVec4Var << " = vec4(1.0);\n"; // \todo [petri] Should we give warning if not expect-failure case? else output << dstVec4Var << " = vec4(RES, RES, RES, 1.0);\n"; } static inline bool supportsFragmentHighp (glu::GLSLVersion version) { return version != glu::GLSL_VERSION_100_ES; } static string genFragmentShader (const ShaderCaseSpecification& spec) { ostringstream shader; const bool usesInout = glslVersionUsesInOutQualifiers(spec.targetVersion); const bool customColorOut = usesInout; const char* const fragIn = usesInout ? "in" : "varying"; const char* const prec = supportsFragmentHighp(spec.targetVersion) ? "highp" : "mediump"; shader << glu::getGLSLVersionDeclaration(spec.targetVersion) << "\n"; shader << "precision " << prec << " float;\n"; shader << "precision " << prec << " int;\n"; shader << "\n"; if (customColorOut) { shader << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n"; shader << "\n"; } genCompareFunctions(shader, spec.values, true); shader << "\n"; // Declarations (varying, reference for each output). for (size_t ndx = 0; ndx < spec.values.outputs.size(); ndx++) { const Value& val = spec.values.outputs[ndx]; const DataType basicType = val.type.getBasicType(); const DataType floatType = getDataTypeFloatScalars(basicType); const char* const floatTypeStr = getDataTypeName(floatType); const char* const refTypeStr = getDataTypeName(basicType); if (getDataTypeScalarType(basicType) == TYPE_FLOAT) shader << fragIn << " " << floatTypeStr << " " << val.name << ";\n"; else shader << fragIn << " " << floatTypeStr << " v_" << val.name << ";\n"; shader << "uniform " << refTypeStr << " ref_" << val.name << ";\n"; } shader << "\n"; shader << "void main()\n"; shader << "{\n"; shader << " "; genCompareOp(shader, customColorOut ? "dEQP_FragColor" : "gl_FragColor", spec.values, "v_", DE_NULL); shader << "}\n"; return shader.str(); } // Specialize a shader for the vertex shader test case. static string specializeVertexShader (const ShaderCaseSpecification& spec, const std::string& src, const vector& extensions) { ostringstream decl; ostringstream setup; ostringstream output; const bool usesInout = glslVersionUsesInOutQualifiers(spec.targetVersion); const char* const vtxIn = usesInout ? "in" : "attribute"; const char* const vtxOut = usesInout ? "out" : "varying"; // generated from "both" case DE_ASSERT(spec.caseType == CASETYPE_VERTEX_ONLY); // Output (write out position). output << "gl_Position = dEQP_Position;\n"; // Declarations (position + attribute for each input, varying for each output). decl << vtxIn << " highp vec4 dEQP_Position;\n"; for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++) { const Value& val = spec.values.inputs[ndx]; const DataType basicType = val.type.getBasicType(); const DataType floatType = getDataTypeFloatScalars(basicType); const char* const floatTypeStr = getDataTypeName(floatType); const char* const refTypeStr = getDataTypeName(basicType); if (getDataTypeScalarType(basicType) == TYPE_FLOAT) { decl << vtxIn << " " << floatTypeStr << " " << val.name << ";\n"; } else { decl << vtxIn << " " << floatTypeStr << " a_" << val.name << ";\n"; setup << refTypeStr << " " << val.name << " = " << refTypeStr << "(a_" << val.name << ");\n"; } } // \todo [2015-07-24 pyry] Why are uniforms missing? for (size_t ndx = 0; ndx < spec.values.outputs.size(); ndx++) { const Value& val = spec.values.outputs[ndx]; const DataType basicType = val.type.getBasicType(); const DataType floatType = getDataTypeFloatScalars(basicType); const char* const floatTypeStr = getDataTypeName(floatType); const char* const refTypeStr = getDataTypeName(basicType); if (getDataTypeScalarType(basicType) == TYPE_FLOAT) decl << vtxOut << " " << floatTypeStr << " " << val.name << ";\n"; else { decl << vtxOut << " " << floatTypeStr << " v_" << val.name << ";\n"; decl << refTypeStr << " " << val.name << ";\n"; output << "v_" << val.name << " = " << floatTypeStr << "(" << val.name << ");\n"; } } // Shader specialization. map params; params.insert(pair("DECLARATIONS", decl.str())); params.insert(pair("SETUP", setup.str())); params.insert(pair("OUTPUT", output.str())); params.insert(pair("POSITION_FRAG_COLOR", "gl_Position")); StringTemplate tmpl (src); const string baseSrc = tmpl.specialize(params); const string withExt = injectExtensionRequirements(baseSrc, extensions, SHADERTYPE_VERTEX); return withExt; } // Specialize a shader for the fragment shader test case. static string specializeFragmentShader (const ShaderCaseSpecification& spec, const std::string& src, const vector& extensions) { ostringstream decl; ostringstream setup; ostringstream output; const bool usesInout = glslVersionUsesInOutQualifiers(spec.targetVersion); const bool customColorOut = usesInout; const char* const fragIn = usesInout ? "in" : "varying"; const char* const fragColor = customColorOut ? "dEQP_FragColor" : "gl_FragColor"; // generated from "both" case DE_ASSERT(spec.caseType == CASETYPE_FRAGMENT_ONLY); genCompareFunctions(decl, spec.values, false); genCompareOp(output, fragColor, spec.values, "", DE_NULL); if (customColorOut) decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n"; for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++) { const Value& val = spec.values.inputs[ndx]; const DataType basicType = val.type.getBasicType(); const DataType floatType = getDataTypeFloatScalars(basicType); const char* const floatTypeStr = getDataTypeName(floatType); const char* const refTypeStr = getDataTypeName(basicType); if (getDataTypeScalarType(basicType) == TYPE_FLOAT) decl << fragIn << " " << floatTypeStr << " " << val.name << ";\n"; else { decl << fragIn << " " << floatTypeStr << " v_" << val.name << ";\n"; std::string offset = isDataTypeIntOrIVec(basicType) ? " * 1.0025" : ""; // \todo [petri] bit of a hack to avoid errors in chop() due to varying interpolation setup << refTypeStr << " " << val.name << " = " << refTypeStr << "(v_" << val.name << offset << ");\n"; } } // \todo [2015-07-24 pyry] Why are uniforms missing? for (size_t ndx = 0; ndx < spec.values.outputs.size(); ndx++) { const Value& val = spec.values.outputs[ndx]; const DataType basicType = val.type.getBasicType(); const char* const refTypeStr = getDataTypeName(basicType); decl << "uniform " << refTypeStr << " ref_" << val.name << ";\n"; decl << refTypeStr << " " << val.name << ";\n"; } /* \todo [2010-04-01 petri] Check all outputs. */ // Shader specialization. map params; params.insert(pair("DECLARATIONS", decl.str())); params.insert(pair("SETUP", setup.str())); params.insert(pair("OUTPUT", output.str())); params.insert(pair("POSITION_FRAG_COLOR", fragColor)); StringTemplate tmpl (src); const string baseSrc = tmpl.specialize(params); const string withExt = injectExtensionRequirements(baseSrc, extensions, SHADERTYPE_FRAGMENT); return withExt; } static void generateUniformDeclarations (std::ostream& dst, const ValueBlock& valueBlock) { for (size_t ndx = 0; ndx < valueBlock.uniforms.size(); ndx++) { const Value& val = valueBlock.uniforms[ndx]; const char* const typeStr = getDataTypeName(val.type.getBasicType()); if (val.name.find('.') == string::npos) dst << "uniform " << typeStr << " " << val.name << ";\n"; } } static map generateVertexSpecialization (const ProgramSpecializationParams& specParams) { const bool usesInout = glslVersionUsesInOutQualifiers(specParams.caseSpec.targetVersion); const char* vtxIn = usesInout ? "in" : "attribute"; ostringstream decl; ostringstream setup; map params; decl << vtxIn << " highp vec4 dEQP_Position;\n"; for (size_t ndx = 0; ndx < specParams.caseSpec.values.inputs.size(); ndx++) { const Value& val = specParams.caseSpec.values.inputs[ndx]; const DataType basicType = val.type.getBasicType(); const char* const typeStr = getDataTypeName(val.type.getBasicType()); if (getDataTypeScalarType(basicType) == TYPE_FLOAT) { decl << vtxIn << " " << typeStr << " " << val.name << ";\n"; } else { const DataType floatType = getDataTypeFloatScalars(basicType); const char* const floatTypeStr = getDataTypeName(floatType); decl << vtxIn << " " << floatTypeStr << " a_" << val.name << ";\n"; setup << typeStr << " " << val.name << " = " << typeStr << "(a_" << val.name << ");\n"; } } generateUniformDeclarations(decl, specParams.caseSpec.values); params.insert(pair("VERTEX_DECLARATIONS", decl.str())); params.insert(pair("VERTEX_SETUP", setup.str())); params.insert(pair("VERTEX_OUTPUT", string("gl_Position = dEQP_Position;\n"))); return params; } static map generateFragmentSpecialization (const ProgramSpecializationParams& specParams) { const bool usesInout = glslVersionUsesInOutQualifiers(specParams.caseSpec.targetVersion); const bool customColorOut = usesInout; const char* const fragColor = customColorOut ? "dEQP_FragColor" : "gl_FragColor"; ostringstream decl; ostringstream output; map params; genCompareFunctions(decl, specParams.caseSpec.values, false); genCompareOp(output, fragColor, specParams.caseSpec.values, "", DE_NULL); if (customColorOut) decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n"; for (size_t ndx = 0; ndx < specParams.caseSpec.values.outputs.size(); ndx++) { const Value& val = specParams.caseSpec.values.outputs[ndx]; const char* const refTypeStr = getDataTypeName(val.type.getBasicType()); decl << "uniform " << refTypeStr << " ref_" << val.name << ";\n"; decl << refTypeStr << " " << val.name << ";\n"; } generateUniformDeclarations(decl, specParams.caseSpec.values); params.insert(pair("FRAGMENT_DECLARATIONS", decl.str())); params.insert(pair("FRAGMENT_OUTPUT", output.str())); params.insert(pair("FRAG_COLOR", fragColor)); return params; } static map generateGeometrySpecialization (const ProgramSpecializationParams& specParams) { ostringstream decl; map params; decl << "layout (triangles) in;\n"; decl << "layout (triangle_strip, max_vertices=3) out;\n"; decl << "\n"; generateUniformDeclarations(decl, specParams.caseSpec.values); params.insert(pair("GEOMETRY_DECLARATIONS", decl.str())); return params; } static map generateTessControlSpecialization (const ProgramSpecializationParams& specParams) { ostringstream decl; ostringstream output; map params; decl << "layout (vertices=3) out;\n"; decl << "\n"; generateUniformDeclarations(decl, specParams.caseSpec.values); output << "gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" "gl_TessLevelInner[0] = 2.0;\n" "gl_TessLevelInner[1] = 2.0;\n" "gl_TessLevelOuter[0] = 2.0;\n" "gl_TessLevelOuter[1] = 2.0;\n" "gl_TessLevelOuter[2] = 2.0;\n" "gl_TessLevelOuter[3] = 2.0;"; params.insert(pair("TESSELLATION_CONTROL_DECLARATIONS", decl.str())); params.insert(pair("TESSELLATION_CONTROL_OUTPUT", output.str())); params.insert(pair("GL_MAX_PATCH_VERTICES", de::toString(specParams.maxPatchVertices))); return params; } static map generateTessEvalSpecialization (const ProgramSpecializationParams& specParams) { ostringstream decl; ostringstream output; map params; decl << "layout (triangles) in;\n"; decl << "\n"; generateUniformDeclarations(decl, specParams.caseSpec.values); output << "gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * gl_in[1].gl_Position + gl_TessCoord[2] * gl_in[2].gl_Position;\n"; params.insert(pair("TESSELLATION_EVALUATION_DECLARATIONS", decl.str())); params.insert(pair("TESSELLATION_EVALUATION_OUTPUT", output.str())); params.insert(pair("GL_MAX_PATCH_VERTICES", de::toString(specParams.maxPatchVertices))); return params; } static void specializeShaderSources (ProgramSources& dst, const ProgramSources& src, const ProgramSpecializationParams& specParams, glu::ShaderType shaderType, map (*specializationGenerator) (const ProgramSpecializationParams& specParams)) { if (!src.sources[shaderType].empty()) { const map tmplParams = specializationGenerator(specParams); for (size_t ndx = 0; ndx < src.sources[shaderType].size(); ++ndx) { const StringTemplate tmpl (src.sources[shaderType][ndx]); const std::string baseGLSLCode = tmpl.specialize(tmplParams); const std::string sourceWithExts = injectExtensionRequirements(baseGLSLCode, specParams.requiredExtensions, shaderType); dst << glu::ShaderSource(shaderType, sourceWithExts); } } } static void specializeProgramSources (glu::ProgramSources& dst, const glu::ProgramSources& src, const ProgramSpecializationParams& specParams) { specializeShaderSources(dst, src, specParams, SHADERTYPE_VERTEX, generateVertexSpecialization); specializeShaderSources(dst, src, specParams, SHADERTYPE_FRAGMENT, generateFragmentSpecialization); specializeShaderSources(dst, src, specParams, SHADERTYPE_GEOMETRY, generateGeometrySpecialization); specializeShaderSources(dst, src, specParams, SHADERTYPE_TESSELLATION_CONTROL, generateTessControlSpecialization); specializeShaderSources(dst, src, specParams, SHADERTYPE_TESSELLATION_EVALUATION, generateTessEvalSpecialization); dst << ProgramSeparable(src.separable); } enum { VIEWPORT_WIDTH = 128, VIEWPORT_HEIGHT = 128 }; class BeforeDrawValidator : public glu::DrawUtilCallback { public: enum TargetType { TARGETTYPE_PROGRAM = 0, TARGETTYPE_PIPELINE, TARGETTYPE_LAST }; BeforeDrawValidator (const glw::Functions& gl, glw::GLuint target, TargetType targetType); void beforeDrawCall (void); const std::string& getInfoLog (void) const; glw::GLint getValidateStatus (void) const; private: const glw::Functions& m_gl; const glw::GLuint m_target; const TargetType m_targetType; glw::GLint m_validateStatus; std::string m_logMessage; }; BeforeDrawValidator::BeforeDrawValidator (const glw::Functions& gl, glw::GLuint target, TargetType targetType) : m_gl (gl) , m_target (target) , m_targetType (targetType) , m_validateStatus (-1) { DE_ASSERT(targetType < TARGETTYPE_LAST); } void BeforeDrawValidator::beforeDrawCall (void) { glw::GLint bytesWritten = 0; glw::GLint infoLogLength; std::vector logBuffer; int stringLength; // validate if (m_targetType == TARGETTYPE_PROGRAM) m_gl.validateProgram(m_target); else if (m_targetType == TARGETTYPE_PIPELINE) m_gl.validateProgramPipeline(m_target); else DE_ASSERT(false); GLU_EXPECT_NO_ERROR(m_gl.getError(), "validate"); // check status m_validateStatus = -1; if (m_targetType == TARGETTYPE_PROGRAM) m_gl.getProgramiv(m_target, GL_VALIDATE_STATUS, &m_validateStatus); else if (m_targetType == TARGETTYPE_PIPELINE) m_gl.getProgramPipelineiv(m_target, GL_VALIDATE_STATUS, &m_validateStatus); else DE_ASSERT(false); GLU_EXPECT_NO_ERROR(m_gl.getError(), "get validate status"); TCU_CHECK(m_validateStatus == GL_TRUE || m_validateStatus == GL_FALSE); // read log infoLogLength = 0; if (m_targetType == TARGETTYPE_PROGRAM) m_gl.getProgramiv(m_target, GL_INFO_LOG_LENGTH, &infoLogLength); else if (m_targetType == TARGETTYPE_PIPELINE) m_gl.getProgramPipelineiv(m_target, GL_INFO_LOG_LENGTH, &infoLogLength); else DE_ASSERT(false); GLU_EXPECT_NO_ERROR(m_gl.getError(), "get info log length"); if (infoLogLength <= 0) { m_logMessage.clear(); return; } logBuffer.resize(infoLogLength + 2, '0'); // +1 for zero terminator (infoLogLength should include it, but better play it safe), +1 to make sure buffer is always larger if (m_targetType == TARGETTYPE_PROGRAM) m_gl.getProgramInfoLog(m_target, infoLogLength + 1, &bytesWritten, &logBuffer[0]); else if (m_targetType == TARGETTYPE_PIPELINE) m_gl.getProgramPipelineInfoLog(m_target, infoLogLength + 1, &bytesWritten, &logBuffer[0]); else DE_ASSERT(false); // just ignore bytesWritten to be safe, find the null terminator stringLength = (int)(std::find(logBuffer.begin(), logBuffer.end(), '0') - logBuffer.begin()); m_logMessage.assign(&logBuffer[0], stringLength); } const std::string& BeforeDrawValidator::getInfoLog (void) const { return m_logMessage; } glw::GLint BeforeDrawValidator::getValidateStatus (void) const { return m_validateStatus; } // ShaderCase. ShaderLibraryCase::ShaderLibraryCase (tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo, const char* name, const char* description, const ShaderCaseSpecification& specification) : tcu::TestCase (testCtx, name, description) , m_renderCtx (renderCtx) , m_contextInfo (contextInfo) , m_spec (specification) { } ShaderLibraryCase::~ShaderLibraryCase (void) { } void ShaderLibraryCase::init (void) { DE_ASSERT(isValid(m_spec)); if (!isGLSLVersionSupported(m_renderCtx.getType(), m_spec.targetVersion)) TCU_THROW(NotSupportedError, (string(getGLSLVersionName(m_spec.targetVersion)) + " is not supported").c_str()); checkImplementationLimits(m_spec.requiredCaps, m_contextInfo); // log the expected result switch (m_spec.expectResult) { case EXPECT_PASS: // Don't write anything break; case EXPECT_COMPILE_FAIL: m_testCtx.getLog() << tcu::TestLog::Message << "Expecting shader compilation to fail." << tcu::TestLog::EndMessage; break; case EXPECT_LINK_FAIL: m_testCtx.getLog() << tcu::TestLog::Message << "Expecting program linking to fail." << tcu::TestLog::EndMessage; break; case EXPECT_COMPILE_LINK_FAIL: m_testCtx.getLog() << tcu::TestLog::Message << "Expecting either shader compilation or program linking to fail." << tcu::TestLog::EndMessage; break; case EXPECT_VALIDATION_FAIL: m_testCtx.getLog() << tcu::TestLog::Message << "Expecting program validation to fail." << tcu::TestLog::EndMessage; break; case EXPECT_BUILD_SUCCESSFUL: m_testCtx.getLog() << tcu::TestLog::Message << "Expecting shader compilation and program linking to succeed. Resulting program will not be executed." << tcu::TestLog::EndMessage; break; default: DE_ASSERT(false); break; } } static void setUniformValue (const glw::Functions& gl, const std::vector& pipelinePrograms, const std::string& name, const Value& val, int arrayNdx, tcu::TestLog& log) { bool foundAnyMatch = false; for (int programNdx = 0; programNdx < (int)pipelinePrograms.size(); ++programNdx) { const DataType dataType = val.type.getBasicType(); const int scalarSize = getDataTypeScalarSize(dataType); const int loc = gl.getUniformLocation(pipelinePrograms[programNdx], name.c_str()); const int elemNdx = arrayNdx * scalarSize; DE_ASSERT(elemNdx+scalarSize <= (int)val.elements.size()); if (loc == -1) continue; foundAnyMatch = true; DE_STATIC_ASSERT(sizeof(Value::Element) == sizeof(glw::GLfloat)); DE_STATIC_ASSERT(sizeof(Value::Element) == sizeof(glw::GLint)); gl.useProgram(pipelinePrograms[programNdx]); switch (dataType) { case TYPE_FLOAT: gl.uniform1fv(loc, 1, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_VEC2: gl.uniform2fv(loc, 1, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_VEC3: gl.uniform3fv(loc, 1, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_VEC4: gl.uniform4fv(loc, 1, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT2: gl.uniformMatrix2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT3: gl.uniformMatrix3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT4: gl.uniformMatrix4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_INT: gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_INT_VEC2: gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_INT_VEC3: gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_INT_VEC4: gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_BOOL: gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_BOOL_VEC2: gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_BOOL_VEC3: gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_BOOL_VEC4: gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32); break; case TYPE_UINT: gl.uniform1uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32); break; case TYPE_UINT_VEC2: gl.uniform2uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32); break; case TYPE_UINT_VEC3: gl.uniform3uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32); break; case TYPE_UINT_VEC4: gl.uniform4uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32); break; case TYPE_FLOAT_MAT2X3: gl.uniformMatrix2x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT2X4: gl.uniformMatrix2x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT3X2: gl.uniformMatrix3x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT3X4: gl.uniformMatrix3x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT4X2: gl.uniformMatrix4x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_FLOAT_MAT4X3: gl.uniformMatrix4x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32); break; case TYPE_SAMPLER_2D: case TYPE_SAMPLER_CUBE: DE_FATAL("implement!"); break; default: DE_ASSERT(false); } } if (!foundAnyMatch) log << tcu::TestLog::Message << "WARNING // Uniform \"" << name << "\" location is not valid, location = -1. Cannot set value to the uniform." << tcu::TestLog::EndMessage; } static bool isTessellationPresent (const ShaderCaseSpecification& spec) { if (spec.programs[0].sources.separable) { const deUint32 tessellationBits = (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) | (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION); for (int programNdx = 0; programNdx < (int)spec.programs.size(); ++programNdx) if (spec.programs[programNdx].activeStages & tessellationBits) return true; return false; } else return !spec.programs[0].sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty() || !spec.programs[0].sources.sources[glu::SHADERTYPE_TESSELLATION_EVALUATION].empty(); } static bool isTessellationSupported (const glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo) { if (renderCtx.getType().getProfile() == PROFILE_ES) { const int majorVer = renderCtx.getType().getMajorVersion(); const int minorVer = renderCtx.getType().getMinorVersion(); return (majorVer > 3) || (majorVer == 3 && minorVer >= 2) || ctxInfo.isExtensionSupported("GL_EXT_tessellation_shader"); } else return false; } static bool checkPixels (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& surface) { bool allWhite = true; bool allBlack = true; bool anyUnexpected = false; for (int y = 0; y < surface.getHeight(); y++) { for (int x = 0; x < surface.getWidth(); x++) { const tcu::IVec4 pixel = surface.getPixelInt(x, y); // Note: we really do not want to involve alpha in the check comparison // \todo [2010-09-22 kalle] Do we know that alpha would be one? If yes, could use color constants white and black. const bool isWhite = (pixel[0] == 255) && (pixel[1] == 255) && (pixel[2] == 255); const bool isBlack = (pixel[0] == 0) && (pixel[1] == 0) && (pixel[2] == 0); allWhite = allWhite && isWhite; allBlack = allBlack && isBlack; anyUnexpected = anyUnexpected || (!isWhite && !isBlack); } } if (!allWhite) { if (anyUnexpected) log << TestLog::Message << "WARNING: expecting all rendered pixels to be white or black, but got other colors as well!" << TestLog::EndMessage; else if (!allBlack) log << TestLog::Message << "WARNING: got inconsistent results over the image, when all pixels should be the same color!" << TestLog::EndMessage; return false; } return true; } bool ShaderLibraryCase::execute (void) { const float quadSize = 1.0f; static const float s_positions[4*4] = { -quadSize, -quadSize, 0.0f, 1.0f, -quadSize, +quadSize, 0.0f, 1.0f, +quadSize, -quadSize, 0.0f, 1.0f, +quadSize, +quadSize, 0.0f, 1.0f }; static const deUint16 s_indices[2*3] = { 0, 1, 2, 1, 3, 2 }; TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_renderCtx.getFunctions(); // Compute viewport. const tcu::RenderTarget& renderTarget = m_renderCtx.getRenderTarget(); de::Random rnd (deStringHash(getName())); const int width = deMin32(renderTarget.getWidth(), VIEWPORT_WIDTH); const int height = deMin32(renderTarget.getHeight(), VIEWPORT_HEIGHT); const int viewportX = rnd.getInt(0, renderTarget.getWidth() - width); const int viewportY = rnd.getInt(0, renderTarget.getHeight() - height); const int numVerticesPerDraw = 4; const bool tessellationPresent = isTessellationPresent(m_spec); const bool separablePrograms = m_spec.programs[0].sources.separable; bool allCompilesOk = true; bool allLinksOk = true; const char* failReason = DE_NULL; vector specializedSources (m_spec.programs.size()); deUint32 vertexProgramID = -1; vector pipelineProgramIDs; vector > programs; SharedPtr programPipeline; GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): start"); if(isCapabilityRequired(CAPABILITY_ONLY_GLSL_ES_100_SUPPORT, m_spec)) { // GL_MAJOR_VERSION query does not exist on GLES2 // so succeeding query implies GLES3+ hardware. glw::GLint majorVersion = 0; gl.getIntegerv(GL_MAJOR_VERSION, &majorVersion); if (gl.getError() == GL_NO_ERROR) return true; } if(isCapabilityRequired(CAPABILITY_EXACTLY_ONE_DRAW_BUFFER, m_spec)) { // on unextended ES2 there is only one draw buffer // and there is no GL_MAX_DRAW_BUFFERS query glw::GLint maxDrawBuffers = 0; gl.getIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); if ((gl.getError() == GL_NO_ERROR) && (maxDrawBuffers > 1)) throw tcu::NotSupportedError("Test requires exactly one draw buffer"); } // Specialize shaders if (m_spec.caseType == CASETYPE_VERTEX_ONLY) { const vector reqExt = checkAndSpecializeExtensions(m_spec.programs[0].requiredExtensions, m_contextInfo); DE_ASSERT(m_spec.programs.size() == 1 && m_spec.programs[0].sources.sources[SHADERTYPE_VERTEX].size() == 1); specializedSources[0] << glu::VertexSource(specializeVertexShader(m_spec, m_spec.programs[0].sources.sources[SHADERTYPE_VERTEX][0], reqExt)) << glu::FragmentSource(genFragmentShader(m_spec)); } else if (m_spec.caseType == CASETYPE_FRAGMENT_ONLY) { const vector reqExt = checkAndSpecializeExtensions(m_spec.programs[0].requiredExtensions, m_contextInfo); DE_ASSERT(m_spec.programs.size() == 1 && m_spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT].size() == 1); specializedSources[0] << glu::VertexSource(genVertexShader(m_spec)) << glu::FragmentSource(specializeFragmentShader(m_spec, m_spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT][0], reqExt)); } else { DE_ASSERT(m_spec.caseType == CASETYPE_COMPLETE); const int maxPatchVertices = isTessellationPresent(m_spec) && isTessellationSupported(m_renderCtx, m_contextInfo) ? m_contextInfo.getInt(GL_MAX_PATCH_VERTICES) : 0; for (size_t progNdx = 0; progNdx < m_spec.programs.size(); progNdx++) { const ProgramSpecializationParams progSpecParams (m_spec, checkAndSpecializeExtensions(m_spec.programs[progNdx].requiredExtensions, m_contextInfo), maxPatchVertices); specializeProgramSources(specializedSources[progNdx], m_spec.programs[progNdx].sources, progSpecParams); } } if (!separablePrograms) { de::SharedPtr program (new glu::ShaderProgram(m_renderCtx, specializedSources[0])); vertexProgramID = program->getProgram(); pipelineProgramIDs.push_back(program->getProgram()); programs.push_back(program); // Check that compile/link results are what we expect. DE_STATIC_ASSERT(glu::SHADERTYPE_VERTEX == 0); for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage) if (program->hasShader((glu::ShaderType)stage) && !program->getShaderInfo((glu::ShaderType)stage).compileOk) allCompilesOk = false; if (!program->getProgramInfo().linkOk) allLinksOk = false; log << *program; } else { // Separate programs for (size_t programNdx = 0; programNdx < m_spec.programs.size(); ++programNdx) { de::SharedPtr program(new glu::ShaderProgram(m_renderCtx, specializedSources[programNdx])); if (m_spec.programs[programNdx].activeStages & (1u << glu::SHADERTYPE_VERTEX)) vertexProgramID = program->getProgram(); pipelineProgramIDs.push_back(program->getProgram()); programs.push_back(program); // Check that compile/link results are what we expect. DE_STATIC_ASSERT(glu::SHADERTYPE_VERTEX == 0); for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage) if (program->hasShader((glu::ShaderType)stage) && !program->getShaderInfo((glu::ShaderType)stage).compileOk) allCompilesOk = false; if (!program->getProgramInfo().linkOk) allLinksOk = false; // Log program and active stages { const tcu::ScopedLogSection section (log, "Program", "Program " + de::toString(programNdx+1)); tcu::MessageBuilder builder (&log); bool firstStage = true; builder << "Pipeline uses stages: "; for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage) { if (m_spec.programs[programNdx].activeStages & (1u << stage)) { if (!firstStage) builder << ", "; builder << glu::getShaderTypeName((glu::ShaderType)stage); firstStage = true; } } builder << tcu::TestLog::EndMessage; log << *program; } } } switch (m_spec.expectResult) { case EXPECT_PASS: case EXPECT_VALIDATION_FAIL: case EXPECT_BUILD_SUCCESSFUL: if (!allCompilesOk) failReason = "expected shaders to compile and link properly, but failed to compile."; else if (!allLinksOk) failReason = "expected shaders to compile and link properly, but failed to link."; break; case EXPECT_COMPILE_FAIL: if (allCompilesOk && !allLinksOk) failReason = "expected compilation to fail, but shaders compiled and link failed."; else if (allCompilesOk) failReason = "expected compilation to fail, but shaders compiled correctly."; break; case EXPECT_LINK_FAIL: if (!allCompilesOk) failReason = "expected linking to fail, but unable to compile."; else if (allLinksOk) failReason = "expected linking to fail, but passed."; break; case EXPECT_COMPILE_LINK_FAIL: if (allCompilesOk && allLinksOk) failReason = "expected compile or link to fail, but passed."; break; default: DE_ASSERT(false); return false; } if (failReason != DE_NULL) { // \todo [2010-06-07 petri] These should be handled in the test case? log << TestLog::Message << "ERROR: " << failReason << TestLog::EndMessage; if (isCapabilityRequired(CAPABILITY_FULL_GLSL_ES_100_SUPPORT, m_spec)) { log << TestLog::Message << "Assuming build failure is caused by implementation not supporting full GLSL ES 100 specification, which is not required." << TestLog::EndMessage; if (allCompilesOk && !allLinksOk) { // Used features are detectable at compile time. If implementation parses shader // at link time, report it as quality warning. m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, failReason); } else m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Full GLSL ES 100 is not supported"); } else if (m_spec.expectResult == EXPECT_COMPILE_FAIL && allCompilesOk && !allLinksOk) { // If implementation parses shader at link time, report it as quality warning. m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, failReason); } else m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failReason); return false; } // Return if shader is not intended to be run if (m_spec.expectResult == EXPECT_COMPILE_FAIL || m_spec.expectResult == EXPECT_COMPILE_LINK_FAIL || m_spec.expectResult == EXPECT_LINK_FAIL || m_spec.expectResult == EXPECT_BUILD_SUCCESSFUL) return true; // Setup viewport. gl.viewport(viewportX, viewportY, width, height); if (separablePrograms) { programPipeline = de::SharedPtr(new glu::ProgramPipeline(m_renderCtx)); // Setup pipeline gl.bindProgramPipeline(programPipeline->getPipeline()); for (int programNdx = 0; programNdx < (int)m_spec.programs.size(); ++programNdx) { deUint32 shaderFlags = 0; for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage) if (m_spec.programs[programNdx].activeStages & (1u << stage)) shaderFlags |= glu::getGLShaderTypeBit((glu::ShaderType)stage); programPipeline->useProgramStages(shaderFlags, pipelineProgramIDs[programNdx]); } programPipeline->activeShaderProgram(vertexProgramID); GLU_EXPECT_NO_ERROR(gl.getError(), "setup pipeline"); } else { // Start using program gl.useProgram(vertexProgramID); GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()"); } // Fetch location for positions positions. int positionLoc = gl.getAttribLocation(vertexProgramID, "dEQP_Position"); if (positionLoc == -1) { string errStr = string("no location found for attribute 'dEQP_Position'"); TCU_FAIL(errStr.c_str()); } // Iterate all value blocks. { const ValueBlock& valueBlock = m_spec.values; // always render at least one pass even if there is no input/output data const int numRenderPasses = valueBlock.outputs.empty() ? 1 : (int)valueBlock.outputs[0].elements.size() / valueBlock.outputs[0].type.getScalarSize(); // Iterate all array sub-cases. for (int arrayNdx = 0; arrayNdx < numRenderPasses; arrayNdx++) { vector vertexArrays; int attribValueNdx = 0; vector > attribValues (valueBlock.inputs.size()); glw::GLenum postDrawError; BeforeDrawValidator beforeDrawValidator (gl, (separablePrograms) ? (programPipeline->getPipeline()) : (vertexProgramID), (separablePrograms) ? (BeforeDrawValidator::TARGETTYPE_PIPELINE) : (BeforeDrawValidator::TARGETTYPE_PROGRAM)); vertexArrays.push_back(va::Float(positionLoc, 4, numVerticesPerDraw, 0, &s_positions[0])); // Collect VA pointer for inputs for (size_t valNdx = 0; valNdx < valueBlock.inputs.size(); valNdx++) { const Value& val = valueBlock.inputs[valNdx]; const char* const valueName = val.name.c_str(); const DataType dataType = val.type.getBasicType(); const int scalarSize = getDataTypeScalarSize(dataType); // Replicate values four times. std::vector& scalars = attribValues[attribValueNdx++]; scalars.resize(numVerticesPerDraw * scalarSize); if (isDataTypeFloatOrVec(dataType) || isDataTypeMatrix(dataType)) { for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++) for (int ndx = 0; ndx < scalarSize; ndx++) scalars[repNdx*scalarSize + ndx] = val.elements[arrayNdx*scalarSize + ndx].float32; } else { // convert to floats. for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++) { for (int ndx = 0; ndx < scalarSize; ndx++) { float v = (float)val.elements[arrayNdx*scalarSize + ndx].int32; DE_ASSERT(val.elements[arrayNdx*scalarSize + ndx].int32 == (int)v); scalars[repNdx*scalarSize + ndx] = v; } } } // Attribute name prefix. string attribPrefix = ""; // \todo [2010-05-27 petri] Should latter condition only apply for vertex cases (or actually non-fragment cases)? if ((m_spec.caseType == CASETYPE_FRAGMENT_ONLY) || (getDataTypeScalarType(dataType) != TYPE_FLOAT)) attribPrefix = "a_"; // Input always given as attribute. string attribName = attribPrefix + valueName; int attribLoc = gl.getAttribLocation(vertexProgramID, attribName.c_str()); if (attribLoc == -1) { log << TestLog::Message << "Warning: no location found for attribute '" << attribName << "'" << TestLog::EndMessage; continue; } if (isDataTypeMatrix(dataType)) { int numCols = getDataTypeMatrixNumColumns(dataType); int numRows = getDataTypeMatrixNumRows(dataType); DE_ASSERT(scalarSize == numCols*numRows); for (int i = 0; i < numCols; i++) vertexArrays.push_back(va::Float(attribLoc + i, numRows, numVerticesPerDraw, scalarSize*(int)sizeof(float), &scalars[i * numRows])); } else { DE_ASSERT(isDataTypeFloatOrVec(dataType) || isDataTypeIntOrIVec(dataType) || isDataTypeUintOrUVec(dataType) || isDataTypeBoolOrBVec(dataType)); vertexArrays.push_back(va::Float(attribLoc, scalarSize, numVerticesPerDraw, 0, &scalars[0])); } GLU_EXPECT_NO_ERROR(gl.getError(), "set vertex attrib array"); } GLU_EXPECT_NO_ERROR(gl.getError(), "before set uniforms"); // set reference values for outputs. for (size_t valNdx = 0; valNdx < valueBlock.outputs.size(); valNdx++) { const Value& val = valueBlock.outputs[valNdx]; const char* const valueName = val.name.c_str(); // Set reference value. string refName = string("ref_") + valueName; setUniformValue(gl, pipelineProgramIDs, refName, val, arrayNdx, m_testCtx.getLog()); GLU_EXPECT_NO_ERROR(gl.getError(), "set reference uniforms"); } // set uniform values for (size_t valNdx = 0; valNdx < valueBlock.uniforms.size(); valNdx++) { const Value& val = valueBlock.uniforms[valNdx]; const char* const valueName = val.name.c_str(); setUniformValue(gl, pipelineProgramIDs, valueName, val, arrayNdx, m_testCtx.getLog()); GLU_EXPECT_NO_ERROR(gl.getError(), "set uniforms"); } // Clear. gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(gl.getError(), "clear buffer"); // Use program or pipeline if (separablePrograms) gl.useProgram(0); else gl.useProgram(vertexProgramID); // Draw. if (tessellationPresent) { gl.patchParameteri(GL_PATCH_VERTICES, 3); GLU_EXPECT_NO_ERROR(gl.getError(), "set patchParameteri(PATCH_VERTICES, 3)"); } draw(m_renderCtx, vertexProgramID, (int)vertexArrays.size(), &vertexArrays[0], (tessellationPresent) ? (pr::Patches(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0])) : (pr::Triangles(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0])), (m_spec.expectResult == EXPECT_VALIDATION_FAIL) ? (&beforeDrawValidator) : (DE_NULL)); postDrawError = gl.getError(); if (m_spec.expectResult == EXPECT_PASS) { // Read back results. Surface surface (width, height); const float w = s_positions[3]; const int minY = deCeilFloatToInt32 (((-quadSize / w) * 0.5f + 0.5f) * (float)height + 1.0f); const int maxY = deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * (float)height - 0.5f); const int minX = deCeilFloatToInt32 (((-quadSize / w) * 0.5f + 0.5f) * (float)width + 1.0f); const int maxX = deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * (float)width - 0.5f); GLU_EXPECT_NO_ERROR(postDrawError, "draw"); glu::readPixels(m_renderCtx, viewportX, viewportY, surface.getAccess()); GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels"); if (!checkPixels(log, tcu::getSubregion(surface.getAccess(), minX, minY, maxX-minX+1, maxY-minY+1))) { log << TestLog::Message << "INCORRECT RESULT for sub-case " << arrayNdx+1 << " of " << numRenderPasses << "):" << TestLog::EndMessage; log << TestLog::Message << "Failing shader input/output values:" << TestLog::EndMessage; dumpValues(log, valueBlock, arrayNdx); // Dump image on failure. log << TestLog::Image("Result", "Rendered result image", surface); gl.useProgram(0); m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); return false; } } else if (m_spec.expectResult == EXPECT_VALIDATION_FAIL) { log << TestLog::Message << "Draw call generated error: " << glu::getErrorStr(postDrawError) << " " << ((postDrawError == GL_INVALID_OPERATION) ? ("(expected)") : ("(unexpected)")) << "\n" << "Validate status: " << glu::getBooleanStr(beforeDrawValidator.getValidateStatus()) << " " << ((beforeDrawValidator.getValidateStatus() == GL_FALSE) ? ("(expected)") : ("(unexpected)")) << "\n" << "Info log: " << ((beforeDrawValidator.getInfoLog().empty()) ? ("[empty string]") : (beforeDrawValidator.getInfoLog())) << "\n" << TestLog::EndMessage; // test result if (postDrawError != GL_NO_ERROR && postDrawError != GL_INVALID_OPERATION) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, ("Draw: got unexpected error: " + de::toString(glu::getErrorStr(postDrawError))).c_str()); return false; } if (beforeDrawValidator.getValidateStatus() == GL_TRUE) { if (postDrawError == GL_NO_ERROR) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but validation and rendering succeeded"); else if (postDrawError == GL_INVALID_OPERATION) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but validation succeeded (rendering failed as expected)"); else DE_ASSERT(false); return false; } else if (beforeDrawValidator.getValidateStatus() == GL_FALSE && postDrawError == GL_NO_ERROR) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but rendering succeeded (validation failed as expected)"); return false; } else if (beforeDrawValidator.getValidateStatus() == GL_FALSE && postDrawError == GL_INVALID_OPERATION) { // Validation does not depend on input values, no need to test all values return true; } else DE_ASSERT(false); } else DE_ASSERT(false); } } gl.useProgram(0); if (separablePrograms) gl.bindProgramPipeline(0); GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): end"); return true; } TestCase::IterateResult ShaderLibraryCase::iterate (void) { // Initialize state to pass. m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); bool executeOk = execute(); DE_ASSERT(executeOk ? m_testCtx.getTestResult() == QP_TEST_RESULT_PASS : m_testCtx.getTestResult() != QP_TEST_RESULT_PASS); DE_UNREF(executeOk); return TestCase::STOP; } } // gls } // deqp