blob: 89011331bbc60c3a4c835910c1c8e1a16e9dcf03 [file] [log] [blame]
/*-------------------------------------------------------------------------
* OpenGL Conformance Test Suite
* -----------------------------
*
* Copyright (c) 2014-2016 The Khronos Group Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/ /*!
* \file
* \brief
*/ /*-------------------------------------------------------------------*/
/**
* \file gl4cTextureBarrierTests.cpp
* \brief Implements conformance tests for "Texture Barrier" functionality.
*/ /*-------------------------------------------------------------------*/
#include "gl4cTextureBarrierTests.hpp"
#include "deMath.h"
#include "deSharedPtr.hpp"
#include "gluContextInfo.hpp"
#include "gluDefs.hpp"
#include "gluPixelTransfer.hpp"
#include "gluShaderProgram.hpp"
#include "tcuFuzzyImageCompare.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "glw.h"
#include "glwFunctions.hpp"
#include "glcWaiver.hpp"
namespace gl4cts
{
/*
Base class of all test cases of the feature. Enforces the requirements below:
* Check that the extension string or GL 4.5 is available.
*/
class TextureBarrierBaseTest : public deqp::TestCase
{
protected:
TextureBarrierBaseTest(deqp::Context& context, TextureBarrierTests::API api, const char* name,
const char* description)
: TestCase(context, name, description), m_api(api)
{
}
/* Checks whether the feature is supported. */
bool featureSupported()
{
return (m_api == TextureBarrierTests::API_GL_ARB_texture_barrier &&
m_context.getContextInfo().isExtensionSupported("GL_ARB_texture_barrier")) ||
m_api == TextureBarrierTests::API_GL_45core;
}
/* Basic test init, child classes must call it. */
virtual void init()
{
if (!featureSupported())
{
throw tcu::NotSupportedError("Required texture_barrier extension is not supported");
}
}
protected:
const TextureBarrierTests::API m_api;
};
/*
Base class of all rendering test cases of the feature. Implements the basic outline below:
This basic outline provides a simple tutorial on how to implement and
what to check in the test cases of this feature.
* Create a set of color textures and fill each of their texels with unique
values using an arbitrary method. Set the minification and magnification
filtering modes of the textures to NEAREST. Bind all of them for
texturing to subsequent texture units starting from texture unit zero.
* Create a framebuffer object and attach the set of textures so that
texture #i is attached as color attachment #i. Set the draw buffers so
that draw buffer #i is set to color attachment #i. Bind the framebuffer
for rendering.
* Render a set of primitives that cover each pixel of the framebuffer
exactly once using the fragment shader described in the particular
test case.
* Expect all texels written by the draw command to have well defined
values in accordance with the used fragment shader's functionality.
*/
class TextureBarrierBasicOutline : public TextureBarrierBaseTest
{
protected:
TextureBarrierBasicOutline(deqp::Context& context, TextureBarrierTests::API api, const char* name,
const char* description)
: TextureBarrierBaseTest(context, api, name, description)
, m_program(0)
, m_vao(0)
, m_vbo(0)
, m_fbo(0)
, m_width(0)
, m_height(0)
, m_actual(DE_NULL)
{
for (size_t i = 0; i < NUM_TEXTURES; ++i)
{
m_tex[i] = 0;
m_reference[i] = DE_NULL;
}
}
/* Actual test cases may override it to provide an alternative vertex shader. */
virtual const char* vsh()
{
return "#version 400 core\n"
"in vec2 Position;\n"
"void main() {\n"
" gl_Position = vec4(Position, 0.0, 1.0);\n"
"}";
}
/* Actual test cases must override it to provide the fragment shader. */
virtual const char* fsh() = 0;
/* Rendering test init. */
virtual void init()
{
// Call parent init (takes care of API requirements)
TextureBarrierBaseTest::init();
const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget();
m_width = renderTarget.getWidth();
m_height = renderTarget.getHeight();
#ifdef WAIVER_WITH_BUG_13788
m_width = m_width >= 16383 ? 16382 : m_width;
#endif
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
// Create textures
gl.genTextures(NUM_TEXTURES, m_tex);
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
gl.activeTexture(GL_TEXTURE0 + i);
gl.bindTexture(GL_TEXTURE_2D, m_tex[i]);
gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_width, m_height);
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
m_reference[i] = new GLuint[m_width * m_height];
}
m_actual = new GLuint[m_width * m_height];
// Create framebuffer
gl.genFramebuffers(1, &m_fbo);
gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
GLenum drawBuffers[NUM_TEXTURES];
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, m_tex[i], 0);
drawBuffers[i] = GL_COLOR_ATTACHMENT0 + i;
}
gl.drawBuffers(NUM_TEXTURES, drawBuffers);
// Create vertex array and buffer
gl.genVertexArrays(1, &m_vao);
gl.bindVertexArray(m_vao);
gl.genBuffers(1, &m_vbo);
gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo);
gl.bufferData(GL_ARRAY_BUFFER, GRID_SIZE * GRID_SIZE * sizeof(float) * 12, NULL, GL_STATIC_DRAW);
generateVertexData((float*)gl.mapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY));
gl.unmapBuffer(GL_ARRAY_BUFFER);
gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
gl.enableVertexAttribArray(0);
// Setup state
gl.pixelStorei(GL_PACK_ALIGNMENT, 1);
gl.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
/* Rendering test deinit. */
virtual void deinit()
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
// Cleanup textures
gl.activeTexture(GL_TEXTURE0);
gl.deleteTextures(NUM_TEXTURES, m_tex);
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
if (DE_NULL != m_reference[i])
{
delete[] m_reference[i];
m_reference[i] = DE_NULL;
}
}
if (DE_NULL != m_actual)
{
delete[] m_actual;
m_actual = DE_NULL;
}
// Cleanup framebuffer
gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
gl.deleteFramebuffers(1, &m_fbo);
// Cleanup vertex array and buffer
gl.bindBuffer(GL_ARRAY_BUFFER, 0);
gl.bindVertexArray(0);
gl.deleteBuffers(1, &m_vbo);
gl.deleteVertexArrays(1, &m_vao);
// Cleanup state
gl.pixelStorei(GL_PACK_ALIGNMENT, 4);
gl.pixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
/* Generate vertex data using the following steps:
(1) Generate an irregular grid covering the whole screen (i.e. (-1,-1) to (1,1));
(2) Generate a list of triangles covering the grid;
(3) Shuffle the generated triangle list;
(4) Write the vertices of the shuffled triangle list to the destination address. */
void generateVertexData(float* destAddr)
{
DE_ASSERT(destAddr != NULL);
typedef struct
{
float x, y;
} Vertex;
typedef struct
{
Vertex v0, v1, v2;
} Triangle;
static Vertex grid[GRID_SIZE + 1][GRID_SIZE + 1];
// Generate grid vertices
for (int x = 0; x < GRID_SIZE + 1; ++x)
for (int y = 0; y < GRID_SIZE + 1; ++y)
{
// Calculate normalized coordinates
float normx = (((float)x) / GRID_SIZE);
float normy = (((float)y) / GRID_SIZE);
// Pseudo-random grid vertex coordinate with scale & bias
grid[x][y].x = normx * 2.f - 1.f + deFloatSin(normx * DE_PI * 13.f) * 0.3f / GRID_SIZE;
grid[x][y].y = normy * 2.f - 1.f + deFloatSin(normy * DE_PI * 13.f) * 0.3f / GRID_SIZE;
}
Triangle list[TRIANGLE_COUNT];
// Generate triangle list
for (int x = 0; x < GRID_SIZE; ++x)
for (int y = 0; y < GRID_SIZE; ++y)
{
// Generate first triangle of grid block
list[(x + y * GRID_SIZE) * 2 + 0].v0 = grid[x][y];
list[(x + y * GRID_SIZE) * 2 + 0].v1 = grid[x + 1][y];
list[(x + y * GRID_SIZE) * 2 + 0].v2 = grid[x + 1][y + 1];
// Generate second triangle of grid block
list[(x + y * GRID_SIZE) * 2 + 1].v0 = grid[x + 1][y + 1];
list[(x + y * GRID_SIZE) * 2 + 1].v1 = grid[x][y + 1];
list[(x + y * GRID_SIZE) * 2 + 1].v2 = grid[x][y];
}
// Shuffle triangle list
for (int i = TRIANGLE_COUNT - 2; i > 0; --i)
{
// Pseudo-random triangle index as one operand of the exchange
int j = (int)((list[i].v1.y + list[i].v2.x + 13.f) * 1345.13f) % i;
Triangle xchg = list[j];
list[j] = list[i];
list[i] = xchg;
}
// Write triange list vertices to destination address
for (int i = 0; i < TRIANGLE_COUNT; ++i)
{
// Write first vertex of triangle
destAddr[i * 6 + 0] = list[i].v0.x;
destAddr[i * 6 + 1] = list[i].v0.y;
// Write second vertex of triangle
destAddr[i * 6 + 2] = list[i].v1.x;
destAddr[i * 6 + 3] = list[i].v1.y;
// Write third vertex of triangle
destAddr[i * 6 + 4] = list[i].v2.x;
destAddr[i * 6 + 5] = list[i].v2.y;
}
}
/* Renders a set of primitives that cover each pixel of the framebuffer exactly once. */
void render()
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
// Issue the whole grid using multiple separate draw commands
int minTriCountPerDraw = TRIANGLE_COUNT / 7;
int first = 0, count = 0;
while (first < VERTEX_COUNT)
{
// Pseudo-random number of vertices per draw
count = deMin32(VERTEX_COUNT - first, (first % 23 + minTriCountPerDraw) * 3);
gl.drawArrays(GL_TRIANGLES, first, count);
first += count;
}
}
/* Returns a reference to the texel value of the specified image at the specified location. */
GLuint& texel(GLuint* image, GLuint x, GLuint y)
{
// If out-of-bounds reads should return zero, writes should be ignored
if ((static_cast<GLint>(x) < 0) || (x >= m_width) || (static_cast<GLint>(y) < 0) || (y >= m_height))
{
static GLuint zero;
return (zero = 0);
}
return image[x + y * m_width];
}
/* Initializes the reference images and uploads them to their corresponding textures. */
void initTextureData()
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
for (GLuint x = 0; x < m_width; ++x)
for (GLuint y = 0; y < m_height; ++y)
{
texel(m_reference[i], x, y) = (i << 24) + (y << 12) + x;
}
gl.activeTexture(GL_TEXTURE0 + i);
gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, GL_RED_INTEGER, GL_UNSIGNED_INT,
m_reference[i]);
}
}
/* Updates the reference images according to a single execution of the fragment shader for each pixel. */
virtual void updateTextureData() = 0;
/* Verifies whether the reference images matches those of the textures we rendered to. */
bool verifyTextureData()
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
gl.activeTexture(GL_TEXTURE0 + i);
gl.getTexImage(GL_TEXTURE_2D, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, m_actual);
for (GLuint x = 0; x < m_width; ++x)
for (GLuint y = 0; y < m_height; ++y)
{
if (texel(m_reference[i], x, y) != texel(m_actual, x, y))
{
return false;
}
}
}
return true;
}
/* Should return the number of separate test passes. */
virtual int numTestPasses() = 0;
/* Should return the number of rendering passes to perform. */
virtual int numRenderPasses() = 0;
/* Should set up configuration for a particular render pass (e.g. setting uniforms). */
virtual void setupRenderPass(int testPass, int renderPass) = 0;
/* Should return whether there is need for a TextureBarrier between subsequent render passes. */
virtual bool needsBarrier() = 0;
/* Test case iterate function. Contains the actual test case logic. */
IterateResult iterate()
{
tcu::TestLog& log = m_testCtx.getLog();
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
// Compile & link the program to use
de::SharedPtr<glu::ShaderProgram> program(
new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vsh(), fsh())));
log << (*program);
if (!program->isOk())
{
TCU_FAIL("Program compilation failed");
}
m_program = program->getProgram();
gl.useProgram(m_program);
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
GLchar samplerName[] = "texInput[0]";
samplerName[9] = static_cast<GLchar>('0' + i);
GLint loc = gl.getUniformLocation(m_program, samplerName);
gl.uniform1i(loc, i);
}
for (int testPass = 0; testPass < numTestPasses(); ++testPass)
{
// Initialize texture data at the beginning of each test pass
initTextureData();
// Perform rendering passes
for (int renderPass = 0; renderPass < numRenderPasses(); ++renderPass)
{
// Setup render pass
setupRenderPass(testPass, renderPass);
// Render a set of primitives that cover each pixel of the framebuffer exactly once
render();
// If a TextureBarrier is needed insert it here
if (needsBarrier())
gl.textureBarrier();
}
// Update reference data after actual rendering to avoid bubbles
for (int renderPass = 0; renderPass < numRenderPasses(); ++renderPass)
{
// Setup render pass
setupRenderPass(testPass, renderPass);
// Update reference data
updateTextureData();
}
// Verify results at the end of each test pass
if (!verifyTextureData())
{
TCU_FAIL("Failed to validate rendering results");
}
}
gl.useProgram(0);
// Test case passed
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
return STOP;
}
protected:
enum
{
NUM_TEXTURES = 8,
GRID_SIZE = 64,
TRIANGLE_COUNT = GRID_SIZE * GRID_SIZE * 2,
VERTEX_COUNT = TRIANGLE_COUNT * 3,
};
GLuint m_program;
GLuint m_vao;
GLuint m_vbo;
GLuint m_fbo;
GLuint m_tex[NUM_TEXTURES];
GLuint m_width, m_height;
GLuint* m_reference[NUM_TEXTURES];
GLuint* m_actual;
};
/*
Base class of the rendering tests which use a fragment shader performing
reads and writes from/to disjoint blocks of texels within a single rendering
pass. The skeleton of these tests is as follows:
* Using the basic outline above test that reads and writes from/to
disjoint sets of texels work as expected. Use the following fragment
shader as a template:
uniform int blockSize;
uniform int modulo;
uniform sampler2D texture[N];
out vec4 output[N];
void main() {
ivec2 texelCoord = ivec2(gl_FragCoord.xy);
ivec2 blockCoord = texelCoord / blockSize;
ivec2 xOffset = ivec2(blockSize, 0);
ivec2 yOffset = ivec2(0, blockSize);
if (((blockCoord.x + blockCoord.y) % 2) == modulo) {
for (int i = 0; i < N; ++i) {
output[i] = function(
texelFetch(texture[i], texelCoord + xOffset, 0),
texelFetch(texture[i], texelCoord - xOffset, 0),
texelFetch(texture[i], texelCoord + yOffset, 0),
texelFetch(texture[i], texelCoord - yOffset, 0)
);
}
} else {
discard;
}
}
Where "blockSize" is the size of the disjoint rectangular sets of texels,
"modulo" should be either zero or one (depending whether even or odd
blocks should be fetched/written), and "function" is an arbitrary
function of its parameters.
*/
class TextureBarrierTexelBlocksBase : public TextureBarrierBasicOutline
{
protected:
TextureBarrierTexelBlocksBase(deqp::Context& context, TextureBarrierTests::API api, const char* name,
const char* description)
: TextureBarrierBasicOutline(context, api, name, description)
, m_blockSize(-1)
, m_modulo(-1)
, m_blockSizeLoc(0)
, m_moduloLoc(0)
{
}
/* Actual fragment shader source based on the provided template. */
virtual const char* fsh()
{
return "#version 400 core\n"
"#define NUM_TEXTURES 8\n"
"uniform int blockSize;\n"
"uniform int modulo;\n"
"uniform usampler2D texInput[NUM_TEXTURES];\n"
"out uvec4 fragOutput[NUM_TEXTURES];\n"
"uvec4 func(uvec4 t0, uvec4 t1, uvec4 t2, uvec4 t3) {\n"
" return t0 + t1 + t2 + t3;\n"
"}\n"
"void main() {\n"
" ivec2 texelCoord = ivec2(gl_FragCoord.xy);\n"
" ivec2 blockCoord = texelCoord / blockSize;\n"
" ivec2 xOffset = ivec2(blockSize, 0);\n"
" ivec2 yOffset = ivec2(0, blockSize);\n"
" if (((blockCoord.x + blockCoord.y) % 2) == modulo) {\n"
" for (int i = 0; i < NUM_TEXTURES; ++i) {\n"
" fragOutput[i] = func(texelFetch(texInput[i], texelCoord + xOffset, 0),\n"
" texelFetch(texInput[i], texelCoord - xOffset, 0),\n"
" texelFetch(texInput[i], texelCoord + yOffset, 0),\n"
" texelFetch(texInput[i], texelCoord - yOffset, 0));\n"
" }\n"
" } else {\n"
" discard;\n"
" }\n"
"}";
}
/* CPU code equivalent to the fragment shader to update reference data. */
virtual void updateTextureData()
{
for (GLuint x = 0; x < m_width; ++x)
for (GLuint y = 0; y < m_height; ++y)
{
GLuint blockX = x / m_blockSize;
GLuint blockY = y / m_blockSize;
if ((static_cast<int>((blockX + blockY) % 2)) == m_modulo)
{
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
texel(m_reference[i], x, y) =
texel(m_reference[i], x + m_blockSize, y) + texel(m_reference[i], x - m_blockSize, y) +
texel(m_reference[i], x, y + m_blockSize) + texel(m_reference[i], x, y - m_blockSize);
}
}
}
}
/* Render pass setup code. Updates uniforms used by the fragment shader and
member variables used by the reference data update code. */
virtual void setupRenderPass(int testPass, int renderPass)
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
if ((testPass == 0) && (renderPass == 0))
{
// Get the uniform locations in the first pass, reuse it afterwards
m_blockSizeLoc = gl.getUniformLocation(m_program, "blockSize");
m_moduloLoc = gl.getUniformLocation(m_program, "modulo");
}
// Update block size if changed
int newBlockSize = getBlockSize(testPass, renderPass);
if (newBlockSize != m_blockSize)
{
m_blockSize = newBlockSize;
gl.uniform1i(m_blockSizeLoc, m_blockSize);
}
// Update modulo if changed
int newModulo = getModulo(testPass, renderPass);
if (newModulo != m_modulo)
{
m_modulo = newModulo;
gl.uniform1i(m_moduloLoc, m_modulo);
}
}
/* Returns the block size to be used in the specified pass. */
virtual int getBlockSize(int testPass, int renderPass) = 0;
/* Returns the modulo value to be used in the specified pass. */
virtual int getModulo(int testPass, int renderPass) = 0;
private:
int m_blockSize;
int m_modulo;
GLint m_blockSizeLoc;
GLint m_moduloLoc;
};
/*
Test case #1: Disjoint texels
* Using the basic outline above test that reads and writes from/to
disjoint sets of texels work as expected.
* Repeat the above test case with various values for blockSize (including a
block size of one).
* Repeat the actual rendering pass multiple times within a single test
using a fixed value for "blockSize" and "modulo". Because the set of
read and written texels stays disjoint the results should still be well
defined even without the use of any synchronization primitive.
*/
class TextureBarrierDisjointTexels : public TextureBarrierTexelBlocksBase
{
public:
TextureBarrierDisjointTexels(deqp::Context& context, TextureBarrierTests::API api, const char* name)
: TextureBarrierTexelBlocksBase(
context, api, name,
"Using the basic outline test that reads and writes from/to disjoint sets of texels work as expected. "
"Repeat the test with multiple different block size values (including a block size of one). "
"Repeat the actual rendering pass multiple times within a single test using a fixed value for "
"blockSize and modulo. Because the set of read and written texels stays disjoint the result "
"should still be well defined even without the use of any synchronization primitive.")
{
}
/* Perform multiple test passes, one for each blockSize value. */
virtual int numTestPasses()
{
return 16;
}
/* Perform multiple render passes. As the same blockSize and modulo value is used between render passes the rendering
results should still stay well defined. */
virtual int numRenderPasses()
{
return 5;
}
/* No need for a texture barrier as reads and writes happen from/to disjoint set of texels within a single test pass. */
virtual bool needsBarrier()
{
return false;
}
/* Try different values for blockSize, but the value must stay constant within a single test pass. */
virtual int getBlockSize(int testPass, int renderPass)
{
(void)renderPass;
return testPass + 1;
}
/* Use a fixed value for modulo. */
virtual int getModulo(int testPass, int renderPass)
{
(void)testPass;
(void)renderPass;
return 0;
}
};
/*
Test case #2: Overlapping texels (with texture barrier)
* Using the basic outline above test that reads and writes from/to
disjoint sets of texels work as expected.
* Repeat the actual rendering pass multiple times within a single test,
but this time use different values for "blockSize" and "modulo" and
call TextureBarrier between subsequent rendering passes to ensure
well defined results.
*/
class TextureBarrierOverlappingTexels : public TextureBarrierTexelBlocksBase
{
public:
TextureBarrierOverlappingTexels(deqp::Context& context, TextureBarrierTests::API api, const char* name)
: TextureBarrierTexelBlocksBase(
context, api, name,
"Using the basic outline test that reads and writes from/to overlapping sets of texels work "
"as expected if there is a call to TextureBarrier between subsequent rendering passes. Test "
"this by using different values for blockSize and modulo for each rendering pass.")
{
}
/* A single test pass is sufficient. */
virtual int numTestPasses()
{
return 1;
}
/* Perform several render passes to provoke a lot of overlap between read and written texel blocks. */
virtual int numRenderPasses()
{
return 42;
}
/* We need texture barriers between render passes as reads and writes in different render passes do overlap. */
virtual bool needsBarrier()
{
return true;
}
/* Use a pseudo-random blockSize for each render pass. */
virtual int getBlockSize(int testPass, int renderPass)
{
(void)testPass;
return (5 + renderPass * 3) % 7 + 1;
}
/* Use a pseudo-random modulo for each render pass. */
virtual int getModulo(int testPass, int renderPass)
{
(void)testPass;
return (renderPass * 3) % 2;
}
};
/*
Base class of the rendering tests which use a fragment shader performing a
single read and write of each texel. The skeleton of these tests is as follows:
* Using the basic outline above test that a single read and write of each
texel, where the read is in the fragment shader invocation that writes
the same texel, works as expected. Use the following fragment shader as
a template:
uniform sampler2D texture[N];
out vec4 output[N];
void main() {
ivec2 texelCoord = ivec2(gl_FragCoord.xy);
for (int i = 0; i < N; ++i) {
output[i] = function(texelFetch(texture[i], texelCoord, 0);
}
}
Where "function" is an arbitrary function of its parameter.
*/
class TextureBarrierSameTexelRWBase : public TextureBarrierBasicOutline
{
protected:
TextureBarrierSameTexelRWBase(deqp::Context& context, TextureBarrierTests::API api, const char* name,
const char* description)
: TextureBarrierBasicOutline(context, api, name, description)
{
}
/* Actual fragment shader source based on the provided template. */
virtual const char* fsh()
{
return "#version 400 core\n"
"#define NUM_TEXTURES 8\n"
"uniform usampler2D texInput[NUM_TEXTURES];\n"
"out uvec4 fragOutput[NUM_TEXTURES];\n"
"uvec4 func(uvec4 t) {\n"
" return t + 1;\n"
"}\n"
"void main() {\n"
" ivec2 texelCoord = ivec2(gl_FragCoord.xy);\n"
" for (int i = 0; i < NUM_TEXTURES; ++i) {\n"
" fragOutput[i] = func(texelFetch(texInput[i], texelCoord, 0));\n"
" }\n"
"}";
}
/* CPU code equivalent to the fragment shader to update reference data. */
virtual void updateTextureData()
{
for (GLuint x = 0; x < m_width; ++x)
for (GLuint y = 0; y < m_height; ++y)
{
for (GLuint i = 0; i < NUM_TEXTURES; ++i)
{
texel(m_reference[i], x, y)++;
}
}
}
/* The fragment shader used by these tests doesn't have any parameters, thus no need for render pass setup code. */
virtual void setupRenderPass(int testPass, int renderPass)
{
(void)testPass;
(void)renderPass;
}
};
/*
Test case #3: Single read and write of the same texel
* Using the basic outline above test that a single read and write of each
texel, where the read is in the fragment shader invocation that writes
the same texel, works as expected.
*/
class TextureBarrierSameTexelRW : public TextureBarrierSameTexelRWBase
{
public:
TextureBarrierSameTexelRW(deqp::Context& context, TextureBarrierTests::API api, const char* name)
: TextureBarrierSameTexelRWBase(
context, api, name,
"Using the basic outline tests that a single read and write of each texel, where the read "
"is in the fragment shader invocation that writes the same texel, works as expected.")
{
}
/* A single test pass is sufficient. */
virtual int numTestPasses()
{
return 1;
}
/* Well defined behavior is guaranteed only in case of a single pass. */
virtual int numRenderPasses()
{
return 1;
}
/* A single read and write of the same texel doesn't require a texture barrier. */
virtual bool needsBarrier()
{
return false;
}
};
/*
Test case #4: Multipass read and write of the same texel (with texture barrier)
* Using the basic outline above test that a single read and write of each
texel, where the read is in the fragment shader invocation that writes
the same texel, works as expected.
* Repeat the above test case but this time perform multiple iterations
of the actual rendering pass and use a call to TextureBarrier between
them to ensure consistency.
*/
class TextureBarrierSameTexelRWMultipass : public TextureBarrierSameTexelRWBase
{
public:
TextureBarrierSameTexelRWMultipass(deqp::Context& context, TextureBarrierTests::API api, const char* name)
: TextureBarrierSameTexelRWBase(
context, api, name,
"Using the basic outline tests that multiple reads and writes of each texel, where the read "
"is in the fragment shader invocation that writes the same texel, works as expected if there "
"is a call to TextureBarrier between each subsequent read-after-write.")
{
}
/* A single test pass is sufficient. */
virtual int numTestPasses()
{
return 1;
}
/* Perform several render passes to provoke read-after-write hazards. */
virtual int numRenderPasses()
{
return 42;
}
/* We need to use texture barriers in between rendering passes to avoid read-after-write hazards. */
virtual bool needsBarrier()
{
return true;
}
};
const char* apiToTestName(TextureBarrierTests::API api)
{
switch (api)
{
case TextureBarrierTests::API_GL_45core:
return "texture_barrier";
case TextureBarrierTests::API_GL_ARB_texture_barrier:
return "texture_barrier_ARB";
}
DE_ASSERT(0);
return "";
}
/** Constructor.
*
* @param context Rendering context.
* @param api API to test (core vs ARB extension)
**/
TextureBarrierTests::TextureBarrierTests(deqp::Context& context, API api)
: TestCaseGroup(context, apiToTestName(api), "Verifies \"texture_barrier\" functionality"), m_api(api)
{
/* Left blank on purpose */
}
/** Destructor.
*
**/
TextureBarrierTests::~TextureBarrierTests()
{
}
/** Initializes the texture_barrier test group.
*
**/
void TextureBarrierTests::init(void)
{
addChild(new TextureBarrierDisjointTexels(m_context, m_api, "disjoint-texels"));
addChild(new TextureBarrierOverlappingTexels(m_context, m_api, "overlapping-texels"));
addChild(new TextureBarrierSameTexelRW(m_context, m_api, "same-texel-rw"));
addChild(new TextureBarrierSameTexelRWMultipass(m_context, m_api, "same-texel-rw-multipass"));
}
} /* glcts namespace */