/*------------------------------------------------------------------------- * OpenGL Conformance Test Suite * ----------------------------- * * Copyright (c) 2014-2016 The Khronos Group Inc. * * 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 */ /*-------------------------------------------------------------------*/ #include "es31cLayoutBindingTests.hpp" #include "tcuRenderTarget.hpp" #include "tcuStringTemplate.hpp" #include "tcuSurface.hpp" #include "tcuTestLog.hpp" #include "tcuTexture.hpp" #include "tcuTextureUtil.hpp" #include "deRandom.hpp" #include "deStringUtil.hpp" #include "glwDefs.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include "gluDrawUtil.hpp" #include "gluPixelTransfer.hpp" #include "gluShaderProgram.hpp" #include "gluTexture.hpp" #include "gluTextureUtil.hpp" namespace glcts { //========================================================================= //= typedefs //========================================================================= typedef std::string String; typedef std::map StringMap; typedef std::map StringIntMap; typedef std::map IntIntMap; typedef std::vector StringVector; typedef std::vector IntVector; typedef std::map Texture2DMap; typedef std::map Texture2DArrayMap; typedef std::map Texture3DMap; //========================================================================= //= utility classes //========================================================================= //= string stream that saves some typing class StringStream : public std::ostringstream { public: void reset() { clear(); str(""); } }; class LayoutBindingProgram; class IProgramContextSupplier { public: virtual ~IProgramContextSupplier() { } virtual Context& getContext() = 0; virtual const LayoutBindingParameters& getTestParameters() = 0; virtual eStageType getStage() = 0; virtual const String& getSource(eStageType stage) = 0; virtual LayoutBindingProgram* createProgram() = 0; }; class LayoutBindingProgram { public: LayoutBindingProgram(IProgramContextSupplier& contextSupplier) : m_contextSupplier(contextSupplier) , m_context(contextSupplier.getContext().getRenderContext()) , m_stage(contextSupplier.getStage()) , m_testParams(contextSupplier.getTestParameters()) , m_gl(contextSupplier.getContext().getRenderContext().getFunctions()) { if (getStage() != ComputeShader) m_program = new glu::ShaderProgram( m_context, glu::makeVtxFragSources(m_contextSupplier.getSource(VertexShader).c_str(), m_contextSupplier.getSource(FragmentShader).c_str())); else m_program = new glu::ShaderProgram( m_context, glu::ProgramSources() << glu::ComputeSource(m_contextSupplier.getSource(ComputeShader).c_str())); } virtual ~LayoutBindingProgram() { delete m_program; } class LayoutBindingProgramAutoPtr { public: LayoutBindingProgramAutoPtr(IProgramContextSupplier& contextSupplier) { m_program = contextSupplier.createProgram(); } ~LayoutBindingProgramAutoPtr() { delete m_program; } LayoutBindingProgram* operator->() { return m_program; } private: LayoutBindingProgram* m_program; }; String getErrorLog(bool dumpShaders = false) { StringStream errLog; if (getStage() != ComputeShader) { const glu::ShaderInfo& fragmentShaderInfo = m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT); const glu::ShaderInfo& vertexShaderInfo = m_program->getShaderInfo(glu::SHADERTYPE_VERTEX); if (!fragmentShaderInfo.compileOk || !m_program->getProgramInfo().linkOk || dumpShaders) { errLog << "### dump of " << stageToName(FragmentShader) << "###\n"; String msg((dumpShaders ? "Fragment shader should not have compiled" : "Vertex shader compile failed while testing ")); errLog << "Fragment shader compile failed while testing " << stageToName(getStage()) << ": " << fragmentShaderInfo.infoLog << "\n"; errLog << m_contextSupplier.getSource(FragmentShader); } if (!vertexShaderInfo.compileOk || !m_program->getProgramInfo().linkOk || dumpShaders) { errLog << "### dump of " << stageToName(VertexShader) << "###\n"; String msg((dumpShaders ? "Vertex shader should not have compiled" : "Vertex shader compile failed while testing ")); errLog << msg << stageToName(getStage()) << ": " << vertexShaderInfo.infoLog << "\n"; errLog << m_contextSupplier.getSource(VertexShader); } } else { const glu::ShaderInfo& computeShaderInfo = m_program->getShaderInfo(glu::SHADERTYPE_COMPUTE); if (!computeShaderInfo.compileOk || !m_program->getProgramInfo().linkOk || dumpShaders) { errLog << "### dump of " << stageToName(ComputeShader) << "###\n"; String msg((dumpShaders ? "Compute shader should not have compiled" : "Compute shader compile failed while testing ")); errLog << msg << stageToName(ComputeShader) << ": " << computeShaderInfo.infoLog << "\n"; errLog << m_contextSupplier.getSource(ComputeShader); } } if (!m_program->getProgramInfo().linkOk) { getStage(); errLog << "Linking failed while testing " << stageToName(getStage()) << ": " << m_program->getProgramInfo().infoLog << "\n"; errLog << "### other stages ###\n"; switch (getStage()) { case FragmentShader: errLog << "### dump of " << stageToName(VertexShader) << "###\n"; errLog << m_contextSupplier.getSource(VertexShader); break; case VertexShader: errLog << "### dump of " << stageToName(FragmentShader) << "###\n"; errLog << stageToName(FragmentShader) << "\n"; errLog << m_contextSupplier.getSource(FragmentShader); break; case ComputeShader: errLog << "### dump of " << stageToName(ComputeShader) << "###\n"; errLog << stageToName(ComputeShader) << "\n"; errLog << m_contextSupplier.getSource(ComputeShader); break; default: DE_ASSERT(0); break; } } return errLog.str(); } private: IProgramContextSupplier& m_contextSupplier; const glu::RenderContext& m_context; const eStageType m_stage; // shader stage currently tested const LayoutBindingParameters& m_testParams; // parameters for shader generation (table at end of file) const glw::Functions& m_gl; glu::ShaderProgram* m_program; private: StringIntMap getUniformLocations(StringVector args) const { StringVector::iterator it; StringIntMap locations; bool passed = true; for (it = args.begin(); it != args.end(); it++) { const char* name = (*it).c_str(); glw::GLint location = m_gl.getUniformLocation(getProgram(), name); passed &= (0 <= location); if (passed) { locations[name] = location; } } return locations; } public: glw::GLint getProgram() const { return m_program->getProgram(); } const glw::Functions& gl() const { return m_gl; } virtual eStageType getStage() const { return m_stage; } String stageToName(eStageType stage) const { switch (stage) { case FragmentShader: return "FragmentShader"; case VertexShader: return "VertexShader"; case ComputeShader: return "ComputeShader"; default: DE_ASSERT(0); break; } return String(); } bool error() const { return (m_gl.getError() == GL_NO_ERROR); } bool compiledAndLinked() const { if (getStage() != ComputeShader) return m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk && m_program->getShaderInfo(glu::SHADERTYPE_VERTEX).compileOk && m_program->getProgramInfo().linkOk; return m_program->getShaderInfo(glu::SHADERTYPE_COMPUTE).compileOk && m_program->getProgramInfo().linkOk; } virtual StringIntMap getBindingPoints(StringVector args) const { StringIntMap bindingPoints; StringIntMap locations = getUniformLocations(args); if (!locations.empty()) { glw::GLint bindingPoint; for (StringIntMap::iterator it = locations.begin(); it != locations.end(); it++) { glw::GLint location = it->second; m_gl.getUniformiv(getProgram(), location, &bindingPoint); bool hasNoError = (GL_NO_ERROR == m_gl.getError()); if (hasNoError) { bindingPoints[it->first] = bindingPoint; } } } return bindingPoints; } virtual bool setBindingPoints(StringVector list, glw::GLint bindingPoint) const { bool bNoError = true; StringIntMap locations = getUniformLocations(list); if (!locations.empty()) { for (StringIntMap::iterator it = locations.begin(); it != locations.end(); it++) { m_gl.uniform1i(it->second, bindingPoint); bNoError &= (GL_NO_ERROR == m_gl.getError()); } } return bNoError; } virtual StringIntMap getOffsets(StringVector /*args*/) const { return StringIntMap(); } }; class LayoutBindingTestResult { public: LayoutBindingTestResult(bool passed = true, const String& reason = String(), bool notRunforThisContext = false) : m_passed(passed), m_notRunForThisContext(notRunforThisContext), m_reason(reason) { } public: bool testPassed() const { return m_passed; } String getReason() const { return m_reason; } bool runForThisContext() const { return !m_notRunForThisContext; } private: bool m_passed; bool m_notRunForThisContext; String m_reason; }; class IntegerConstant { public: enum Literals { decimal = 0, decimal_u, decimal_U, octal, octal_u, octal_U, hex_x, hex_X, hex_u, hex_u_X, hex_U, hex_U_X, last }; public: IntegerConstant(Literals lit, int ai) : asInt(ai) { StringStream s; switch (lit) { case decimal: s << asInt; break; case decimal_u: s << asInt << "u"; break; case decimal_U: s << asInt << "U"; break; case octal: s << "0" << std::oct << asInt; break; case octal_u: s << "0" << std::oct << asInt << "u"; break; case octal_U: s << "0" << std::oct << asInt << "U"; break; case hex_x: s << "0x" << std::hex << asInt; break; case hex_X: s << "0X" << std::hex << asInt; break; case hex_u: s << "0x" << std::hex << asInt << "u"; break; case hex_u_X: s << "0X" << std::hex << asInt << "u"; break; case hex_U: s << "0x" << std::hex << asInt << "U"; break; case hex_U_X: s << "0X" << std::hex << asInt << "U"; break; case last: default: DE_ASSERT(0); } asString = s.str(); } public: String asString; int asInt; }; //***************************************************************************** class LayoutBindingBaseCase : public TestCase, public IProgramContextSupplier { public: LayoutBindingBaseCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion); virtual ~LayoutBindingBaseCase(void); IterateResult iterate(void); // overrideable subtests virtual LayoutBindingTestResult binding_basic_default(void); virtual LayoutBindingTestResult binding_basic_explicit(void); virtual LayoutBindingTestResult binding_basic_multiple(void); virtual LayoutBindingTestResult binding_basic_render(void); virtual LayoutBindingTestResult binding_integer_constant(void); virtual LayoutBindingTestResult binding_array_size(void); virtual LayoutBindingTestResult binding_array_implicit(void); virtual LayoutBindingTestResult binding_array_multiple(void); virtual LayoutBindingTestResult binding_api_update(void); virtual LayoutBindingTestResult binding_compilation_errors(void); virtual LayoutBindingTestResult binding_link_errors(void); virtual LayoutBindingTestResult binding_examples(void); virtual LayoutBindingTestResult binding_mixed_order(void); private: // drawTest normal vs. compute typedef LayoutBindingTestResult (LayoutBindingBaseCase::*LayoutBindingDrawTestPtr)(glw::GLint program, int binding); LayoutBindingDrawTestPtr m_drawTest; // pointer type for subtests typedef LayoutBindingTestResult (LayoutBindingBaseCase::*LayoutBindingSubTestPtr)(); // test table entry struct LayoutBindingSubTest { char const* name; char const* description; LayoutBindingSubTestPtr test; }; // IProgramContextSupplier interface protected: const LayoutBindingParameters& getTestParameters() { return m_testParams; } const glu::RenderContext& getRenderContext() { return m_context.getRenderContext(); } virtual eStageType getStage() { return m_stage.type; } const String& getSource(eStageType stage) { return m_sources[stage]; } Context& getContext() { return m_context; } const glw::Functions& gl() { return m_context.getRenderContext().getFunctions(); } bool needsPrecision() const { if (isContextTypeES(m_context.getRenderContext().getType()) || m_glslVersion == glu::GLSL_VERSION_450) { return (m_testParams.surface_type != UniformBlock) && (m_testParams.surface_type != ShaderStorageBuffer); } else { return (m_testParams.surface_type != UniformBlock) && (m_testParams.surface_type != ShaderStorageBuffer) && (m_testParams.surface_type != AtomicCounter) && (m_testParams.surface_type != Image); } } protected: std::vector makeSparseRange(int maxElement, int minElement = 0) const { static float rangeT[] = { 0.0f, 0.1f, 0.5f, 0.6f, 0.9f, 1.0f }; std::vector rangeTemplate = makeVector(rangeT); float max = rangeTemplate.back(); float range = (float)((maxElement - 1) - minElement); std::vector result; for (std::vector::iterator it = rangeTemplate.begin(); it != rangeTemplate.end(); it++) { float e = *it; e = (e * range) / max; result.insert(result.end(), minElement + (int)e); } return result; } glu::GLSLVersion getGLSLVersion() { return m_glslVersion; } bool isStage(eStageType stage) { return (stage == m_stage.type); } void setTemplateParam(eStageType stage, const char* param, const String& value) { m_templateParams[stage][param] = value.c_str(); } void setTemplateParam(const char* param, const String& value) { setTemplateParam(m_stage.type, param, value); } void updateTemplate(eStageType stage) { m_sources[stage] = tcu::StringTemplate(m_templates[stage]).specialize(m_templateParams[stage]); } void updateTemplate() { updateTemplate(m_stage.type); } template String generateLog(const String& msg, T0 result, T1 expected) { StringStream s; s << msg << " expected: " << expected << " actual: " << result << "\n"; s << getSource(VertexShader) << "\n"; s << getSource(FragmentShader) << "\n"; return s.str(); } private: std::vector m_tests; void init(void) { m_drawTest = (getStage() == ComputeShader) ? &LayoutBindingBaseCase::drawTestCompute : &LayoutBindingBaseCase::drawTest; #define MAKE_TEST_ENTRY(__subtest_name__) { #__subtest_name__, "", &LayoutBindingBaseCase::__subtest_name__ } LayoutBindingSubTest tests[] = { MAKE_TEST_ENTRY(binding_basic_default), MAKE_TEST_ENTRY(binding_basic_explicit), MAKE_TEST_ENTRY(binding_basic_multiple), MAKE_TEST_ENTRY(binding_basic_render), MAKE_TEST_ENTRY(binding_integer_constant), MAKE_TEST_ENTRY(binding_array_size), MAKE_TEST_ENTRY(binding_array_implicit), MAKE_TEST_ENTRY(binding_array_multiple), MAKE_TEST_ENTRY(binding_api_update), MAKE_TEST_ENTRY(binding_compilation_errors), MAKE_TEST_ENTRY(binding_link_errors), MAKE_TEST_ENTRY(binding_examples), MAKE_TEST_ENTRY(binding_mixed_order) }; m_tests = makeVector(tests); m_uniformDeclTemplate = "${LAYOUT}${KEYWORD}${UNIFORM_TYPE}${UNIFORM_BLOCK_NAME}${UNIFORM_BLOCK}${UNIFORM_" "INSTANCE_NAME}${UNIFORM_ARRAY};\n"; m_expectedColor = tcu::Vec4(0.0, 1.0f, 0.0f, 1.0f); switch (getTestParameters().texture_type) { case TwoD: { // 2D glu::ImmutableTexture2D* texture2D = new glu::ImmutableTexture2D(getContext().getRenderContext(), GL_RGBA8, 2, 2); texture2D->getRefTexture().allocLevel(0); tcu::clear(texture2D->getRefTexture().getLevel(0), m_expectedColor); texture2D->upload(); if (m_textures2D.find(0) != m_textures2D.end()) { delete m_textures2D[0]; } m_textures2D[0] = texture2D; } break; case TwoDArray: { // 2DArray glu::Texture2DArray* texture2DArray = new glu::Texture2DArray(getContext().getRenderContext(), GL_RGBA8, 2, 2, 1); texture2DArray->getRefTexture().allocLevel(0); tcu::clear(texture2DArray->getRefTexture().getLevel(0), m_expectedColor); texture2DArray->upload(); if (m_textures2DArray.find(0) != m_textures2DArray.end()) { delete m_textures2DArray[0]; } m_textures2DArray[0] = texture2DArray; } break; // 3D case ThreeD: { glu::Texture3D* texture3D = new glu::Texture3D(getContext().getRenderContext(), GL_RGBA8, 2, 2, 1); texture3D->getRefTexture().allocLevel(0); tcu::clear(texture3D->getRefTexture().getLevel(0), m_expectedColor); texture3D->upload(); if (m_textures3D.find(0) != m_textures3D.end()) { delete m_textures3D[0]; } m_textures3D[0] = texture3D; } break; case None: // test case where no texture allocation is needed break; default: DE_ASSERT(0); break; } } String initDefaultVSContext() { m_templates[VertexShader] = "${VERSION}" "layout(location=0) in vec2 inPosition;\n" "${UNIFORM_DECL}\n" "flat out ${OUT_VAR_TYPE} fragColor;\n" "${OPTIONAL_FUNCTION_BLOCK}\n" "void main(void)\n" "{\n" " ${OUT_ASSIGNMENT} ${UNIFORM_ACCESS}\n" " gl_Position = vec4(inPosition, 0.0, 1.0);\n" "}\n"; StringMap& args = m_templateParams[VertexShader]; // some samplers and all images don't have default precision qualifier (sampler3D) // so append a precision default in all sampler and image cases. StringStream s; s << glu::getGLSLVersionDeclaration(m_glslVersion) << "\n"; s << "precision highp float;\n"; if (needsPrecision()) { s << "precision highp " << getTestParameters().uniform_type << ";\n"; } args["VERSION"] = s.str(); args["UNIFORM_DECL"] = ""; args["OPTIONAL_FUNCTION_BLOCK"] = ""; args["UNIFORM_ACCESS"] = ""; args["OUT_ASSIGNMENT"] = "fragColor ="; args["OUT_VAR_TYPE"] = getTestParameters().vector_type; args["OUT_VAR"] = "fragColor"; if (m_stage.type != VertexShader) { args["OUT_ASSIGNMENT"] = ""; } return tcu::StringTemplate(m_templates[VertexShader]).specialize(args); } String initDefaultFSContext() { // build fragment shader m_templates[FragmentShader] = "${VERSION}" "layout(location=0) out ${OUT_VAR_TYPE} ${OUT_VAR};\n" "flat in ${OUT_VAR_TYPE} fragColor;\n" "${UNIFORM_DECL}\n" "${OPTIONAL_FUNCTION_BLOCK}\n" "void main(void)\n" "{\n" " ${OUT_ASSIGNMENT} ${UNIFORM_ACCESS}\n" "}\n"; StringMap& args = m_templateParams[FragmentShader]; // samplers and images don't have default precision qualifier StringStream s; s << glu::getGLSLVersionDeclaration(m_glslVersion) << "\n"; s << "precision highp float;\n"; if (needsPrecision()) { s << "precision highp " << getTestParameters().uniform_type << ";\n"; } args["VERSION"] = s.str(); args["OUT_VAR_TYPE"] = getTestParameters().vector_type; args["UNIFORM_ACCESS"] = "vec4(0.0,0.0,0.0,0.0);"; args["UNIFORM_DECL"] = ""; args["OPTIONAL_FUNCTION_BLOCK"] = ""; args["OUT_ASSIGNMENT"] = "outColor ="; args["OUT_VAR"] = "outColor"; // must have a linkage between stage and fragment shader // that the compiler can't optimize away if (FragmentShader != m_stage.type) { args["UNIFORM_ACCESS"] = "fragColor;"; } return tcu::StringTemplate(m_templates[FragmentShader]).specialize(args); } String initDefaultCSContext() { // build compute shader m_templates[ComputeShader] = "${VERSION}" "${UNIFORM_DECL}\n" "${OPTIONAL_FUNCTION_BLOCK}\n" "void main(void)\n" "{\n" " ${OUT_VAR_TYPE} tmp = ${UNIFORM_ACCESS}\n" " ${OUT_ASSIGNMENT} tmp ${OUT_END}\n" "}\n"; StringMap& args = m_templateParams[ComputeShader]; // images don't have default precision qualifier StringStream s; s << glu::getGLSLVersionDeclaration(m_glslVersion) << "\n"; s << "layout (local_size_x = 1) in;\n" "precision highp float;\n"; if (needsPrecision()) { s << "precision highp " << getTestParameters().uniform_type << ";\n"; } // bindings are per uniform type... if (getTestParameters().surface_type == Image) { s << "layout(binding=0, std430) buffer outData {\n" " " << getTestParameters().vector_type << " outColor;\n" "};\n"; args["OUT_ASSIGNMENT"] = "outColor ="; args["OUT_END"] = ";"; } else { s << "layout(binding=0, rgba8) uniform highp writeonly image2D outImage;\n"; args["OUT_ASSIGNMENT"] = "imageStore(outImage, ivec2(0), "; args["OUT_END"] = ");"; } args["VERSION"] = s.str(); args["OUT_VAR_TYPE"] = getTestParameters().vector_type; args["UNIFORM_ACCESS"] = "vec4(0.0,0.0,0.0,0.0);"; args["UNIFORM_DECL"] = ""; args["OPTIONAL_FUNCTION_BLOCK"] = ""; return tcu::StringTemplate(m_templates[ComputeShader]).specialize(args); } protected: String buildUniformDecl(const String& keyword, const String& layout, const String& uniform_type, const String& uniform_block_name, const String& uniform_block, const String& uniform_instance, const String& uniform_array) const { StringMap args; setArg(args["LAYOUT"], layout); setArg(args["KEYWORD"], keyword); if (uniform_block_name.empty()) setArg(args["UNIFORM_TYPE"], uniform_type); else args["UNIFORM_TYPE"] = ""; if (!uniform_instance.empty() && !uniform_block_name.empty()) setArg(args["UNIFORM_BLOCK_NAME"], uniform_block_name + "_block"); else setArg(args["UNIFORM_BLOCK_NAME"], uniform_block_name); setArg(args["UNIFORM_BLOCK"], uniform_block); if ((uniform_block_name.empty() && uniform_block.empty()) || !uniform_array.empty()) args["UNIFORM_INSTANCE_NAME"] = uniform_instance; else args["UNIFORM_INSTANCE_NAME"] = ""; args["UNIFORM_ARRAY"] = uniform_array; return tcu::StringTemplate(m_uniformDeclTemplate).specialize(args); } virtual String getDefaultUniformName(int idx = 0) { StringStream s; s << "uniform" << idx; return s.str(); } virtual String buildUniformName(String& var) { std::ostringstream s; s << var; return s.str(); } virtual String buildLayout(const String& binding) { std::ostringstream s; if (!binding.empty()) s << "layout(binding=" << binding << ") "; return s.str(); } virtual String buildLayout(int binding) { std::ostringstream bindingStr; bindingStr << binding; return buildLayout(bindingStr.str()); } virtual String buildAccess(const String& var) { std::ostringstream s; s << getTestParameters().access_function << "(" << var << "," << getTestParameters().coord_vector_type << "(0)" << ")"; return s.str(); } virtual String buildBlockName(const String& /*name*/) { return String(); } virtual String buildBlock(const String& /*name*/, const String& /*type*/ = String("float")) { return String(); } virtual String buildArray(int idx) { StringStream s; s << "[" << idx << "]"; return s.str(); } virtual String buildArrayAccess(int uniform, int idx) { StringStream s; s << getDefaultUniformName(uniform) << buildArray(idx); if (!buildBlockName(getDefaultUniformName()).empty()) { s << "." << getDefaultUniformName(uniform); } return s.str(); } // return max. binding point allowed virtual int maxBindings() { int units = 0; if (getTestParameters().surface_type == Image) { gl().getIntegerv(GL_MAX_IMAGE_UNITS, &units); } else { gl().getIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &units); } return units; } // return max. array size allowed virtual int maxArraySize() { int units = 0; if (getTestParameters().surface_type == Image) { switch (m_stage.type) { case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &units); break; case FragmentShader: gl().getIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &units); break; default: DE_ASSERT(0); break; } } else { switch (m_stage.type) { case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &units); break; case FragmentShader: gl().getIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &units); break; default: DE_ASSERT(0); break; } } return units; } virtual bool isSupported() { return (maxArraySize() > 0); } virtual void bind(int binding) { glw::GLint texTarget = 0; glw::GLint texName = 0; switch (getTestParameters().texture_type) { case TwoD: texTarget = GL_TEXTURE_2D; texName = m_textures2D[0]->getGLTexture(); break; case TwoDArray: texTarget = GL_TEXTURE_2D_ARRAY; texName = m_textures2DArray[0]->getGLTexture(); break; case ThreeD: texTarget = GL_TEXTURE_3D; texName = m_textures3D[0]->getGLTexture(); break; default: DE_ASSERT(0); break; } switch (getTestParameters().surface_type) { case Texture: gl().activeTexture(GL_TEXTURE0 + binding); gl().bindTexture(texTarget, texName); gl().texParameteri(texTarget, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(tcu::Sampler::CLAMP_TO_EDGE)); gl().texParameteri(texTarget, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(tcu::Sampler::CLAMP_TO_EDGE)); gl().texParameteri(texTarget, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(tcu::Sampler::NEAREST)); gl().texParameteri(texTarget, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(tcu::Sampler::NEAREST)); break; case Image: gl().bindImageTexture(binding, texName, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); break; default: DE_ASSERT(0); break; } } virtual void unbind(int binding) { glw::GLint texTarget = 0; switch (getTestParameters().texture_type) { case TwoD: texTarget = GL_TEXTURE_2D; break; case TwoDArray: texTarget = GL_TEXTURE_2D_ARRAY; break; case ThreeD: texTarget = GL_TEXTURE_3D; break; default: DE_ASSERT(0); break; } switch (getTestParameters().surface_type) { case Texture: gl().activeTexture(GL_TEXTURE0 + binding); gl().bindTexture(texTarget, 0); break; case Image: gl().bindImageTexture(binding, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32F); break; default: DE_ASSERT(0); break; } } virtual LayoutBindingTestResult drawTest(glw::GLint program, int binding); virtual LayoutBindingTestResult drawTestCompute(glw::GLint program, int binding); // allocate resources needed for all subtests, i.e. textures virtual void setupTest() { } // cleanup resources needed for all subtests virtual void teardownTest() { for (Texture2DMap::iterator it = m_textures2D.begin(); it != m_textures2D.end(); it++) { delete it->second; } m_textures2D.clear(); for (Texture2DArrayMap::iterator it = m_textures2DArray.begin(); it != m_textures2DArray.end(); it++) { delete it->second; } m_textures2DArray.clear(); for (Texture3DMap::iterator it = m_textures3D.begin(); it != m_textures3D.end(); it++) { delete it->second; } m_textures3D.clear(); } private: // appends a space to the argument (make shader source pretty) void setArg(String& arg, const String& value) const { if (!value.empty()) arg = value + String(" "); else arg = String(); } private: LayoutBindingParameters m_testParams; StageType m_stage; std::map m_sources; std::map m_templateParams; std::map m_templates; Texture2DMap m_textures2D; Texture2DArrayMap m_textures2DArray; Texture3DMap m_textures3D; tcu::Vec4 m_expectedColor; const char* m_uniformDeclTemplate; glu::GLSLVersion m_glslVersion; }; LayoutBindingBaseCase::LayoutBindingBaseCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion) : TestCase(context, name, description) , m_drawTest(DE_NULL) , m_testParams(samplerType) , m_stage(stage) , m_uniformDeclTemplate(DE_NULL) , m_glslVersion(glslVersion) { } LayoutBindingBaseCase::~LayoutBindingBaseCase(void) { teardownTest(); } LayoutBindingBaseCase::IterateResult LayoutBindingBaseCase::iterate(void) { tcu::TestLog& log = m_context.getTestContext().getLog(); bool passed = true; if (!isSupported()) { log << tcu::TestLog::Section("NotSupported", ""); log << tcu::TestLog::Message << "This test was not run as minimum requirements were not met." << tcu::TestLog::EndMessage; log << tcu::TestLog::EndSection; getContext().getTestContext().setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "NotSupported"); return STOP; } // init test case (create shader templates and textures) init(); // allocate resources for all subtests setupTest(); for (std::vector::iterator it = m_tests.begin(); it != m_tests.end(); it++) { // need to reset templates and their args to a clean state before every // test to avoid bleeding. m_sources[VertexShader] = initDefaultVSContext(); m_sources[FragmentShader] = initDefaultFSContext(); m_sources[ComputeShader] = initDefaultCSContext(); LayoutBindingTestResult result = ((*this).*((*it).test))(); if (!result.testPassed()) { log << tcu::TestLog::Section((*it).name, (*it).description); log << tcu::TestLog::Message << result.getReason() << tcu::TestLog::EndMessage; log << tcu::TestLog::EndSection; } if (!result.runForThisContext()) { log << tcu::TestLog::Section((*it).name, (*it).description); log << tcu::TestLog::Message << "This test was not run for this context as it does not apply." << tcu::TestLog::EndMessage; log << tcu::TestLog::EndSection; } passed &= result.testPassed(); } // cleanup resources teardownTest(); /*========================================================================= TEST results =========================================================================*/ getContext().getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, passed ? "Pass" : "Fail"); return STOP; } /*========================================================================= // bind resource to specified binding point and program and // dispatch a computation, read back image at (0,0) =========================================================================*/ LayoutBindingTestResult LayoutBindingBaseCase::drawTestCompute(glw::GLint program, int binding) { const glw::Functions& l_gl = getContext().getRenderContext().getFunctions(); bool passed = true; DE_TEST_ASSERT(getStage() == ComputeShader); l_gl.useProgram(program); deUint32 fb_or_ssb; if (getTestParameters().surface_type == Image) { bind(binding); glw::GLfloat buffer[4] = { 0.1f, 0.2f, 0.3f, 0.4f }; l_gl.genBuffers(1, &fb_or_ssb); l_gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, fb_or_ssb); l_gl.bufferData(GL_SHADER_STORAGE_BUFFER, 4 * sizeof(glw::GLfloat), buffer, GL_DYNAMIC_COPY); l_gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, fb_or_ssb); l_gl.dispatchCompute(1, 1, 1); l_gl.memoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); tcu::Vec4 pixel = *(tcu::Vec4*)l_gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 4 * sizeof(glw::GLfloat), GL_MAP_READ_BIT); l_gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER); l_gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, 0); l_gl.deleteBuffers(1, &fb_or_ssb); unbind(binding); tcu::Vec4 expected(0.0f, 1.0f, 0.0f, 1.0f); passed = (pixel == expected); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("drawTestCompute failed"), expected, pixel)); } } else { deUint32 something = 0x01020304, tex; l_gl.genTextures(1, &tex); l_gl.bindTexture(GL_TEXTURE_2D, tex); l_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); l_gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); l_gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &something); bind(binding); l_gl.bindImageTexture(0, tex, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); l_gl.dispatchCompute(1, 1, 1); l_gl.memoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT); l_gl.bindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); l_gl.genFramebuffers(1, &fb_or_ssb); l_gl.bindFramebuffer(GL_FRAMEBUFFER, fb_or_ssb); l_gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); l_gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &something); l_gl.bindFramebuffer(GL_FRAMEBUFFER, 0); l_gl.deleteFramebuffers(1, &fb_or_ssb); l_gl.deleteTextures(1, &tex); unbind(binding); const deUint32 expected = 0xff00ff00; passed = (expected == something); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("drawTestCompute failed"), tcu::RGBA(expected), tcu::RGBA(something))); } } return passed; } /*========================================================================= // bind resource to specified binding point and program and // return result of comparison of rendered pixel at (0,0) with expected =========================================================================*/ LayoutBindingTestResult LayoutBindingBaseCase::drawTest(glw::GLint program, int binding) { const glw::Functions& GL = getContext().getRenderContext().getFunctions(); const tcu::RenderTarget& renderTarget = getContext().getRenderContext().getRenderTarget(); glw::GLuint viewportW = renderTarget.getWidth(); glw::GLuint viewportH = renderTarget.getHeight(); tcu::Surface renderedFrame(viewportW, viewportH); bool passed = true; DE_TEST_ASSERT(getStage() != ComputeShader); GL.viewport(0, 0, viewportW, viewportH); GL.clearColor(0.0f, 0.0f, 0.0f, 1.0f); GL.clear(GL_COLOR_BUFFER_BIT); static const float position[] = { -1.0f, -1.0f, -1.0f, +1.0f, +1.0f, -1.0f, +1.0f, +1.0f, }; static const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 }; GL.useProgram(program); bind(binding); static const glu::VertexArrayBinding vertexArrays[] = { glu::va::Float("inPosition", 2, 4, 0, &position[0]), }; glu::draw(getContext().getRenderContext(), program, DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0], glu::pr::TriangleStrip(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0])); glu::readPixels(getContext().getRenderContext(), 0, 0, renderedFrame.getAccess()); tcu::RGBA pixel = renderedFrame.getPixel(0, 0); passed = (pixel == tcu::RGBA(m_expectedColor)); unbind(binding); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("drawTest failed"), m_expectedColor, pixel.getPacked())); } return true; } //== verify that binding point is default w/o layout binding LayoutBindingTestResult LayoutBindingBaseCase::binding_basic_default() { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(String()), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; const String& u = buildBlockName(getDefaultUniformName()); if (!u.empty()) list.push_back(u + "_block"); else list.push_back(getDefaultUniformName()); StringIntMap bindingPoints = program->getBindingPoints(list); passed &= bindingPoints.size() == list.size() && (bindingPoints[u] == 0); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[u], 0)); } return true; } //== verify that binding point has specified value LayoutBindingTestResult LayoutBindingBaseCase::binding_basic_explicit() { bool passed = true; { StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); } std::vector bindings = makeSparseRange(maxBindings()); for (std::vector::iterator it = bindings.begin(); it < bindings.end(); it++) { int binding = *it; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(binding), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; const String& s = buildBlockName(getDefaultUniformName()); if (!s.empty()) list.push_back(s + "_block"); else list.push_back(getDefaultUniformName()); StringIntMap bindingPoints = program->getBindingPoints(list); passed &= bindingPoints.size() == list.size() && (binding == bindingPoints[list[0]]); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("binding point did not match default"), bindingPoints[list[0]], binding)); } } return true; } //== verify that binding works with multiple samplers (same binding points) LayoutBindingTestResult LayoutBindingBaseCase::binding_basic_multiple() { bool passed = true; glw::GLint baseBindingPoint = maxBindings() - 1; String decl0 = buildUniformDecl(String(getTestParameters().keyword), buildLayout(baseBindingPoint), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); String decl1 = buildUniformDecl(String(getTestParameters().keyword), buildLayout(baseBindingPoint), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName(1)), buildBlock(getDefaultUniformName(1)), getDefaultUniformName(1), String()); setTemplateParam("UNIFORM_DECL", decl0 + decl1); StringStream s; s << buildAccess(getDefaultUniformName()) << " + " << buildAccess(getDefaultUniformName(1)) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; String u = buildBlockName(getDefaultUniformName()); if (!u.empty()) list.push_back(u + "_block"); else list.push_back(getDefaultUniformName()); u = buildBlockName(getDefaultUniformName(1)); if (!u.empty()) list.push_back(u + "_block"); else list.push_back(getDefaultUniformName(1)); StringIntMap bindingPoints = program->getBindingPoints(list); passed &= (baseBindingPoint == bindingPoints[list[0]]) && (baseBindingPoint == bindingPoints[list[1]]); if (!passed) { String err; err = generateLog(String("binding point did not match default"), bindingPoints[list[0]], baseBindingPoint); err += generateLog(String("binding point did not match default"), bindingPoints[list[1]], baseBindingPoint); return LayoutBindingTestResult(passed, err); } return true; } //== verify that binding point has specified value LayoutBindingTestResult LayoutBindingBaseCase::binding_basic_render() { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); std::vector bindings = makeSparseRange(maxBindings()); for (std::vector::iterator it = bindings.begin(); it < bindings.end(); it++) { int binding = *it; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(binding), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } LayoutBindingTestResult drawTestResult = ((*this).*(m_drawTest))(program->getProgram(), binding); if (!drawTestResult.testPassed()) { return LayoutBindingTestResult(drawTestResult.testPassed(), drawTestResult.getReason()); } } return true; } LayoutBindingTestResult LayoutBindingBaseCase::binding_integer_constant() { bool passed = true; std::vector integers = makeSparseRange(maxBindings(), 0); std::vector integerConstants; for (int idx = 0; idx < IntegerConstant::last; idx++) { for (IntVector::iterator it = integers.begin(); it != integers.end(); it++) { integerConstants.push_back(IntegerConstant((IntegerConstant::Literals)idx, (*it))); } } //== verify that binding point can be set with integer constant for (std::vector::iterator it = integerConstants.begin(); it != integerConstants.end(); it++) { String& intConst = (*it).asString; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(intConst), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; const String& u = buildBlockName(getDefaultUniformName()); if (!u.empty()) list.push_back(u + "_block"); else list.push_back(getDefaultUniformName()); StringIntMap bindingPoints = program->getBindingPoints(list); passed &= ((*it).asInt == bindingPoints[list[0]]); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[list[0]], (*it).asInt)); } } //== verify that binding point can be set with integer constant resulting from a preprocessor substitution for (std::vector::iterator it = integerConstants.begin(); it != integerConstants.end(); it++) { String& intConst = (*it).asString; StringStream s; s << "#define INT_CONST " << intConst << std::endl; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(String("INT_CONST")), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", s.str() + decl); s.reset(); s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; const String& u = buildBlockName(getDefaultUniformName()); if (!u.empty()) list.push_back(u + "_block"); else list.push_back(getDefaultUniformName()); StringIntMap bindingPoints = program->getBindingPoints(list); passed &= ((*it).asInt == bindingPoints[list[0]]); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[list[0]], (*it).asInt)); } } return true; } //== test different sized arrays LayoutBindingTestResult LayoutBindingBaseCase::binding_array_size(void) { bool passed = true; std::vector arraySizes = makeSparseRange(maxArraySize(), 1); for (std::vector::iterator it = arraySizes.begin(); it < arraySizes.end(); it++) { int arraySize = *it; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(maxBindings() - arraySize - 1), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), buildArray(arraySize)); setTemplateParam("UNIFORM_DECL", decl); StringStream s; for (int idx = 0; idx < arraySize; idx++) { s << (idx ? " + " : "") << buildAccess(buildArrayAccess(0, idx)); } s << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; for (int idx = 0; idx < arraySize; idx++) { std::ostringstream texUnitStr; texUnitStr << getDefaultUniformName(); const String& u = buildBlockName(getDefaultUniformName()); if (!u.empty()) texUnitStr << "_block"; texUnitStr << buildArray(idx); list.push_back(texUnitStr.str()); } StringIntMap bindingPoints = program->getBindingPoints(list); for (int idx = 0; idx < arraySize; idx++) { passed &= (((maxBindings() - arraySize - 1) + idx) == bindingPoints[list[idx]]); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[list[idx]], (maxBindings() - arraySize - 1) + idx)); } } } return LayoutBindingTestResult(passed, String()); } //== verify first element takes binding point specified in binding and //== subsequent entries take the next consecutive units LayoutBindingTestResult LayoutBindingBaseCase::binding_array_implicit(void) { bool passed = true; std::vector bindings = makeSparseRange(maxBindings(), 0); for (std::vector::iterator it = bindings.begin(); it < bindings.end(); it++) { int baseBindingPoint = *it; int arraySize = std::min(maxBindings() - baseBindingPoint, 4); String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(baseBindingPoint), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), buildArray(arraySize)); setTemplateParam("UNIFORM_DECL", decl); StringStream s; for (int idx = 0; idx < arraySize; idx++) { s << (idx ? " + " : "") << buildAccess(buildArrayAccess(0, idx)); } s << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; for (int idx = 0; idx < arraySize; idx++) { std::ostringstream texUnitStr; texUnitStr << getDefaultUniformName(); const String& u = buildBlockName(getDefaultUniformName()); if (!u.empty()) texUnitStr << "_block"; texUnitStr << buildArray(idx); list.push_back(texUnitStr.str()); } StringIntMap bindingPoints = program->getBindingPoints(list); for (int idx = 0; idx < arraySize; idx++) { passed &= ((baseBindingPoint + idx) == bindingPoints[list[idx]]); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[list[idx]], (baseBindingPoint + idx))); } } } return LayoutBindingTestResult(passed, String()); } //== multiple arrays :verify first element takes binding point specified in binding and //== subsequent entries take the next consecutive units LayoutBindingTestResult LayoutBindingBaseCase::binding_array_multiple(void) { bool passed = true; // two arrays, limit max. binding to one std::vector bindings = makeSparseRange(maxBindings() - 2, 0); for (std::vector::iterator it = bindings.begin(); it < bindings.end(); it++) { int baseBindingPoint = *it; // total distance from current binding point to end of binding range // split over two arrays, making sure that the array sizes don't // exceed max. array sizes per stage int arraySize = (maxBindings() - baseBindingPoint - 1) / 2; arraySize = std::min(arraySize, maxArraySize() / 2); StringStream s; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(baseBindingPoint), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), buildArray(arraySize)); String another_decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(baseBindingPoint), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName(1)), buildBlock(getDefaultUniformName(1)), getDefaultUniformName(1), buildArray(arraySize)); setTemplateParam("UNIFORM_DECL", decl + another_decl); s.reset(); for (int uniform = 0; uniform < 2; uniform++) { for (int idx = 0; idx < arraySize; idx++) { s << ((idx | uniform) ? " + " : "") << buildAccess(buildArrayAccess(uniform, idx)); } } s << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; for (int uniform = 0; uniform < 2; uniform++) { list.clear(); for (int idx = 0; idx < arraySize; idx++) { std::ostringstream texUnitStr; texUnitStr << getDefaultUniformName(uniform); const String& u = buildBlockName(getDefaultUniformName(uniform)); if (!u.empty()) texUnitStr << "_block"; texUnitStr << buildArray(idx); list.push_back(texUnitStr.str()); } StringIntMap bindingPoints = program->getBindingPoints(list); for (int idx = 0; idx < arraySize; idx++) { passed &= ((baseBindingPoint + idx) == bindingPoints[list[idx]]); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[list[idx]], (baseBindingPoint + idx))); } } } } return LayoutBindingTestResult(passed, String()); } //== verify that explicit binding point can be changed via API LayoutBindingTestResult LayoutBindingBaseCase::binding_api_update(void) { bool passed = true; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(1), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; const String& u = buildBlockName(getDefaultUniformName()); if (!u.empty()) list.push_back(u + "_block"); else list.push_back(getDefaultUniformName()); StringIntMap bindingPoints = program->getBindingPoints(list); gl().useProgram(program->getProgram()); program->setBindingPoints(list, maxBindings() - 1); gl().useProgram(0); bindingPoints = program->getBindingPoints(list); passed &= bindingPoints[list[0]] == (maxBindings() - 1); if (!passed) { return LayoutBindingTestResult(passed, generateLog(String("binding point did not match default"), bindingPoints[list[0]], maxBindings() - 1)); } return LayoutBindingTestResult(passed, String()); } LayoutBindingTestResult LayoutBindingBaseCase::binding_compilation_errors(void) { bool passed = true; String decl; // verify "uniform float var;" doesn't compile { StringStream s; s << getTestParameters().vector_type << "(0.0);"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=0) " << "uniform float tex0;"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= !program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog(true)); } } // verify that non-constant integer expression in binding fails { decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(String("0.0")), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= !program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog(true)); } } { decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(String("-1")), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= !program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog(true)); } } { decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(maxBindings()), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= !program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog(true)); } } { decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(maxBindings()), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); StringStream s; s << "vec4(0.0);\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= !program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog(true)); } } return LayoutBindingTestResult(passed, String()); } LayoutBindingTestResult LayoutBindingBaseCase::binding_link_errors(void) { bool passed = true; // same sampler with different binding in two compilation units if (isStage(VertexShader)) { String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(1), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam(VertexShader, "UNIFORM_DECL", decl); setTemplateParam(VertexShader, "OUT_ASSIGNMENT", String("fragColor =")); StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam(VertexShader, "UNIFORM_ACCESS", s.str()); updateTemplate(VertexShader); decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(3), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam(FragmentShader, "UNIFORM_DECL", decl); s.reset(); s << "fragColor + " << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam(FragmentShader, "UNIFORM_ACCESS", s.str()); updateTemplate(FragmentShader); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed = !program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog(true)); } } return LayoutBindingTestResult(passed, String()); } // this subtest is generically empty. Overwritten in test cases as needed. LayoutBindingTestResult LayoutBindingBaseCase::binding_examples(void) { return true; } // just for image and atomic counter cases LayoutBindingTestResult LayoutBindingBaseCase::binding_mixed_order(void) { return true; } //========================================================================= // test case Sampler layout binding //========================================================================= class SamplerLayoutBindingCase : public LayoutBindingBaseCase { public: SamplerLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion); ~SamplerLayoutBindingCase(void); private: /*virtual*/ LayoutBindingProgram* createProgram() { return new LayoutBindingProgram(*this); } /*virtual*/ String getDefaultUniformName(int idx = 0) { StringStream s; s << "sampler" << idx; return s.str(); } /*virtual*/ String buildLayout(const String& binding) { std::ostringstream s; if (!binding.empty()) s << "layout(binding=" << binding << ") "; return s.str(); } virtual int maxBindings() { int units = 0; gl().getIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &units); return units; } // return max. array size allowed virtual int maxArraySize() { int units = 0; switch (getStage()) { case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &units); break; case FragmentShader: gl().getIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &units); break; case ComputeShader: gl().getIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &units); break; default: DE_ASSERT(0); break; } return units; } }; SamplerLayoutBindingCase::SamplerLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion) : LayoutBindingBaseCase(context, name, description, stage, samplerType, glslVersion) { } SamplerLayoutBindingCase::~SamplerLayoutBindingCase(void) { } //========================================================================= // test case Image layout binding //========================================================================= class ImageLayoutBindingCase : public LayoutBindingBaseCase { public: ImageLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion); ~ImageLayoutBindingCase(void); private: class ImageLayoutBindingProgram : public LayoutBindingProgram { public: ImageLayoutBindingProgram(IProgramContextSupplier& contextSupplier) : LayoutBindingProgram(contextSupplier) { } }; private: // IProgramContextSupplier /*virtual*/ LayoutBindingProgram* createProgram() { return new ImageLayoutBindingProgram(*this); } private: /*virtual*/ String getDefaultUniformName(int idx = 0) { StringStream s; s << "image" << idx; return s.str(); } /*virtual*/ String buildLayout(const String& binding) { std::ostringstream s; if (!binding.empty()) s << "layout(binding=" << binding << ", rgba8) readonly "; else s << "layout(rgba8) readonly "; return s.str(); } /*virtual*/ int maxBindings() { int units = 0; gl().getIntegerv(GL_MAX_IMAGE_UNITS, &units); return units; } /*virtual*/ int maxArraySize() { int units = 0; switch (getStage()) { case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &units); break; case FragmentShader: gl().getIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &units); break; case ComputeShader: gl().getIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &units); break; default: DE_ASSERT(0); break; } return units; } private: //virtual LayoutBindingTestResult binding_basic_default (void); //virtual LayoutBindingTestResult binding_basic_explicit (void); //virtual LayoutBindingTestResult binding_basic_multiple (void); //virtual LayoutBindingTestResult binding_basic_render (void); //virtual LayoutBindingTestResult binding_integer_constant (void); //virtual LayoutBindingTestResult binding_array_size (void); //virtual LayoutBindingTestResult binding_array_implicit (void); //virtual LayoutBindingTestResult binding_array_multiple (void); /*virtual*/ LayoutBindingTestResult binding_api_update(void) { // only for GL if (getGLSLVersion() == glu::GLSL_VERSION_310_ES) return LayoutBindingTestResult(true, String(), true); return LayoutBindingBaseCase::binding_api_update(); } //virtual LayoutBindingTestResult binding_compilation_errors (void); //virtual LayoutBindingTestResult binding_link_errors (void); //virtual LayoutBindingTestResult binding_link_examples (void); /*virtual*/ LayoutBindingTestResult binding_mixed_order(void) { bool passed = true; { StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); String decl = buildUniformDecl(String(getTestParameters().keyword), String("layout(binding=0, rgba8) readonly"), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } { String decl = buildUniformDecl(String(getTestParameters().keyword), String("layout(r32f, binding=0) readonly"), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } return true; } }; ImageLayoutBindingCase::ImageLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion) : LayoutBindingBaseCase(context, name, description, stage, samplerType, glslVersion) { } ImageLayoutBindingCase::~ImageLayoutBindingCase(void) { } //========================================================================= // test case Atomic counter binding //========================================================================= class AtomicCounterLayoutBindingCase : public LayoutBindingBaseCase { public: AtomicCounterLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion); ~AtomicCounterLayoutBindingCase(void) { } private: class AtomicCounterLayoutBindingProgram : public LayoutBindingProgram { public: AtomicCounterLayoutBindingProgram(IProgramContextSupplier& contextSupplier) : LayoutBindingProgram(contextSupplier) { } private: /*virtual*/ StringIntMap getBindingPoints(StringVector args) const { StringIntMap bindingPoints; for (StringVector::iterator it = args.begin(); it != args.end(); it++) { glw::GLuint idx = gl().getProgramResourceIndex(getProgram(), GL_UNIFORM, (*it).c_str()); if (idx != GL_INVALID_INDEX) { glw::GLenum param = GL_ATOMIC_COUNTER_BUFFER_INDEX; glw::GLint atomic_counter_buffer_idx = -1; gl().getProgramResourceiv(getProgram(), GL_UNIFORM, idx, 1, ¶m, 1, NULL, &atomic_counter_buffer_idx); bool hasNoError = (GL_NO_ERROR == gl().getError()) && (-1 != atomic_counter_buffer_idx); if (!hasNoError) continue; param = GL_BUFFER_BINDING; glw::GLint value = -1; gl().getProgramResourceiv(getProgram(), GL_ATOMIC_COUNTER_BUFFER, atomic_counter_buffer_idx, 1, ¶m, 1, NULL, &value); hasNoError = (GL_NO_ERROR == gl().getError()) && (-1 != value); if (!hasNoError) continue; bindingPoints[*it] = value; } } return bindingPoints; } /*virtual*/ StringIntMap getOffsets(StringVector args) const { StringIntMap bindingPoints; for (StringVector::iterator it = args.begin(); it != args.end(); it++) { glw::GLuint idx = gl().getProgramResourceIndex(getProgram(), GL_UNIFORM, (*it).c_str()); if (idx != GL_INVALID_INDEX) { glw::GLenum param = GL_OFFSET; glw::GLint value = -1; gl().getProgramResourceiv(getProgram(), GL_UNIFORM, idx, 1, ¶m, 1, NULL, &value); bool hasNoError = (GL_NO_ERROR == gl().getError()); if (hasNoError) { bindingPoints[*it] = value; } } } return bindingPoints; } }; private: // IProgramContextSupplier /*virtual*/ LayoutBindingProgram* createProgram() { return new AtomicCounterLayoutBindingProgram(*this); } private: /*virtual*/ String getDefaultUniformName(int idx = 0) { StringStream s; s << "atomic" << idx; return s.str(); } /*virtual*/ String buildAccess(const String& var) { std::ostringstream s; s << "vec4(float(atomicCounter(" << var << ")), 1.0, 0.0, 1.0)"; return s.str(); } int maxBindings() { int units = 0; gl().getIntegerv(GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, &units); return units; } // return max. array size allowed int maxArraySize() { int units = 0; switch (getStage()) { case FragmentShader: gl().getIntegerv(GL_MAX_FRAGMENT_ATOMIC_COUNTERS, &units); break; case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_ATOMIC_COUNTERS, &units); break; case ComputeShader: gl().getIntegerv(GL_MAX_COMPUTE_ATOMIC_COUNTERS, &units); break; default: DE_ASSERT(0); break; } return units; } //========================================================================= // sub-tests overrides //========================================================================= private: LayoutBindingTestResult binding_basic_default(void) { return true; } //virtual LayoutBindingTestResult binding_basic_explicit (void); //virtual LayoutBindingTestResult binding_basic_multiple (void); LayoutBindingTestResult binding_basic_render(void) { return true; } //virtual LayoutBindingTestResult binding_integer_constant (void); /*virtual*/ LayoutBindingTestResult binding_array_size(void) { bool passed = true; //== test different sized arrays std::vector arraySizes = makeSparseRange(maxArraySize(), 1); for (std::vector::iterator it = arraySizes.begin(); it < arraySizes.end(); it++) { int arraySize = *it; StringStream s; s << "[" << arraySize << "]"; String decl = buildUniformDecl(String(getTestParameters().keyword), buildLayout(0), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), buildArray(arraySize)); setTemplateParam("UNIFORM_DECL", decl); s.reset(); // build a function that accesses the whole array s << "float accumulate(void)\n"; s << "{\n"; s << " float acc = 0.0;\n"; s << " for(int i=0; i < " << arraySize << " ; i++)\n"; s << " acc = float(atomicCounter(" << getDefaultUniformName() << "[i]));\n"; s << " return acc;\n"; s << "}\n"; setTemplateParam("OPTIONAL_FUNCTION_BLOCK", s.str()); s.reset(); s << "vec4(accumulate(), 1.0, 0.0, 1.0);\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); updateTemplate(); setTemplateParam("OPTIONAL_FUNCTION_BLOCK", String()); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; std::ostringstream texUnitStr; texUnitStr << getDefaultUniformName() << "[0]"; list.push_back(texUnitStr.str()); StringIntMap bindingPoints = program->getBindingPoints(list); passed &= (0 == bindingPoints[list[0]]); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("binding point did not match default"), bindingPoints[list[0]], 1)); } } return LayoutBindingTestResult(passed, String()); } LayoutBindingTestResult binding_array_implicit(void) { return true; } LayoutBindingTestResult binding_array_multiple(void) { return true; } LayoutBindingTestResult binding_api_update(void) { return true; } //virtual LayoutBindingTestResult binding_compilation_errors (void); //virtual LayoutBindingTestResult binding_link_errors (void); LayoutBindingTestResult binding_examples_many_bindings(void) { int max_bindings = 0; if (isStage(VertexShader)) { gl().getIntegerv(GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS, &max_bindings); } else if (isStage(FragmentShader)) { gl().getIntegerv(GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS, &max_bindings); } else { gl().getIntegerv(GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS, &max_bindings); } // example 1 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=2, offset=4) uniform atomic_uint;\n"; s << "layout(binding=2) uniform atomic_uint " << getDefaultUniformName() << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; list.push_back(getDefaultUniformName()); StringIntMap offsets = program->getOffsets(list); passed &= (4 == offsets[list[0]]); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("offset did not match requested"), offsets[list[0]], 1)); } } // example 2 in atomic counter CTS spec ac-binding-examples if (max_bindings >= 2) { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << "\n"; s << "+" << buildAccess(getDefaultUniformName(1)) << "\n"; s << "+" << buildAccess(getDefaultUniformName(2)) << "\n"; s << "+" << buildAccess(getDefaultUniformName(3)) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=3, offset=4) uniform atomic_uint " << getDefaultUniformName() << ";\n"; s << "layout(binding=2) uniform atomic_uint " << getDefaultUniformName(1) << ";\n"; s << "layout(binding=3) uniform atomic_uint " << getDefaultUniformName(2) << ";\n"; s << "layout(binding=2) uniform atomic_uint " << getDefaultUniformName(3) << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; list.push_back(getDefaultUniformName()); list.push_back(getDefaultUniformName(1)); list.push_back(getDefaultUniformName(2)); list.push_back(getDefaultUniformName(3)); StringIntMap offsets = program->getOffsets(list); IntVector expected; expected.insert(expected.end(), 4); expected.insert(expected.end(), 0); expected.insert(expected.end(), 8); expected.insert(expected.end(), 4); for (unsigned int idx = 0; idx < list.size(); idx++) { passed &= (expected[idx] == offsets[list[idx]]); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("offset of") + String(list[idx]) + String("did not match requested"), offsets[list[idx]], 4)); } } } // example 3 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=2, offset=4) uniform atomic_uint;\n"; s << "layout(offset=8) uniform atomic_uint " << getDefaultUniformName() << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (passed) { return LayoutBindingTestResult(!passed, String("should not compile")); } } // example 4 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(offset=4) uniform atomic_uint " << getDefaultUniformName() << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (passed) { return LayoutBindingTestResult(!passed, String("should not compile")); } } // example 5 in atomic counter CTS spec ac-binding-examples // first check working example, then amend it with an error if (max_bindings >= 2) { for (int pass = 0; pass < 2; pass++) { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << "\n"; if (pass) { s << "+" << buildAccess(getDefaultUniformName(1)) << "\n"; s << "+" << buildAccess(getDefaultUniformName(2)) << ";\n"; } else { s << "+" << buildAccess(getDefaultUniformName(1)) << ";\n"; } setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=1, offset=0) uniform atomic_uint " << getDefaultUniformName() << ";\n"; s << "layout(binding=2, offset=0) uniform atomic_uint " << getDefaultUniformName(1) << ";\n"; if (pass) s << "layout(binding=1, offset=0) uniform atomic_uint " << getDefaultUniformName(2) << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (pass != 0) { if (passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } else { if (!passed) { return LayoutBindingTestResult(passed, String("should not compile")); } } } } // example 6 in atomic counter CTS spec ac-binding-examples // first check working example, then amend it with an error if (max_bindings >= 2) { for (int pass = 0; pass < 2; pass++) { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << "\n"; if (pass) { s << "+" << buildAccess(getDefaultUniformName(1)) << "\n"; s << "+" << buildAccess(getDefaultUniformName(2)) << ";\n"; } else { s << "+" << buildAccess(getDefaultUniformName(1)) << ";\n"; } setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=1, offset=0) uniform atomic_uint " << getDefaultUniformName() << ";\n"; s << "layout(binding=2, offset=0) uniform atomic_uint " << getDefaultUniformName(1) << ";\n"; if (pass) s << "layout(binding=1, offset=2) uniform atomic_uint " << getDefaultUniformName(2) << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (pass != 0) { if (passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } else { if (!passed) { return LayoutBindingTestResult(passed, String("should not compile")); } } } } return true; } LayoutBindingTestResult binding_examples_one_binding(void) { // example 1 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=0, offset=4) uniform atomic_uint;\n"; s << "layout(binding=0) uniform atomic_uint " << getDefaultUniformName() << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; list.push_back(getDefaultUniformName()); StringIntMap offsets = program->getOffsets(list); passed &= (4 == offsets[list[0]]); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("offset did not match requested"), offsets[list[0]], 1)); } } // example 2 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << "\n"; s << "+" << buildAccess(getDefaultUniformName(1)) << "\n"; s << "+" << buildAccess(getDefaultUniformName(2)) << "\n"; s << "+" << buildAccess(getDefaultUniformName(3)) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=0, offset=4) uniform atomic_uint " << getDefaultUniformName() << ";\n"; s << "layout(binding=0) uniform atomic_uint " << getDefaultUniformName(1) << ";\n"; s << "layout(binding=0) uniform atomic_uint " << getDefaultUniformName(2) << ";\n"; s << "layout(binding=0) uniform atomic_uint " << getDefaultUniformName(3) << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } StringVector list; list.push_back(getDefaultUniformName()); list.push_back(getDefaultUniformName(1)); list.push_back(getDefaultUniformName(2)); list.push_back(getDefaultUniformName(3)); StringIntMap offsets = program->getOffsets(list); IntVector expected; expected.insert(expected.end(), 4); expected.insert(expected.end(), 8); expected.insert(expected.end(), 12); expected.insert(expected.end(), 16); for (unsigned int idx = 0; idx < list.size(); idx++) { passed &= (expected[idx] == offsets[list[idx]]); if (!passed) { return LayoutBindingTestResult( passed, generateLog(String("offset of") + String(list[idx]) + String("did not match requested"), offsets[list[idx]], expected[idx])); } } } // example 3 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=0, offset=4) uniform atomic_uint;\n"; s << "layout(offset=8) uniform atomic_uint " << getDefaultUniformName() << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (passed) { return LayoutBindingTestResult(!passed, String("should not compile")); } } // example 4 in atomic counter CTS spec ac-binding-examples { bool passed = true; StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(offset=4) uniform atomic_uint " << getDefaultUniformName() << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (passed) { return LayoutBindingTestResult(!passed, String("should not compile")); } } // example 5 in atomic counter CTS spec ac-binding-examples // first check working example, then amend it with an error for (int pass = 0; pass < 2; pass++) { bool passed = true; StringStream s; if (pass) { s << buildAccess(getDefaultUniformName()) << "\n"; s << "+" << buildAccess(getDefaultUniformName(1)) << ";\n"; } else { s << buildAccess(getDefaultUniformName()) << ";\n"; } setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=0, offset=0) uniform atomic_uint " << getDefaultUniformName() << ";\n"; if (pass) s << "layout(binding=0, offset=0) uniform atomic_uint " << getDefaultUniformName(1) << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (pass != 0) { if (passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } else { if (!passed) { return LayoutBindingTestResult(passed, String("should not compile")); } } } // example 6 in atomic counter CTS spec ac-binding-examples // first check working example, then amend it with an error for (int pass = 0; pass < 2; pass++) { bool passed = true; StringStream s; if (pass) { s << buildAccess(getDefaultUniformName()) << "\n"; s << "+" << buildAccess(getDefaultUniformName(1)) << ";\n"; } else { s << buildAccess(getDefaultUniformName()) << ";\n"; } setTemplateParam("UNIFORM_ACCESS", s.str()); s.reset(); s << "layout(binding=0, offset=0) uniform atomic_uint " << getDefaultUniformName() << ";\n"; if (pass) s << "layout(binding=0, offset=2) uniform atomic_uint " << getDefaultUniformName(1) << ";\n"; setTemplateParam("UNIFORM_DECL", s.str()); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (pass != 0) { if (passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } else { if (!passed) { return LayoutBindingTestResult(passed, String("should not compile")); } } } return true; } /*virtual*/ LayoutBindingTestResult binding_examples(void) { if (maxBindings() >= 4) { return binding_examples_many_bindings(); } else { return binding_examples_one_binding(); } } /*virtual*/ LayoutBindingTestResult binding_mixed_order(void) { bool passed = true; { StringStream s; s << buildAccess(getDefaultUniformName()) << ";\n"; setTemplateParam("UNIFORM_ACCESS", s.str()); String decl = buildUniformDecl(String(getTestParameters().keyword), String("layout(binding=0, offset=0)"), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } { String decl = buildUniformDecl(String(getTestParameters().keyword), String("layout(offset=0, binding=0)"), String(getTestParameters().uniform_type), buildBlockName(getDefaultUniformName()), buildBlock(getDefaultUniformName()), getDefaultUniformName(), String()); setTemplateParam("UNIFORM_DECL", decl); updateTemplate(); LayoutBindingProgram::LayoutBindingProgramAutoPtr program(*this); passed &= program->compiledAndLinked(); if (!passed) { return LayoutBindingTestResult(passed, program->getErrorLog()); } } return true; } }; AtomicCounterLayoutBindingCase::AtomicCounterLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion) : LayoutBindingBaseCase(context, name, description, stage, samplerType, glslVersion) { } //========================================================================= // test case Uniform blocks binding //========================================================================= class UniformBlocksLayoutBindingCase : public LayoutBindingBaseCase { public: UniformBlocksLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion); ~UniformBlocksLayoutBindingCase(void); private: class UniformBlocksLayoutBindingProgram : public LayoutBindingProgram { public: UniformBlocksLayoutBindingProgram(IProgramContextSupplier& contextSupplier) : LayoutBindingProgram(contextSupplier) { } ~UniformBlocksLayoutBindingProgram() { } private: /*virtual*/ StringIntMap getBindingPoints(StringVector args) const { StringIntMap bindingPoints; for (StringVector::iterator it = args.begin(); it != args.end(); it++) { glw::GLuint idx = gl().getProgramResourceIndex(getProgram(), GL_UNIFORM_BLOCK, (*it).c_str()); if (idx != glw::GLuint(-1)) { glw::GLenum param = GL_BUFFER_BINDING; glw::GLint value = -1; gl().getProgramResourceiv(getProgram(), GL_UNIFORM_BLOCK, idx, 1, ¶m, 1, NULL, &value); bool hasNoError = (GL_NO_ERROR == gl().getError()); if (hasNoError) { bindingPoints[*it] = value; } } } return bindingPoints; } /*virtual*/ bool setBindingPoints(StringVector list, glw::GLint bindingPoint) const { bool bNoError = true; for (StringVector::iterator it = list.begin(); it != list.end(); it++) { glw::GLuint blockIndex = gl().getUniformBlockIndex(getProgram(), (*it).c_str()); if (blockIndex == GL_INVALID_INDEX) { return false; } gl().uniformBlockBinding(getProgram(), blockIndex, bindingPoint); } return bNoError; } }; private: // IProgramContextSupplier /*virtual*/ LayoutBindingProgram* createProgram() { return new UniformBlocksLayoutBindingProgram(*this); } private: /*virtual*/ String buildBlockName(const String& name) { return name; } /*virtual*/ String buildBlock(const String& name, const String& type) { std::ostringstream s; s << "{"; s << type << " " << name << "_a; "; s << type << " " << name << "_b; "; s << "}"; return s.str(); } /*virtual*/ String buildAccess(const String& var) { std::ostringstream s; s << "vec4(0.0, " << var << "_a, 0.0, 1.0) + vec4(0.0, " << var << "_b, 0.0, 1.0)"; return s.str(); } /*virtual*/ String buildLayout(const String& binding) { std::ostringstream s; if (!binding.empty()) s << "layout(binding=" << binding << ", std140) "; else s << "layout(std140) "; return s.str(); } /*virtual*/ int maxBindings() { int units = 0; gl().getIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &units); return units; } // return max. array size allowed /*virtual*/ int maxArraySize() { int units = 0; switch (getStage()) { case FragmentShader: gl().getIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, &units); break; case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &units); break; case ComputeShader: gl().getIntegerv(GL_MAX_COMPUTE_UNIFORM_BLOCKS, &units); break; default: DE_ASSERT(0); break; } return units; } /*virtual*/ void bind(int binding) { gl().bindBufferBase(GL_UNIFORM_BUFFER, binding, m_buffername); } /*virtual*/ void unbind(int binding) { gl().bindBufferBase(GL_UNIFORM_BUFFER, binding, 0); } /*virtual*/ void setupTest(void) { const float f[2] = { 0.25f, 0.75f }; gl().genBuffers(1, &m_buffername); gl().bindBuffer(GL_UNIFORM_BUFFER, m_buffername); gl().bufferData(GL_UNIFORM_BUFFER, sizeof(f), f, GL_STATIC_DRAW); } /*virtual*/ void teardownTest(void) { gl().bindBuffer(GL_UNIFORM_BUFFER, 0); gl().deleteBuffers(1, &m_buffername); } private: glw::GLuint m_buffername; }; UniformBlocksLayoutBindingCase::UniformBlocksLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion) : LayoutBindingBaseCase(context, name, description, stage, samplerType, glslVersion), m_buffername(0) { } UniformBlocksLayoutBindingCase::~UniformBlocksLayoutBindingCase() { } //***************************************************************************** //========================================================================= // test case Shader storage buffer binding //========================================================================= class ShaderStorageBufferLayoutBindingCase : public LayoutBindingBaseCase { public: ShaderStorageBufferLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion); ~ShaderStorageBufferLayoutBindingCase(void); private: class ShaderStorageBufferLayoutBindingProgram : public LayoutBindingProgram { public: ShaderStorageBufferLayoutBindingProgram(IProgramContextSupplier& contextSupplier) : LayoutBindingProgram(contextSupplier) { } ~ShaderStorageBufferLayoutBindingProgram() { } //******************************************************************************* // overwritten virtual methods private: /*virtual*/ StringIntMap getBindingPoints(StringVector args) const { StringIntMap bindingPoints; for (StringVector::iterator it = args.begin(); it != args.end(); it++) { glw::GLuint idx = gl().getProgramResourceIndex(getProgram(), GL_SHADER_STORAGE_BLOCK, (*it).c_str()); if (idx != GL_INVALID_INDEX) { glw::GLenum param = GL_BUFFER_BINDING; glw::GLint value = -1; gl().getProgramResourceiv(getProgram(), GL_SHADER_STORAGE_BLOCK, idx, 1, ¶m, 1, NULL, &value); bool hasNoError = (GL_NO_ERROR == gl().getError()); if (hasNoError) { bindingPoints[*it] = value; } } } return bindingPoints; } /*virtual*/ bool setBindingPoints(StringVector list, glw::GLint bindingPoint) const { for (StringVector::iterator it = list.begin(); it != list.end(); it++) { glw::GLuint blockIndex = gl().getProgramResourceIndex(getProgram(), GL_SHADER_STORAGE_BLOCK, (*it).c_str()); if (blockIndex == GL_INVALID_INDEX) { return false; } gl().shaderStorageBlockBinding(getProgram(), blockIndex, bindingPoint); } return true; } }; private: // IProgramContextSupplier /*virtual*/ LayoutBindingProgram* createProgram() { return new ShaderStorageBufferLayoutBindingProgram(*this); } private: /*virtual*/ String buildLayout(const String& binding) { std::ostringstream s; if (!binding.empty()) s << "layout(binding=" << binding << ", std430) "; else s << "layout(std430) "; return s.str(); } /*virtual*/ String getDefaultUniformName(int idx = 0) { StringStream s; s << "buffer" << idx; return s.str(); } /*virtual*/ String buildBlockName(const String& name) { return name; } /*virtual*/ String buildBlock(const String& name, const String& type) { std::ostringstream s; s << "{"; s << type << " " << name << "_a[2];\n"; s << "}"; return s.str(); } /*virtual*/ String buildAccess(const String& var) { std::ostringstream s; s << "vec4(0.0, " << var << "_a[0] + " << var << "_a[1], 0.0, 1.0)"; return s.str(); } int maxBindings() { int units = 0; gl().getIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &units); return units; } // return max. array size allowed int maxArraySize() { int units = 0; switch (getStage()) { case FragmentShader: gl().getIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &units); break; case VertexShader: gl().getIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &units); break; case ComputeShader: gl().getIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &units); break; default: DE_ASSERT(0); break; } return units; } /*virtual*/ void bind(int binding) { gl().bindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, m_buffername); } /*virtual*/ void unbind(int binding) { gl().bindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, 0); } /*virtual*/ void setupTest(void) { const float f[2] = { 0.25f, 0.75f }; gl().genBuffers(1, &m_buffername); gl().bindBuffer(GL_SHADER_STORAGE_BUFFER, m_buffername); gl().bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(f), f, GL_STATIC_DRAW); } /*virtual*/ void teardownTest(void) { gl().bindBuffer(GL_SHADER_STORAGE_BUFFER, 0); gl().deleteBuffers(1, &m_buffername); } //========================================================================= // sub-tests overrides //========================================================================= private: private: //virtual LayoutBindingTestResult binding_basic_default (void); //virtual LayoutBindingTestResult binding_basic_explicit (void); //virtual LayoutBindingTestResult binding_basic_multiple (void); //virtual LayoutBindingTestResult binding_basic_render (void); //virtual LayoutBindingTestResult binding_integer_constant (void); //virtual LayoutBindingTestResult binding_array_size (void); //virtual LayoutBindingTestResult binding_array_implicit (void); //virtual LayoutBindingTestResult binding_array_multiple (void); /*virtual*/ LayoutBindingTestResult binding_api_update(void) { // only for GL if (getGLSLVersion() == glu::GLSL_VERSION_310_ES) return LayoutBindingTestResult(true, String(), true); return LayoutBindingBaseCase::binding_api_update(); } //virtual LayoutBindingTestResult binding_compilation_errors (void); //virtual LayoutBindingTestResult binding_link_errors (void); //virtual LayoutBindingTestResult binding_examples (void); private: glw::GLuint m_buffername; }; ShaderStorageBufferLayoutBindingCase::ShaderStorageBufferLayoutBindingCase(Context& context, const char* name, const char* description, StageType stage, LayoutBindingParameters& samplerType, glu::GLSLVersion glslVersion) : LayoutBindingBaseCase(context, name, description, stage, samplerType, glslVersion), m_buffername(0) { } ShaderStorageBufferLayoutBindingCase::~ShaderStorageBufferLayoutBindingCase() { } //***************************************************************************** LayoutBindingTests::LayoutBindingTests(Context& context, glu::GLSLVersion glslVersion) : TestCaseGroup(context, "layout_binding", "Layout Binding LayoutBindingSubTest"), m_glslVersion(glslVersion) { } LayoutBindingTests::~LayoutBindingTests(void) { } StageType LayoutBindingTests::stageTypes[] = { { "ComputeShader", ComputeShader }, { "FragmentShader", FragmentShader }, { "VertexShader", VertexShader }, }; // uniform_type must match vector_type, i.e. isampler2D=>ivec4 LayoutBindingParameters LayoutBindingTests::test_args[] = { { "uniform", Texture, TwoD, "vec4", "sampler2D", "vec2", "texture" }, { "uniform", Texture, ThreeD, "vec4", "sampler3D", "vec3", "texture" }, { "uniform", Texture, TwoDArray, "vec4", "sampler2DArray", "vec3", "texture" }, { "uniform", Image, TwoD, "vec4", "image2D", "ivec2", "imageLoad" }, { "uniform", AtomicCounter, TwoD, "vec4", "atomic_uint", "vec3", "atomic" }, { "uniform", UniformBlock, None, "vec4", "block", "vec3", "block" }, { "buffer", ShaderStorageBuffer, None, "vec4", "buffer", "vec3", "atomicAdd" }, }; // create test name which must be unique or dEQP framework will throw // example: sampler2D_layout_binding_vec4_texture_0 String LayoutBindingTests::createTestName(const StageType& stageType, const LayoutBindingParameters& testArgs) { StringStream s; s << testArgs.uniform_type; s << "_layout_binding_"; s << testArgs.access_function << "_"; s << stageType.name; return s.str(); } void LayoutBindingTests::init(void) { std::vector stages = makeVector(stageTypes); std::vector samplers = makeVector(test_args); for (std::vector::iterator stagesIter = stages.begin(); stagesIter != stages.end(); stagesIter++) { for (std::vector::iterator testArgsIter = samplers.begin(); testArgsIter != samplers.end(); testArgsIter++) { String testName = createTestName(*stagesIter, *testArgsIter); switch ((*testArgsIter).surface_type) { case Texture: addChild(new SamplerLayoutBindingCase(m_context, testName.c_str(), "test sampler layout binding functionality", *stagesIter, *testArgsIter, m_glslVersion)); break; case Image: addChild(new ImageLayoutBindingCase(m_context, testName.c_str(), "test image layout binding functionality", *stagesIter, *testArgsIter, m_glslVersion)); break; case AtomicCounter: addChild(new AtomicCounterLayoutBindingCase(m_context, testName.c_str(), "test atomic counters layout binding functionality", *stagesIter, *testArgsIter, m_glslVersion)); break; case UniformBlock: addChild(new UniformBlocksLayoutBindingCase(m_context, testName.c_str(), "test uniform block layout binding functionality", *stagesIter, *testArgsIter, m_glslVersion)); break; case ShaderStorageBuffer: addChild(new ShaderStorageBufferLayoutBindingCase( m_context, testName.c_str(), "test shader storage buffer layout binding functionality", *stagesIter, *testArgsIter, m_glslVersion)); break; default: DE_ASSERT(0); break; } } } } } // glcts