blob: 48f2d9918111a1666ea1564fa6e933d98a8e628e [file] [log] [blame]
//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ComputeShaderTest:
// Compute shader specific tests.
#include <vector>
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
class ComputeShaderTest : public ANGLETest
{
protected:
ComputeShaderTest() {}
void createDummyOutputImage(GLuint texture, GLenum internalFormat, GLint width, GLint height)
{
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, width, height);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);
EXPECT_GL_NO_ERROR();
}
template <class T, GLint kWidth, GLint kHeight>
void runSharedMemoryTest(const char *kCS,
GLenum internalFormat,
GLenum format,
const std::array<T, kWidth * kHeight> &inputData,
const std::array<T, kWidth * kHeight> &expectedValues)
{
GLTexture texture[2];
GLFramebuffer framebuffer;
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, format,
inputData.data());
EXPECT_GL_NO_ERROR();
constexpr T initData[kWidth * kHeight] = {};
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, format, initData);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, internalFormat);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
T outputValues[kWidth * kHeight] = {};
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1],
0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, format, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValues[i], outputValues[i]);
}
}
};
class ComputeShaderTestES3 : public ANGLETest
{
protected:
ComputeShaderTestES3() {}
};
class WebGL2ComputeTest : public ComputeShaderTest
{
protected:
WebGL2ComputeTest() { setWebGLCompatibilityEnabled(true); }
};
// link a simple compute program. It should be successful.
TEST_P(ComputeShaderTest, LinkComputeProgram)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
EXPECT_GL_NO_ERROR();
}
// Link a simple compute program. Then detach the shader and dispatch compute.
// It should be successful.
TEST_P(ComputeShaderTest, DetachShaderAfterLinkSuccess)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
GLuint program = glCreateProgram();
GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
EXPECT_NE(0u, cs);
glAttachShader(program, cs);
glDeleteShader(cs);
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_GL_TRUE(linkStatus);
glDetachShader(program, cs);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
glDispatchCompute(8, 4, 2);
EXPECT_GL_NO_ERROR();
}
// link a simple compute program. There is no local size and linking should fail.
TEST_P(ComputeShaderTest, LinkComputeProgramNoLocalSizeLinkError)
{
constexpr char kCS[] = R"(#version 310 es
void main()
{
})";
GLuint program = CompileComputeProgram(kCS, false);
EXPECT_EQ(0u, program);
glDeleteProgram(program);
EXPECT_GL_NO_ERROR();
}
// link a simple compute program.
// make sure that uniforms and uniform samplers get recorded
TEST_P(ComputeShaderTest, LinkComputeProgramWithUniforms)
{
constexpr char kCS[] = R"(#version 310 es
precision mediump sampler2D;
layout(local_size_x=1) in;
uniform int myUniformInt;
uniform sampler2D myUniformSampler;
layout(rgba32i) uniform highp writeonly iimage2D imageOut;
void main()
{
int q = myUniformInt;
vec4 v = textureLod(myUniformSampler, vec2(0.0), 0.0);
imageStore(imageOut, ivec2(0), ivec4(v) * q);
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
GLint uniformLoc = glGetUniformLocation(program.get(), "myUniformInt");
EXPECT_NE(-1, uniformLoc);
uniformLoc = glGetUniformLocation(program.get(), "myUniformSampler");
EXPECT_NE(-1, uniformLoc);
EXPECT_GL_NO_ERROR();
}
// Attach both compute and non-compute shaders. A link time error should occur.
// OpenGL ES 3.10, 7.3 Program Objects
TEST_P(ComputeShaderTest, AttachMultipleShaders)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
constexpr char kVS[] = R"(#version 310 es
void main()
{
})";
constexpr char kFS[] = R"(#version 310 es
void main()
{
})";
GLuint program = glCreateProgram();
GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
EXPECT_NE(0u, vs);
EXPECT_NE(0u, fs);
EXPECT_NE(0u, cs);
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glAttachShader(program, cs);
glDeleteShader(cs);
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_GL_FALSE(linkStatus);
EXPECT_GL_NO_ERROR();
}
// Attach a vertex, fragment and compute shader.
// Query for the number of attached shaders and check the count.
TEST_P(ComputeShaderTest, AttachmentCount)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
constexpr char kVS[] = R"(#version 310 es
void main()
{
})";
constexpr char kFS[] = R"(#version 310 es
void main()
{
})";
GLuint program = glCreateProgram();
GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
EXPECT_NE(0u, vs);
EXPECT_NE(0u, fs);
EXPECT_NE(0u, cs);
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glAttachShader(program, cs);
glDeleteShader(cs);
GLint numAttachedShaders;
glGetProgramiv(program, GL_ATTACHED_SHADERS, &numAttachedShaders);
EXPECT_EQ(3, numAttachedShaders);
glDeleteProgram(program);
EXPECT_GL_NO_ERROR();
}
// Attach a compute shader and link, but start rendering.
TEST_P(ComputeShaderTest, StartRenderingWithComputeProgram)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
glDrawArrays(GL_POINTS, 0, 2);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
// Attach a vertex and fragment shader and link, but dispatch compute.
TEST_P(ComputeShaderTest, DispatchComputeWithRenderingProgram)
{
constexpr char kVS[] = R"(#version 310 es
void main() {})";
constexpr char kFS[] = R"(#version 310 es
void main() {})";
GLuint program = glCreateProgram();
GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
EXPECT_NE(0u, vs);
EXPECT_NE(0u, fs);
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_GL_TRUE(linkStatus);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
glDispatchCompute(8, 4, 2);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
// Access all compute shader special variables.
TEST_P(ComputeShaderTest, AccessAllSpecialVariables)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=4, local_size_y=3, local_size_z=2) in;
layout(rgba32ui) uniform highp writeonly uimage2D imageOut;
void main()
{
uvec3 temp1 = gl_NumWorkGroups;
uvec3 temp2 = gl_WorkGroupSize;
uvec3 temp3 = gl_WorkGroupID;
uvec3 temp4 = gl_LocalInvocationID;
uvec3 temp5 = gl_GlobalInvocationID;
uint temp6 = gl_LocalInvocationIndex;
imageStore(imageOut, ivec2(gl_LocalInvocationIndex, 0), uvec4(temp1 + temp2 + temp3 + temp4 + temp5, temp6));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
}
// Access part compute shader special variables.
TEST_P(ComputeShaderTest, AccessPartSpecialVariables)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=4, local_size_y=3, local_size_z=2) in;
layout(rgba32ui) uniform highp writeonly uimage2D imageOut;
void main()
{
uvec3 temp1 = gl_WorkGroupSize;
uvec3 temp2 = gl_WorkGroupID;
uint temp3 = gl_LocalInvocationIndex;
imageStore(imageOut, ivec2(gl_LocalInvocationIndex, 0), uvec4(temp1 + temp2, temp3));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
}
// Use glDispatchCompute to define work group count.
TEST_P(ComputeShaderTest, DispatchCompute)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=4, local_size_y=3, local_size_z=2) in;
layout(rgba32ui) uniform highp writeonly uimage2D imageOut;
void main()
{
uvec3 temp = gl_NumWorkGroups;
imageStore(imageOut, ivec2(gl_GlobalInvocationID.xy), uvec4(temp, 0u));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
GLTexture texture;
createDummyOutputImage(texture, GL_RGBA32UI, 4, 3);
glUseProgram(program.get());
glDispatchCompute(8, 4, 2);
EXPECT_GL_NO_ERROR();
}
// Test that binds UAV with type buffer to slot 0, then binds UAV with type image to slot 0, then
// buffer again.
TEST_P(ComputeShaderTest, BufferImageBuffer)
{
// See http://anglebug.com/3536
ANGLE_SKIP_TEST_IF(IsOpenGL() && IsIntel() && IsWindows());
// Skipping due to a bug on the Qualcomm Vulkan Android driver.
// http://anglebug.com/3726
ANGLE_SKIP_TEST_IF(IsAndroid() && IsVulkan());
constexpr char kCS0[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(binding = 0, offset = 4) uniform atomic_uint ac[2];
void main()
{
atomicCounterIncrement(ac[0]);
atomicCounterDecrement(ac[1]);
})";
ANGLE_GL_COMPUTE_PROGRAM(program0, kCS0);
glUseProgram(program0);
unsigned int bufferData[3] = {11u, 4u, 4u};
GLBuffer atomicCounterBuffer;
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(bufferData), bufferData, GL_STATIC_DRAW);
glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
void *mappedBuffer =
glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, GL_MAP_READ_BIT);
memcpy(bufferData, mappedBuffer, sizeof(bufferData));
glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
EXPECT_EQ(11u, bufferData[0]);
EXPECT_EQ(5u, bufferData[1]);
EXPECT_EQ(3u, bufferData[2]);
constexpr char kCS1[] = R"(#version 310 es
layout(local_size_x=4, local_size_y=3, local_size_z=2) in;
layout(rgba32ui) uniform highp writeonly uimage2D imageOut;
void main()
{
uvec3 temp = gl_NumWorkGroups;
imageStore(imageOut, ivec2(gl_GlobalInvocationID.xy), uvec4(temp, 0u));
})";
ANGLE_GL_COMPUTE_PROGRAM(program1, kCS1);
GLTexture texture;
createDummyOutputImage(texture, GL_RGBA32UI, 4, 3);
glUseProgram(program1);
glDispatchCompute(8, 4, 2);
glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT);
glUseProgram(program0);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
mappedBuffer =
glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, GL_MAP_READ_BIT);
memcpy(bufferData, mappedBuffer, sizeof(bufferData));
glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
EXPECT_EQ(11u, bufferData[0]);
EXPECT_EQ(6u, bufferData[1]);
EXPECT_EQ(2u, bufferData[2]);
EXPECT_GL_NO_ERROR();
}
// Test that binds UAV with type image to slot 0, then binds UAV with type buffer to slot 0.
TEST_P(ComputeShaderTest, ImageAtomicCounterBuffer)
{
// Flaky hang. http://anglebug.com/3636
ANGLE_SKIP_TEST_IF(IsWindows() && IsNVIDIA() && IsDesktopOpenGL());
// Skipping due to a bug on the Qualcomm Vulkan Android driver.
// http://anglebug.com/3726
ANGLE_SKIP_TEST_IF(IsAndroid() && IsVulkan());
constexpr char kCS0[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) writeonly uniform highp uimage2D uImage[2];
void main()
{
imageStore(uImage[0], ivec2(gl_LocalInvocationIndex, gl_WorkGroupID.x), uvec4(100, 0,
0, 0));
imageStore(uImage[1], ivec2(gl_LocalInvocationIndex, gl_WorkGroupID.x), uvec4(100, 0,
0, 0));
})";
ANGLE_GL_COMPUTE_PROGRAM(program0, kCS0);
glUseProgram(program0);
int width = 1, height = 1;
GLuint inputValues[] = {200};
GLTexture mTexture[2];
glBindTexture(GL_TEXTURE_2D, mTexture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
glBindTexture(GL_TEXTURE_2D, mTexture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
glBindImageTexture(0, mTexture[0], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glBindImageTexture(1, mTexture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
constexpr char kCS1[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(binding = 0, offset = 4) uniform atomic_uint ac[2];
void main()
{
atomicCounterIncrement(ac[0]);
atomicCounterDecrement(ac[1]);
})";
ANGLE_GL_COMPUTE_PROGRAM(program1, kCS1);
unsigned int bufferData[3] = {11u, 4u, 4u};
GLBuffer atomicCounterBuffer;
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(bufferData), bufferData, GL_STATIC_DRAW);
glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
glUseProgram(program1);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
void *mappedBuffer =
glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, GL_MAP_READ_BIT);
memcpy(bufferData, mappedBuffer, sizeof(bufferData));
glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
EXPECT_EQ(11u, bufferData[0]);
EXPECT_EQ(5u, bufferData[1]);
EXPECT_EQ(3u, bufferData[2]);
EXPECT_GL_NO_ERROR();
}
// Test that binds UAV with type image to slot 0, then binds UAV with type buffer to slot 0.
TEST_P(ComputeShaderTest, ImageShaderStorageBuffer)
{
constexpr char kCS0[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) writeonly uniform highp uimage2D uImage[2];
void main()
{
imageStore(uImage[0], ivec2(gl_LocalInvocationIndex, gl_WorkGroupID.x), uvec4(100, 0,
0, 0));
imageStore(uImage[1], ivec2(gl_LocalInvocationIndex, gl_WorkGroupID.x), uvec4(100, 0,
0, 0));
})";
ANGLE_GL_COMPUTE_PROGRAM(program0, kCS0);
glUseProgram(program0);
int width = 1, height = 1;
GLuint inputValues[] = {200};
GLTexture mTexture[2];
glBindTexture(GL_TEXTURE_2D, mTexture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
glBindTexture(GL_TEXTURE_2D, mTexture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
glBindImageTexture(0, mTexture[0], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glBindImageTexture(1, mTexture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
constexpr char kCS1[] =
R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(std140, binding = 0) buffer blockOut {
uvec2 data;
} instanceOut;
layout(std140, binding = 1) buffer blockIn {
uvec2 data;
} instanceIn;
void main()
{
instanceOut.data = instanceIn.data;
}
)";
ANGLE_GL_COMPUTE_PROGRAM(program1, kCS1);
constexpr unsigned int kBufferSize = 2;
constexpr unsigned int kBufferData[kBufferSize] = {10, 20};
GLBuffer blockIn;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, blockIn);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kBufferData), kBufferData, GL_STATIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, blockIn);
GLBuffer blockOut;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, blockOut);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kBufferData), nullptr, GL_STATIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, blockOut);
glUseProgram(program1);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, blockOut);
unsigned int bufferDataOut[kBufferSize] = {};
const GLColor *ptr = reinterpret_cast<GLColor *>(
glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kBufferData), GL_MAP_READ_BIT));
memcpy(bufferDataOut, ptr, sizeof(kBufferData));
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
for (unsigned int index = 0; index < kBufferSize; ++index)
{
EXPECT_EQ(bufferDataOut[index], kBufferData[index]) << " index " << index;
}
}
// Basic test for DispatchComputeIndirect.
TEST_P(ComputeShaderTest, DispatchComputeIndirect)
{
// Flaky crash on teardown, see http://anglebug.com/3349
ANGLE_SKIP_TEST_IF(IsD3D11() && IsIntel() && IsWindows());
GLTexture texture;
GLFramebuffer framebuffer;
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) uniform highp uimage2D uImage;
void main()
{
imageStore(uImage, ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y), uvec4(100, 0, 0, 0));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
glUseProgram(program.get());
const int kWidth = 4, kHeight = 6;
GLuint inputValues[] = {0};
GLBuffer buffer;
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer);
GLuint params[] = {kWidth, kHeight, 1};
glBufferData(GL_DISPATCH_INDIRECT_BUFFER, sizeof(params), params, GL_STATIC_DRAW);
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchComputeIndirect(0);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
GLuint outputValues[kWidth][kHeight];
GLuint expectedValue = 100u;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth; i++)
{
for (int j = 0; j < kHeight; j++)
{
EXPECT_EQ(expectedValue, outputValues[i][j]);
}
}
}
// Use image uniform to write texture in compute shader, and verify the content is expected.
TEST_P(ComputeShaderTest, BindImageTexture)
{
GLTexture mTexture[2];
GLFramebuffer mFramebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) writeonly uniform highp uimage2D uImage[2];
void main()
{
imageStore(uImage[0], ivec2(gl_LocalInvocationIndex, gl_WorkGroupID.x), uvec4(100, 0,
0, 0));
imageStore(uImage[1], ivec2(gl_LocalInvocationIndex, gl_WorkGroupID.x), uvec4(100, 0,
0, 0));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
int width = 1, height = 1;
GLuint inputValues[] = {200};
glBindTexture(GL_TEXTURE_2D, mTexture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, mTexture[0], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, mTexture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT,
inputValues);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, mTexture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
GLuint outputValues[2][1];
GLuint expectedValue = 100;
glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture[0],
0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues[0]);
EXPECT_GL_NO_ERROR();
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture[1],
0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues[1]);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < width * height; i++)
{
EXPECT_EQ(expectedValue, outputValues[0][i]);
EXPECT_EQ(expectedValue, outputValues[1][i]);
}
}
// When declare a image array without a binding qualifier, all elements are bound to unit zero.
TEST_P(ComputeShaderTest, ImageArrayWithoutBindingQualifier)
{
ANGLE_SKIP_TEST_IF(IsD3D11());
// TODO(xinghua.cao@intel.com): On AMD desktop OpenGL, bind two image variables to unit 0,
// only one variable is valid.
ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
GLTexture mTexture;
GLFramebuffer mFramebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui) writeonly uniform highp uimage2D uImage[2];
void main()
{
imageStore(uImage[0], ivec2(gl_LocalInvocationIndex, 0), uvec4(100, 0, 0, 0));
imageStore(uImage[1], ivec2(gl_LocalInvocationIndex, 1), uvec4(100, 0, 0, 0));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
constexpr int kTextureWidth = 1, kTextureHeight = 2;
GLuint inputValues[] = {200, 200};
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kTextureWidth, kTextureHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureWidth, kTextureHeight, GL_RED_INTEGER,
GL_UNSIGNED_INT, inputValues);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, mTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
GLuint outputValues[kTextureWidth * kTextureHeight];
glReadPixels(0, 0, kTextureWidth, kTextureHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
outputValues);
EXPECT_GL_NO_ERROR();
GLuint expectedValue = 100;
for (int i = 0; i < kTextureWidth * kTextureHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
}
// imageLoad functions
TEST_P(ComputeShaderTest, ImageLoad)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(rgba8) uniform highp readonly image2D mImage2DInput;
layout(rgba16i) uniform highp readonly iimageCube mImageCubeInput;
layout(rgba32ui) uniform highp readonly uimage3D mImage3DInput;
layout(r32i) uniform highp writeonly iimage2D imageOut;
void main()
{
vec4 result2d = imageLoad(mImage2DInput, ivec2(gl_LocalInvocationID.xy));
ivec4 resultCube = imageLoad(mImageCubeInput, ivec3(gl_LocalInvocationID.xyz));
uvec4 result3d = imageLoad(mImage3DInput, ivec3(gl_LocalInvocationID.xyz));
imageStore(imageOut, ivec2(gl_LocalInvocationIndex, 0), ivec4(result2d) + resultCube + ivec4(result3d));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
EXPECT_GL_NO_ERROR();
}
// imageStore functions
TEST_P(ComputeShaderTest, ImageStore)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(rgba16f) uniform highp writeonly imageCube mImageCubeOutput;
layout(r32f) uniform highp writeonly image3D mImage3DOutput;
layout(rgba8ui) uniform highp writeonly uimage2DArray mImage2DArrayOutput;
void main()
{
imageStore(mImageCubeOutput, ivec3(gl_LocalInvocationID.xyz), vec4(0.0));
imageStore(mImage3DOutput, ivec3(gl_LocalInvocationID.xyz), vec4(0.0));
imageStore(mImage2DArrayOutput, ivec3(gl_LocalInvocationID.xyz), uvec4(0));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
EXPECT_GL_NO_ERROR();
}
// imageSize functions
TEST_P(ComputeShaderTest, ImageSize)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(rgba8) uniform highp readonly imageCube mImageCubeInput;
layout(r32i) uniform highp readonly iimage2D mImage2DInput;
layout(rgba16ui) uniform highp readonly uimage2DArray mImage2DArrayInput;
layout(r32i) uniform highp writeonly iimage2D imageOut;
void main()
{
ivec2 sizeCube = imageSize(mImageCubeInput);
ivec2 size2D = imageSize(mImage2DInput);
ivec3 size2DArray = imageSize(mImage2DArrayInput);
imageStore(imageOut, ivec2(gl_LocalInvocationIndex, 0), ivec4(sizeCube, size2D.x, size2DArray.x));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
EXPECT_GL_NO_ERROR();
}
// Test that texelFetch works well in compute shader.
TEST_P(ComputeShaderTest, TexelFetchFunction)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=16, local_size_y=16) in;
precision highp usampler2D;
uniform usampler2D tex;
layout(std140, binding = 0) buffer buf {
uint outData[16][16];
};
void main()
{
uint x = gl_LocalInvocationID.x;
uint y = gl_LocalInvocationID.y;
outData[y][x] = texelFetch(tex, ivec2(x, y), 0).x;
})";
constexpr unsigned int kWidth = 16;
constexpr unsigned int kHeight = 16;
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
GLuint texels[kHeight][kWidth] = {{0}};
for (unsigned int y = 0; y < kHeight; ++y)
{
for (unsigned int x = 0; x < kWidth; ++x)
{
texels[y][x] = x + y * kWidth;
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
texels);
glBindTexture(GL_TEXTURE_2D, 0);
// The array stride are rounded up to the base alignment of a vec4 for std140 layout.
constexpr unsigned int kArrayStride = 16;
GLBuffer ssbo;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, kWidth * kHeight * kArrayStride, nullptr,
GL_STREAM_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glUniform1i(glGetUniformLocation(program, "tex"), 0);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
glDispatchCompute(1, 1, 1);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
const GLuint *ptr = reinterpret_cast<const GLuint *>(glMapBufferRange(
GL_SHADER_STORAGE_BUFFER, 0, kWidth * kHeight * kArrayStride, GL_MAP_READ_BIT));
EXPECT_GL_NO_ERROR();
for (unsigned int idx = 0; idx < kWidth * kHeight; idx++)
{
EXPECT_EQ(idx, *(ptr + idx * kArrayStride / 4));
}
}
// Test that texture function works well in compute shader.
TEST_P(ComputeShaderTest, TextureFunction)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=16, local_size_y=16) in;
precision highp usampler2D;
uniform usampler2D tex;
layout(std140, binding = 0) buffer buf {
uint outData[16][16];
};
void main()
{
uint x = gl_LocalInvocationID.x;
uint y = gl_LocalInvocationID.y;
float xCoord = float(x) / float(16);
float yCoord = float(y) / float(16);
outData[y][x] = texture(tex, vec2(xCoord, yCoord)).x;
})";
constexpr unsigned int kWidth = 16;
constexpr unsigned int kHeight = 16;
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
GLuint texels[kHeight][kWidth] = {{0}};
for (unsigned int y = 0; y < kHeight; ++y)
{
for (unsigned int x = 0; x < kWidth; ++x)
{
texels[y][x] = x + y * kWidth;
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
texels);
glBindTexture(GL_TEXTURE_2D, 0);
// The array stride are rounded up to the base alignment of a vec4 for std140 layout.
constexpr unsigned int kArrayStride = 16;
GLBuffer ssbo;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, kWidth * kHeight * kArrayStride, nullptr,
GL_STREAM_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glUniform1i(glGetUniformLocation(program, "tex"), 0);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
glDispatchCompute(1, 1, 1);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
const GLuint *ptr = reinterpret_cast<const GLuint *>(glMapBufferRange(
GL_SHADER_STORAGE_BUFFER, 0, kWidth * kHeight * kArrayStride, GL_MAP_READ_BIT));
EXPECT_GL_NO_ERROR();
for (unsigned int idx = 0; idx < kWidth * kHeight; idx++)
{
EXPECT_EQ(idx, *(ptr + idx * kArrayStride / 4));
}
}
// Test mixed use of sampler and image.
TEST_P(ComputeShaderTest, SamplingAndImageReadWrite)
{
GLTexture texture[3];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
precision highp usampler2D;
uniform usampler2D tex;
void main()
{
uvec4 value_1 = texelFetch(tex, ivec2(gl_LocalInvocationID.xy), 0);
uvec4 value_2 = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value_1 + value_2);
})";
constexpr int kWidth = 1, kHeight = 1;
constexpr GLuint kInputValues[3][1] = {{50}, {100}, {20}};
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[0]);
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[2]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, 0);
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[2], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
constexpr GLuint expectedValue = 150;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[2], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
}
// Use image uniform to read and write Texture2D in compute shader, and verify the contents.
TEST_P(ComputeShaderTest, BindImageTextureWithTexture2D)
{
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth = 1, kHeight = 1;
constexpr GLuint kInputValues[2][1] = {{200}, {100}};
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
constexpr GLuint expectedValue = 200;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
}
// Use image uniform to read and write Texture2DArray in compute shader, and verify the contents.
TEST_P(ComputeShaderTest, BindImageTextureWithTexture2DArray)
{
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=2, local_size_y=2, local_size_z=2) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2DArray uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2DArray uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec3(gl_LocalInvocationID.xyz));
imageStore(uImage_2, ivec3(gl_LocalInvocationID.xyz), value);
})";
constexpr int kWidth = 1, kHeight = 1, kDepth = 2;
constexpr GLuint kInputValues[2][2] = {{200, 200}, {100, 100}};
glBindTexture(GL_TEXTURE_2D_ARRAY, texture[0]);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D_ARRAY, texture[1]);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_TRUE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
constexpr GLuint expectedValue = 200;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture[1], 0, 0);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture[1], 0, 1);
EXPECT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
}
// Use image uniform to read and write Texture3D in compute shader, and verify the contents.
TEST_P(ComputeShaderTest, BindImageTextureWithTexture3D)
{
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=2) in;
layout(r32ui, binding = 0) readonly uniform highp uimage3D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage3D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec3(gl_LocalInvocationID.xyz));
imageStore(uImage_2, ivec3(gl_LocalInvocationID.xyz), value);
})";
constexpr int kWidth = 1, kHeight = 1, kDepth = 2;
constexpr GLuint kInputValues[2][2] = {{200, 200}, {100, 100}};
glBindTexture(GL_TEXTURE_3D, texture[0]);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_3D, texture[1]);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_TRUE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
constexpr GLuint expectedValue = 200;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture[1], 0, 0);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture[1], 0, 1);
EXPECT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
}
// Use image uniform to read and write TextureCube in compute shader, and verify the contents.
TEST_P(ComputeShaderTest, BindImageTextureWithTextureCube)
{
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimageCube uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimageCube uImage_2;
void main()
{
for (int i = 0; i < 6; i++)
{
uvec4 value = imageLoad(uImage_1, ivec3(gl_LocalInvocationID.xy, i));
imageStore(uImage_2, ivec3(gl_LocalInvocationID.xy, i), value);
}
})";
constexpr int kWidth = 1, kHeight = 1;
constexpr GLuint kInputValues[2][1] = {{200}, {100}};
glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_R32UI, kWidth, kHeight);
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
face++)
{
glTexSubImage2D(face, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[0]);
}
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_CUBE_MAP, texture[1]);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_R32UI, kWidth, kHeight);
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
face++)
{
glTexSubImage2D(face, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[1]);
}
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_TRUE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
constexpr GLuint expectedValue = 200;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
for (GLenum face = 0; face < 6; face++)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture[1], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]);
}
}
}
// Use image uniform to read and write one layer of Texture2DArray in compute shader, and verify the
// contents.
TEST_P(ComputeShaderTest, BindImageTextureWithOneLayerTexture2DArray)
{
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth = 1, kHeight = 1, kDepth = 2;
constexpr int kResultSize = kWidth * kHeight;
constexpr GLuint kInputValues[2][2] = {{200, 150}, {100, 50}};
constexpr GLuint expectedValue_1 = 200;
constexpr GLuint expectedValue_2 = 100;
GLuint outputValues[kResultSize];
glBindTexture(GL_TEXTURE_2D_ARRAY, texture[0]);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D_ARRAY, texture[1]);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 1, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture[1], 0, 0);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture[1], 0, 1);
EXPECT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_2, outputValues[i]);
}
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_1, outputValues[i]);
}
}
// Use image uniform to read and write one layer of Texture3D in compute shader, and verify the
// contents.
TEST_P(ComputeShaderTest, BindImageTextureWithOneLayerTexture3D)
{
// Vulkan validation error creating a 2D image view of a 3D image layer.
// http://anglebug.com/3886
ANGLE_SKIP_TEST_IF(IsVulkan());
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth = 1, kHeight = 1, kDepth = 2;
constexpr int kResultSize = kWidth * kHeight;
constexpr GLuint kInputValues[2][2] = {{200, 150}, {100, 50}};
constexpr GLuint expectedValue_1 = 150;
constexpr GLuint expectedValue_2 = 50;
GLuint outputValues[kResultSize];
glBindTexture(GL_TEXTURE_3D, texture[0]);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_3D, texture[1]);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 1, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture[1], 0, 0);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture[1], 0, 1);
EXPECT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_1, outputValues[i]);
}
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_2, outputValues[i]);
}
}
// Use image uniform to read and write one layer of TextureCube in compute shader, and verify the
// contents.
TEST_P(ComputeShaderTest, BindImageTextureWithOneLayerTextureCube)
{
// GL_FRAMEBUFFER_BARRIER_BIT is invalid on Nvidia Linux platform.
// http://anglebug.com/3736
ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL() && IsLinux());
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth = 1, kHeight = 1;
constexpr int kResultSize = kWidth * kHeight;
constexpr GLuint kInputValues[2][1] = {{200}, {100}};
constexpr GLuint expectedValue_1 = 200;
constexpr GLuint expectedValue_2 = 100;
GLuint outputValues[kResultSize];
glBindTexture(GL_TEXTURE_CUBE_MAP, texture[0]);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_R32UI, kWidth, kHeight);
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
face++)
{
glTexSubImage2D(face, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[0]);
}
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_CUBE_MAP, texture[1]);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_R32UI, kWidth, kHeight);
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
face++)
{
glTexSubImage2D(face, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[1]);
}
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 3, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 4, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
for (GLenum face = 0; face < 6; face++)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture[1], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
if (face == 4)
{
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_1, outputValues[i]);
}
}
else
{
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_2, outputValues[i]);
}
}
}
}
// Test to bind kinds of texture types, bind either the entire texture
// level or a single layer or face of the face level.
TEST_P(ComputeShaderTest, BindImageTextureWithMixTextureTypes)
{
// GL_FRAMEBUFFER_BARRIER_BIT is invalid on Nvidia Linux platform.
// http://anglebug.com/3736
ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL() && IsLinux());
GLTexture texture[4];
GLFramebuffer framebuffer;
const char csSource[] =
R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) readonly uniform highp uimage2D uImage_2;
layout(r32ui, binding = 2) readonly uniform highp uimage3D uImage_3;
layout(r32ui, binding = 3) writeonly uniform highp uimage2D uImage_4;
void main()
{
uvec4 value_1 = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
uvec4 value_2 = imageLoad(uImage_2, ivec2(gl_LocalInvocationID.xy));
uvec4 value_3 = imageLoad(uImage_3, ivec3(gl_LocalInvocationID.xyz));
imageStore(uImage_4, ivec2(gl_LocalInvocationID.xy), value_1 + value_2 + value_3);
})";
constexpr int kWidth = 1, kHeight = 1, kDepth = 2;
constexpr int kResultSize = kWidth * kHeight;
constexpr GLuint kInputValues2D[1] = {11};
constexpr GLuint KInputValues2DArray[2] = {23, 35};
constexpr GLuint KInputValues3D[2] = {102, 67};
constexpr GLuint KInputValuesCube[1] = {232};
constexpr GLuint expectedValue_1 = 148;
constexpr GLuint expectedValue_2 = 232;
GLuint outputValues[kResultSize];
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues2D);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D_ARRAY, texture[1]);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, KInputValues2DArray);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_3D, texture[2]);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_R32UI, kWidth, kHeight, kDepth);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, kDepth, GL_RED_INTEGER,
GL_UNSIGNED_INT, KInputValues3D);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_CUBE_MAP, texture[3]);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_R32UI, kWidth, kHeight);
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
face++)
{
glTexSubImage2D(face, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
KInputValuesCube);
}
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, csSource);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_TRUE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 1, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(2, texture[2], 0, GL_TRUE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(3, texture[3], 0, GL_FALSE, 4, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
for (GLenum face = 0; face < 6; face++)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture[3], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
if (face == 4)
{
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_1, outputValues[i]);
}
}
else
{
for (int i = 0; i < kResultSize; i++)
{
EXPECT_EQ(expectedValue_2, outputValues[i]);
}
}
}
}
// Verify an INVALID_OPERATION error is reported when querying GL_COMPUTE_WORK_GROUP_SIZE for a
// program which has not been linked successfully or which does not contain objects to form a
// compute shader.
TEST_P(ComputeShaderTest, QueryComputeWorkGroupSize)
{
constexpr char kVS[] = R"(#version 310 es
void main()
{
})";
constexpr char kFS[] = R"(#version 310 es
void main()
{
})";
GLint workGroupSize[3];
ANGLE_GL_PROGRAM(graphicsProgram, kVS, kFS);
glGetProgramiv(graphicsProgram, GL_COMPUTE_WORK_GROUP_SIZE, workGroupSize);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
GLuint computeProgram = glCreateProgram();
GLShader computeShader(GL_COMPUTE_SHADER);
glAttachShader(computeProgram, computeShader);
glLinkProgram(computeProgram);
glDetachShader(computeProgram, computeShader);
GLint linkStatus;
glGetProgramiv(computeProgram, GL_LINK_STATUS, &linkStatus);
ASSERT_GL_FALSE(linkStatus);
glGetProgramiv(computeProgram, GL_COMPUTE_WORK_GROUP_SIZE, workGroupSize);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
glDeleteProgram(computeProgram);
ASSERT_GL_NO_ERROR();
}
// Use groupMemoryBarrier and barrier to sync reads/writes order and the execution
// order of multiple shader invocations in compute shader.
TEST_P(ComputeShaderTest, groupMemoryBarrierAndBarrierTest)
{
// TODO(xinghua.cao@intel.com): Figure out why we get this error message
// that shader uses features not recognized by this D3D version.
ANGLE_SKIP_TEST_IF((IsAMD() || IsNVIDIA()) && IsD3D11());
GLTexture texture;
GLFramebuffer framebuffer;
// Each invocation first stores a single value in an image, then each invocation sums up
// all the values in the image and stores the sum in the image. groupMemoryBarrier is
// used to order reads/writes to variables stored in memory accessible to other shader
// invocations, and barrier is used to control the relative execution order of multiple
// shader invocations used to process a local work group.
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=2, local_size_y=2, local_size_z=1) in;
layout(r32i, binding = 0) uniform highp iimage2D image;
void main()
{
uint x = gl_LocalInvocationID.x;
uint y = gl_LocalInvocationID.y;
imageStore(image, ivec2(gl_LocalInvocationID.xy), ivec4(x + y));
groupMemoryBarrier();
barrier();
int sum = 0;
for (int i = 0; i < 2; i++)
{
for(int j = 0; j < 2; j++)
{
sum += imageLoad(image, ivec2(i, j)).x;
}
}
groupMemoryBarrier();
barrier();
imageStore(image, ivec2(gl_LocalInvocationID.xy), ivec4(sum));
})";
constexpr int kWidth = 2, kHeight = 2;
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, kWidth, kHeight);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
constexpr GLuint kExpectedValue = 4;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(kExpectedValue, outputValues[i]);
}
}
// Verify that a link error is generated when the sum of the number of active image uniforms and
// active shader storage blocks in a compute shader exceeds GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES.
TEST_P(ComputeShaderTest, ExceedCombinedShaderOutputResourcesInCS)
{
GLint maxCombinedShaderOutputResources;
GLint maxComputeShaderStorageBlocks;
GLint maxComputeImageUniforms;
glGetIntegerv(GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES, &maxCombinedShaderOutputResources);
glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &maxComputeShaderStorageBlocks);
glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms);
ANGLE_SKIP_TEST_IF(maxCombinedShaderOutputResources >=
maxComputeShaderStorageBlocks + maxComputeImageUniforms);
std::ostringstream computeShaderStream;
computeShaderStream << "#version 310 es\n"
"layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;\n"
"layout(shared, binding = 0) buffer blockName"
"{\n"
" uint data;\n"
"} instance["
<< maxComputeShaderStorageBlocks << "];\n";
ASSERT_GE(maxComputeImageUniforms, 4);
int numImagesInArray = maxComputeImageUniforms / 2;
int numImagesNonArray = maxComputeImageUniforms - numImagesInArray;
for (int i = 0; i < numImagesNonArray; ++i)
{
computeShaderStream << "layout(r32f, binding = " << i << ") uniform highp image2D image"
<< i << ";\n";
}
computeShaderStream << "layout(r32f, binding = " << numImagesNonArray
<< ") uniform highp image2D imageArray[" << numImagesInArray << "];\n";
computeShaderStream << "void main()\n"
"{\n"
" uint val = 0u;\n"
" vec4 val2 = vec4(0.0);\n";
for (int i = 0; i < maxComputeShaderStorageBlocks; ++i)
{
computeShaderStream << " val += instance[" << i << "].data; \n";
}
for (int i = 0; i < numImagesNonArray; ++i)
{
computeShaderStream << " val2 += imageLoad(image" << i
<< ", ivec2(gl_LocalInvocationID.xy)); \n";
}
for (int i = 0; i < numImagesInArray; ++i)
{
computeShaderStream << " val2 += imageLoad(imageArray[" << i << "]"
<< ", ivec2(gl_LocalInvocationID.xy)); \n";
}
computeShaderStream << " instance[0].data = val + uint(val2.x);\n"
"}\n";
GLuint computeProgram = CompileComputeProgram(computeShaderStream.str().c_str());
EXPECT_EQ(0u, computeProgram);
}
// Test that uniform block with struct member in compute shader is supported.
TEST_P(ComputeShaderTest, UniformBlockWithStructMember)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(rgba8) uniform highp readonly image2D mImage2DInput;
layout(rgba8) uniform highp writeonly image2D mImage2DOutput;
struct S {
ivec3 a;
ivec2 b;
};
layout(std140, binding=0) uniform blockName {
S bd;
} instanceName;
void main()
{
ivec2 t1 = instanceName.bd.b;
vec4 result2d = imageLoad(mImage2DInput, t1);
imageStore(mImage2DOutput, ivec2(gl_LocalInvocationID.xy), result2d);
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
EXPECT_GL_NO_ERROR();
}
// Verify shared non-array variables can work correctly.
TEST_P(ComputeShaderTest, NonArraySharedVariable)
{
const char kCSShader[] = R"(#version 310 es
layout (local_size_x = 2, local_size_y = 2, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
shared uint temp;
void main()
{
if (gl_LocalInvocationID == uvec3(0, 0, 0))
{
temp = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
}
groupMemoryBarrier();
barrier();
if (gl_LocalInvocationID == uvec3(1, 1, 0))
{
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy), uvec4(temp));
}
else
{
uint inputValue = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy), uvec4(inputValue));
}
})";
const std::array<GLuint, 4> inputData = {{250, 200, 150, 100}};
const std::array<GLuint, 4> expectedValues = {{250, 200, 150, 250}};
runSharedMemoryTest<GLuint, 2, 2>(kCSShader, GL_R32UI, GL_UNSIGNED_INT, inputData,
expectedValues);
}
// Verify shared non-struct array variables can work correctly.
TEST_P(ComputeShaderTest, NonStructArrayAsSharedVariable)
{
const char kCSShader[] = R"(#version 310 es
layout (local_size_x = 2, local_size_y = 2, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
shared uint sharedData[2][2];
void main()
{
uint inputData = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
sharedData[gl_LocalInvocationID.x][gl_LocalInvocationID.y] = inputData;
groupMemoryBarrier();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
uvec4(sharedData[gl_LocalInvocationID.y][gl_LocalInvocationID.x]));
})";
const std::array<GLuint, 4> inputData = {{250, 200, 150, 100}};
const std::array<GLuint, 4> expectedValues = {{250, 150, 200, 100}};
runSharedMemoryTest<GLuint, 2, 2>(kCSShader, GL_R32UI, GL_UNSIGNED_INT, inputData,
expectedValues);
}
// Verify shared struct array variables work correctly.
TEST_P(ComputeShaderTest, StructArrayAsSharedVariable)
{
const char kCSShader[] = R"(#version 310 es
layout (local_size_x = 2, local_size_y = 2, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
struct SharedStruct
{
uint data;
};
shared SharedStruct sharedData[2][2];
void main()
{
uint inputData = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
sharedData[gl_LocalInvocationID.x][gl_LocalInvocationID.y].data = inputData;
groupMemoryBarrier();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
uvec4(sharedData[gl_LocalInvocationID.y][gl_LocalInvocationID.x].data));
})";
const std::array<GLuint, 4> inputData = {{250, 200, 150, 100}};
const std::array<GLuint, 4> expectedValues = {{250, 150, 200, 100}};
runSharedMemoryTest<GLuint, 2, 2>(kCSShader, GL_R32UI, GL_UNSIGNED_INT, inputData,
expectedValues);
}
// Verify using atomic functions without return value can work correctly.
TEST_P(ComputeShaderTest, AtomicFunctionsNoReturnValue)
{
// Fails on AMD windows drivers. http://anglebug.com/3872
ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
// Fails to link on Android. http://anglebug.com/3874
ANGLE_SKIP_TEST_IF(IsAndroid());
const char kCSShader[] = R"(#version 310 es
layout (local_size_x = 8, local_size_y = 1, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
const uint kSumIndex = 0u;
const uint kMinIndex = 1u;
const uint kMaxIndex = 2u;
const uint kOrIndex = 3u;
const uint kAndIndex = 4u;
const uint kXorIndex = 5u;
const uint kExchangeIndex = 6u;
const uint kCompSwapIndex = 7u;
shared highp uint results[8];
void main()
{
if (gl_LocalInvocationID.x == kMinIndex || gl_LocalInvocationID.x == kAndIndex)
{
results[gl_LocalInvocationID.x] = 0xFFFFu;
}
else if (gl_LocalInvocationID.x == kCompSwapIndex)
{
results[gl_LocalInvocationID.x] = 1u;
}
else
{
results[gl_LocalInvocationID.x] = 0u;
}
memoryBarrierShared();
barrier();
uint value = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
atomicAdd(results[kSumIndex], value);
atomicMin(results[kMinIndex], value);
atomicMax(results[kMaxIndex], value);
atomicOr(results[kOrIndex], value);
atomicAnd(results[kAndIndex], value);
atomicXor(results[kXorIndex], value);
atomicExchange(results[kExchangeIndex], value);
atomicCompSwap(results[kCompSwapIndex], value, 256u);
memoryBarrierShared();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
uvec4(results[gl_LocalInvocationID.x]));
})";
const std::array<GLuint, 8> inputData = {{1, 2, 4, 8, 16, 32, 64, 128}};
const std::array<GLuint, 8> expectedValues = {{255, 1, 128, 255, 0, 255, 128, 256}};
runSharedMemoryTest<GLuint, 8, 1>(kCSShader, GL_R32UI, GL_UNSIGNED_INT, inputData,
expectedValues);
}
// Verify using atomic functions in a non-initializer single assignment can work correctly.
TEST_P(ComputeShaderTest, AtomicFunctionsInNonInitializerSingleAssignment)
{
// Fails on AMD windows drivers. http://anglebug.com/3872
ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
const char kCSShader[] = R"(#version 310 es
layout (local_size_x = 9, local_size_y = 1, local_size_z = 1) in;
layout (r32i, binding = 0) readonly uniform highp iimage2D srcImage;
layout (r32i, binding = 1) writeonly uniform highp iimage2D dstImage;
shared highp int sharedVariable;
shared highp int inputData[9];
shared highp int outputData[9];
void main()
{
int inputValue = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
inputData[gl_LocalInvocationID.x] = inputValue;
memoryBarrierShared();
barrier();
if (gl_LocalInvocationID.x == 0u)
{
sharedVariable = 0;
outputData[0] = atomicAdd(sharedVariable, inputData[0]);
outputData[1] = atomicMin(sharedVariable, inputData[1]);
outputData[2] = atomicMax(sharedVariable, inputData[2]);
outputData[3] = atomicAnd(sharedVariable, inputData[3]);
outputData[4] = atomicOr(sharedVariable, inputData[4]);
outputData[5] = atomicXor(sharedVariable, inputData[5]);
outputData[6] = atomicExchange(sharedVariable, inputData[6]);
outputData[7] = atomicCompSwap(sharedVariable, 64, inputData[7]);
outputData[8] = atomicAdd(sharedVariable, inputData[8]);
}
memoryBarrierShared();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
ivec4(outputData[gl_LocalInvocationID.x]));
})";
const std::array<GLint, 9> inputData = {{1, 2, 4, 8, 16, 32, 64, 128, 1}};
const std::array<GLint, 9> expectedValues = {{0, 1, 1, 4, 0, 16, 48, 64, 128}};
runSharedMemoryTest<GLint, 9, 1>(kCSShader, GL_R32I, GL_INT, inputData, expectedValues);
}
// Verify using atomic functions in an initializers and using unsigned int works correctly.
TEST_P(ComputeShaderTest, AtomicFunctionsInitializerWithUnsigned)
{
// Fails on AMD windows drivers. http://anglebug.com/3872
ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
constexpr char kCShader[] = R"(#version 310 es
layout (local_size_x = 9, local_size_y = 1, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
shared highp uint sharedVariable;
shared highp uint inputData[9];
shared highp uint outputData[9];
void main()
{
uint inputValue = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
inputData[gl_LocalInvocationID.x] = inputValue;
memoryBarrierShared();
barrier();
if (gl_LocalInvocationID.x == 0u)
{
sharedVariable = 0u;
uint addValue = atomicAdd(sharedVariable, inputData[0]);
outputData[0] = addValue;
uint minValue = atomicMin(sharedVariable, inputData[1]);
outputData[1] = minValue;
uint maxValue = atomicMax(sharedVariable, inputData[2]);
outputData[2] = maxValue;
uint andValue = atomicAnd(sharedVariable, inputData[3]);
outputData[3] = andValue;
uint orValue = atomicOr(sharedVariable, inputData[4]);
outputData[4] = orValue;
uint xorValue = atomicXor(sharedVariable, inputData[5]);
outputData[5] = xorValue;
uint exchangeValue = atomicExchange(sharedVariable, inputData[6]);
outputData[6] = exchangeValue;
uint compSwapValue = atomicCompSwap(sharedVariable, 64u, inputData[7]);
outputData[7] = compSwapValue;
uint sharedVariable = atomicAdd(sharedVariable, inputData[8]);
outputData[8] = sharedVariable;
}
memoryBarrierShared();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
uvec4(outputData[gl_LocalInvocationID.x]));
})";
constexpr std::array<GLuint, 9> kInputData = {{1, 2, 4, 8, 16, 32, 64, 128, 1}};
constexpr std::array<GLuint, 9> kExpectedValues = {{0, 1, 1, 4, 0, 16, 48, 64, 128}};
runSharedMemoryTest<GLuint, 9, 1>(kCShader, GL_R32UI, GL_UNSIGNED_INT, kInputData,
kExpectedValues);
}
// Verify using atomic functions inside expressions as unsigned int.
TEST_P(ComputeShaderTest, AtomicFunctionsReturnWithUnsigned)
{
// Fails on AMD windows drivers. http://anglebug.com/3872
ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
constexpr char kCShader[] = R"(#version 310 es
layout (local_size_x = 9, local_size_y = 1, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
shared highp uint sharedVariable;
shared highp uint inputData[9];
shared highp uint outputData[9];
void main()
{
uint inputValue = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
inputData[gl_LocalInvocationID.x] = inputValue;
memoryBarrierShared();
barrier();
if (gl_LocalInvocationID.x == 0u)
{
sharedVariable = 0u;
outputData[0] = 1u + atomicAdd(sharedVariable, inputData[0]);
outputData[1] = 1u + atomicMin(sharedVariable, inputData[1]);
outputData[2] = 1u + atomicMax(sharedVariable, inputData[2]);
outputData[3] = 1u + atomicAnd(sharedVariable, inputData[3]);
outputData[4] = 1u + atomicOr(sharedVariable, inputData[4]);
outputData[5] = 1u + atomicXor(sharedVariable, inputData[5]);
outputData[6] = 1u + atomicExchange(sharedVariable, inputData[6]);
outputData[7] = 1u + atomicCompSwap(sharedVariable, 64u, inputData[7]);
outputData[8] = 1u + atomicAdd(sharedVariable, inputData[8]);
}
memoryBarrierShared();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
uvec4(outputData[gl_LocalInvocationID.x]));
})";
constexpr std::array<GLuint, 9> kInputData = {{1, 2, 4, 8, 16, 32, 64, 128, 1}};
constexpr std::array<GLuint, 9> kExpectedValues = {{1, 2, 2, 5, 1, 17, 49, 65, 129}};
runSharedMemoryTest<GLuint, 9, 1>(kCShader, GL_R32UI, GL_UNSIGNED_INT, kInputData,
kExpectedValues);
}
// Verify using nested atomic functions in expressions.
TEST_P(ComputeShaderTest, AtomicFunctionsReturnWithMultipleTypes)
{
constexpr char kCShader[] = R"(#version 310 es
layout (local_size_x = 4, local_size_y = 1, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
shared highp uint sharedVariable;
shared highp int indexVariable;
shared highp uint inputData[4];
shared highp uint outputData[4];
void main()
{
uint inputValue = imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)).x;
inputData[gl_LocalInvocationID.x] = inputValue;
memoryBarrierShared();
barrier();
if (gl_LocalInvocationID.x == 0u)
{
sharedVariable = 0u;
indexVariable = 2;
outputData[0] = 1u + atomicAdd(sharedVariable, inputData[atomicAdd(indexVariable, -1)]);
outputData[1] = 1u + atomicAdd(sharedVariable, inputData[atomicAdd(indexVariable, -1)]);
outputData[2] = 1u + atomicAdd(sharedVariable, inputData[atomicAdd(indexVariable, -1)]);
outputData[3] = atomicAdd(sharedVariable, 0u);
}
memoryBarrierShared();
barrier();
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
uvec4(outputData[gl_LocalInvocationID.x]));
})";
constexpr std::array<GLuint, 4> kInputData = {{1, 2, 3, 0}};
constexpr std::array<GLuint, 4> kExpectedValues = {{1, 4, 6, 6}};
runSharedMemoryTest<GLuint, 4, 1>(kCShader, GL_R32UI, GL_UNSIGNED_INT, kInputData,
kExpectedValues);
}
// Basic uniform buffer functionality.
TEST_P(ComputeShaderTest, UniformBuffer)
{
GLTexture texture;
GLBuffer buffer;
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
uniform uni
{
uvec4 value;
};
layout(rgba32ui, binding = 0) writeonly uniform highp uimage2D uImage;
void main()
{
imageStore(uImage, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth = 1, kHeight = 1;
constexpr GLuint kInputValues[4] = {56, 57, 58, 59};
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA_INTEGER, GL_UNSIGNED_INT,
kInputValues);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
GLint uniformBufferIndex = glGetUniformBlockIndex(program, "uni");
EXPECT_NE(uniformBufferIndex, -1);
GLuint data[4] = {201, 202, 203, 204};
glBindBuffer(GL_UNIFORM_BUFFER, buffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(GLuint) * 4, data, GL_STATIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);
glUniformBlockBinding(program, uniformBufferIndex, 0);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32UI);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight * 4];
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RGBA_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight * 4; i++)
{
EXPECT_EQ(data[i], outputValues[i]);
}
}
// Test that storing data to image and then loading the same image data works correctly.
TEST_P(ComputeShaderTest, StoreImageThenLoad)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr GLuint kInputValues[3][1] = {{300}, {200}, {100}};
GLTexture texture[3];
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, kInputValues[1]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, kInputValues[2]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, texture[1], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[2], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
GLuint outputValue;
GLFramebuffer framebuffer;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[2], 0);
glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(300u, outputValue);
}
// Test that loading image data and then storing data to the same image works correctly.
TEST_P(ComputeShaderTest, LoadImageThenStore)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr GLuint kInputValues[3][1] = {{300}, {200}, {100}};
GLTexture texture[3];
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, kInputValues[1]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, kInputValues[2]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
glUseProgram(program.get());
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, texture[2], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[0], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
GLuint outputValue;
GLFramebuffer framebuffer;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[0], 0);
glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(100u, outputValue);
}
// Test that scalar buffer variables are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksScalar)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(std140, binding = 0) buffer blockA {
uvec3 uv;
float f;
} instanceA;
layout(std140, binding = 1) buffer blockB {
vec2 v;
uint u[3];
float f;
};
void main()
{
f = instanceA.f;
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that vector buffer variables are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksVector)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(std140, binding = 0) buffer blockA {
vec2 f;
} instanceA;
layout(std140, binding = 1) buffer blockB {
vec3 f;
};
void main()
{
f[1] = instanceA.f[0];
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that matrix buffer variables are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksMatrix)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(std140, binding = 0) buffer blockA {
mat3x4 m;
} instanceA;
layout(std140, binding = 1) buffer blockB {
mat3x4 m;
};
void main()
{
m[0][1] = instanceA.m[0][1];
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that scalar array buffer variables are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksScalarArray)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(std140, binding = 0) buffer blockA {
float f[8];
} instanceA;
layout(std140, binding = 1) buffer blockB {
float f[8];
};
void main()
{
f[gl_LocalInvocationIndex] = instanceA.f[gl_LocalInvocationIndex];
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that vector array buffer variables are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksVectorArray)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=4) in;
layout(std140, binding = 0) buffer blockA {
vec2 v[4];
} instanceA;
layout(std140, binding = 1) buffer blockB {
vec4 v[4];
};
void main()
{
v[0][gl_LocalInvocationIndex] = instanceA.v[gl_LocalInvocationIndex][1];
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that matrix array buffer variables are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksMatrixArray)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(std140, binding = 0) buffer blockA {
float v1[5];
mat4 m[8];
} instanceA;
layout(std140, binding = 1) buffer blockB {
vec2 v1[3];
mat4 m[8];
};
void main()
{
float data = instanceA.m[gl_LocalInvocationIndex][0][0];
m[gl_LocalInvocationIndex][gl_LocalInvocationIndex][gl_LocalInvocationIndex] = data;
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that shader storage blocks only in assignment right is supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksInAssignmentRight)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(std140, binding = 0) buffer blockA {
float data[8];
} instanceA;
layout(r32f, binding = 0) writeonly uniform highp image2D imageOut;
void main()
{
float data = 1.0;
data = instanceA.data[gl_LocalInvocationIndex];
imageStore(imageOut, ivec2(gl_LocalInvocationID.xy), vec4(data));
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that shader storage blocks with unsized array are supported.
TEST_P(ComputeShaderTest, ShaderStorageBlocksWithUnsizedArray)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(std140, binding = 0) buffer blockA {
float v[];
} instanceA;
layout(std140, binding = 0) buffer blockB {
float v[];
} instanceB[1];
void main()
{
float data = instanceA.v[gl_LocalInvocationIndex];
instanceB[0].v[gl_LocalInvocationIndex * 2u + 1u] = data;
}
)";
ANGLE_GL_COMPUTE_PROGRAM(program, kCSSource);
EXPECT_GL_NO_ERROR();
}
// Test that EOpIndexDirect/EOpIndexIndirect/EOpIndexDirectStruct nodes in ssbo EOpIndexInDirect
// don't need to calculate the offset and should be translated by OutputHLSL directly.
TEST_P(ComputeShaderTest, IndexAndDotOperatorsInSSBOIndexIndirectOperator)
{
constexpr char kComputeShaderSource[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(std140, binding = 0) buffer blockA {
float v[4];
};
layout(std140, binding = 1) buffer blockB {
float v[4];
} instanceB[1];
struct S
{
uvec4 index[2];
} s;
void main()
{
s.index[0] = uvec4(0u, 1u, 2u, 3u);
float data = v[s.index[0].y];
instanceB[0].v[s.index[0].x] = data;
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShaderSource);
EXPECT_GL_NO_ERROR();
}
// Test that swizzle node in non-SSBO symbol works well.
TEST_P(ComputeShaderTest, ShaderStorageBlocksWithNonSSBOSwizzle)
{
constexpr char kComputeShaderSource[] = R"(#version 310 es
layout(local_size_x=8) in;
layout(std140, binding = 0) buffer blockA {
float v[8];
};
layout(std140, binding = 1) buffer blockB {
float v[8];
} instanceB[1];
void main()
{
float data = v[gl_GlobalInvocationID.x];
instanceB[0].v[gl_GlobalInvocationID.x] = data;
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShaderSource);
EXPECT_GL_NO_ERROR();
}
// Test that swizzle node in SSBO symbol works well.
TEST_P(ComputeShaderTest, ShaderStorageBlocksWithSSBOSwizzle)
{
constexpr char kComputeShaderSource[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(std140, binding = 0) buffer blockA {
vec2 v;
};
layout(std140, binding = 1) buffer blockB {
float v;
} instanceB[1];
void main()
{
instanceB[0].v = v.x;
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShaderSource);
EXPECT_GL_NO_ERROR();
}
// Test that a large struct array in std140 uniform block won't consume too much time.
TEST_P(ComputeShaderTest, LargeStructArraySize)
{
constexpr char kComputeShaderSource[] = R"(#version 310 es
layout(local_size_x=8) in;
precision mediump float;
struct InstancingData
{
mat4 transformation;
};
#define MAX_INSTANCE_COUNT 800
layout(std140) uniform InstanceBlock
{
InstancingData instances[MAX_INSTANCE_COUNT];
};
layout(std140, binding = 1) buffer blockB {
mat4 v[];
} instanceB;
void main()
{
instanceB.v[gl_GlobalInvocationID.x] = instances[gl_GlobalInvocationID.x].transformation;
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShaderSource);
EXPECT_GL_NO_ERROR();
}
// Check that it is not possible to create a compute shader when the context does not support ES
// 3.10
TEST_P(ComputeShaderTestES3, NotSupported)
{
GLuint computeShaderHandle = glCreateShader(GL_COMPUTE_SHADER);
EXPECT_EQ(0u, computeShaderHandle);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
}
// The contents of shared variables should be cleared to zero at the beginning of shader execution.
TEST_P(WebGL2ComputeTest, sharedVariablesShouldBeZero)
{
// http://anglebug.com/3226
ANGLE_SKIP_TEST_IF(IsD3D11());
// Fails on Android, AMD/windows and Intel/windows. Probably works by chance on other
// platforms, so suppressing on all platforms to avoid possible flakiness.
// http://anglebug.com/3869
ANGLE_SKIP_TEST_IF(IsVulkan());
const char kCSShader[] = R"(#version 310 es
layout (local_size_x = 4, local_size_y = 4, local_size_z = 1) in;
layout (r32ui, binding = 0) readonly uniform highp uimage2D srcImage;
layout (r32ui, binding = 1) writeonly uniform highp uimage2D dstImage;
struct S {
float f;
int i;
uint u;
bool b;
vec4 v[64];
};
shared S vars[16];
void main()
{
S zeroS;
zeroS.f = 0.0f;
zeroS.i = 0;
zeroS.u = 0u;
zeroS.b = false;
for (int i = 0; i < 64; i++)
{
zeroS.v[i] = vec4(0.0f);
}
uint tid = gl_LocalInvocationID.x + gl_LocalInvocationID.y * 4u;
uint value = (zeroS == vars[tid] ? 127u : 0u);
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy), uvec4(value));
})";
const std::array<GLuint, 16> inputData = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
const std::array<GLuint, 16> expectedValues = {
{127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127}};
runSharedMemoryTest<GLuint, 4, 4>(kCSShader, GL_R32UI, GL_UNSIGNED_INT, inputData,
expectedValues);
}
// Test uniform dirty in compute shader, and verify the contents.
TEST_P(ComputeShaderTest, UniformDirty)
{
// glReadPixels is getting the result of the first dispatch call. http://anglebug.com/3879
ANGLE_SKIP_TEST_IF(IsVulkan() && IsWindows() && (IsAMD() || IsNVIDIA()));
// Flaky on Linux FYI Release (Intel HD 630). http://anglebug.com/3934
ANGLE_SKIP_TEST_IF(IsVulkan() && IsLinux() && IsIntel());
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
uniform uint factor;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value * factor);
})";
constexpr int kWidth = 1, kHeight = 1;
constexpr GLuint kInputValues[2][1] = {{200}, {100}};
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[0]);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues[1]);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
EXPECT_GL_NO_ERROR();
glUniform1ui(glGetUniformLocation(program, "factor"), 2);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glUniform1ui(glGetUniformLocation(program, "factor"), 3);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth * kHeight];
GLuint expectedValue = 600;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth * kHeight; i++)
{
EXPECT_EQ(expectedValue, outputValues[i]) << " index " << i;
}
}
// Test storage buffer bound is unchanged, shader writes it, buffer content should be updated.
TEST_P(ComputeShaderTest, StorageBufferBoundUnchanged)
{
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=16, local_size_y=16) in;
precision highp usampler2D;
uniform usampler2D tex;
uniform uint factor;
layout(std140, binding = 0) buffer buf {
uint outData[16][16];
};
void main()
{
uint x = gl_LocalInvocationID.x;
uint y = gl_LocalInvocationID.y;
float xCoord = float(x) / float(16);
float yCoord = float(y) / float(16);
outData[y][x] = texture(tex, vec2(xCoord, yCoord)).x + factor;
})";
constexpr unsigned int kWidth = 16;
constexpr unsigned int kHeight = 16;
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
GLuint texels[kHeight][kWidth] = {{0}};
for (unsigned int y = 0; y < kHeight; ++y)
{
for (unsigned int x = 0; x < kWidth; ++x)
{
texels[y][x] = x + y * kWidth;
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
texels);
glBindTexture(GL_TEXTURE_2D, 0);
// The array stride are rounded up to the base alignment of a vec4 for std140 layout.
constexpr unsigned int kArrayStride = 16;
GLBuffer ssbo;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, kWidth * kHeight * kArrayStride, nullptr,
GL_STREAM_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glUniform1i(glGetUniformLocation(program, "tex"), 0);
glUniform1ui(glGetUniformLocation(program, "factor"), 2);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
glDispatchCompute(1, 1, 1);
const GLuint *ptr1 = reinterpret_cast<const GLuint *>(glMapBufferRange(
GL_SHADER_STORAGE_BUFFER, 0, kWidth * kHeight * kArrayStride, GL_MAP_READ_BIT));
EXPECT_GL_NO_ERROR();
for (unsigned int idx = 0; idx < kWidth * kHeight; idx++)
{
EXPECT_EQ(idx + 2, *(ptr1 + idx * kArrayStride / 4));
}
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
glUniform1ui(glGetUniformLocation(program, "factor"), 3);
glDispatchCompute(1, 1, 1);
const GLuint *ptr2 = reinterpret_cast<const GLuint *>(glMapBufferRange(
GL_SHADER_STORAGE_BUFFER, 0, kWidth * kHeight * kArrayStride, GL_MAP_READ_BIT));
EXPECT_GL_NO_ERROR();
for (unsigned int idx = 0; idx < kWidth * kHeight; idx++)
{
EXPECT_EQ(idx + 3, *(ptr2 + idx * kArrayStride / 4));
}
}
// Test imageSize to access mipmap slice.
TEST_P(ComputeShaderTest, ImageSizeMipmapSlice)
{
// TODO(xinghua.cao@intel.com): http://anglebug.com/3101
ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux());
GLTexture texture[2];
GLFramebuffer framebuffer;
const char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(rgba32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
ivec2 size = imageSize(uImage_1);
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), uvec4(size, 0, 0));
})";
constexpr int kWidth1 = 8, kHeight1 = 4, kWidth2 = 1, kHeight2 = 1;
constexpr GLuint kInputValues[] = {0, 0, 0, 0};
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 2, GL_R32UI, kWidth1, kHeight1);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32UI, kWidth2, kHeight2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth2, kHeight2, GL_RGBA_INTEGER, GL_UNSIGNED_INT,
kInputValues);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glBindImageTexture(0, texture[0], 1, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues[kWidth2 * kHeight2 * 4];
constexpr GLuint expectedValue[] = {4, 2};
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth2, kHeight2, GL_RGBA_INTEGER, GL_UNSIGNED_INT, outputValues);
EXPECT_GL_NO_ERROR();
for (int i = 0; i < kWidth2 * kHeight2; i++)
{
EXPECT_EQ(expectedValue[i], outputValues[i]);
EXPECT_EQ(expectedValue[i + 1], outputValues[i + 1]);
}
}
// Test imageLoad to access mipmap slice.
TEST_P(ComputeShaderTest, ImageLoadMipmapSlice)
{
// TODO(xinghua.cao@intel.com): http://anglebug.com/3101
ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux());
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth1 = 2, kHeight1 = 2, kWidth2 = 1, kHeight2 = 1;
constexpr GLuint kInputValues11[] = {3, 3, 3, 3};
constexpr GLuint kInputValues12[] = {2};
constexpr GLuint kInputValues2[] = {1};
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 2, GL_R32UI, kWidth1, kHeight1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth1, kHeight1, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues11);
glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, kWidth2, kHeight2, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues12);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth2, kHeight2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth2, kHeight2, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues2);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glBindImageTexture(0, texture[0], 1, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues;
constexpr GLuint expectedValue = 2;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth2, kHeight2, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValues);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(expectedValue, outputValues);
}
// Test imageStore to access mipmap slice.
TEST_P(ComputeShaderTest, ImageStoreMipmapSlice)
{
// TODO(xinghua.cao@intel.com): http://anglebug.com/3101
ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());
// Non-zero-level render target attachments are not yet supported. http://anglebug.com/3184
ANGLE_SKIP_TEST_IF(IsVulkan());
GLTexture texture[2];
GLFramebuffer framebuffer;
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
constexpr int kWidth1 = 1, kHeight1 = 1, kWidth2 = 2, kHeight2 = 2;
constexpr GLuint kInputValues1[] = {3};
constexpr GLuint kInputValues21[] = {2, 2, 2, 2};
constexpr GLuint kInputValues22[] = {1};
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth1, kHeight1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth1, kHeight1, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues1);
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 2, GL_R32UI, kWidth2, kHeight2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth2, kHeight2, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues21);
glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, kWidth1, kHeight1, GL_RED_INTEGER, GL_UNSIGNED_INT,
kInputValues22);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program);
glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
glBindImageTexture(1, texture[1], 1, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
GLuint outputValues;
constexpr GLuint expectedValue = 3;
glUseProgram(0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 1);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, kWidth1, kHeight1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValues);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(expectedValue, outputValues);
}
// Test that a resource is bound on render pipeline output, and then it's bound as the compute
// pipeline input. It works well. See http://anglebug.com/3658
TEST_P(ComputeShaderTest, DrawTexture1DispatchTexture2)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
precision highp sampler2D;
uniform sampler2D tex;
layout(rgba32f, binding = 0) writeonly uniform highp image2D image;
void main()
{
vec4 value = texelFetch(tex, ivec2(gl_LocalInvocationID.xy), 0);
imageStore(image, ivec2(gl_LocalInvocationID.xy), vec4(value.x - 1.0, 1.0, 0.0, value.w - 1.0));
})";
const char kVSSource[] = R"(#version 310 es
layout (location = 0) in vec2 pos;
out vec2 texCoord;
void main(void) {
texCoord = 0.5*pos + 0.5;
gl_Position = vec4(pos, 0.0, 1.0);
})";
const char kFSSource[] = R"(#version 310 es
precision highp float;
uniform sampler2D tex;
in vec2 texCoord;
out vec4 fragColor;
void main(void) {
fragColor = texture(tex, texCoord);
})";
GLuint aPosLoc = 0;
ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
glBindAttribLocation(program, aPosLoc, "pos");
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(aPosLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(aPosLoc);
constexpr GLfloat kInputValues[4] = {1.0, 0.0, 0.0, 1.0};
constexpr GLfloat kZero[4] = {0.0, 0.0, 0.0, 0.0};
GLFramebuffer framebuffer;
GLTexture texture[3];
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, kInputValues);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, kZero);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, kZero);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glUseProgram(program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glUniform1i(glGetUniformLocation(program, "tex"), 0);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_GL_NO_ERROR();
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
GLfloat actual[4];
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, actual);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(1.0, actual[0]);
EXPECT_EQ(0.0, actual[1]);
EXPECT_EQ(0.0, actual[2]);
EXPECT_EQ(1.0, actual[3]);
glUseProgram(csProgram);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[1]);
glUniform1i(glGetUniformLocation(program, "tex"), 0);
glBindImageTexture(0, texture[2], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[2], 0);
glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, actual);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(0.0, actual[0]);
EXPECT_EQ(1.0, actual[1]);
EXPECT_EQ(0.0, actual[2]);
EXPECT_EQ(0.0, actual[3]);
}
// Test that render pipeline and compute pipeline access to the same texture.
// Steps:
// 1. DispatchCompute.
// 2. DrawArrays.
TEST_P(ComputeShaderTest, DispatchDraw)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32f, binding = 0) writeonly uniform highp image2D image;
void main()
{
imageStore(image, ivec2(gl_LocalInvocationID.xy), vec4(0.0, 0.0, 1.0, 1.0));
})";
const char kVSSource[] = R"(#version 310 es
layout (location = 0) in vec2 pos;
out vec2 texCoord;
void main(void) {
texCoord = 0.5*pos + 0.5;
gl_Position = vec4(pos, 0.0, 1.0);
})";
const char kFSSource[] = R"(#version 310 es
precision highp float;
uniform sampler2D tex;
in vec2 texCoord;
out vec4 fragColor;
void main(void) {
fragColor = texture(tex, texCoord);
})";
GLuint aPosLoc = 0;
ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
glBindAttribLocation(program, aPosLoc, "pos");
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(aPosLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(aPosLoc);
constexpr GLfloat kInputValues[4] = {1.0, 0.0, 0.0, 1.0};
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, kInputValues);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
glUseProgram(csProgram);
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
glUseProgram(program);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::blue);
}
// Test that render pipeline and compute pipeline access to the same texture.
// Steps:
// 1. DrawArrays.
// 2. DispatchCompute.
// 3. DispatchCompute.
// 4. DrawArrays.
TEST_P(ComputeShaderTest, DrawDispatchDispatchDraw)
{
// Fails on Intel and AMD windows drivers. http://anglebug.com/3871
ANGLE_SKIP_TEST_IF(IsWindows() && (IsIntel() || IsAMD()) && IsVulkan());
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32f, binding = 0) writeonly uniform highp image2D image;
uniform float factor;
void main()
{
imageStore(image, ivec2(gl_LocalInvocationID.xy), vec4(factor, 0.0, 1.0, 1.0));
})";
const char kVSSource[] = R"(#version 310 es
layout (location = 0) in vec2 pos;
out vec2 texCoord;
void main(void) {
texCoord = 0.5*pos + 0.5;
gl_Position = vec4(pos, 0.0, 1.0);
})";
const char kFSSource[] = R"(#version 310 es
precision highp float;
uniform sampler2D tex;
in vec2 texCoord;
out vec4 fragColor;
void main(void) {
fragColor = texture(tex, texCoord);
})";
GLuint aPosLoc = 0;
ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
glBindAttribLocation(program, aPosLoc, "pos");
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(aPosLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(aPosLoc);
constexpr GLfloat kInputValues[4] = {1.0, 0.0, 0.0, 1.0};
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, kInputValues);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glUseProgram(program);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
glUseProgram(csProgram);
glUniform1f(glGetUniformLocation(csProgram, "factor"), 0.0);
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
glUniform1f(glGetUniformLocation(csProgram, "factor"), 1.0);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::magenta);
}
// Test that render pipeline and compute pipeline access to the same texture.
// Steps:
// 1. DispatchCompute.
// 2. DrawArrays.
// 3. DrawArrays.
// 4. DispatchCompute.
TEST_P(ComputeShaderTest, DispatchDrawDrawDispatch)
{
const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32f, binding = 0) writeonly uniform highp image2D image;
void main()
{
imageStore(image, ivec2(gl_LocalInvocationID.xy), vec4(0.0, 0.0, 1.0, 1.0));
})";
const char kVSSource[] = R"(#version 310 es
layout (location = 0) in vec2 pos;
out vec2 texCoord;
void main(void) {
texCoord = 0.5*pos + 0.5;
gl_Position = vec4(pos, 0.0, 1.0);
})";
const char kFSSource[] = R"(#version 310 es
precision highp float;
uniform sampler2D tex;
in vec2 texCoord;
uniform float factor;
out vec4 fragColor;
void main(void) {
fragColor = texture(tex, texCoord) + vec4(factor, 0.0, 0.0, 0.0);
})";
GLuint aPosLoc = 0;
ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
glBindAttribLocation(program, aPosLoc, "pos");
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(aPosLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(aPosLoc);
constexpr GLfloat kInputValues[4] = {1.0, 0.0, 0.0, 1.0};
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, kInputValues);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
glUseProgram(csProgram);
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
glUniform1f(glGetUniformLocation(program, "factor"), 0.0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_GL_NO_ERROR();
glUniform1f(glGetUniformLocation(program, "factor"), 1.0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_GL_NO_ERROR();
glUseProgram(csProgram);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
glUniform1f(glGetUniformLocation(program, "factor"), 0.0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::blue);
}
// Test that invalid memory barrier will produce an error.
TEST_P(ComputeShaderTest, InvalidMemoryBarrier)
{
GLbitfield barriers = 0;
glMemoryBarrier(barriers);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
}
// test atomic counter increment
// http://anglebug.com/3246
TEST_P(ComputeShaderTest, AtomicCounterIncrement)
{
constexpr char kComputeShader[] = R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 1, std430) buffer Output {
uint preGet[1];
uint increment[1];
uint postGet[1];
} sb_in;
layout(binding=0) uniform atomic_uint counter0;
void main(void)
{
uint id = (gl_GlobalInvocationID.x);
sb_in.preGet[0u] = atomicCounter(counter0);
sb_in.increment[0u] = atomicCounterIncrement(counter0);
sb_in.postGet[0u] = atomicCounter(counter0);
}
)";
ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
constexpr unsigned int kBytesPerComponent = sizeof(GLuint);
GLBuffer shaderStorageBuffer;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, 3 * kBytesPerComponent, nullptr, GL_STATIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, shaderStorageBuffer);
EXPECT_GL_NO_ERROR();
constexpr GLuint atomicBufferInitialData[] = {2u};
GLuint atomicBuffer;
glGenBuffers(1, &atomicBuffer);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicBuffer);
glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(atomicBufferInitialData), atomicBufferInitialData,
GL_DYNAMIC_DRAW);
glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicBuffer);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
glFinish();
EXPECT_GL_NO_ERROR();
// read back
const GLuint *ptr = reinterpret_cast<const GLuint *>(
glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 3 * kBytesPerComponent, GL_MAP_READ_BIT));
EXPECT_EQ(2u, ptr[0]);
EXPECT_EQ(2u, ptr[1]);
EXPECT_EQ(3u, ptr[2]);
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}
// Create a 'very large' array inside of a function in a compute shader.
TEST_P(ComputeShaderTest, VeryLargeArrayInsideFunction)
{
constexpr char kComputeShader[] = R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0, std430) buffer Output {
int value[1];
} output_data;
void main()
{
int values[1000];
for (int i = 0; i < values.length(); i++)
{
values[i] = 0;
}
int total = 0;
for (int i = 0; i < values.length(); i++)
{
total += i;
values[i] = total;
}
output_data.value[0u] = values[1000-1];
})";
ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
EXPECT_GL_NO_ERROR();
glUseProgram(program);
constexpr unsigned int kBytesPerComponent = sizeof(GLint);
GLBuffer shaderStorageBuffer;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, 1 * kBytesPerComponent, nullptr, GL_STATIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, shaderStorageBuffer);
EXPECT_GL_NO_ERROR();
glDispatchCompute(1, 1, 1);
glFinish();
EXPECT_GL_NO_ERROR();
// read back
const GLint *ptr = reinterpret_cast<const GLint *>(
glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 1 * kBytesPerComponent, GL_MAP_READ_BIT));
EXPECT_EQ(499500, ptr[0]);
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}
ANGLE_INSTANTIATE_TEST(ComputeShaderTest,
ES31_OPENGL(),
ES31_OPENGLES(),
ES31_D3D11(),
ES31_VULKAN());
ANGLE_INSTANTIATE_TEST(ComputeShaderTestES3, ES3_OPENGL(), ES3_OPENGLES(), ES3_VULKAN());
ANGLE_INSTANTIATE_TEST(WebGL2ComputeTest, ES31_D3D11(), ES31_VULKAN());
} // namespace