| /*------------------------------------------------------------------------- |
| * OpenGL Conformance Test Suite |
| * ----------------------------- |
| * |
| * Copyright (c) 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 glcRobustBufferAccessBehaviorTests.cpp |
| * \brief Implements conformance tests for "Robust Buffer Access Behavior" functionality. |
| */ /*-------------------------------------------------------------------*/ |
| |
| #include "glcRobustBufferAccessBehaviorTests.hpp" |
| |
| #include "deSharedPtr.hpp" |
| #include "gluContextInfo.hpp" |
| #include "gluDefs.hpp" |
| #include "gluShaderUtil.hpp" |
| #include "glwEnums.hpp" |
| #include "glwFunctions.hpp" |
| #include "tcuCommandLine.hpp" |
| #include "tcuStringTemplate.hpp" |
| #include "tcuTestLog.hpp" |
| |
| #include <cstring> |
| #include <string> |
| |
| using namespace glw; |
| |
| namespace glcts |
| { |
| namespace RobustBufferAccessBehavior |
| { |
| /* Buffer constants */ |
| const GLuint Buffer::m_invalid_id = -1; |
| |
| const GLenum Buffer::m_targets[Buffer::m_n_targets] = { |
| GL_ARRAY_BUFFER, /* 0 */ |
| GL_ATOMIC_COUNTER_BUFFER, /* 1 */ |
| GL_COPY_READ_BUFFER, /* 2 */ |
| GL_COPY_WRITE_BUFFER, /* 3 */ |
| GL_DISPATCH_INDIRECT_BUFFER, /* 4 */ |
| GL_DRAW_INDIRECT_BUFFER, /* 5 */ |
| GL_ELEMENT_ARRAY_BUFFER, /* 6 */ |
| GL_PIXEL_PACK_BUFFER, /* 7 */ |
| GL_PIXEL_UNPACK_BUFFER, /* 8 */ |
| GL_QUERY_BUFFER, /* 9 */ |
| GL_SHADER_STORAGE_BUFFER, /* 10 */ |
| GL_TRANSFORM_FEEDBACK_BUFFER, /* 11 */ |
| GL_UNIFORM_BUFFER, /* 12 */ |
| }; |
| |
| /** Constructor. |
| * |
| * @param context CTS context. |
| **/ |
| Buffer::Buffer(const glw::Functions& gl) : m_id(m_invalid_id), m_gl(gl), m_target(GL_ARRAY_BUFFER) |
| { |
| } |
| |
| /** Destructor |
| * |
| **/ |
| Buffer::~Buffer() |
| { |
| Release(); |
| } |
| |
| /** Initialize buffer instance |
| * |
| * @param target Buffer target |
| * @param usage Buffer usage enum |
| * @param size <size> parameter |
| * @param data <data> parameter |
| **/ |
| void Buffer::InitData(glw::GLenum target, glw::GLenum usage, glw::GLsizeiptr size, const glw::GLvoid* data) |
| { |
| /* Delete previous buffer instance */ |
| Release(); |
| |
| m_target = target; |
| |
| Generate(m_gl, m_id); |
| Bind(m_gl, m_id, m_target); |
| Data(m_gl, m_target, usage, size, data); |
| } |
| |
| /** Release buffer instance |
| * |
| **/ |
| void Buffer::Release() |
| { |
| if (m_invalid_id != m_id) |
| { |
| m_gl.deleteBuffers(1, &m_id); |
| m_id = m_invalid_id; |
| } |
| } |
| |
| /** Binds buffer to its target |
| * |
| **/ |
| void Buffer::Bind() const |
| { |
| Bind(m_gl, m_id, m_target); |
| } |
| |
| /** Binds indexed buffer |
| * |
| * @param index <index> parameter |
| **/ |
| void Buffer::BindBase(glw::GLuint index) const |
| { |
| BindBase(m_gl, m_id, m_target, index); |
| } |
| |
| /** Bind buffer to given target |
| * |
| * @param gl GL functions |
| * @param id Id of buffer |
| * @param target Buffer target |
| **/ |
| void Buffer::Bind(const glw::Functions& gl, glw::GLuint id, glw::GLenum target) |
| { |
| gl.bindBuffer(target, id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindBuffer"); |
| } |
| |
| /** Binds indexed buffer |
| * |
| * @param gl GL functions |
| * @param id Id of buffer |
| * @param target Buffer target |
| * @param index <index> parameter |
| **/ |
| void Buffer::BindBase(const glw::Functions& gl, glw::GLuint id, glw::GLenum target, glw::GLuint index) |
| { |
| gl.bindBufferBase(target, index, id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindBufferBase"); |
| } |
| |
| /** Allocate memory for buffer and sends initial content |
| * |
| * @param gl GL functions |
| * @param target Buffer target |
| * @param usage Buffer usage enum |
| * @param size <size> parameter |
| * @param data <data> parameter |
| **/ |
| void Buffer::Data(const glw::Functions& gl, glw::GLenum target, glw::GLenum usage, glw::GLsizeiptr size, |
| const glw::GLvoid* data) |
| { |
| gl.bufferData(target, size, data, usage); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BufferData"); |
| } |
| |
| /** Generate buffer |
| * |
| * @param gl GL functions |
| * @param out_id Id of buffer |
| **/ |
| void Buffer::Generate(const glw::Functions& gl, glw::GLuint& out_id) |
| { |
| GLuint id = m_invalid_id; |
| |
| gl.genBuffers(1, &id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenBuffers"); |
| |
| if (m_invalid_id == id) |
| { |
| TCU_FAIL("Got invalid id"); |
| } |
| |
| out_id = id; |
| } |
| |
| /** Update range of buffer |
| * |
| * @param gl GL functions |
| * @param target Buffer target |
| * @param offset Offset in buffer |
| * @param size <size> parameter |
| * @param data <data> parameter |
| **/ |
| void Buffer::SubData(const glw::Functions& gl, glw::GLenum target, glw::GLintptr offset, glw::GLsizeiptr size, |
| glw::GLvoid* data) |
| { |
| gl.bufferSubData(target, offset, size, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BufferSubData"); |
| } |
| |
| /* Framebuffer constants */ |
| const GLuint Framebuffer::m_invalid_id = -1; |
| |
| /** Constructor. |
| * |
| * @param context CTS context. |
| **/ |
| Framebuffer::Framebuffer(const glw::Functions& gl) : m_id(m_invalid_id), m_gl(gl) |
| { |
| /* Nothing to done here */ |
| } |
| |
| /** Destructor |
| * |
| **/ |
| Framebuffer::~Framebuffer() |
| { |
| Release(); |
| } |
| |
| /** Release texture instance |
| * |
| **/ |
| void Framebuffer::Release() |
| { |
| if (m_invalid_id != m_id) |
| { |
| m_gl.deleteFramebuffers(1, &m_id); |
| m_id = m_invalid_id; |
| } |
| } |
| |
| /** Attach texture to specified attachment |
| * |
| * @param gl GL functions |
| * @param target Framebuffer target |
| * @param attachment Attachment |
| * @param texture_id Texture id |
| * @param level Level of mipmap |
| * @param width Texture width |
| * @param height Texture height |
| **/ |
| void Framebuffer::AttachTexture(const glw::Functions& gl, glw::GLenum target, glw::GLenum attachment, |
| glw::GLuint texture_id, glw::GLint level, glw::GLuint width, glw::GLuint height) |
| { |
| gl.framebufferTexture(target, attachment, texture_id, level); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "FramebufferTexture"); |
| |
| gl.viewport(0 /* x */, 0 /* y */, width, height); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Viewport"); |
| } |
| |
| /** Binds framebuffer to DRAW_FRAMEBUFFER |
| * |
| * @param gl GL functions |
| * @param target Framebuffer target |
| * @param id ID of framebuffer |
| **/ |
| void Framebuffer::Bind(const glw::Functions& gl, glw::GLenum target, glw::GLuint id) |
| { |
| gl.bindFramebuffer(target, id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindFramebuffer"); |
| } |
| |
| /** Generate framebuffer |
| * |
| **/ |
| void Framebuffer::Generate(const glw::Functions& gl, glw::GLuint& out_id) |
| { |
| GLuint id = m_invalid_id; |
| |
| gl.genFramebuffers(1, &id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenFramebuffers"); |
| |
| if (m_invalid_id == id) |
| { |
| TCU_FAIL("Invalid id"); |
| } |
| |
| out_id = id; |
| } |
| |
| /* Program constants */ |
| const GLuint Program::m_invalid_id = 0; |
| |
| /** Constructor. |
| * |
| * @param context CTS context. |
| **/ |
| Program::Program(const glw::Functions& gl) |
| : m_id(m_invalid_id) |
| , m_compute(gl) |
| , m_fragment(gl) |
| , m_geometry(gl) |
| , m_tess_ctrl(gl) |
| , m_tess_eval(gl) |
| , m_vertex(gl) |
| , m_gl(gl) |
| { |
| /* Nothing to be done here */ |
| } |
| |
| /** Destructor |
| * |
| **/ |
| Program::~Program() |
| { |
| Release(); |
| } |
| |
| /** Initialize program instance |
| * |
| * @param compute_shader Compute shader source code |
| * @param fragment_shader Fragment shader source code |
| * @param geometry_shader Geometry shader source code |
| * @param tesselation_control_shader Tesselation control shader source code |
| * @param tesselation_evaluation_shader Tesselation evaluation shader source code |
| * @param vertex_shader Vertex shader source code |
| **/ |
| void Program::Init(const std::string& compute_shader, const std::string& fragment_shader, |
| const std::string& geometry_shader, const std::string& tesselation_control_shader, |
| const std::string& tesselation_evaluation_shader, const std::string& vertex_shader) |
| { |
| /* Delete previous program */ |
| Release(); |
| |
| /* Initialize shaders */ |
| m_compute.Init(GL_COMPUTE_SHADER, compute_shader); |
| m_fragment.Init(GL_FRAGMENT_SHADER, fragment_shader); |
| m_geometry.Init(GL_GEOMETRY_SHADER, geometry_shader); |
| m_tess_ctrl.Init(GL_TESS_CONTROL_SHADER, tesselation_control_shader); |
| m_tess_eval.Init(GL_TESS_EVALUATION_SHADER, tesselation_evaluation_shader); |
| m_vertex.Init(GL_VERTEX_SHADER, vertex_shader); |
| |
| /* Create program, set up transform feedback and attach shaders */ |
| Create(m_gl, m_id); |
| Attach(m_gl, m_id, m_compute.m_id); |
| Attach(m_gl, m_id, m_fragment.m_id); |
| Attach(m_gl, m_id, m_geometry.m_id); |
| Attach(m_gl, m_id, m_tess_ctrl.m_id); |
| Attach(m_gl, m_id, m_tess_eval.m_id); |
| Attach(m_gl, m_id, m_vertex.m_id); |
| |
| /* Link program */ |
| Link(m_gl, m_id); |
| } |
| |
| /** Release program instance |
| * |
| **/ |
| void Program::Release() |
| { |
| if (m_invalid_id != m_id) |
| { |
| Use(m_gl, m_invalid_id); |
| |
| m_gl.deleteProgram(m_id); |
| m_id = m_invalid_id; |
| } |
| |
| m_compute.Release(); |
| m_fragment.Release(); |
| m_geometry.Release(); |
| m_tess_ctrl.Release(); |
| m_tess_eval.Release(); |
| m_vertex.Release(); |
| } |
| |
| /** Set program as active |
| * |
| **/ |
| void Program::Use() const |
| { |
| Use(m_gl, m_id); |
| } |
| |
| /** Attach shader to program |
| * |
| * @param gl GL functions |
| * @param program_id Id of program |
| * @param shader_id Id of shader |
| **/ |
| void Program::Attach(const glw::Functions& gl, glw::GLuint program_id, glw::GLuint shader_id) |
| { |
| /* Sanity checks */ |
| if ((m_invalid_id == program_id) || (Shader::m_invalid_id == shader_id)) |
| { |
| return; |
| } |
| |
| gl.attachShader(program_id, shader_id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "AttachShader"); |
| } |
| |
| /** Create program instance |
| * |
| * @param gl GL functions |
| * @param out_id Id of program |
| **/ |
| void Program::Create(const glw::Functions& gl, glw::GLuint& out_id) |
| { |
| const GLuint id = gl.createProgram(); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CreateProgram"); |
| |
| if (m_invalid_id == id) |
| { |
| TCU_FAIL("Failed to create program"); |
| } |
| |
| out_id = id; |
| } |
| |
| /** Link program |
| * |
| * @param gl GL functions |
| * @param id Id of program |
| **/ |
| void Program::Link(const glw::Functions& gl, glw::GLuint id) |
| { |
| GLint status = GL_FALSE; |
| |
| gl.linkProgram(id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "LinkProgram"); |
| |
| /* Get link status */ |
| gl.getProgramiv(id, GL_LINK_STATUS, &status); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetProgramiv"); |
| |
| /* Log link error */ |
| if (GL_TRUE != status) |
| { |
| glw::GLint length = 0; |
| std::string message; |
| |
| /* Get error log length */ |
| gl.getProgramiv(id, GL_INFO_LOG_LENGTH, &length); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetProgramiv"); |
| |
| message.resize(length, 0); |
| |
| /* Get error log */ |
| gl.getProgramInfoLog(id, length, 0, &message[0]); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetProgramInfoLog"); |
| |
| TCU_FAIL(message.c_str()); |
| } |
| } |
| |
| /** Use program |
| * |
| * @param gl GL functions |
| * @param id Id of program |
| **/ |
| void Program::Use(const glw::Functions& gl, glw::GLuint id) |
| { |
| gl.useProgram(id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "UseProgram"); |
| } |
| |
| /* Shader's constants */ |
| const GLuint Shader::m_invalid_id = 0; |
| |
| /** Constructor. |
| * |
| * @param context CTS context. |
| **/ |
| Shader::Shader(const glw::Functions& gl) : m_id(m_invalid_id), m_gl(gl) |
| { |
| /* Nothing to be done here */ |
| } |
| |
| /** Destructor |
| * |
| **/ |
| Shader::~Shader() |
| { |
| Release(); |
| } |
| |
| /** Initialize shader instance |
| * |
| * @param stage Shader stage |
| * @param source Source code |
| **/ |
| void Shader::Init(glw::GLenum stage, const std::string& source) |
| { |
| if (true == source.empty()) |
| { |
| /* No source == no shader */ |
| return; |
| } |
| |
| /* Delete any previous shader */ |
| Release(); |
| |
| Create(m_gl, stage, m_id); |
| Source(m_gl, m_id, source); |
| |
| Compile(m_gl, m_id); |
| } |
| |
| /** Release shader instance |
| * |
| **/ |
| void Shader::Release() |
| { |
| if (m_invalid_id != m_id) |
| { |
| m_gl.deleteShader(m_id); |
| m_id = m_invalid_id; |
| } |
| } |
| |
| /** Compile shader |
| * |
| * @param gl GL functions |
| * @param id Shader id |
| **/ |
| void Shader::Compile(const glw::Functions& gl, glw::GLuint id) |
| { |
| GLint status = GL_FALSE; |
| |
| /* Compile */ |
| gl.compileShader(id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CompileShader"); |
| |
| /* Get compilation status */ |
| gl.getShaderiv(id, GL_COMPILE_STATUS, &status); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetShaderiv"); |
| |
| /* Log compilation error */ |
| if (GL_TRUE != status) |
| { |
| glw::GLint length = 0; |
| std::string message; |
| |
| /* Error log length */ |
| gl.getShaderiv(id, GL_INFO_LOG_LENGTH, &length); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetShaderiv"); |
| |
| /* Prepare storage */ |
| message.resize(length, 0); |
| |
| /* Get error log */ |
| gl.getShaderInfoLog(id, length, 0, &message[0]); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetShaderInfoLog"); |
| |
| TCU_FAIL(message.c_str()); |
| } |
| } |
| |
| /** Create shader |
| * |
| * @param gl GL functions |
| * @param stage Shader stage |
| * @param out_id Shader id |
| **/ |
| void Shader::Create(const glw::Functions& gl, glw::GLenum stage, glw::GLuint& out_id) |
| { |
| const GLuint id = gl.createShader(stage); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CreateShader"); |
| |
| if (m_invalid_id == id) |
| { |
| TCU_FAIL("Failed to create shader"); |
| } |
| |
| out_id = id; |
| } |
| |
| /** Set shader's source code |
| * |
| * @param gl GL functions |
| * @param id Shader id |
| * @param source Shader source code |
| **/ |
| void Shader::Source(const glw::Functions& gl, glw::GLuint id, const std::string& source) |
| { |
| const GLchar* code = source.c_str(); |
| |
| gl.shaderSource(id, 1 /* count */, &code, 0 /* lengths */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderSource"); |
| } |
| |
| /* Texture static fields */ |
| const GLuint Texture::m_invalid_id = -1; |
| |
| /** Constructor. |
| * |
| * @param context CTS context. |
| **/ |
| Texture::Texture(const glw::Functions& gl) : m_id(m_invalid_id), m_gl(gl) |
| { |
| /* Nothing to done here */ |
| } |
| |
| /** Destructor |
| * |
| **/ |
| Texture::~Texture() |
| { |
| Release(); |
| } |
| |
| /** Release texture instance |
| * |
| **/ |
| void Texture::Release() |
| { |
| if (m_invalid_id != m_id) |
| { |
| m_gl.deleteTextures(1, &m_id); |
| m_id = m_invalid_id; |
| } |
| } |
| |
| /** Bind texture to target |
| * |
| * @param gl GL functions |
| * @param id Id of texture |
| * @param tex_type Type of texture |
| **/ |
| void Texture::Bind(const glw::Functions& gl, glw::GLuint id, glw::GLenum target) |
| { |
| gl.bindTexture(target, id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindTexture"); |
| } |
| |
| /** Set contents of compressed texture |
| * |
| * @param gl GL functions |
| * @param target Texture target |
| * @param level Mipmap level |
| * @param internal_format Format of data |
| * @param width Width of texture |
| * @param height Height of texture |
| * @param depth Depth of texture |
| * @param image_size Size of data |
| * @param data Buffer with image data |
| **/ |
| void Texture::CompressedImage(const glw::Functions& gl, glw::GLenum target, glw::GLint level, |
| glw::GLenum internal_format, glw::GLuint width, glw::GLuint height, glw::GLuint depth, |
| glw::GLsizei image_size, const glw::GLvoid* data) |
| { |
| switch (target) |
| { |
| case GL_TEXTURE_1D: |
| gl.compressedTexImage1D(target, level, internal_format, width, 0 /* border */, image_size, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CompressedTexImage1D"); |
| break; |
| case GL_TEXTURE_1D_ARRAY: |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_RECTANGLE: |
| gl.compressedTexImage2D(target, level, internal_format, width, height, 0 /* border */, image_size, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CompressedTexImage2D"); |
| break; |
| case GL_TEXTURE_CUBE_MAP: |
| gl.compressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, level, internal_format, width, height, 0 /* border */, |
| image_size, data); |
| gl.compressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, level, internal_format, width, height, 0 /* border */, |
| image_size, data); |
| gl.compressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, level, internal_format, width, height, 0 /* border */, |
| image_size, data); |
| gl.compressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, level, internal_format, width, height, 0 /* border */, |
| image_size, data); |
| gl.compressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, level, internal_format, width, height, 0 /* border */, |
| image_size, data); |
| gl.compressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, level, internal_format, width, height, 0 /* border */, |
| image_size, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CompressedTexImage2D"); |
| break; |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_2D_ARRAY: |
| gl.compressedTexImage3D(target, level, internal_format, width, height, depth, 0 /* border */, image_size, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CompressedTexImage3D"); |
| break; |
| default: |
| TCU_FAIL("Invliad enum"); |
| break; |
| } |
| } |
| |
| /** Generate texture instance |
| * |
| * @param gl GL functions |
| * @param out_id Id of texture |
| **/ |
| void Texture::Generate(const glw::Functions& gl, glw::GLuint& out_id) |
| { |
| GLuint id = m_invalid_id; |
| |
| gl.genTextures(1, &id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenTextures"); |
| |
| if (m_invalid_id == id) |
| { |
| TCU_FAIL("Invalid id"); |
| } |
| |
| out_id = id; |
| } |
| |
| /** Get texture data |
| * |
| * @param gl GL functions |
| * @param target Texture target |
| * @param format Format of data |
| * @param type Type of data |
| * @param out_data Buffer for data |
| **/ |
| void Texture::GetData(const glw::Functions& gl, glw::GLint level, glw::GLenum target, glw::GLenum format, |
| glw::GLenum type, glw::GLvoid* out_data) |
| { |
| gl.getTexImage(target, level, format, type, out_data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetTexImage"); |
| } |
| |
| /** Get texture data |
| * |
| * @param gl GL functions |
| * @param id Texture id |
| * @param level Mipmap level |
| * @param width Texture width |
| * @param height Texture height |
| * @param format Format of data |
| * @param type Type of data |
| * @param out_data Buffer for data |
| **/ |
| void Texture::GetData(const glw::Functions& gl, glw::GLuint id, glw::GLint level, glw::GLuint width, glw::GLuint height, |
| glw::GLenum format, glw::GLenum type, glw::GLvoid* out_data) |
| { |
| GLuint fbo; |
| gl.genFramebuffers(1, &fbo); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenFramebuffers"); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindFramebuffer"); |
| gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, level); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "FramebufferTexture2D"); |
| |
| gl.readPixels(0, 0, width, height, format, type, out_data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "ReadPixels"); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, 0); |
| } |
| |
| /** Generate texture instance |
| * |
| * @param gl GL functions |
| * @param target Texture target |
| * @param level Mipmap level |
| * @param pname Parameter to query |
| * @param param Result of query |
| **/ |
| void Texture::GetLevelParameter(const glw::Functions& gl, glw::GLenum target, glw::GLint level, glw::GLenum pname, |
| glw::GLint* param) |
| { |
| gl.getTexLevelParameteriv(target, level, pname, param); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GetTexLevelParameteriv"); |
| } |
| |
| /** Set contents of texture |
| * |
| * @param gl GL functions |
| * @param target Texture target |
| * @param level Mipmap level |
| * @param internal_format Format of data |
| * @param width Width of texture |
| * @param height Height of texture |
| * @param depth Depth of texture |
| * @param format Format of data |
| * @param type Type of data |
| * @param data Buffer with image data |
| **/ |
| void Texture::Image(const glw::Functions& gl, glw::GLenum target, glw::GLint level, glw::GLenum internal_format, |
| glw::GLuint width, glw::GLuint height, glw::GLuint depth, glw::GLenum format, glw::GLenum type, |
| const glw::GLvoid* data) |
| { |
| switch (target) |
| { |
| case GL_TEXTURE_1D: |
| gl.texImage1D(target, level, internal_format, width, 0 /* border */, format, type, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexImage1D"); |
| break; |
| case GL_TEXTURE_1D_ARRAY: |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_RECTANGLE: |
| gl.texImage2D(target, level, internal_format, width, height, 0 /* border */, format, type, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexImage2D"); |
| break; |
| case GL_TEXTURE_CUBE_MAP: |
| gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, level, internal_format, width, height, 0 /* border */, format, |
| type, data); |
| gl.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, level, internal_format, width, height, 0 /* border */, format, |
| type, data); |
| gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, level, internal_format, width, height, 0 /* border */, format, |
| type, data); |
| gl.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, level, internal_format, width, height, 0 /* border */, format, |
| type, data); |
| gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, level, internal_format, width, height, 0 /* border */, format, |
| type, data); |
| gl.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, level, internal_format, width, height, 0 /* border */, format, |
| type, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexImage2D"); |
| break; |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_2D_ARRAY: |
| gl.texImage3D(target, level, internal_format, width, height, depth, 0 /* border */, format, type, data); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexImage3D"); |
| break; |
| default: |
| TCU_FAIL("Invliad enum"); |
| break; |
| } |
| } |
| |
| /** Allocate storage for texture |
| * |
| * @param gl GL functions |
| * @param target Texture target |
| * @param levels Number of levels |
| * @param internal_format Internal format of texture |
| * @param width Width of texture |
| * @param height Height of texture |
| * @param depth Depth of texture |
| **/ |
| void Texture::Storage(const glw::Functions& gl, glw::GLenum target, glw::GLsizei levels, glw::GLenum internal_format, |
| glw::GLuint width, glw::GLuint height, glw::GLuint depth) |
| { |
| switch (target) |
| { |
| case GL_TEXTURE_1D: |
| gl.texStorage1D(target, levels, internal_format, width); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexStorage1D"); |
| break; |
| case GL_TEXTURE_1D_ARRAY: |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_RECTANGLE: |
| case GL_TEXTURE_CUBE_MAP: |
| gl.texStorage2D(target, levels, internal_format, width, height); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexStorage2D"); |
| break; |
| case GL_TEXTURE_2D_MULTISAMPLE: |
| gl.texStorage2DMultisample(target, levels, internal_format, width, height, GL_FALSE); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexStorage2DMultisample"); |
| break; |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_2D_ARRAY: |
| gl.texStorage3D(target, levels, internal_format, width, height, depth); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexStorage3D"); |
| break; |
| default: |
| TCU_FAIL("Invliad enum"); |
| break; |
| } |
| } |
| |
| /** Set contents of texture |
| * |
| * @param gl GL functions |
| * @param target Texture target |
| * @param level Mipmap level |
| * @param x X offset |
| * @param y Y offset |
| * @param z Z offset |
| * @param width Width of texture |
| * @param height Height of texture |
| * @param depth Depth of texture |
| * @param format Format of data |
| * @param type Type of data |
| * @param pixels Buffer with image data |
| **/ |
| void Texture::SubImage(const glw::Functions& gl, glw::GLenum target, glw::GLint level, glw::GLint x, glw::GLint y, |
| glw::GLint z, glw::GLsizei width, glw::GLsizei height, glw::GLsizei depth, glw::GLenum format, |
| glw::GLenum type, const glw::GLvoid* pixels) |
| { |
| switch (target) |
| { |
| case GL_TEXTURE_1D: |
| gl.texSubImage1D(target, level, x, width, format, type, pixels); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexSubImage1D"); |
| break; |
| case GL_TEXTURE_1D_ARRAY: |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_RECTANGLE: |
| gl.texSubImage2D(target, level, x, y, width, height, format, type, pixels); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexSubImage2D"); |
| break; |
| case GL_TEXTURE_CUBE_MAP: |
| gl.texSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, level, x, y, width, height, format, type, pixels); |
| gl.texSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, level, x, y, width, height, format, type, pixels); |
| gl.texSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, level, x, y, width, height, format, type, pixels); |
| gl.texSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, level, x, y, width, height, format, type, pixels); |
| gl.texSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, level, x, y, width, height, format, type, pixels); |
| gl.texSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, level, x, y, width, height, format, type, pixels); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexSubImage2D"); |
| break; |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_2D_ARRAY: |
| gl.texSubImage3D(target, level, x, y, z, width, height, depth, format, type, pixels); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "TexSubImage3D"); |
| break; |
| default: |
| TCU_FAIL("Invliad enum"); |
| break; |
| } |
| } |
| |
| /* VertexArray constants */ |
| const GLuint VertexArray::m_invalid_id = -1; |
| |
| /** Constructor. |
| * |
| * @param context CTS context. |
| **/ |
| VertexArray::VertexArray(const glw::Functions& gl) : m_id(m_invalid_id), m_gl(gl) |
| { |
| } |
| |
| /** Destructor |
| * |
| **/ |
| VertexArray::~VertexArray() |
| { |
| Release(); |
| } |
| |
| /** Release vertex array object instance |
| * |
| **/ |
| void VertexArray::Release() |
| { |
| if (m_invalid_id != m_id) |
| { |
| Bind(m_gl, 0); |
| |
| m_gl.deleteVertexArrays(1, &m_id); |
| |
| m_id = m_invalid_id; |
| } |
| } |
| |
| /** Binds Vertex array object |
| * |
| * @param gl GL functions |
| * @param id ID of vertex array object |
| **/ |
| void VertexArray::Bind(const glw::Functions& gl, glw::GLuint id) |
| { |
| gl.bindVertexArray(id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindVertexArray"); |
| } |
| |
| /** Generates Vertex array object |
| * |
| * @param gl GL functions |
| * @param out_id ID of vertex array object |
| **/ |
| void VertexArray::Generate(const glw::Functions& gl, glw::GLuint& out_id) |
| { |
| GLuint id = m_invalid_id; |
| |
| gl.genVertexArrays(1, &id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "GenVertexArrays"); |
| |
| if (m_invalid_id == id) |
| { |
| TCU_FAIL("Invalid id"); |
| } |
| |
| out_id = id; |
| } |
| |
| template <typename TYPE> |
| void initPixels(std::vector<TYPE>& pixels, GLuint n_pixels, GLuint n_channels) |
| { |
| if (n_channels == 1) |
| { |
| for (GLuint i = 0; i < n_pixels; ++i) |
| pixels[i] = static_cast<TYPE>(i); |
| } |
| else if (n_channels == 2) |
| { |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| GLuint idx = i * 2; |
| pixels[idx] = static_cast<TYPE>(i); |
| pixels[idx + 1] = pixels[idx]; |
| } |
| } |
| else if (n_channels == 4) |
| { |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| GLuint idx = i * 4; |
| pixels[idx] = static_cast<TYPE>(i); |
| pixels[idx + 1] = pixels[idx]; |
| pixels[idx + 2] = pixels[idx]; |
| pixels[idx + 3] = pixels[idx]; |
| } |
| } |
| else |
| TCU_FAIL("Unsuported number of channels"); |
| } |
| |
| RobustnessBase::RobustnessBase(tcu::TestContext& testCtx, const char* name, const char* description, |
| glu::ApiType apiType) |
| : tcu::TestCase(testCtx, name, description), m_api_type(apiType), m_has_khr_robust_buffer_access(false) |
| { |
| } |
| |
| glu::RenderContext* RobustnessBase::createRobustContext(glu::ResetNotificationStrategy reset) |
| { |
| // Create test context to verify if required extensions are available |
| { |
| deqp::Context context(m_testCtx, glu::ContextType(m_api_type)); |
| const glu::ContextInfo& contextInfo = context.getContextInfo(); |
| glu::ContextType context_type = context.getRenderContext().getType(); |
| if (!contextInfo.isExtensionSupported("GL_KHR_robustness") && |
| !contextSupports(context_type, glu::ApiType::es(3, 2))) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "GL_KHR_robustness extension not supported"); |
| return NULL; |
| } |
| |
| m_has_khr_robust_buffer_access = contextInfo.isExtensionSupported("GL_KHR_robust_buffer_access_behavior") || |
| contextInfo.isExtensionSupported("GL_ARB_robust_buffer_access_behavior") || |
| contextSupports(context_type, glu::ApiType::core(4, 5)); |
| if (!m_has_khr_robust_buffer_access && !contextSupports(context_type, glu::ApiType::core(4, 3))) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, |
| "robust_buffer_access_behavior extension not supported"); |
| return NULL; |
| } |
| |
| glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(context_type); |
| m_specializationMap["VERSION"] = glu::getGLSLVersionDeclaration(glslVersion); |
| m_context_is_es = glu::isContextTypeES(context_type); |
| } |
| |
| glu::RenderConfig renderCfg(glu::ContextType(m_api_type, glu::CONTEXT_ROBUST)); |
| const tcu::CommandLine& commandLine = m_testCtx.getCommandLine(); |
| glu::parseRenderConfig(&renderCfg, commandLine); |
| |
| if (commandLine.getSurfaceType() == tcu::SURFACETYPE_WINDOW) |
| renderCfg.resetNotificationStrategy = reset; |
| else |
| throw tcu::NotSupportedError("Test not supported in non-windowed context"); |
| |
| /* Try to create core/es robusness context */ |
| return createRenderContext(m_testCtx.getPlatform(), commandLine, renderCfg); |
| } |
| |
| /** Constructor |
| * |
| * @param testCtx Test context |
| **/ |
| VertexBufferObjectsTest::VertexBufferObjectsTest(tcu::TestContext& testCtx, glu::ApiType apiType) |
| : RobustnessBase(testCtx, "vertex_buffer_objects", "Verifies that out-of-bound reads from VB result in zero", |
| apiType) |
| { |
| /* Nothing to be done */ |
| } |
| |
| /** Execute test |
| * |
| * @return tcu::TestNode::STOP |
| **/ |
| tcu::TestNode::IterateResult VertexBufferObjectsTest::iterate() |
| { |
| de::SharedPtr<glu::RenderContext> robustContext(createRobustContext()); |
| if (!robustContext.get()) |
| return STOP; |
| |
| static const GLuint invalid_elements[] = { |
| 9, 1, 12, 10, 2, 3, 11, 3, 4, 12, 4, 5, 13, 5, 6, 14, 6, 7, 15, 7, 8, 16, 8, 1, |
| }; |
| |
| static const GLuint valid_elements[] = { |
| 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7, 0, 7, 8, 0, 8, 1, |
| }; |
| |
| static const GLfloat vertices[] = { |
| 0.0f, 0.0f, 0.0f, /* 0 */ |
| -1.0f, 0.0f, 0.0f, /* 1 */ |
| -1.0f, 1.0f, 0.0f, /* 2 */ |
| 0.0f, 1.0f, 0.0f, /* 3 */ |
| 1.0f, 1.0f, 0.0f, /* 4 */ |
| 1.0f, 0.0f, 0.0f, /* 5 */ |
| 1.0f, -1.0f, 0.0f, /* 6 */ |
| 0.0f, -1.0f, 0.0f, /* 7 */ |
| -1.0f, -1.0f, 0.0f, /* 8 */ |
| }; |
| |
| static const GLuint height = 8; |
| static const GLuint n_vertices = 24; |
| static const GLuint width = 8; |
| |
| /* GL entry points */ |
| const Functions& gl = robustContext->getFunctions(); |
| |
| /* Test case objects */ |
| Framebuffer framebuffer(gl); |
| Program program(gl); |
| Texture texture(gl); |
| Buffer elements_buffer(gl); |
| Buffer vertices_buffer(gl); |
| VertexArray vao(gl); |
| |
| /* Vertex array */ |
| VertexArray::Generate(gl, vao.m_id); |
| VertexArray::Bind(gl, vao.m_id); |
| |
| /* Buffers initialization */ |
| elements_buffer.InitData(GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(valid_elements), valid_elements); |
| vertices_buffer.InitData(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(vertices), vertices); |
| |
| /* Texture initialization */ |
| Texture::Generate(gl, texture.m_id); |
| Texture::Bind(gl, texture.m_id, GL_TEXTURE_2D); |
| Texture::Storage(gl, GL_TEXTURE_2D, 1, GL_R8UI, width, height, 0); |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Framebuffer initialization*/ |
| Framebuffer::Generate(gl, framebuffer.m_id); |
| Framebuffer::Bind(gl, GL_DRAW_FRAMEBUFFER, framebuffer.m_id); |
| Framebuffer::AttachTexture(gl, GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture.m_id, 0 /* level */, width, |
| height); |
| |
| /* Shaders initialization */ |
| program.Init("" /* cs */, getFragmentShader(), "" /* gs */, "" /* tcs */, "" /* tes */, getVertexShader()); |
| Program::Use(gl, program.m_id); |
| |
| /* Vertex buffer initialization */ |
| vertices_buffer.Bind(); |
| gl.bindVertexBuffer(0 /* bindindex = location */, vertices_buffer.m_id, 0 /* offset */, 12 /* stride */); |
| gl.vertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, NULL); |
| gl.enableVertexAttribArray(0 /* location */); |
| |
| /* Binding elements/indices buffer */ |
| elements_buffer.Bind(); |
| |
| cleanTexture(gl, texture.m_id); |
| |
| gl.drawElements(GL_TRIANGLES, n_vertices, GL_UNSIGNED_INT, 0 /* indices */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DrawElements"); |
| |
| if (false == verifyValidResults(gl, texture.m_id)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid result for valid input" << tcu::TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| return tcu::TestNode::STOP; |
| } |
| |
| /* Generate invalid data sets */ |
| const GLuint invalid_elements_offsets[] = { |
| 0, // close fetch |
| 4 * 1024, // near fetch (4K of the end of the object) |
| 1024 * 1024, // medium fetch (1MB past the end of the object) |
| 10 * 1024 * 1024 // high fetch (10MB beyond the end of the object) |
| }; |
| const GLuint invalid_buffers_count = DE_LENGTH_OF_ARRAY(invalid_elements_offsets); |
| const GLuint item_count = DE_LENGTH_OF_ARRAY(invalid_elements); |
| GLuint invalid_elements_set[invalid_buffers_count][item_count]; |
| for (GLuint buffer_index = 0; buffer_index < invalid_buffers_count; ++buffer_index) |
| { |
| for (GLuint item_index = 0; item_index < item_count; ++item_index) |
| invalid_elements_set[buffer_index][item_index] = |
| invalid_elements[item_index] + invalid_elements_offsets[buffer_index]; |
| } |
| |
| for (GLuint buffer_index = 0; buffer_index < invalid_buffers_count; ++buffer_index) |
| { |
| /* Create elements/indices buffer */ |
| elements_buffer.InitData(GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(invalid_elements_set[buffer_index]), |
| invalid_elements_set[buffer_index]); |
| elements_buffer.Bind(); |
| |
| cleanTexture(gl, texture.m_id); |
| |
| gl.drawElements(GL_TRIANGLES, n_vertices, GL_UNSIGNED_INT, 0 /* indices */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DrawElements"); |
| |
| if (false == verifyInvalidResults(gl, texture.m_id)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid result for invalid input" |
| << tcu::TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| return tcu::TestNode::STOP; |
| } |
| } |
| |
| /* Done */ |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return tcu::TestNode::STOP; |
| } |
| |
| /** Prepare shader for current test case |
| * |
| * @return Source |
| **/ |
| std::string VertexBufferObjectsTest::getFragmentShader() |
| { |
| const char* source = "${VERSION}\n" |
| "layout (location = 0) out lowp uvec4 out_fs_color;\n" |
| "void main()\n" |
| "{\n" |
| " out_fs_color = uvec4(1, 255, 255, 255);\n" |
| "}\n"; |
| return tcu::StringTemplate(source).specialize(m_specializationMap); |
| } |
| |
| /** Prepare shader for current test case |
| * |
| * @return Source |
| **/ |
| std::string VertexBufferObjectsTest::getVertexShader() |
| { |
| const char* source = "${VERSION}\n" |
| "layout (location = 0) in vec4 in_vs_position;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = in_vs_position;\n" |
| "}\n"; |
| |
| return tcu::StringTemplate(source).specialize(m_specializationMap); |
| } |
| |
| /** Fill texture with value 128 |
| * |
| * @param texture_id Id of texture |
| **/ |
| void VertexBufferObjectsTest::cleanTexture(const Functions& gl, glw::GLuint texture_id) |
| { |
| static const GLuint height = 8; |
| static const GLuint width = 8; |
| |
| GLubyte pixels[width * height]; |
| for (GLuint i = 0; i < width * height; ++i) |
| { |
| pixels[i] = 128; |
| } |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::SubImage(gl, GL_TEXTURE_2D, 0 /* level */, 0 /* x */, 0 /* y */, 0 /* z */, width, height, 0 /* depth */, |
| GL_RED_INTEGER, GL_UNSIGNED_BYTE, pixels); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| } |
| |
| /** Verifies that texutre is not filled with 1 |
| * |
| * @param texture_id Id of texture |
| * |
| * @return false when image is filled with 1, true otherwise |
| **/ |
| bool VertexBufferObjectsTest::verifyInvalidResults(const Functions& gl, glw::GLuint texture_id) |
| { |
| // In OpenGL ES there is undefined out-of-bound behavior - no verification |
| if (m_context_is_es) |
| return true; |
| return !verifyResults(gl, texture_id); |
| } |
| |
| /** Verifies that texutre is filled with 1 |
| * |
| * @param texture_id Id of texture |
| * |
| * @return true when image is filled with 1, false otherwise |
| **/ |
| bool VertexBufferObjectsTest::verifyValidResults(const Functions& gl, glw::GLuint texture_id) |
| { |
| return verifyResults(gl, texture_id); |
| } |
| |
| /** Verifies that texutre is filled with 1 |
| * |
| * @param texture_id Id of texture |
| * |
| * @return true when image is filled with 1, false otherwise |
| **/ |
| bool VertexBufferObjectsTest::verifyResults(const Functions& gl, glw::GLuint texture_id) |
| { |
| static const GLuint height = 8; |
| static const GLuint width = 8; |
| GLuint pixel_size = 4 * sizeof(GLuint); |
| GLuint expected_value = 1; |
| |
| std::vector<GLubyte> pixels(width * height * pixel_size, 0); |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| Texture::GetData(gl, texture_id, 0 /* level */, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < pixels.size(); i += pixel_size) |
| { |
| if (expected_value != pixels[i]) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** Constructor |
| * |
| * @param testCtx Test context |
| **/ |
| TexelFetchTest::TexelFetchTest(tcu::TestContext& testCtx, glu::ApiType apiType) |
| : RobustnessBase(testCtx, "texel_fetch", "Verifies that out-of-bound fetches from texture result in zero", apiType) |
| , m_test_case(R8) |
| { |
| /* Nothing to be done */ |
| } |
| |
| /** Constructor |
| * |
| * @param testCtx Test context |
| * @param name Test name |
| * @param description Test description |
| * @param apiType Api type |
| **/ |
| TexelFetchTest::TexelFetchTest(tcu::TestContext& testCtx, const char* name, const char* description, |
| glu::ApiType apiType) |
| : RobustnessBase(testCtx, name, description, apiType), m_test_case(R8) |
| { |
| /* Nothing to be done */ |
| } |
| |
| /** Execute test |
| * |
| * @return tcu::TestNode::STOP |
| **/ |
| tcu::TestNode::IterateResult TexelFetchTest::iterate() |
| { |
| de::SharedPtr<glu::RenderContext> robustContext(createRobustContext()); |
| if (!robustContext.get()) |
| return STOP; |
| |
| /* Constants */ |
| static const GLuint height = 16; |
| static const GLuint width = 16; |
| |
| /* GL entry points */ |
| const Functions& gl = robustContext->getFunctions(); |
| |
| /* Test result indicator */ |
| bool test_result = true; |
| |
| GLuint invalid_fetch_offsets[] = { |
| 16, // near fetch |
| 512, // medium fetch |
| 1008, // high fetch |
| }; |
| GLuint fetch_offsets_count = sizeof(invalid_fetch_offsets) / sizeof(GLuint); |
| glu::ContextType contextType = robustContext->getType(); |
| |
| /* Iterate over all cases */ |
| for (; m_test_case < LAST; m_test_case = (TEST_CASES)((GLuint)m_test_case + 1)) |
| { |
| GLint level = 0; |
| GLenum texture_target = GL_TEXTURE_2D; |
| |
| if (R32UI_MULTISAMPLE == m_test_case || RG8_SNORM == m_test_case) |
| { |
| // 1. RG8_SNORM case: |
| // Skip RG8_SNORM format case. |
| // RG8_SNORM is not required to be used as a render target |
| // OpenGL 4.5 Core Spec, Page 197 |
| // |
| // 2. R32UI_MULTISAMPLE case |
| // Skip test in multi sample case |
| // texelFetch with invalid lod plane results undefined value |
| // OpenGL 4.5 Core Spec, around page 377 |
| m_test_case = (TEST_CASES)((GLuint)m_test_case + 1); |
| continue; |
| } |
| |
| /* */ |
| Texture destination_texture(gl); |
| Framebuffer framebuffer(gl); |
| Texture source_texture(gl); |
| Program program(gl); |
| VertexArray vao(gl); |
| |
| /* Prepare VAO */ |
| VertexArray::Generate(gl, vao.m_id); |
| VertexArray::Bind(gl, vao.m_id); |
| |
| /* Prepare textures */ |
| Texture::Generate(gl, destination_texture.m_id); |
| Texture::Generate(gl, source_texture.m_id); |
| |
| if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| GLint max_integer_samples; |
| gl.getIntegerv(GL_MAX_INTEGER_SAMPLES, &max_integer_samples); |
| GLint max_image_samples; |
| gl.getIntegerv(GL_MAX_IMAGE_SAMPLES, &max_image_samples); |
| if (max_integer_samples < 4 || max_image_samples < 4) |
| { |
| /* prepareTexture() hard-codes 4 samples (n_levels) for |
| * R32UI_MULTISAMPLE case. This value exceeds the required |
| * min-max value (1 in OpenGL ES 3.2) and is not supported |
| * by all implementations. |
| * |
| * Also, the test uses a compute shader with images |
| * to upload the texture so max_image_samples >= 4 |
| * is also required. |
| */ |
| m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << getTestCaseName() << " not supported" |
| << tcu::TestLog::EndMessage; |
| |
| continue; |
| } |
| } |
| |
| prepareTexture(gl, false, destination_texture.m_id); |
| prepareTexture(gl, true, source_texture.m_id); |
| |
| /* Select FBO settings */ |
| if (R32UI_MIPMAP == m_test_case) |
| { |
| level = 1; |
| } |
| else if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| texture_target = GL_TEXTURE_2D_MULTISAMPLE; |
| } |
| |
| /* Prepare FBO */ |
| Framebuffer::Generate(gl, framebuffer.m_id); |
| Framebuffer::Bind(gl, GL_DRAW_FRAMEBUFFER, framebuffer.m_id); |
| Framebuffer::AttachTexture(gl, GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, destination_texture.m_id, level, |
| width, height); |
| |
| /* Prepare valid program */ |
| program.Init("" /* cs */, getFragmentShader(contextType, true), getGeometryShader(), "" /* tcs */, "" /* tes */, |
| getVertexShader()); |
| |
| /* Test valid case */ |
| /* Set program */ |
| Program::Use(gl, program.m_id); |
| |
| /* Set texture */ |
| gl.activeTexture(GL_TEXTURE0); /* location = 0 */ |
| GLU_EXPECT_NO_ERROR(gl.getError(), "ActiveTexture"); |
| Texture::Bind(gl, source_texture.m_id, texture_target); |
| gl.uniform1i(0 /* location */, 0 /* texture unit */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| /* Check if setup is supported */ |
| GLenum fbo_status = gl.checkFramebufferStatus(GL_DRAW_FRAMEBUFFER); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "CheckFramebufferStatus"); |
| if (GL_FRAMEBUFFER_COMPLETE != fbo_status) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << getTestCaseName() << " not supported" |
| << tcu::TestLog::EndMessage; |
| |
| continue; |
| } |
| |
| /* Enable multisampling */ |
| if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| gl.enable(GL_MULTISAMPLE); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Enable"); |
| } |
| |
| /* Draw */ |
| gl.drawArrays(GL_POINTS, 0 /* first */, 1 /* count */); |
| { |
| /* Get error from draw */ |
| GLenum error = gl.getError(); |
| |
| /* Disable multisampling */ |
| if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| gl.disable(GL_MULTISAMPLE); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Disable"); |
| } |
| |
| /* Handle error from draw */ |
| GLU_EXPECT_NO_ERROR(error, "DrawArrays"); |
| } |
| |
| /* Verification */ |
| if (false == verifyValidResults(gl, destination_texture.m_id)) |
| { |
| test_result = false; |
| } |
| |
| /* Test invalid cases */ |
| for (GLuint index = 0; index < fetch_offsets_count; ++index) |
| { |
| /* Prepare invalid program */ |
| program.Init("" /* cs */, getFragmentShader(contextType, false, invalid_fetch_offsets[index]), |
| getGeometryShader(), "" /* tcs */, "" /* tes */, getVertexShader()); |
| Program::Use(gl, program.m_id); |
| Framebuffer::Bind(gl, GL_DRAW_FRAMEBUFFER, framebuffer.m_id); |
| |
| /* Set texture */ |
| gl.activeTexture(GL_TEXTURE0); /* location = 0 */ |
| GLU_EXPECT_NO_ERROR(gl.getError(), "ActiveTexture"); |
| Texture::Bind(gl, source_texture.m_id, texture_target); |
| gl.uniform1i(0 /* location */, 0 /* texture unit */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| /* Draw */ |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| gl.drawArrays(GL_POINTS, 0 /* first */, 1 /* count */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DrawArrays"); |
| |
| /* Verification */ |
| if (false == verifyInvalidResults(gl, destination_texture.m_id)) |
| { |
| test_result = false; |
| m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << getTestCaseName() << " failed for " |
| << invalid_fetch_offsets[index] << " offset" << tcu::TestLog::EndMessage; |
| } |
| } |
| } |
| |
| /* Set result */ |
| if (true == test_result) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| } |
| else |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| } |
| |
| /* Done */ |
| return tcu::TestNode::STOP; |
| } |
| |
| /** Prepares source code for fragment shader |
| * |
| * @param is_case_valid Selects if valid or invalid case is tested |
| * |
| * @return string with prepared code |
| **/ |
| std::string TexelFetchTest::getFragmentShader(const glu::ContextType&, bool is_case_valid, GLuint fetch_offset) |
| { |
| const GLchar* source = "${VERSION}\n" |
| "in lowp vec2 gs_fs_tex_coord;\n" |
| "layout (location = 0) out lowp ${TYPE} out_fs_color;\n" |
| "layout (location = 0) uniform lowp ${SAMPLER} uni_texture;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " ivec2 point = ivec2(gs_fs_tex_coord * 16.0 + float(${OFFSET}));\n" |
| " out_fs_color = texelFetch(uni_texture, point, ${PLANE});\n" |
| "}\n"; |
| |
| m_specializationMap["PLANE"] = "0"; |
| m_specializationMap["SAMPLER"] = "sampler2D"; |
| m_specializationMap["TYPE"] = "vec4"; |
| |
| if (R32UI_MIPMAP == m_test_case) |
| { |
| m_specializationMap["PLANE"] = "1"; |
| m_specializationMap["SAMPLER"] = "usampler2D"; |
| m_specializationMap["TYPE"] = "uvec4"; |
| |
| if (false == is_case_valid) |
| { |
| fetch_offset = 0; |
| m_specializationMap["PLANE"] = "2"; |
| } |
| } |
| else if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| m_specializationMap["PLANE"] = "9"; |
| m_specializationMap["SAMPLER"] = "usampler2DMS"; |
| m_specializationMap["TYPE"] = "uvec4"; |
| |
| if (false == is_case_valid) |
| { |
| fetch_offset = 0; |
| m_specializationMap["PLANE"] = "gl_SampleID"; |
| } |
| } |
| |
| std::stringstream offset; |
| offset << fetch_offset; |
| m_specializationMap["OFFSET"] = offset.str(); |
| |
| return tcu::StringTemplate(source).specialize(m_specializationMap); |
| } |
| |
| /** Prepare shader for current test case |
| * |
| * @return Source |
| **/ |
| std::string TexelFetchTest::getGeometryShader() |
| { |
| static const GLchar* source = "${VERSION}\n" |
| "layout(points) in;\n" |
| "layout(triangle_strip, max_vertices = 4) out;\n" |
| "\n" |
| "out vec2 gs_fs_tex_coord;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gs_fs_tex_coord = vec2(0, 0);\n" |
| " gl_Position = vec4(-1, -1, 0, 1);\n" |
| " EmitVertex();\n" |
| "\n" |
| " gs_fs_tex_coord = vec2(0, 1);\n" |
| " gl_Position = vec4(-1, 1, 0, 1);\n" |
| " EmitVertex();\n" |
| "\n" |
| " gs_fs_tex_coord = vec2(1, 0);\n" |
| " gl_Position = vec4(1, -1, 0, 1);\n" |
| " EmitVertex();\n" |
| "\n" |
| " gs_fs_tex_coord = vec2(1, 1);\n" |
| " gl_Position = vec4(1, 1, 0, 1);\n" |
| " EmitVertex();\n" |
| "}\n"; |
| |
| return tcu::StringTemplate(source).specialize(m_specializationMap); |
| } |
| |
| /** Prepare shader for current test case |
| * |
| * @return Source |
| **/ |
| std::string TexelFetchTest::getVertexShader() |
| { |
| static const GLchar* source = "${VERSION}\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(0, 0, 0, 1);\n" |
| "}\n"; |
| return tcu::StringTemplate(source).specialize(m_specializationMap); |
| } |
| |
| /** Returns name of current test case |
| * |
| * @return Name of test case |
| **/ |
| const glw::GLchar* TexelFetchTest::getTestCaseName() const |
| { |
| const GLchar* name = ""; |
| |
| switch (m_test_case) |
| { |
| case R8: |
| name = "Sampling GL_R8 texture"; |
| break; |
| case RG8_SNORM: |
| name = "Sampling GL_RG8_SNORM texture"; |
| break; |
| case RGBA32F: |
| name = "Sampling GL_RGBA32F texture"; |
| break; |
| case R32UI_MIPMAP: |
| name = "Sampling mipmap of GL_32UI texture"; |
| break; |
| case R32UI_MULTISAMPLE: |
| name = "Sampling GL_32UI multisampled texture"; |
| break; |
| default: |
| TCU_FAIL("Invalid enum"); |
| break; |
| } |
| |
| return name; |
| } |
| |
| /** Prepare a texture |
| * |
| * @param is_source Selects if texutre will be used as source or destination |
| * @param texture_id Id of texutre |
| **/ |
| void TexelFetchTest::prepareTexture(const Functions& gl, bool is_source, glw::GLuint texture_id) |
| { |
| /* Image size */ |
| static const GLuint image_height = 16; |
| static const GLuint image_width = 16; |
| |
| /* Texture storage parameters */ |
| GLuint height = image_height; |
| GLenum internal_format = 0; |
| GLsizei n_levels = 1; |
| GLenum target = GL_TEXTURE_2D; |
| GLuint width = image_width; |
| |
| /* Prepare texture storage parameters */ |
| switch (m_test_case) |
| { |
| case R8: |
| internal_format = GL_R8; |
| break; |
| case RG8_SNORM: |
| internal_format = GL_RG8_SNORM; |
| break; |
| case RGBA32F: |
| internal_format = GL_RGBA32F; |
| break; |
| case R32UI_MIPMAP: |
| height = 2 * image_height; |
| internal_format = GL_R32UI; |
| n_levels = 2; |
| width = 2 * image_width; |
| break; |
| case R32UI_MULTISAMPLE: |
| internal_format = GL_R32UI; |
| n_levels = 4; |
| target = GL_TEXTURE_2D_MULTISAMPLE; |
| break; |
| default: |
| TCU_FAIL("Invalid enum"); |
| } |
| |
| /* Prepare storage */ |
| Texture::Bind(gl, texture_id, target); |
| Texture::Storage(gl, target, n_levels, internal_format, width, height, 0); |
| |
| /* Set samplers to NEAREST/NEAREST if required. The results of texelFetch builtins |
| are undefined if the computed level of detail is not the texture's base level and |
| the texture's minification filter is NEAREST or LINEAR. */ |
| if (R32UI_MIPMAP == m_test_case) |
| { |
| gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); |
| gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| } |
| else if (R32UI_MULTISAMPLE != m_test_case) |
| { |
| gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| } |
| |
| /* Destination image can be left empty */ |
| if (false == is_source) |
| { |
| Texture::Bind(gl, 0, target); |
| return; |
| } |
| |
| /* Prepare texture */ |
| if (R8 == m_test_case) |
| { |
| GLubyte source_pixels[image_width * image_height]; |
| for (GLuint i = 0; i < image_width * image_height; ++i) |
| { |
| source_pixels[i] = static_cast<GLubyte>(i); |
| } |
| |
| Texture::SubImage(gl, GL_TEXTURE_2D, 0 /* level */, 0 /* x */, 0 /* y */, 0 /* z */, width, height, |
| 0 /* depth */, GL_RED, GL_UNSIGNED_BYTE, source_pixels); |
| } |
| else if (RG8_SNORM == m_test_case) |
| { |
| static const GLuint n_components = 2; |
| |
| GLbyte source_pixels[image_width * image_height * n_components]; |
| for (GLuint i = 0; i < image_width * image_height; ++i) |
| { |
| source_pixels[i * n_components + 0] = static_cast<GLubyte>((i % 16) - 8); |
| source_pixels[i * n_components + 1] = static_cast<GLubyte>((i / 16) - 8); |
| } |
| |
| Texture::SubImage(gl, GL_TEXTURE_2D, 0 /* level */, 0 /* x */, 0 /* y */, 0 /* z */, width, height, |
| 0 /* depth */, GL_RG, GL_BYTE, source_pixels); |
| } |
| else if (RGBA32F == m_test_case) |
| { |
| static const GLuint n_components = 4; |
| |
| GLfloat source_pixels[image_width * image_height * n_components]; |
| for (GLuint i = 0; i < image_width * image_height; ++i) |
| { |
| source_pixels[i * n_components + 0] = (GLfloat)(i % 16) / 16.0f; |
| source_pixels[i * n_components + 1] = (GLfloat)(i / 16) / 16.0f; |
| source_pixels[i * n_components + 2] = (GLfloat)i / 256.0f; |
| source_pixels[i * n_components + 3] = 1.0f; |
| } |
| |
| Texture::SubImage(gl, GL_TEXTURE_2D, 0 /* level */, 0 /* x */, 0 /* y */, 0 /* z */, width, height, |
| 0 /* depth */, GL_RGBA, GL_FLOAT, source_pixels); |
| } |
| else if (R32UI_MIPMAP == m_test_case) |
| { |
| GLuint source_pixels[image_width * image_height]; |
| for (GLuint i = 0; i < image_width * image_height; ++i) |
| { |
| source_pixels[i] = i; |
| } |
| |
| Texture::SubImage(gl, GL_TEXTURE_2D, 1 /* level */, 0 /* x */, 0 /* y */, 0 /* z */, image_width, image_height, |
| 0 /* depth */, GL_RED_INTEGER, GL_UNSIGNED_INT, source_pixels); |
| } |
| else if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| /* Compute shader */ |
| static const GLchar* source = |
| "${VERSION}\n" |
| "\n" |
| "layout (local_size_x = ${LOCAL_SIZE}, local_size_y = ${LOCAL_SIZE}, local_size_z = 1) in;\n" |
| "layout (${QUALIFIERS}) writeonly uniform highp uimage2DMS uni_image;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " const ivec2 point = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);\n" |
| " const uint index = gl_WorkGroupID.y * 16U + gl_WorkGroupID.x;\n" |
| "\n" |
| " imageStore(uni_image, point, 0, uvec4(index + 0U, 0, 0, 0));\n" |
| " imageStore(uni_image, point, 1, uvec4(index + 1U, 0, 0, 0));\n" |
| " imageStore(uni_image, point, 2, uvec4(index + 2U, 0, 0, 0));\n" |
| " imageStore(uni_image, point, 3, uvec4(index + 3U, 0, 0, 0));\n" |
| "}\n" |
| "\n"; |
| |
| if (m_context_is_es) |
| { |
| m_specializationMap["LOCAL_SIZE"] = "16"; |
| m_specializationMap["QUALIFIERS"] = "binding = 0, r32ui"; |
| } |
| else |
| { |
| m_specializationMap["LOCAL_SIZE"] = "1"; |
| m_specializationMap["QUALIFIERS"] = "location = 0"; |
| } |
| |
| Program program(gl); |
| std::string cs = tcu::StringTemplate(source).specialize(m_specializationMap); |
| program.Init(cs, "", "", "", "", ""); |
| program.Use(); |
| |
| gl.bindImageTexture(0 /* unit */, texture_id, 0 /* level */, GL_FALSE /* layered */, 0 /* layer */, |
| GL_WRITE_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| |
| if (!m_context_is_es) |
| { |
| gl.uniform1i(0 /* location */, 0 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| } |
| |
| gl.dispatchCompute(16, 16, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| } |
| |
| Texture::Bind(gl, 0, target); |
| } |
| |
| /** Verifies that texutre is filled with 0 or with (0, 0, 0, x), |
| * where x may be 0, 1 or the biggest representable integer value. |
| * |
| * @param texture_id Id of texture |
| * |
| * @return true when image is filled with 0, 1 or biggest represetable integer number, false otherwise |
| **/ |
| bool TexelFetchTest::verifyInvalidResults(const Functions& gl, glw::GLuint texture_id) |
| { |
| static const GLuint height = 16; |
| static const GLuint width = 16; |
| static const GLuint n_pixels = height * width; |
| |
| // OpenGL ES has undefined out-of-bound behavior - no verification |
| if (m_context_is_es) |
| return true; |
| |
| bool result = true; |
| |
| if (R8 == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| std::vector<GLubyte> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLubyte expected_red = 0; |
| const GLubyte drawn_red = pixels[i * n_channels]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLuint)drawn_red |
| << ". Expected value: " << (GLuint)expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (RG8_SNORM == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| std::vector<GLbyte> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RGBA, GL_BYTE, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLbyte expected_red = 0; |
| const GLbyte expected_green = 0; |
| const GLbyte drawn_red = pixels[i * n_channels + 0]; |
| const GLbyte drawn_green = pixels[i * n_channels + 1]; |
| |
| if ((expected_red != drawn_red) || (expected_green != drawn_green)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLint)drawn_red << ", " |
| << (GLint)drawn_green << ". Expected value: " << (GLint)expected_red << ", " |
| << (GLint)expected_green << ". At offset: " << i << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (RGBA32F == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| std::vector<GLfloat> pixels(n_pixels * n_channels); |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint idx = i * n_channels; |
| const GLfloat value = static_cast<GLfloat>(i) / n_pixels; |
| pixels[idx + 0] = value; |
| pixels[idx + 1] = value; |
| pixels[idx + 2] = value; |
| pixels[idx + 3] = value; |
| } |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RGBA, GL_FLOAT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLfloat expected_red = 0.0f; |
| const GLfloat expected_green = 0.0f; |
| const GLfloat expected_blue = 0.0f; |
| const GLfloat expected_alpha_0 = |
| 0.0f; /* OpenGL 4.5 (and ES) specifies two possiblities (0 or 1) for alpha channel (Chapter 11.1.3.12). */ |
| const GLfloat expected_alpha_1 = 1.0f; |
| const GLfloat drawn_red = pixels[i * n_channels + 0]; |
| const GLfloat drawn_green = pixels[i * n_channels + 1]; |
| const GLfloat drawn_blue = pixels[i * n_channels + 2]; |
| const GLfloat drawn_alpha = pixels[i * n_channels + 3]; |
| |
| const GLfloat precision = 0.0009765625; /* (1.f / 1024.f) */ |
| |
| if ((de::abs(expected_red - drawn_red) > precision) || |
| (de::abs(expected_green - drawn_green) > precision) || |
| (de::abs(expected_blue - drawn_blue) > precision) || |
| ((de::abs(expected_alpha_0 - drawn_alpha) > precision) && |
| (de::abs(expected_alpha_1 - drawn_alpha) > precision))) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red << ", " << drawn_green |
| << ", " << drawn_blue << ", " << drawn_alpha << ". Expected value: " << expected_red |
| << ", " << expected_green << ", " << expected_blue << ", " << expected_alpha_0 |
| << " or " << expected_alpha_1 << ". At offset: " << i << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (R32UI_MIPMAP == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| std::vector<GLuint> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, texture_id, 1 /* level */, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint expected_red = 0; |
| const GLuint drawn_red = pixels[i * n_channels]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red |
| << ". Expected value: " << expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| /* Compute shader */ |
| static const GLchar* source = |
| "${VERSION}\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "\n" |
| "layout (location = 1) writeonly uniform uimage2D uni_destination_image;\n" |
| "layout (location = 0, r32ui) readonly uniform uimage2DMS uni_source_image;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " const ivec2 point = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);\n" |
| "\n" |
| " const uvec4 color_0 = imageLoad(uni_source_image, point, 0);\n" |
| " const uvec4 color_1 = imageLoad(uni_source_image, point, 1);\n" |
| " const uvec4 color_2 = imageLoad(uni_source_image, point, 2);\n" |
| " const uvec4 color_3 = imageLoad(uni_source_image, point, 3);\n" |
| "\n" |
| " if (any(equal(uvec4(color_0.r, color_1.r, color_2.r, color_3.r), uvec4(0))))\n" |
| " {\n" |
| " imageStore(uni_destination_image, point, uvec4(1, 1, 1, 1));\n" |
| " }\n" |
| " else\n" |
| " {\n" |
| " imageStore(uni_destination_image, point, uvec4(0, 0, 0, 0));\n" |
| " }\n" |
| "}\n" |
| "\n"; |
| |
| Program program(gl); |
| Texture destination_texture(gl); |
| |
| Texture::Generate(gl, destination_texture.m_id); |
| Texture::Bind(gl, destination_texture.m_id, GL_TEXTURE_2D); |
| Texture::Storage(gl, GL_TEXTURE_2D, 1, GL_R32UI, width, height, 0 /* depth */); |
| |
| std::string cs = tcu::StringTemplate(source).specialize(m_specializationMap); |
| program.Init(cs, "", "", "", "", ""); |
| program.Use(); |
| gl.bindImageTexture(0 /* unit */, texture_id, 0 /* level */, GL_FALSE /* layered */, 0 /* layer */, |
| GL_READ_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| gl.bindImageTexture(1 /* unit */, destination_texture.m_id, 0 /* level */, GL_FALSE /* layered */, |
| 0 /* layer */, GL_WRITE_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| |
| gl.uniform1i(0 /* location */, 0 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| gl.uniform1i(1 /* location */, 1 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| gl.dispatchCompute(16, 16, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| |
| /* Pixels buffer initialization */ |
| std::vector<GLuint> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint expected_red = 1; |
| const GLuint drawn_red = pixels[i * n_channels]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red |
| << ". Expected value: " << expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Verifies that texutre is filled with increasing values |
| * |
| * @param texture_id Id of texture |
| * |
| * @return true when image is filled with increasing values, false otherwise |
| **/ |
| bool TexelFetchTest::verifyValidResults(const Functions& gl, glw::GLuint texture_id) |
| { |
| static const GLuint height = 16; |
| static const GLuint width = 16; |
| static const GLuint n_pixels = height * width; |
| |
| bool result = true; |
| |
| if (R8 == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| std::vector<GLubyte> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::GetData(gl, texture_id, 0 /* level */, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLubyte expected_red = static_cast<GLubyte>(i); |
| const GLubyte drawn_red = pixels[i * n_channels]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLuint)drawn_red |
| << ". Expected value: " << (GLuint)expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (RG8_SNORM == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| std::vector<GLbyte> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::GetData(gl, texture_id, 0 /* level */, width, height, GL_RGBA, GL_BYTE, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLbyte expected_red = static_cast<GLubyte>((i % 16) - 8); |
| const GLbyte expected_green = static_cast<GLubyte>((i / 16) - 8); |
| const GLbyte drawn_red = pixels[i * n_channels + 0]; |
| const GLbyte drawn_green = pixels[i * n_channels + 1]; |
| |
| if ((expected_red != drawn_red) || (expected_green != drawn_green)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLint)drawn_red << ", " |
| << (GLint)drawn_green << ". Expected value: " << (GLint)expected_red << ", " |
| << (GLint)expected_green << ". At offset: " << i << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (RGBA32F == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| std::vector<GLfloat> pixels(n_pixels * n_channels); |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint idx = i * n_channels; |
| const GLfloat value = static_cast<GLfloat>(i) / n_pixels; |
| pixels[idx + 0] = value; |
| pixels[idx + 1] = value; |
| pixels[idx + 2] = value; |
| pixels[idx + 3] = value; |
| } |
| |
| Texture::GetData(gl, texture_id, 0 /* level */, width, height, GL_RGBA, GL_FLOAT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLfloat expected_red = (GLfloat)(i % 16) / 16.0f; |
| const GLfloat expected_green = (GLfloat)(i / 16) / 16.0f; |
| const GLfloat expected_blue = (GLfloat)i / 256.0f; |
| const GLfloat expected_alpha = 1.0f; |
| const GLuint idx = i * n_channels; |
| const GLfloat drawn_red = pixels[idx + 0]; |
| const GLfloat drawn_green = pixels[idx + 1]; |
| const GLfloat drawn_blue = pixels[idx + 2]; |
| const GLfloat drawn_alpha = pixels[idx + 3]; |
| |
| if ((expected_red != drawn_red) || (expected_green != drawn_green) || (expected_blue != drawn_blue) || |
| (expected_alpha != drawn_alpha)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red << ", " << drawn_green |
| << ", " << drawn_blue << ", " << drawn_alpha << ". Expected value: " << expected_red |
| << ", " << expected_green << ", " << expected_blue << ", " << expected_alpha |
| << ". At offset: " << i << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (R32UI_MIPMAP == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| std::vector<GLuint> pixels(n_pixels * n_channels, 0); |
| |
| Texture::GetData(gl, texture_id, 1 /* level */, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint expected_red = i; |
| const GLuint drawn_red = pixels[i * n_channels]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red |
| << ". Expected value: " << expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| /* Compute shader */ |
| static const GLchar* source = |
| "${VERSION}\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "\n" |
| "layout (location = 1, r32ui) writeonly uniform uimage2D uni_destination_image;\n" |
| "layout (location = 0, r32ui) readonly uniform uimage2DMS uni_source_image;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " const ivec2 point = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);\n" |
| " const uint index = gl_WorkGroupID.y * 16U + gl_WorkGroupID.x;\n" |
| "\n" |
| " const uvec4 color_0 = imageLoad(uni_source_image, point, 0);\n" |
| " const uvec4 color_1 = imageLoad(uni_source_image, point, 1);\n" |
| " const uvec4 color_2 = imageLoad(uni_source_image, point, 2);\n" |
| " const uvec4 color_3 = imageLoad(uni_source_image, point, 3);\n" |
| "\n" |
| " if (any(equal(uvec4(color_0.r, color_1.r, color_2.r, color_3.r), uvec4(index + 3U))))\n" |
| " {\n" |
| " imageStore(uni_destination_image, point, uvec4(1U));\n" |
| " }\n" |
| " else\n" |
| " {\n" |
| " imageStore(uni_destination_image, point, uvec4(0U));\n" |
| " }\n" |
| "}\n" |
| "\n"; |
| |
| Program program(gl); |
| Texture destination_texture(gl); |
| |
| Texture::Generate(gl, destination_texture.m_id); |
| Texture::Bind(gl, destination_texture.m_id, GL_TEXTURE_2D); |
| Texture::Storage(gl, GL_TEXTURE_2D, 1, GL_R32UI, width, height, 0 /* depth */); |
| |
| std::string cs = tcu::StringTemplate(source).specialize(m_specializationMap); |
| program.Init(cs, "", "", "", "", ""); |
| program.Use(); |
| gl.bindImageTexture(0 /* unit */, texture_id, 0 /* level */, GL_FALSE /* layered */, 0 /* layer */, |
| GL_READ_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| gl.bindImageTexture(1 /* unit */, destination_texture.m_id, 0 /* level */, GL_FALSE /* layered */, |
| 0 /* layer */, GL_WRITE_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| |
| if (!m_context_is_es) |
| { |
| gl.uniform1i(0 /* location */, 0 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| gl.uniform1i(1 /* location */, 1 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| } |
| |
| gl.dispatchCompute(16, 16, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| |
| /* Pixels buffer initialization */ |
| std::vector<GLuint> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::GetData(gl, destination_texture.m_id, 0 /* level */, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, |
| &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint expected_red = 1; |
| const GLuint drawn_red = pixels[i * n_channels]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red |
| << ". Expected value: " << expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Constructor |
| * |
| * @param testCtx Test context |
| * @param apiType Api type |
| **/ |
| ImageLoadStoreTest::ImageLoadStoreTest(tcu::TestContext& testCtx, glu::ApiType apiType) |
| : TexelFetchTest(testCtx, "image_load_store", "Verifies that out-of-bound to image result in zero or is discarded", |
| apiType) |
| { |
| } |
| |
| /** Execute test |
| * |
| * @return tcu::TestNode::STOP |
| **/ |
| tcu::TestNode::IterateResult ImageLoadStoreTest::iterate() |
| { |
| de::SharedPtr<glu::RenderContext> robustContext(createRobustContext()); |
| if (!robustContext.get()) |
| return STOP; |
| |
| /* Constants */ |
| static const GLuint height = 16; |
| static const GLuint width = 16; |
| |
| /* GL entry points */ |
| const Functions& gl = robustContext->getFunctions(); |
| |
| struct FetchingOffset |
| { |
| GLuint coord_offset; |
| GLuint sample_offset; |
| }; |
| const FetchingOffset fetching_offsets[] = { |
| { 16, 4 }, { 512, 4 }, { 1024, 8 }, { 2048, 8 }, |
| }; |
| |
| /* For ES start from RGBA32F as R8, R32UI_MULTISAMPLE and R8_SNORM are not supported */ |
| if (m_context_is_es) |
| m_test_case = RGBA32F; |
| |
| /* Test result indicator */ |
| bool test_result = true; |
| |
| /* Iterate over all cases */ |
| for (; m_test_case < LAST; m_test_case = (TEST_CASES)((GLuint)m_test_case + 1)) |
| { |
| /* Test case result indicator */ |
| bool case_result = true; |
| |
| if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| // Skip invalid program test in multi sample case |
| // texelFetch with invalid lod plane results undefined value |
| // OpenGL 4.5 Core Spec, around page 377 |
| continue; |
| } |
| |
| /* Test case objects */ |
| Texture destination_texture(gl); |
| Texture source_texture(gl); |
| Program program(gl); |
| |
| /* Prepare textures */ |
| Texture::Generate(gl, destination_texture.m_id); |
| Texture::Generate(gl, source_texture.m_id); |
| |
| if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| GLint max_integer_samples; |
| gl.getIntegerv(GL_MAX_INTEGER_SAMPLES, &max_integer_samples); |
| GLint max_image_samples; |
| gl.getIntegerv(GL_MAX_IMAGE_SAMPLES, &max_image_samples); |
| if (max_integer_samples < 4 || max_image_samples < 4) |
| { |
| /* prepareTexture() hard-codes 4 samples (n_levels) for |
| * R32UI_MULTISAMPLE case. This value exceeds the required |
| * min-max value (1 in OpenGL ES 3.2) and is not supported |
| * by all implementations. |
| * |
| * Also, the test uses a compute shader with images |
| * to upload the texture so max_image_samples >= 4 |
| * is also required. |
| */ |
| m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << getTestCaseName() << " not supported" |
| << tcu::TestLog::EndMessage; |
| continue; |
| } |
| } |
| |
| prepareTexture(gl, false, destination_texture.m_id); |
| prepareTexture(gl, true, source_texture.m_id); |
| |
| /* Test invalid source cases */ |
| for (GLuint i = 0; i < DE_LENGTH_OF_ARRAY(fetching_offsets); ++i) |
| { |
| const FetchingOffset& fo = fetching_offsets[i]; |
| const std::string& cs = getComputeShader(SOURCE_INVALID, fo.coord_offset, fo.sample_offset); |
| program.Init(cs, "", "", "", "", ""); |
| program.Use(); |
| |
| /* Set texture */ |
| setTextures(gl, destination_texture.m_id, source_texture.m_id); |
| |
| /* Dispatch */ |
| gl.dispatchCompute(width, height, 1 /* depth */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| |
| /* Verification */ |
| if (false == verifyInvalidResults(gl, destination_texture.m_id)) |
| { |
| case_result = false; |
| } |
| } |
| |
| /* Test valid case */ |
| program.Init(getComputeShader(VALID), "", "", "", "", ""); |
| program.Use(); |
| |
| /* Set texture */ |
| setTextures(gl, destination_texture.m_id, source_texture.m_id); |
| |
| /* Set memory barrier with previous invalid tests */ |
| gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "MemoryBarrier"); |
| |
| /* Dispatch */ |
| gl.dispatchCompute(width, height, 1 /* depth */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| |
| /* Verification */ |
| if (false == verifyValidResults(gl, destination_texture.m_id)) |
| { |
| case_result = false; |
| } |
| |
| /* Test invalid destination cases */ |
| for (GLuint i = 0; i < DE_LENGTH_OF_ARRAY(fetching_offsets); ++i) |
| { |
| const FetchingOffset& fo = fetching_offsets[i]; |
| const std::string& cs = getComputeShader(DESTINATION_INVALID, fo.coord_offset, fo.sample_offset); |
| program.Init(cs, "", "", "", "", ""); |
| program.Use(); |
| |
| /* Set texture */ |
| setTextures(gl, destination_texture.m_id, source_texture.m_id); |
| |
| /* Dispatch */ |
| gl.dispatchCompute(width, height, 1 /* depth */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| |
| /* Verification */ |
| if (false == verifyValidResults(gl, destination_texture.m_id)) |
| { |
| case_result = false; |
| } |
| } |
| |
| /* Set test result */ |
| if (false == case_result) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << getTestCaseName() << " failed" |
| << tcu::TestLog::EndMessage; |
| |
| test_result = false; |
| } |
| } |
| |
| /* Set result */ |
| if (true == test_result) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| } |
| else |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| } |
| |
| /* Done */ |
| return tcu::TestNode::STOP; |
| } |
| |
| /** Prepare shader for current test case |
| * |
| * @param version Specify which version should be prepared |
| * |
| * @return Source |
| **/ |
| std::string ImageLoadStoreTest::getComputeShader(VERSION version, GLuint coord_offset, GLuint sample_offset) |
| { |
| static const GLchar* source = |
| "${VERSION}\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "\n" |
| "layout (${QUALIFIER} = 1, ${FORMAT}) writeonly uniform highp ${IMAGE} uni_destination_image;\n" |
| "layout (${QUALIFIER} = 0, ${FORMAT}) readonly uniform highp ${IMAGE} uni_source_image;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " ivec2 point_destination = ivec2(gl_WorkGroupID.xy) + ivec2(${DST_COORD_OFFSET}U);\n" |
| " ivec2 point_source = ivec2(gl_WorkGroupID.xy) + ivec2(${SRC_COORD_OFFSET}U);\n" |
| "\n" |
| "${COPY}" |
| "}\n"; |
| |
| static const GLchar* copy_multisampled = |
| " ${TYPE} color_0 = imageLoad(uni_source_image, point_source, 0 + ${SRC_SAMPLE_OFFSET});\n" |
| " ${TYPE} color_1 = imageLoad(uni_source_image, point_source, 1 + ${SRC_SAMPLE_OFFSET});\n" |
| " ${TYPE} color_2 = imageLoad(uni_source_image, point_source, 2 + ${SRC_SAMPLE_OFFSET});\n" |
| " ${TYPE} color_3 = imageLoad(uni_source_image, point_source, 3 + ${SRC_SAMPLE_OFFSET});\n" |
| " imageStore(uni_destination_image, point_destination, 0 + ${DST_SAMPLE_OFFSET}, color_0);\n" |
| " imageStore(uni_destination_image, point_destination, 1 + ${DST_SAMPLE_OFFSET}, color_1);\n" |
| " imageStore(uni_destination_image, point_destination, 2 + ${DST_SAMPLE_OFFSET}, color_2);\n" |
| " imageStore(uni_destination_image, point_destination, 3 + ${DST_SAMPLE_OFFSET}, color_3);\n"; |
| |
| static const GLchar* copy_regular = " ${TYPE} color = imageLoad(uni_source_image, point_source);\n" |
| " imageStore(uni_destination_image, point_destination, color);\n"; |
| |
| std::string src_coord_offset_str("0"); |
| std::string dst_coord_offset_str("0"); |
| std::string src_sample_offset_str("0"); |
| std::string dst_sample_offset_str("0"); |
| |
| std::stringstream coord_offset_stream; |
| coord_offset_stream << coord_offset; |
| std::stringstream sample_offset_stream; |
| sample_offset_stream << sample_offset; |
| |
| m_specializationMap["QUALIFIER"] = m_context_is_es ? "binding" : "location"; |
| m_specializationMap["IMAGE"] = "image2D"; |
| m_specializationMap["TYPE"] = "vec4"; |
| switch (m_test_case) |
| { |
| case R8: |
| m_specializationMap["FORMAT"] = "r8"; |
| break; |
| case RG8_SNORM: |
| m_specializationMap["FORMAT"] = "rg8_snorm"; |
| break; |
| case RGBA32F: |
| m_specializationMap["FORMAT"] = "rgba32f"; |
| break; |
| case R32UI_MIPMAP: |
| m_specializationMap["FORMAT"] = "r32ui"; |
| m_specializationMap["IMAGE"] = "uimage2D"; |
| m_specializationMap["TYPE"] = "uvec4"; |
| break; |
| case R32UI_MULTISAMPLE: |
| m_specializationMap["FORMAT"] = "r32ui"; |
| m_specializationMap["IMAGE"] = "uimage2DMS"; |
| m_specializationMap["TYPE"] = "uvec4"; |
| break; |
| default: |
| TCU_FAIL("Invalid enum"); |
| }; |
| |
| m_specializationMap["SRC_COORD_OFFSET"] = "0"; |
| m_specializationMap["SRC_SAMPLE_OFFSET"] = "0"; |
| m_specializationMap["DST_COORD_OFFSET"] = "0"; |
| m_specializationMap["DST_SAMPLE_OFFSET"] = "0"; |
| |
| if (version == SOURCE_INVALID) |
| { |
| m_specializationMap["SRC_COORD_OFFSET"] = coord_offset_stream.str(); |
| m_specializationMap["SRC_SAMPLE_OFFSET"] = sample_offset_stream.str(); |
| } |
| else if (version == DESTINATION_INVALID) |
| { |
| m_specializationMap["DST_COORD_OFFSET"] = coord_offset_stream.str(); |
| m_specializationMap["DST_SAMPLE_OFFSET"] = sample_offset_stream.str(); |
| } |
| |
| const GLchar* copy = (m_test_case == R32UI_MULTISAMPLE) ? copy_multisampled : copy_regular; |
| m_specializationMap["COPY"] = tcu::StringTemplate(copy).specialize(m_specializationMap); |
| |
| return tcu::StringTemplate(source).specialize(m_specializationMap); |
| } |
| |
| /** Set textures as images |
| * |
| * @param id_destination Id of texture used as destination |
| * @param id_source Id of texture used as source |
| **/ |
| void ImageLoadStoreTest::setTextures(const Functions& gl, glw::GLuint id_destination, glw::GLuint id_source) |
| { |
| GLenum format = 0; |
| GLint level = 0; |
| |
| switch (m_test_case) |
| { |
| case R8: |
| format = GL_R8; |
| break; |
| case RG8_SNORM: |
| format = GL_RG8_SNORM; |
| break; |
| case RGBA32F: |
| format = GL_RGBA32F; |
| break; |
| case R32UI_MIPMAP: |
| format = GL_R32UI; |
| level = 1; |
| break; |
| case R32UI_MULTISAMPLE: |
| format = GL_R32UI; |
| break; |
| default: |
| TCU_FAIL("Invalid enum"); |
| } |
| |
| gl.bindImageTexture(0 /* unit */, id_source, level, GL_FALSE /* layered */, 0 /* layer */, GL_READ_ONLY, format); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| |
| gl.bindImageTexture(1 /* unit */, id_destination, level, GL_FALSE /* layered */, 0 /* layer */, GL_WRITE_ONLY, |
| format); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| |
| if (!m_context_is_es) |
| { |
| gl.uniform1i(0 /* location */, 0 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| gl.uniform1i(1 /* location */, 1 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| } |
| } |
| |
| /** Verifies that texutre is filled with 0 |
| * |
| * @param texture_id Id of texture |
| * |
| * @return true when image is filled with 0, false otherwise |
| **/ |
| bool ImageLoadStoreTest::verifyInvalidResults(const Functions& gl, glw::GLuint texture_id) |
| { |
| static const GLuint height = 16; |
| static const GLuint width = 16; |
| static const GLuint n_pixels = height * width; |
| |
| // OpenGL ES has undefined out-of-bound behavior - no verification |
| if (m_context_is_es) |
| return true; |
| |
| gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "MemoryBarrier"); |
| |
| bool result = true; |
| |
| if (R8 == m_test_case) |
| { |
| static const GLuint n_channels = 1; |
| |
| std::vector<GLubyte> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RED, GL_UNSIGNED_BYTE, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLubyte expected_red = 0; |
| const GLubyte drawn_red = pixels[i]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLuint)drawn_red |
| << ". Expected value: " << (GLuint)expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (RG8_SNORM == m_test_case) |
| { |
| static const GLuint n_channels = 2; |
| |
| std::vector<GLbyte> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RG, GL_BYTE, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLbyte expected_red = 0; |
| const GLbyte expected_green = 0; |
| const GLbyte drawn_red = pixels[i * n_channels + 0]; |
| const GLbyte drawn_green = pixels[i * n_channels + 1]; |
| |
| if ((expected_red != drawn_red) || (expected_green != drawn_green)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLint)drawn_red << ", " |
| << (GLint)drawn_green << ". Expected value: " << (GLint)expected_red << ", " |
| << (GLint)expected_green << ". At offset: " << i << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (RGBA32F == m_test_case) |
| { |
| static const GLuint n_channels = 4; |
| |
| std::vector<GLfloat> pixels(n_pixels * n_channels); |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| GLuint idx = i * n_channels; |
| GLfloat value = static_cast<GLfloat>(i) / n_pixels; |
| pixels[idx + 0] = value; |
| pixels[idx + 1] = value; |
| pixels[idx + 2] = value; |
| pixels[idx + 3] = value; |
| } |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RGBA, GL_FLOAT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLfloat expected_red = 0.0f; |
| const GLfloat expected_green = 0.0f; |
| const GLfloat expected_blue = 0.0f; |
| const GLfloat expected_alpha = 0.0f; |
| const GLuint idx = i * n_channels; |
| const GLfloat drawn_red = pixels[idx + 0]; |
| const GLfloat drawn_green = pixels[idx + 1]; |
| const GLfloat drawn_blue = pixels[idx + 2]; |
| const GLfloat drawn_alpha = pixels[idx + 3]; |
| |
| if ((expected_red != drawn_red) || (expected_green != drawn_green) || (expected_blue != drawn_blue) || |
| (expected_alpha != drawn_alpha)) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red << ", " << drawn_green |
| << ", " << drawn_blue << ", " << drawn_alpha << ". Expected value: " << expected_red |
| << ", " << expected_green << ", " << expected_blue << ", " << expected_alpha |
| << ". At offset: " << i << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (R32UI_MIPMAP == m_test_case) |
| { |
| static const GLuint n_channels = 1; |
| |
| std::vector<GLuint> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::Bind(gl, texture_id, GL_TEXTURE_2D); |
| |
| Texture::GetData(gl, 1 /* level */, GL_TEXTURE_2D, GL_RED_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint expected_red = 0; |
| const GLuint drawn_red = pixels[i]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red |
| << ". Expected value: " << expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| else if (R32UI_MULTISAMPLE == m_test_case) |
| { |
| static const GLuint n_channels = 1; |
| |
| /* Compute shader */ |
| static const GLchar* cs = "${VERSION}\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "\n" |
| "layout (location = 1) writeonly uniform uimage2D uni_destination_image;\n" |
| "layout (location = 0, r32ui) readonly uniform uimage2DMS uni_source_image;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " const ivec2 point = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);\n" |
| "\n" |
| " const uvec4 color_0 = imageLoad(uni_source_image, point, 0);\n" |
| " const uvec4 color_1 = imageLoad(uni_source_image, point, 1);\n" |
| " const uvec4 color_2 = imageLoad(uni_source_image, point, 2);\n" |
| " const uvec4 color_3 = imageLoad(uni_source_image, point, 3);\n" |
| "\n" |
| " if (any(equal(uvec4(color_0.r, color_1.r, color_2.r, color_3.r), uvec4(0))))\n" |
| " {\n" |
| " imageStore(uni_destination_image, point, uvec4(1, 1, 1, 1));\n" |
| " }\n" |
| " else\n" |
| " {\n" |
| " imageStore(uni_destination_image, point, uvec4(0, 0, 0, 0));\n" |
| " }\n" |
| "}\n" |
| "\n"; |
| |
| Program program(gl); |
| Texture destination_texture(gl); |
| |
| Texture::Generate(gl, destination_texture.m_id); |
| Texture::Bind(gl, destination_texture.m_id, GL_TEXTURE_2D); |
| Texture::Storage(gl, GL_TEXTURE_2D, 1, GL_R32UI, width, height, 0 /* depth */); |
| |
| program.Init(cs, "", "", "", "", ""); |
| program.Use(); |
| gl.bindImageTexture(0 /* unit */, texture_id, 0 /* level */, GL_FALSE /* layered */, 0 /* layer */, |
| GL_READ_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| gl.bindImageTexture(1 /* unit */, destination_texture.m_id, 0 /* level */, GL_FALSE /* layered */, |
| 0 /* layer */, GL_WRITE_ONLY, GL_R32UI); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "BindImageTexture"); |
| |
| gl.uniform1i(0 /* location */, 0 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| gl.uniform1i(1 /* location */, 1 /* image unit*/); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform1i"); |
| |
| gl.dispatchCompute(16, 16, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); |
| |
| /* Pixels buffer initialization */ |
| std::vector<GLuint> pixels(n_pixels * n_channels); |
| initPixels(pixels, n_pixels, n_channels); |
| |
| Texture::GetData(gl, 0 /* level */, GL_TEXTURE_2D, GL_RED_INTEGER, GL_UNSIGNED_INT, &pixels[0]); |
| |
| /* Unbind */ |
| Texture::Bind(gl, 0, GL_TEXTURE_2D); |
| |
| /* Verify */ |
| for (GLuint i = 0; i < n_pixels; ++i) |
| { |
| const GLuint expected_red = 1; |
| const GLuint drawn_red = pixels[i]; |
| |
| if (expected_red != drawn_red) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << drawn_red |
| << ". Expected value: " << expected_red << " at offset: " << i |
| << tcu::TestLog::EndMessage; |
| |
| result = false; |
| break; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Verifies that texutre is filled with increasing values |
| * |
| * @param texture_id Id of texture |
| * |
| * @return true when image is filled with increasing values, false otherwise |
| **/ |
| bool ImageLoadStoreTest::verifyValidResults(const glw::Functions& gl, glw::GLuint texture_id) |
| |