blob: da151342a5c09d568f81a9da47d1edc980b33cb6 [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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
* \brief
*/ /*-------------------------------------------------------------------*/
/**
* \file es32cRobustBufferAccessBehaviorTests.cpp
* \brief Implements conformance tests for "Robust Buffer Access Behavior" functionality.
*/ /*-------------------------------------------------------------------*/
#include "es32cRobustBufferAccessBehaviorTests.hpp"
#include "gluDefs.hpp"
#include "gluStrUtil.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "tcuTestLog.hpp"
#include <cstring>
#include <string>
using namespace glw;
using namespace deqp::RobustBufferAccessBehavior;
namespace es32cts
{
namespace RobustBufferAccessBehavior
{
/** Constructor
*
* @param context Test context
**/
VertexBufferObjectsTest::VertexBufferObjectsTest(tcu::TestContext& testCtx, glu::ApiType apiType)
: deqp::RobustBufferAccessBehavior::VertexBufferObjectsTest(
testCtx, apiType, "vertex_buffer_objects", "Verifies that out-of-bound reads from VB result in zero")
{
/* Nothing to be done */
}
/** Execute test
*
* @return tcu::TestNode::STOP
**/
tcu::TestNode::IterateResult VertexBufferObjectsTest::iterate()
{
return deqp::RobustBufferAccessBehavior::VertexBufferObjectsTest::iterate();
}
/** Prepare shader for current test case
*
* @return Source
**/
std::string VertexBufferObjectsTest::getFragmentShader()
{
return std::string("#version 320 es\n"
"\n"
"layout (location = 0) out lowp uvec4 out_fs_color;\n"
"\n"
"void main()\n"
"{\n"
" out_fs_color = uvec4(1, 255, 255, 255);\n"
"}\n"
"\n");
}
/** Prepare shader for current test case
*
* @return Source
**/
std::string VertexBufferObjectsTest::getVertexShader()
{
return std::string("#version 320 es\n"
"\n"
"layout (location = 0) in vec4 in_vs_position;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = in_vs_position;\n"
"}\n"
"\n");
}
/** No verification because of undefined out-of-bound behavior in OpenGL ES
*
* @param texture_id Id of texture
*
* @return true
**/
bool VertexBufferObjectsTest::verifyInvalidResults(glw::GLuint texture_id)
{
(void)texture_id;
return true;
}
/** 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(glw::GLuint texture_id)
{
static const GLuint height = 8;
static const GLuint width = 8;
static const GLuint pixel_size = 4 * sizeof(GLuint);
const Functions& gl = m_renderContext->getFunctions();
const GLint buf_size = width * height * pixel_size;
GLubyte pixels[buf_size];
deMemset(pixels, 0, buf_size);
Texture::Bind(gl, texture_id, GL_TEXTURE_2D);
Texture::GetData(gl, texture_id, 0 /* level */, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixels);
/* Unbind */
Texture::Bind(gl, 0, GL_TEXTURE_2D);
/* Verify */
for (GLuint i = 0; i < buf_size; i += pixel_size)
{
if (1 != pixels[i])
{
m_testCtx.getLog() << tcu::TestLog::Message << "Invalid value: " << (GLuint)pixels[i]
<< " at offset: " << i << tcu::TestLog::EndMessage;
return false;
}
}
return true;
}
/** Constructor
*
* @param context Test context
**/
TexelFetchTest::TexelFetchTest(tcu::TestContext& testCtx, glu::ApiType apiType)
: deqp::RobustBufferAccessBehavior::TexelFetchTest(testCtx, apiType, "texel_fetch",
"Verifies that out-of-bound fetches from texture result in zero")
{
/* Nothing to be done */
}
TexelFetchTest::TexelFetchTest(tcu::TestContext& testCtx, glu::ApiType apiType, const glw::GLchar* name, const glw::GLchar* description)
: deqp::RobustBufferAccessBehavior::TexelFetchTest(testCtx, apiType, name, description)
{
/* Nothing to be done */
}
/** Execute test
*
* @return tcu::TestNode::STOP
**/
tcu::TestNode::IterateResult TexelFetchTest::iterate()
{
return deqp::RobustBufferAccessBehavior::TexelFetchTest::iterate();
}
/** 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(bool is_case_valid)
{
static const GLchar* plane_0 = " int plane = 0;\n";
static const GLchar* plane_1 = " int plane = 1;\n";
static const GLchar* plane_2 = " int plane = 2;\n";
static const GLchar* plane_sample_invalid = " int plane = 9;\n";
static const GLchar* plane_sample_valid = " int plane = gl_SampleID;\n";
static const GLchar* point_invalid = " ivec2 point = ivec2(gs_fs_tex_coord * 16.0) + ivec2(16, 16);\n";
static const GLchar* point_valid = " ivec2 point = ivec2(gs_fs_tex_coord * 16.0);\n";
static const GLchar* sampler_regular = "sampler2D";
static const GLchar* sampler_regular_u = "usampler2D";
static const GLchar* sampler_multisampled_u = "usampler2DMS";
static const GLchar* template_code = "#version 320 es\n"
"\n"
" in lowp vec2 gs_fs_tex_coord;\n"
"layout (location = 0) out lowp TYPE out_fs_color;\n"
"\n"
"layout (location = 0) uniform lowp SAMPLER uni_texture;\n"
"\n"
"void main()\n"
"{\n"
"PLANE\n"
"POINT\n"
" out_fs_color = texelFetch(uni_texture, point, plane);\n"
"}\n"
"\n";
static const GLchar* type_vec4 = "vec4";
static const GLchar* type_uvec4 = "uvec4";
const GLchar* plane = plane_0;
const GLchar* point = point_valid;
const GLchar* sampler = sampler_regular;
const GLchar* type = type_vec4;
if ((R8 == m_test_case) || (RG8_SNORM == m_test_case) || (RGBA32F == m_test_case))
{
if (false == is_case_valid)
{
point = point_invalid;
}
}
else if (R32UI_MIPMAP == m_test_case)
{
plane = plane_1;
sampler = sampler_regular_u;
type = type_uvec4;
if (false == is_case_valid)
{
plane = plane_2;
}
}
else if (R32UI_MULTISAMPLE == m_test_case)
{
plane = plane_sample_valid;
sampler = sampler_multisampled_u;
type = type_uvec4;
if (false == is_case_valid)
{
plane = plane_sample_invalid;
}
}
size_t position = 0;
std::string source = template_code;
replaceToken("TYPE", position, type, source);
replaceToken("SAMPLER", position, sampler, source);
replaceToken("PLANE", position, plane, source);
replaceToken("POINT", position, point, source);
return source;
}
/** Prepare shader for current test case
*
* @return Source
**/
std::string TexelFetchTest::getGeometryShader()
{
return std::string("#version 320 es\n"
"\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"
"\n");
}
/** Prepare shader for current test case
*
* @return Source
**/
std::string TexelFetchTest::getVertexShader()
{
return std::string("#version 320 es\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = vec4(0, 0, 0, 1);\n"
"}\n"
"\n");
}
/** 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(bool is_source, glw::GLuint texture_id)
{
/* Image size */
static const GLuint image_height = 16;
static const GLuint image_width = 16;
/* GL entry points */
const Functions& gl = m_renderContext->getFunctions();
/* 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 */
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] = (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] = (GLubyte)((i % 16));
source_pixels[i * n_components + 1] = (GLubyte)((i / 16));
}
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);
/* texelFetch() undefined if the computed level of detail is not the texture’s base level and the texture’s
minification filter is NEAREST or LINEAR */
gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
}
else if (R32UI_MULTISAMPLE == m_test_case)
{
/* Compute Shader */
static const GLchar* cs = "#version 320 es\n"
"\n"
"layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;\n"
"\n"
"layout (binding = 0, r32ui) writeonly uniform highp uimage2DMS uni_image;\n"
"\n"
"void main()\n"
"{\n"
" ivec2 point = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);\n"
" 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";
Program program(*m_renderContext);
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");
gl.dispatchCompute(16, 16, 1);
GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute");
}
Texture::Bind(gl, 0, target);
}
/** No verification because of undefined out-of-bound behavior in OpenGL ES
*
* @param texture_id Id of texture
*
* @return true
**/
bool TexelFetchTest::verifyInvalidResults(glw::GLuint texture_id)
{
(void)texture_id;
return true;
}
/** 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(glw::GLuint texture_id)
{
static const GLuint height = 16;
static const GLuint width = 16;
static const GLuint n_pixels = height * width;
const Functions& gl = m_renderContext->getFunctions();
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;
pixels.resize(n_pixels * n_channels);
for (GLuint i = 0; i < n_pixels; ++i)
{
pixels[i] = (GLubyte)i;
}
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 = (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<GLubyte> pixels;
pixels.resize(n_pixels * n_channels);
for (GLuint i = 0; i < n_pixels; ++i)
{
pixels[i * n_channels + 0] = (GLubyte)i;
pixels[i * n_channels + 1] = (GLubyte)i;
}
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 = (GLubyte)(i % 16);
const GLbyte expected_green = (GLubyte)(i / 16);
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;
pixels.resize(n_pixels * n_channels);
for (GLuint i = 0; i < n_pixels; ++i)
{
pixels[i * n_channels + 0] = (GLfloat)i / (GLfloat)n_pixels;
pixels[i * n_channels + 1] = (GLfloat)i / (GLfloat)n_pixels;
pixels[i * n_channels + 2] = (GLfloat)i / (GLfloat)n_pixels;
pixels[i * n_channels + 3] = (GLfloat)i / (GLfloat)n_pixels;
}
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 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];
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;
pixels.resize(n_pixels * n_channels);
deMemset(&pixels[0], 0, n_pixels * n_channels * sizeof(GLuint));
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* cs =
"#version 320 es\n"
"\n"
"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
"\n"
"layout (binding = 1, r32ui) writeonly uniform lowp uimage2D uni_destination_image;\n"
"layout (binding = 0, r32ui) readonly uniform lowp uimage2DMS uni_source_image;\n"
"\n"
"void main()\n"
"{\n"
" ivec2 point = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);\n"
" uint index = gl_WorkGroupID.y * 16U + gl_WorkGroupID.x;\n"
"\n"
" uvec4 color_0 = imageLoad(uni_source_image, point, 0);\n"
" uvec4 color_1 = imageLoad(uni_source_image, point, 1);\n"
" uvec4 color_2 = imageLoad(uni_source_image, point, 2);\n"
" 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(*m_renderContext);
Texture destination_texture(*m_renderContext);
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.dispatchCompute(16, 16, 1);
GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute");
/* Pixels buffer initialization */
std::vector<GLuint> pixels;
pixels.resize(n_pixels * n_channels);
for (GLuint i = 0; i < n_pixels * n_channels; ++i)
{
pixels[i] = i;
}
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 context Test context
**/
ImageLoadStoreTest::ImageLoadStoreTest(tcu::TestContext& testCtx, glu::ApiType apiType)
: TexelFetchTest(testCtx, apiType, "image_load_store", "Verifies that out-of-bound to image result in zero or is discarded")
{
/* start from RGBA32F as R8, R32UI_MULTISAMPLE and R8_SNORM are not supported under GLES */
m_test_case = RGBA32F;
}
/** Execute test
*
* @return tcu::TestNode::STOP
**/
tcu::TestNode::IterateResult ImageLoadStoreTest::iterate()
{
RobustnessEnabledContext context(m_testCtx, m_apiType);
m_renderContext = context.getRenderContext();
if (!m_renderContext)
return STOP;
/* Constants */
static const GLuint height = 16;
static const GLuint width = 16;
/* GL entry points */
const Functions& gl = m_renderContext->getFunctions();
/* Test result indicator */
bool test_result = true;
/* Iterate over all cases */
while (LAST != m_test_case)
{
/* Test case result indicator */
bool case_result = true;
/* Test case objects */
Texture destination_texture(*m_renderContext);
Program invalid_destination_program(*m_renderContext);
Program invalid_source_program(*m_renderContext);
Texture source_texture(*m_renderContext);
Program valid_program(*m_renderContext);
const std::string& cs_invalid_destination = getComputeShader(DESTINATION_INVALID);
const std::string& cs_invalid_source = getComputeShader(SOURCE_INVALID);
const std::string& cs_valid = getComputeShader(VALID);
/* Prepare textures */
Texture::Generate(gl, destination_texture.m_id);
Texture::Generate(gl, source_texture.m_id);
prepareTexture(false, destination_texture.m_id);
prepareTexture(true, source_texture.m_id);
/* Prepare programs */
invalid_destination_program.Init(cs_invalid_destination, "" /* fs */, "" /* gs */, "" /* tcs */, "" /* tes */,
"" /* vs */);
invalid_source_program.Init(cs_invalid_source, "" /* fs */, "" /* gs */, "" /* tcs */, "" /* tes */,
"" /* vs */);
valid_program.Init(cs_valid, "" /* fs */, "" /* gs */, "" /* tcs */, "" /* tes */, "" /* vs */);
/* Test invalid source case */
/* Set program */
invalid_source_program.Use();
/* Set texture */
setTextures(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(destination_texture.m_id))
{
case_result = false;
}
/* Test valid case */
/* Set program */
valid_program.Use();
/* Set texture */
setTextures(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(destination_texture.m_id))
{
case_result = false;
}
/* Test invalid destination case */
/* Set program */
invalid_destination_program.Use();
/* Set texture */
setTextures(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(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;
}
/* Increment */
m_test_case = (TEST_CASES)((GLuint)m_test_case + 1);
}
/* 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)
{
static const GLchar* template_code =
"#version 320 es\n"
"\n"
"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
"\n"
"layout (binding = 1, FORMAT) writeonly uniform highp IMAGE uni_destination_image;\n"
"layout (binding = 0, FORMAT) readonly uniform highp IMAGE uni_source_image;\n"
"\n"
"void main()\n"
"{\n"
" ivec2 point_destination = POINT;\n"
" ivec2 point_source = POINT;\n"
"\n"
"COPY"
"}\n"
"\n";
static const GLchar* copy_regular = " TYPE color = imageLoad(uni_source_image, point_source);\n"
" imageStore(uni_destination_image, point_destination, color);\n";
static const GLchar* format_rgba32f = "rgba32f";
static const GLchar* format_r32ui = "r32ui";
static const GLchar* image_vec4 = "image2D";
static const GLchar* image_uvec4 = "uimage2D";
static const GLchar* point_invalid = "ivec2(gl_WorkGroupID.x + 16U, gl_WorkGroupID.y + 16U)";
static const GLchar* point_valid = "ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y)";
static const GLchar* type_vec4 = "vec4";
static const GLchar* type_uvec4 = "uvec4";
const GLchar* copy = copy_regular;
const GLchar* format = format_rgba32f;
const GLchar* image = image_vec4;
const GLchar* point_destination = point_valid;
const GLchar* point_source = point_valid;
const GLchar* type = type_vec4;
switch (version)
{
case VALID:
break;
case SOURCE_INVALID:
point_source = point_invalid;
break;
case DESTINATION_INVALID:
point_destination = point_invalid;
break;
default:
TCU_FAIL("Invalid enum");
}
switch (m_test_case)
{
case RGBA32F:
format = format_rgba32f;
break;
case R32UI_MIPMAP:
format = format_r32ui;
image = image_uvec4;
type = type_uvec4;
break;
default:
TCU_FAIL("Invalid enum");
};
size_t position = 0;
std::string source = template_code;
replaceToken("FORMAT", position, format, source);
replaceToken("IMAGE", position, image, source);
replaceToken("FORMAT", position, format, source);
replaceToken("IMAGE", position, image, source);
replaceToken("POINT", position, point_destination, source);
replaceToken("POINT", position, point_source, source);
size_t temp_position = position;
replaceToken("COPY", position, copy, source);
position = temp_position;
switch (m_test_case)
{
case RGBA32F:
case R32UI_MIPMAP:
replaceToken("TYPE", position, type, source);
break;
default:
TCU_FAIL("Invalid enum");
}
return source;
}
/** 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(glw::GLuint id_destination, glw::GLuint id_source)
{
const Functions& gl = m_renderContext->getFunctions();
GLenum format = 0;
GLint level = 0;
switch (m_test_case)
{
case RGBA32F:
format = GL_RGBA32F;
break;
case R32UI_MIPMAP:
format = GL_R32UI;
level = 1;
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");
}
/** No verification because of undefined out-of-bound behavior in OpenGL ES
*
* @param texture_id Id of texture
*
* @return true
**/
bool ImageLoadStoreTest::verifyInvalidResults(glw::GLuint texture_id)
{
(void)texture_id;
return true;
}
/** 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(glw::GLuint texture_id)
{
static const GLuint height = 16;
static const GLuint width = 16;
static const GLuint n_pixels = height * width;
const Functions& gl = m_renderContext->getFunctions();
gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "MemoryBarrier");
bool result = true;
if (RGBA32F == m_test_case)
{
static const GLuint n_channels = 4;
Texture::Bind(gl, texture_id, GL_TEXTURE_2D);
std::vector<GLfloat> pixels;
pixels.resize(n_pixels * n_channels);
for (GLuint i = 0; i < n_pixels; ++i)
{
pixels[i * n_channels + 0] = (GLfloat)i / (GLfloat)n_pixels;
pixels[i * n_channels + 1] = (GLfloat)i / (GLfloat)n_pixels;
pixels[i * n_channels + 2] = (GLfloat)i / (GLfloat)n_pixels;
pixels[i * n_channels + 3] = (GLfloat)i / (GLfloat)n_pixels;
}
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 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];
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;
pixels.resize(n_pixels * n_channels);
for (GLuint i = 0; i < n_pixels * n_channels; ++i)
{
pixels[i] = 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;
}
}
}
return result;
}
/** Constructor
*
* @param context Test context
**/
StorageBufferTest::StorageBufferTest(tcu::TestContext& testCtx, glu::ApiType apiType)
: deqp::RobustBufferAccessBehavior::StorageBufferTest(
testCtx, apiType, "storage_buffer", "Verifies that out-of-bound access to SSBO results with no error")
{
/* Nothing to be done here */
}
/** Execute test
*
* @return tcu::TestNode::STOP
**/
tcu::TestNode::IterateResult StorageBufferTest::iterate()
{
return deqp::RobustBufferAccessBehavior::StorageBufferTest::iterate();
}
/** Prepare shader for current test case
*
* @return Source
**/
std::string StorageBufferTest::getComputeShader()
{
static const GLchar* cs = "#version 320 es\n"
"\n"
"layout (local_size_x = 4, local_size_y = 1, local_size_z = 1) in;\n"
"\n"
"layout (binding = 1) buffer Source {\n"
" float data[];\n"
"} source;\n"
"\n"
"layout (binding = 0) buffer Destination {\n"
" float data[];\n"
"} destination;\n"
"\n"
"void main()\n"
"{\n"
" uint index_destination = gl_LocalInvocationID.x + OFFSETU;\n"
" uint index_source = gl_LocalInvocationID.x + OFFSETU;\n"
"\n"
" destination.data[index_destination] = source.data[index_source];\n"
"}\n"
"\n";
const GLchar* destination_offset;
size_t position = 0;
std::string source = cs;
const GLchar* source_offset;
switch (m_test_case)
{
case VALID:
destination_offset = "0";
source_offset = "0";
break;
case SOURCE_INVALID:
destination_offset = "0";
source_offset = "16";
break;
case DESTINATION_INVALID:
destination_offset = "16";
source_offset = "0";
break;
default:
TCU_FAIL("Invalid enum");
}
replaceToken("OFFSET", position, destination_offset, source);
replaceToken("OFFSET", position, source_offset, source);
return source;
}
/** Verify test case results
*
* @param buffer_data Buffer data to verify
*
* @return true if buffer_data is as expected, false othrewise
**/
bool StorageBufferTest::verifyResults(GLfloat* buffer_data)
{
static const GLfloat expected_data_valid[4] = { 2.0f, 3.0f, 4.0f, 5.0f };
static const GLfloat expected_data_invalid_destination[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
static const GLfloat expected_data_invalid_source[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
int size = sizeof(GLfloat) * 4;
/* Prepare expected data const for proper case*/
const GLfloat* expected_data = 0;
const GLchar* name = 0;
switch (m_test_case)
{
case VALID:
expected_data = expected_data_valid;
name = "valid indices";
break;
case SOURCE_INVALID:
expected_data = expected_data_invalid_source;
name = "invalid source indices";
break;
case DESTINATION_INVALID:
expected_data = expected_data_invalid_destination;
name = "invalid destination indices";
break;
default:
TCU_FAIL("Invalid enum");
}
/* Verify buffer data */
if (m_test_case == VALID && memcmp(expected_data, buffer_data, size) != 0)
{
m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << name << " failed"
<< tcu::TestLog::EndMessage;
return false;
}
return true;
}
/** Constructor
*
* @param context Test context
**/
UniformBufferTest::UniformBufferTest(tcu::TestContext& testCtx, glu::ApiType apiType)
: deqp::RobustBufferAccessBehavior::UniformBufferTest(
testCtx, apiType, "uniform_buffer", "Verifies that out-of-bound access to UBO resutls with no error")
{
/* Nothing to be done here */
}
/** Execute test
*
* @return tcu::TestNode::STOP
**/
tcu::TestNode::IterateResult UniformBufferTest::iterate()
{
return deqp::RobustBufferAccessBehavior::UniformBufferTest::iterate();
}
/** Prepare shader for current test case
*
* @return Source
**/
std::string UniformBufferTest::getComputeShader()
{
static const GLchar* cs = "#version 320 es\n"
"\n"
"layout (local_size_x = 4, local_size_y = 1, local_size_z = 1) in;\n"
"\n"
"layout (binding = 0) uniform Source {\n"
" float data[16];\n"
"} source;\n"
"\n"
"layout (binding = 0) buffer Destination {\n"
" float data[];\n"
"} destination;\n"
"\n"
"void main()\n"
"{\n"
" uint index_destination = gl_LocalInvocationID.x + OFFSETU;\n"
" uint index_source = gl_LocalInvocationID.x + OFFSETU;\n"
"\n"
" destination.data[index_destination] = source.data[index_source];\n"
"}\n"
"\n";
const GLchar* destination_offset;
size_t position = 0;
std::string source = cs;
const GLchar* source_offset;
switch (m_test_case)
{
case VALID:
destination_offset = "0";
source_offset = "0";
break;
case SOURCE_INVALID:
destination_offset = "0";
source_offset = "16";
break;
default:
TCU_FAIL("Invalid enum");
}
replaceToken("OFFSET", position, destination_offset, source);
replaceToken("OFFSET", position, source_offset, source);
return source;
}
/** Verify test case results
*
* @param buffer_data Buffer data to verify
*
* @return true if buffer_data is as expected, false othrewise
**/
bool UniformBufferTest::verifyResults(GLfloat* buffer_data)
{
static const GLfloat expected_data_valid[4] = { 2.0f, 3.0f, 4.0f, 5.0f };
static const GLfloat expected_data_invalid_source[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
int size = sizeof(GLfloat) * 4;
/* Prepare expected data const for proper case*/
const GLfloat* expected_data = 0;
const GLchar* name = 0;
switch (m_test_case)
{
case VALID:
expected_data = expected_data_valid;
name = "valid indices";
break;
case SOURCE_INVALID:
expected_data = expected_data_invalid_source;
name = "invalid source indices";
break;
default:
TCU_FAIL("Invalid enum");
}
/* Verify buffer data */
if (m_test_case == VALID && memcmp(expected_data, buffer_data, size) != 0)
{
m_testCtx.getLog() << tcu::TestLog::Message << "Test case: " << name << " failed"
<< tcu::TestLog::EndMessage;
return false;
}
return true;
}
} /* RobustBufferAccessBehavior */
/** Constructor.
*
* @param context Rendering context.
**/
RobustBufferAccessBehaviorTests::RobustBufferAccessBehaviorTests(tcu::TestContext& testCtx, glu::ApiType apiType)
: deqp::RobustBufferAccessBehaviorTests(testCtx, apiType)
{
/* Left blank on purpose */
}
/** Initializes a multi_bind test group.
*
**/
void RobustBufferAccessBehaviorTests::init(void)
{
addChild(new RobustBufferAccessBehavior::VertexBufferObjectsTest(m_testCtx, m_apiType));
addChild(new RobustBufferAccessBehavior::TexelFetchTest(m_testCtx, m_apiType));
addChild(new RobustBufferAccessBehavior::ImageLoadStoreTest(m_testCtx, m_apiType));
addChild(new RobustBufferAccessBehavior::StorageBufferTest(m_testCtx, m_apiType));
addChild(new RobustBufferAccessBehavior::UniformBufferTest(m_testCtx, m_apiType));
}
} /* es32cts namespace */