/*------------------------------------------------------------------------- * 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 "es31cFramebufferNoAttachmentsTests.hpp" #include "gluContextInfo.hpp" #include "gluDefs.hpp" #include "gluStrUtil.hpp" #include "glw.h" #include "glwFunctions.hpp" #include "tcuTestLog.hpp" namespace glcts { using tcu::TestLog; using std::string; using std::vector; using glcts::Context; // I tried to find something like this, but failed void checkErrorEqualsExpected(GLenum err, GLenum expected, const char* msg, const char* file, int line) { if (err != expected) { std::ostringstream msgStr; msgStr << "glGetError() returned " << glu::getErrorStr(err) << ", expected " << glu::getErrorStr(expected); if (msg) msgStr << " in '" << msg << "'"; if (err == GL_OUT_OF_MEMORY) throw glu::OutOfMemoryError(msgStr.str().c_str(), "", file, line); else throw glu::Error(err, msgStr.str().c_str(), "", file, line); } } #define GLU_EXPECT_ERROR(ERR, EXPECTED, MSG) checkErrorEqualsExpected((ERR), EXPECTED, MSG, __FILE__, __LINE__) // Contains expect_fbo_status() class FramebufferNoAttachmentsBaseCase : public TestCase { public: FramebufferNoAttachmentsBaseCase(Context& context, const char* name, const char* description); ~FramebufferNoAttachmentsBaseCase(); protected: void expect_fbo_status(GLenum target, GLenum expected_status, const char* fail_message); }; FramebufferNoAttachmentsBaseCase::FramebufferNoAttachmentsBaseCase(Context& context, const char* name, const char* description) : TestCase(context, name, description) { } FramebufferNoAttachmentsBaseCase::~FramebufferNoAttachmentsBaseCase() { } // API tests class FramebufferNoAttachmentsApiCase : public FramebufferNoAttachmentsBaseCase { public: FramebufferNoAttachmentsApiCase(Context& context, const char* name, const char* description); ~FramebufferNoAttachmentsApiCase(); IterateResult iterate(); private: void begin_fbo_no_attachments(GLenum target); void begin_fbo_with_multisample_renderbuffer(GLenum target); void begin_fbo(GLenum target, unsigned test_case); void end_fbo(GLenum target); private: GLuint m_fbo; GLuint m_renderbuffer; GLuint m_texture; }; FramebufferNoAttachmentsApiCase::FramebufferNoAttachmentsApiCase(Context& context, const char* name, const char* description) : FramebufferNoAttachmentsBaseCase(context, name, description), m_fbo(0), m_renderbuffer(0), m_texture(0) { } FramebufferNoAttachmentsApiCase::~FramebufferNoAttachmentsApiCase() { } void FramebufferNoAttachmentsBaseCase::expect_fbo_status(GLenum target, GLenum expected_status, const char* fail_message) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); GLenum error; error = gl.getError(); if (error != GL_NO_ERROR) { std::ostringstream msgStr; msgStr << "Error before glCheckFramebufferStatus() for '" << fail_message << "'\n"; GLU_EXPECT_NO_ERROR(error, "Error before glCheckFramebufferStatus()"); } TCU_CHECK_MSG(gl.checkFramebufferStatus(target) == expected_status, fail_message); error = gl.getError(); if (error != GL_NO_ERROR) { std::ostringstream msgStr; msgStr << "Error after glCheckFramebufferStatus() for '" << fail_message << "'\n"; GLU_EXPECT_NO_ERROR(error, "Error after glCheckFramebufferStatus()"); } } void FramebufferNoAttachmentsApiCase::begin_fbo_no_attachments(GLenum target) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.genFramebuffers(1, &m_fbo); gl.bindFramebuffer(target, m_fbo); // A freshly created framebuffer with no attachment is expected to be incomplete // until default width and height is set. expect_fbo_status(target, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, "error setting up framebuffer with multisample attachment"); } void FramebufferNoAttachmentsApiCase::begin_fbo_with_multisample_renderbuffer(GLenum target) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.genFramebuffers(1, &m_fbo); gl.bindFramebuffer(target, m_fbo); gl.genRenderbuffers(1, &m_renderbuffer); gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer); gl.renderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 101, 102); gl.framebufferRenderbuffer(target, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer); expect_fbo_status(target, GL_FRAMEBUFFER_COMPLETE, "framebuffer with an attachment should be complete"); } void FramebufferNoAttachmentsApiCase::begin_fbo(GLenum target, unsigned test_case) { switch (test_case) { case 0: begin_fbo_no_attachments(target); break; case 1: begin_fbo_with_multisample_renderbuffer(target); break; } } void FramebufferNoAttachmentsApiCase::end_fbo(GLenum target) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.bindFramebuffer(target, 0); gl.deleteFramebuffers(1, &m_fbo); gl.deleteRenderbuffers(1, &m_renderbuffer); gl.deleteTextures(1, &m_texture); GLU_EXPECT_NO_ERROR(gl.getError(), "error deleting framebuffer / renderbuffer / texture"); m_fbo = 0; m_renderbuffer = 0; m_texture = 0; } FramebufferNoAttachmentsApiCase::IterateResult FramebufferNoAttachmentsApiCase::iterate() { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); bool isOk = true; GLint binding; GLenum targets[] = { GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER // equivalent to DRAW_FRAMEBUFFER }; GLenum bindings[] = { GL_DRAW_FRAMEBUFFER_BINDING, GL_READ_FRAMEBUFFER_BINDING, GL_FRAMEBUFFER_BINDING // equivalent to DRAW_FRAMEBUFFER_BINDING }; GLenum pnames[] = { GL_FRAMEBUFFER_DEFAULT_WIDTH, GL_FRAMEBUFFER_DEFAULT_HEIGHT, GL_FRAMEBUFFER_DEFAULT_SAMPLES, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS }; GLenum enums_invalid_list[] = { GL_NOTEQUAL, GL_FRONT_FACE, GL_PACK_ROW_LENGTH, GL_FIXED, GL_LINEAR_MIPMAP_NEAREST, GL_RGBA4, GL_TEXTURE_MAX_LOD, GL_RG32F, GL_ALIASED_POINT_SIZE_RANGE, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_DRAW_BUFFER7, GL_MAX_COMBINED_UNIFORM_BLOCKS, GL_MAX_VARYING_COMPONENTS, GL_SRGB, GL_RGB8UI, GL_IMAGE_BINDING_NAME, GL_TEXTURE_2D_MULTISAMPLE, GL_COMPRESSED_R11_EAC, GL_BUFFER_DATA_SIZE }; GLint default_values[] = { 0, 0, 0, GL_FALSE }; GLint valid_values[] = { 103, 104, 4, GL_TRUE }; GLint min_values[] = { 0, 0, 0, -1 }; // Skip min_value test for boolean GLint max_values[] = { 0, 0, 0, -1 }; // Skip max_value test for boolean. unsigned num_targets = sizeof(targets) / sizeof(GLenum); unsigned num_pnames = sizeof(pnames) / sizeof(GLenum); unsigned num_enums_invalid_list = sizeof(enums_invalid_list) / sizeof(GLenum); // Check for extra pnames allowed from supported extensions. vector<GLenum> pnames_ext; if (m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader") || m_context.getContextInfo().isExtensionSupported("GL_OES_geometry_shader")) { pnames_ext.push_back(GL_FRAMEBUFFER_DEFAULT_LAYERS); } // "Random" invalid enums distributed roughly evenly throughout 16bit enum number range. vector<GLenum> enums_invalid; if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader") && !m_context.getContextInfo().isExtensionSupported("GL_OES_geometry_shader")) { enums_invalid.push_back(GL_FRAMEBUFFER_DEFAULT_LAYERS); } for (unsigned i = 0; i < num_enums_invalid_list; ++i) { enums_invalid.push_back(enums_invalid_list[i]); } gl.getIntegerv(GL_MAX_FRAMEBUFFER_WIDTH, &max_values[0]); gl.getIntegerv(GL_MAX_FRAMEBUFFER_HEIGHT, &max_values[1]); gl.getIntegerv(GL_MAX_FRAMEBUFFER_SAMPLES, &max_values[2]); GLU_EXPECT_NO_ERROR( gl.getError(), "Querying GL_MAX_FRAMEBUFFER_WIDTH / GL_MAX_FRAMEBUFFER_HEIGHT / GL_MAX_FRAMEBUFFER_SAMPLES failed"); TCU_CHECK_MSG(max_values[0] >= 2048, "GL_MAX_FRAMEBUFFER_WIDTH does not meet minimum requirements"); TCU_CHECK_MSG(max_values[1] >= 2048, "GL_MAX_FRAMEBUFFER_HEIGHT does not meet minimum requirements"); TCU_CHECK_MSG(max_values[2] >= 4, "GL_MAX_FRAMEBUFFER_SAMPLES does not meet minimum requirements"); // It is valid to ask for number of samples > 0 and get // implementation defined value which is above the requested // value. We can use simple equality comparison by using // reported maximum number of samples in our valid value // set and get test. valid_values[2] = max_values[2]; // Invalid target for (unsigned i = 0; i < enums_invalid.size(); ++i) { GLenum target = enums_invalid[i]; bool is_valid = false; for (unsigned j = 0; j < num_targets; ++j) { if (target == targets[j]) { is_valid = true; break; } } if (is_valid) continue; for (unsigned j = 0; j < num_pnames; ++j) { gl.framebufferParameteri(target, pnames[j], valid_values[j]); GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_ENUM, "Using glFramebufferParameteri() with invalid target should set GL_INVALID_ENUM"); } } // For all valid targets for (unsigned i = 0; i < num_targets; ++i) { GLenum target = targets[i]; glGetIntegerv(bindings[i], &binding); GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetIntegerv() " "should not set GL error"); // Using default framebuffer - GL_INVALID_OPERATION for (unsigned j = 0; j < num_pnames; ++j) { GLint get_value = ~0; GLenum pname = pnames[j]; gl.framebufferParameteri(target, pname, valid_values[j]); if (binding == 0) { GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_OPERATION, "Using glFramebufferParameteri() on default framebuffer " "should set GL_INVALID_OPERATION"); } else { GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() " "should not set GL error"); } gl.getFramebufferParameteriv(target, pname, &get_value); if (binding == 0) { GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_OPERATION, "Using glGetFramebufferParameteriv() on default framebuffer " "should set GL_INVALID_OPERATION"); TCU_CHECK_MSG(get_value == ~0, "failed call to glGetFramebufferParameteriv() " "should not modify params"); } else { GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() " "should not set GL error"); } } // j == 0 : fbo without attachments // j == 1 : fbo with a multisample attachment for (unsigned j = 0; j < 2; ++j) { glGetIntegerv(bindings[i], &binding); GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetIntegerv() " "should not set GL error"); if (binding == 0) { // Check FBO status of default framebuffer // TODO Check presence of default framebuffer - default framebuffer is complete // only if it exists expect_fbo_status(target, GL_FRAMEBUFFER_COMPLETE, "Default framebuffer should be complete"); } // Invalid pname - GL_INVALID_VALUE begin_fbo(target, j); for (unsigned k = 0; k < enums_invalid.size(); ++k) { GLenum pname = enums_invalid[k]; bool is_valid = false; for (unsigned m = 0; m < num_pnames; ++m) { if (pname == pnames[m]) { is_valid = true; break; } } // Ignore any pnames that are added by extensions. for (unsigned m = 0; m < pnames_ext.size(); ++m) { if (pname == pnames_ext[m]) { is_valid = true; break; } } if (is_valid) continue; GLint get_value = ~0; gl.framebufferParameteri(target, pname, 0); GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_ENUM, "Calling glFramebufferParameteri() with invalid pname " "should set GL_INVALID_ENUM"); gl.getFramebufferParameteriv(target, pname, &get_value); GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_ENUM, "Calling glGetFramebufferParameteriv() with invalid pname " "should set GL_INVALID_ENUM"); TCU_CHECK_MSG(get_value == ~0, "Calling glGetFramebufferParameteriv() with invalid pname " "should not modify params"); } end_fbo(target); // Valid set and get begin_fbo(target, j); { for (unsigned k = 0; k < num_pnames; ++k) { GLint get_value = ~0; gl.framebufferParameteri(target, pnames[k], valid_values[k]); GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glFramebufferParameteri() " "should not set GL error"); gl.getFramebufferParameteriv(target, pnames[k], &get_value); GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() " "should not set GL error"); TCU_CHECK_MSG(get_value == valid_values[k], "glGetFramebufferParameteriv() " "should have returned the value set with glFramebufferParameteri()"); } // After valid set, check FBO status of user FBO expect_fbo_status(target, GL_FRAMEBUFFER_COMPLETE, "Framebuffer should be complete after setting valid valid"); } end_fbo(target); // Negative or too large values - GL_INVALID_VALUE // Also check for correct default values begin_fbo(target, j); for (unsigned k = 0; k < num_pnames; ++k) { GLint get_value = ~0; GLenum pname = pnames[k]; if (min_values[k] >= 0) { gl.framebufferParameteri(target, pname, min_values[k] - 1); GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_VALUE, "Calling glFramebufferParameteri() with negative value " "should set GL_INVALID_VALUE"); } gl.getFramebufferParameteriv(target, pname, &get_value); GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() " "should not set GL error"); TCU_CHECK_MSG(get_value == default_values[k], "glGetFramebufferParameteriv() " "did not return a valid default value"); get_value = ~0; if (max_values[k] >= 0) { gl.framebufferParameteri(target, pname, max_values[k] + 1); GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_VALUE, "Calling glFramebufferParameteri() too large value " "should set GL_INVALID_VALUE"); } gl.getFramebufferParameteriv(target, pname, &get_value); GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() " "should not set GL error"); TCU_CHECK_MSG(get_value == default_values[k], "glGetFramebufferParameteriv() " "did not return a valid default value"); } end_fbo(target); } } m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, isOk ? "Pass" : "Fail"); return STOP; } // Draw with imageStore, validate that framebuffer // default width and height is respected. class FramebufferNoAttachmentsRenderCase : public FramebufferNoAttachmentsBaseCase { public: FramebufferNoAttachmentsRenderCase(Context& context, const char* name, const char* description); IterateResult iterate(); void deinit(void); private: GLuint m_program; GLuint m_vertex_shader; GLuint m_fragment_shader; GLuint m_vao; GLuint m_framebuffer; GLuint m_texture; }; FramebufferNoAttachmentsRenderCase::FramebufferNoAttachmentsRenderCase(Context& context, const char* name, const char* description) : FramebufferNoAttachmentsBaseCase(context, name, description) , m_program(0) , m_vertex_shader(0) , m_fragment_shader(0) , m_vao(0) , m_framebuffer(0) , m_texture(0) { } void FramebufferNoAttachmentsRenderCase::deinit(void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.deleteShader(m_vertex_shader); gl.deleteShader(m_fragment_shader); gl.deleteProgram(m_program); gl.deleteVertexArrays(1, &m_vao); gl.deleteTextures(1, &m_texture); gl.deleteFramebuffers(1, &m_framebuffer); } FramebufferNoAttachmentsRenderCase::IterateResult FramebufferNoAttachmentsRenderCase::iterate() { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); int max_fragment_image_uniforms; bool isOk = true; // Check GL_MAX_FRAGMENT_IMAGE_UNIFORMS, we need at least one glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &max_fragment_image_uniforms); GLU_EXPECT_NO_ERROR(gl.getError(), "Querying GL_MAX_FRAGMENT_IMAGE_UNIFORMS"); if (max_fragment_image_uniforms < 1) { m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "GL_MAX_FRAGMENT_IMAGE_UNIFORMS<1"); return STOP; } // Create program and VAO { const char* vs_source = "#version 310 es\n" "void main()\n" "{\n" " if (gl_VertexID == 0) gl_Position = vec4(-1, -1, 0, 1);\n" " else if (gl_VertexID == 1) gl_Position = vec4(-1, 1, 0, 1);\n" " else if (gl_VertexID == 2) gl_Position = vec4( 1, 1, 0, 1);\n" " else gl_Position = vec4( 1, -1, 0, 1);\n" "}\n"; const char* fs_source = "#version 310 es\n" "precision highp uimage2D;\n" "layout(r32ui) uniform uimage2D data;\n" "void main()\n" "{\n" " ivec2 image_info = ivec2(gl_FragCoord.xy);\n" " imageStore(data, image_info, uvec4(1, 2, 3, 4));\n" "}\n"; m_program = gl.createProgram(); m_vertex_shader = gl.createShader(GL_VERTEX_SHADER); m_fragment_shader = gl.createShader(GL_FRAGMENT_SHADER); gl.shaderSource(m_vertex_shader, 1, &vs_source, NULL); gl.compileShader(m_vertex_shader); gl.attachShader(m_program, m_vertex_shader); gl.shaderSource(m_fragment_shader, 1, &fs_source, NULL); gl.compileShader(m_fragment_shader); gl.attachShader(m_program, m_fragment_shader); gl.linkProgram(m_program); gl.useProgram(m_program); gl.genVertexArrays(1, &m_vao); gl.bindVertexArray(m_vao); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating program and VAO"); } // Create framebuffer with no attachments gl.genFramebuffers(1, &m_framebuffer); gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebuffer); gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 32); gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, 32); expect_fbo_status(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, "Creating framebuffer with no attachments"); // Create texture and clear it, temporarily attaching to FBO { GLuint zero[] = { 0, 0, 0, 0 }; gl.genTextures(1, &m_texture); gl.bindTexture(GL_TEXTURE_2D, m_texture); gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 64, 64); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl.framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); gl.viewport(0, 0, 64, 64); gl.clearBufferuiv(GL_COLOR, 0, zero); gl.framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating and clearing texture"); } // Draw using storeImage gl.drawBuffers(0, NULL); gl.viewport(0, 0, 64, 64); gl.bindImageTexture(0, m_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI); gl.drawArrays(GL_TRIANGLE_FAN, 0, 4); gl.memoryBarrier(GL_ALL_BARRIER_BITS); GLU_EXPECT_NO_ERROR(gl.getError(), "Draw with imageStore"); // Read and validate texture contents { GLuint pixels[64 * 64 * 4]; gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_framebuffer); gl.framebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); expect_fbo_status(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, "ReadPixels to texture for validation"); gl.readPixels(0, 0, 64, 64, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); GLU_EXPECT_NO_ERROR(gl.getError(), "ReadPixels"); for (unsigned y = 0; y < 64; ++y) { for (unsigned x = 0; x < 64; ++x) { GLuint expected_value = (x < 32) && (y < 32) ? 1 : 0; GLuint value = pixels[(y * 64 + x) * 4]; TCU_CHECK_MSG(value == expected_value, "Validating draw with imageStore"); } } } m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, isOk ? "Pass" : "Fail"); return STOP; } FramebufferNoAttachmentsTests::FramebufferNoAttachmentsTests(Context& context) : TestCaseGroup(context, "framebuffer_no_attachments", "Framebuffer no attachments tests") { } FramebufferNoAttachmentsTests::~FramebufferNoAttachmentsTests(void) { } void FramebufferNoAttachmentsTests::init(void) { addChild(new FramebufferNoAttachmentsApiCase(m_context, "api", "Basic API verification")); addChild(new FramebufferNoAttachmentsRenderCase(m_context, "render", "Rendering with imageStore")); } } // glcts