blob: f19f66d3c1e2cd085bf7e07baacda0eefc9f34cc [file] [log] [blame]
//
// Copyright 2022 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.
//
#include <sstream>
#include <string>
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
#define ASSERT_GL_INTEGER(pname, expected) \
{ \
GLint value; \
glGetIntegerv(pname, &value); \
ASSERT_EQ(value, GLint(expected)); \
}
#define EXPECT_GL_INTEGER(pname, expected) \
{ \
GLint value; \
glGetIntegerv(pname, &value); \
EXPECT_EQ(value, GLint(expected)); \
}
#define EXPECT_PLS_INTEGER(plane, pname, expected) \
{ \
GLint value = 0xbaadc0de; \
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(plane, pname, 1, nullptr, &value); \
EXPECT_EQ(value, GLint(expected)); \
value = 0xbaadc0de; \
glGetFramebufferPixelLocalStorageParameterivANGLE(plane, pname, &value); \
EXPECT_EQ(value, GLint(expected)); \
}
#define EXPECT_GL_SINGLE_ERROR(err) \
EXPECT_GL_ERROR(err); \
while (GLenum nextError = glGetError()) \
{ \
EXPECT_EQ(nextError, GLenum(GL_NO_ERROR)); \
}
#define EXPECT_GL_SINGLE_ERROR_MSG(msg) \
if (mHasDebugKHR) \
{ \
EXPECT_EQ(mErrorMessages.size(), size_t(1)); \
if (mErrorMessages.size() == 1) \
{ \
EXPECT_EQ(std::string(msg), mErrorMessages.back()); \
} \
mErrorMessages.clear(); \
}
#define EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(plane, rgba) \
{ \
std::array<GLfloat, 4> expected rgba; \
std::array<GLfloat, 4> value; \
value.fill(std::numeric_limits<GLfloat>::quiet_NaN()); \
glGetFramebufferPixelLocalStorageParameterfvANGLE( \
plane, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, value.data()); \
EXPECT_EQ(value, expected); \
value.fill(std::numeric_limits<GLfloat>::quiet_NaN()); \
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE( \
plane, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, nullptr, value.data()); \
EXPECT_EQ(value, expected); \
}
#define EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(plane, rgba) \
{ \
std::array<GLint, 4> expected rgba; \
std::array<GLint, 4> value; \
value.fill(0xbaadc0de); \
glGetFramebufferPixelLocalStorageParameterivANGLE( \
plane, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE, value.data()); \
EXPECT_EQ(value, expected); \
value.fill(0xbaadc0de); \
glGetFramebufferPixelLocalStorageParameterivRobustANGLE( \
plane, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE, 4, nullptr, value.data()); \
EXPECT_EQ(value, expected); \
}
#define EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(plane, rgba) \
{ \
std::array<GLuint, 4> expected rgba; \
std::array<GLuint, 4> value; \
std::array<GLint, 4> valuei; \
valuei.fill(0xbaadc0de); \
glGetFramebufferPixelLocalStorageParameterivANGLE( \
plane, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, valuei.data()); \
memcpy(value.data(), valuei.data(), sizeof(value)); \
EXPECT_EQ(value, expected); \
valuei.fill(0xbaadc0de); \
glGetFramebufferPixelLocalStorageParameterivRobustANGLE( \
plane, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 4, nullptr, valuei.data()); \
memcpy(value.data(), valuei.data(), sizeof(value)); \
EXPECT_EQ(value, expected); \
}
#define EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(target, pname, expectedValue) \
{ \
GLint value = 0xbaadc0de; \
glGetFramebufferParameterivMESA(target, pname, &value); \
EXPECT_EQ(value, static_cast<GLint>(expectedValue)); \
}
#define EXPECT_FRAMEBUFFER_PARAMETER_INT(target, pname, expectedValue) \
{ \
GLint value = 0xbaadc0de; \
glGetFramebufferParameteriv(target, pname, &value); \
EXPECT_EQ(value, static_cast<GLint>(expectedValue)); \
}
#define EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(framebuffer, attachment, value) \
{ \
GLint attachmentType = 1234567890; \
glGetFramebufferAttachmentParameteriv( \
framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &attachmentType); \
EXPECT_EQ(attachmentType, static_cast<GLint>(value)); \
}
#define EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(framebuffer, attachment, value) \
{ \
GLint attachmentName = 1234567890; \
glGetFramebufferAttachmentParameteriv( \
framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &attachmentName); \
EXPECT_EQ(attachmentName, static_cast<GLint>(value)); \
}
#define EXPECT_FRAMEBUFFER_ATTACHMENT_LEVEL(framebuffer, attachment, value) \
{ \
GLint attachmentLevel = 1234567890; \
glGetFramebufferAttachmentParameteriv( \
framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &attachmentLevel); \
EXPECT_EQ(attachmentLevel, static_cast<GLint>(value)); \
}
#define EXPECT_FRAMEBUFFER_ATTACHMENT_LAYER(framebuffer, attachment, value) \
{ \
GLint attachmentLayer = 1234567890; \
glGetFramebufferAttachmentParameteriv( \
framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &attachmentLayer); \
EXPECT_EQ(attachmentLayer, static_cast<GLint>(value)); \
}
#define EXPECT_GL_COLOR_MASK(r, g, b, a) \
{ \
std::array<GLboolean, 4> mask; \
glGetBooleanv(GL_COLOR_WRITEMASK, mask.data()); \
EXPECT_EQ(mask, (std::array<GLboolean, 4>{r, g, b, a})); \
}
// glGetBooleani_v is not in ES3.
#define EXPECT_GL_COLOR_MASK_INDEXED(index, r, g, b, a) \
{ \
std::array<GLint, 4> mask; \
glGetIntegeri_v(GL_COLOR_WRITEMASK, index, mask.data()); \
EXPECT_EQ(mask, (std::array<GLint, 4>{r, g, b, a})); \
}
constexpr static int W = 128, H = 128;
constexpr static std::array<float, 4> FULLSCREEN = {0, 0, W, H};
// For building the <loadops> parameter of glBeginPixelLocalStorageANGLE.
template <typename T>
struct Array
{
Array(const std::initializer_list<T> &list) : mVec(list) {}
operator const T *() const { return mVec.data(); }
std::vector<T> mVec;
};
template <typename T>
static Array<T> MakeArray(const std::initializer_list<T> &list)
{
return Array<T>(list);
}
static Array<GLenum> GLenumArray(const std::initializer_list<GLenum> &list)
{
return Array<GLenum>(list);
}
static Array<GLfloat> ClearF(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
return MakeArray({r, g, b, a});
}
static Array<GLint> ClearI(GLint r, GLint g, GLint b, GLint a)
{
return MakeArray({r, g, b, a});
}
static Array<GLuint> ClearUI(GLuint r, GLuint g, GLuint b, GLuint a)
{
return MakeArray({r, g, b, a});
}
class PLSTestTexture
{
public:
PLSTestTexture(GLenum internalformat) { reset(internalformat); }
PLSTestTexture(GLenum internalformat, int w, int h) { reset(internalformat, w, h, 1); }
PLSTestTexture(GLenum internalformat, int w, int h, int mipLevels)
{
reset(internalformat, w, h, mipLevels);
}
PLSTestTexture(PLSTestTexture &&that) : mID(std::exchange(that.mID, 0)) {}
void reset()
{
glDeleteTextures(1, &mID);
mID = 0;
}
void reset(GLenum internalformat) { reset(internalformat, W, H, 1); }
void reset(GLenum internalformat, int w, int h, int mipLevels)
{
GLuint id;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexStorage2D(GL_TEXTURE_2D, mipLevels, internalformat, w, h);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (mID)
{
glDeleteTextures(1, &mID);
}
mID = id;
}
~PLSTestTexture()
{
if (mID)
{
glDeleteTextures(1, &mID);
}
}
operator GLuint() const { return mID; }
GLuint id() const { return mID; }
private:
PLSTestTexture &operator=(const PLSTestTexture &) = delete;
PLSTestTexture(const PLSTestTexture &) = delete;
GLuint mID = 0;
};
struct Box
{
using float4 = std::array<float, 4>;
constexpr Box(float4 rect) : rect(rect), color{}, aux1{}, aux2{} {}
constexpr Box(float4 rect, float4 incolor) : rect(rect), color(incolor), aux1{}, aux2{} {}
constexpr Box(float4 rect, float4 incolor, float4 inaux1)
: rect(rect), color(incolor), aux1(inaux1), aux2{}
{}
constexpr Box(float4 rect, float4 incolor, float4 inaux1, float4 inaux2)
: rect(rect), color(incolor), aux1(inaux1), aux2(inaux2)
{}
float4 rect;
float4 color;
float4 aux1;
float4 aux2;
};
enum class UseBarriers : bool
{
No = false,
IfNotCoherent
};
class PLSProgram
{
public:
enum class VertexArray
{
Default,
VAO
};
PLSProgram(VertexArray vertexArray = VertexArray::VAO)
{
if (vertexArray == VertexArray::VAO)
{
glGenVertexArrays(1, &mVertexArray);
glGenBuffers(1, &mVertexBuffer);
}
}
~PLSProgram() { reset(); }
void reset()
{
if (mVertexArray != 0)
{
glDeleteVertexArrays(1, &mVertexArray);
mVertexArray = 0;
}
if (mVertexBuffer != 0)
{
glDeleteBuffers(1, &mVertexBuffer);
mVertexBuffer = 0;
}
}
int widthUniform() const { return mWidthUniform; }
int heightUniform() const { return mHeightUniform; }
void compile(std::string fsMain) { compile("", fsMain); }
void compile(std::string extensions, std::string fsMain)
{
glBindVertexArray(mVertexArray);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
if (mVertexArray == 0)
{
if (mLTRBLocation >= 0)
{
glDisableVertexAttribArray(mLTRBLocation);
}
if (mRGBALocation >= 0)
{
glDisableVertexAttribArray(mRGBALocation);
}
if (mAux1Location >= 0)
{
glDisableVertexAttribArray(mAux1Location);
}
if (mAux2Location >= 0)
{
glDisableVertexAttribArray(mAux2Location);
}
}
mProgram.makeRaster(
R"(#version 300 es
precision highp float;
uniform float W, H;
in vec4 rect;
in vec4 incolor;
in vec4 inaux1;
in vec4 inaux2;
out vec4 color;
out vec4 aux1;
out vec4 aux2;
void main()
{
color = incolor;
aux1 = inaux1;
aux2 = inaux2;
gl_Position.x = ((gl_VertexID & 1) == 0 ? rect.x : rect.z) * 2.0/W - 1.0;
gl_Position.y = ((gl_VertexID & 2) == 0 ? rect.y : rect.w) * 2.0/H - 1.0;
gl_Position.zw = vec2(0, 1);
})",
std::string(R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
)")
.append(extensions)
.append(R"(
precision highp float;
in vec4 color;
in vec4 aux1;
in vec4 aux2;)")
.append(fsMain)
.c_str());
EXPECT_TRUE(mProgram.valid());
glUseProgram(mProgram);
mWidthUniform = glGetUniformLocation(mProgram, "W");
glUniform1f(mWidthUniform, W);
mHeightUniform = glGetUniformLocation(mProgram, "H");
glUniform1f(mHeightUniform, H);
mLTRBLocation = glGetAttribLocation(mProgram, "rect");
glEnableVertexAttribArray(mLTRBLocation);
glVertexAttribDivisor(mLTRBLocation, 1);
mRGBALocation = glGetAttribLocation(mProgram, "incolor");
glEnableVertexAttribArray(mRGBALocation);
glVertexAttribDivisor(mRGBALocation, 1);
mAux1Location = glGetAttribLocation(mProgram, "inaux1");
glEnableVertexAttribArray(mAux1Location);
glVertexAttribDivisor(mAux1Location, 1);
mAux2Location = glGetAttribLocation(mProgram, "inaux2");
glEnableVertexAttribArray(mAux2Location);
glVertexAttribDivisor(mAux2Location, 1);
}
GLuint get() const { return mProgram; }
operator GLuint() { return get(); }
operator GLuint() const { return get(); }
void bind()
{
glUseProgram(mProgram);
glBindVertexArray(mVertexArray);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
}
void drawBoxes(std::vector<Box> boxes, UseBarriers useBarriers = UseBarriers::IfNotCoherent)
{
uintptr_t base;
if (mVertexBuffer == 0)
{
base = reinterpret_cast<uintptr_t>(boxes.data());
}
else
{
glBufferData(GL_ARRAY_BUFFER, boxes.size() * sizeof(Box), boxes.data(), GL_STATIC_DRAW);
base = 0;
}
if (useBarriers == UseBarriers::IfNotCoherent &&
!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"))
{
for (size_t i = 0; i < boxes.size(); ++i)
{
glVertexAttribPointer(
mLTRBLocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, rect)));
glVertexAttribPointer(
mRGBALocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, color)));
glVertexAttribPointer(
mAux1Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, aux1)));
glVertexAttribPointer(
mAux2Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, aux2)));
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 1);
glPixelLocalStorageBarrierANGLE();
}
}
else
{
glVertexAttribPointer(mLTRBLocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + offsetof(Box, rect)));
glVertexAttribPointer(mRGBALocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + offsetof(Box, color)));
glVertexAttribPointer(mAux1Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + offsetof(Box, aux1)));
glVertexAttribPointer(mAux2Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
reinterpret_cast<void *>(base + offsetof(Box, aux2)));
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, boxes.size());
}
}
private:
GLProgram mProgram;
GLuint mVertexArray = 0;
GLuint mVertexBuffer = 0;
GLint mWidthUniform = -1;
GLint mHeightUniform = -1;
GLint mLTRBLocation = -1;
GLint mRGBALocation = -1;
GLint mAux1Location = -1;
GLint mAux2Location = -1;
};
class ShaderInfoLog
{
public:
bool compileFragmentShader(const char *source)
{
return compileShader(source, GL_FRAGMENT_SHADER);
}
bool compileShader(const char *source, GLenum shaderType)
{
mInfoLog.clear();
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint compileResult;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
if (compileResult == 0)
{
GLint infoLogLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
// Info log length includes the null terminator; std::string::reserve does not.
mInfoLog.resize(std::max(infoLogLength - 1, 0));
glGetShaderInfoLog(shader, infoLogLength, nullptr, mInfoLog.data());
}
glDeleteShader(shader);
return compileResult != 0;
}
bool has(const char *subStr) const { return strstr(mInfoLog.c_str(), subStr); }
const char *c_str() const { return mInfoLog.c_str(); }
private:
std::string mInfoLog;
};
class PixelLocalStorageTest : public ANGLETest<>
{
public:
PixelLocalStorageTest()
{
setWindowWidth(W);
setWindowHeight(H);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setExtensionsEnabled(false);
}
void testTearDown() override
{
mProgram.reset();
mRenderTextureProgram.reset();
if (mScratchFBO)
{
glDeleteFramebuffers(1, &mScratchFBO);
}
}
void testSetUp() override
{
if (EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"))
{
glGetIntegerv(GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE, &MAX_PIXEL_LOCAL_STORAGE_PLANES);
glGetIntegerv(GL_MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE,
&MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &MAX_COLOR_ATTACHMENTS);
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &MAX_DRAW_BUFFERS);
}
// INVALID_OPERATION is generated if DITHER is enabled.
glDisable(GL_DITHER);
ANGLETest::testSetUp();
}
int isContextVersionAtLeast(int major, int minor)
{
return getClientMajorVersion() > major ||
(getClientMajorVersion() == major && getClientMinorVersion() >= minor);
}
void attachTexture2DToScratchFBO(GLuint tex, GLint level = 0)
{
if (!mScratchFBO)
{
glGenFramebuffers(1, &mScratchFBO);
}
glBindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, level);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
}
void attachTextureLayerToScratchFBO(GLuint tex, int level, int layer)
{
if (!mScratchFBO)
{
glGenFramebuffers(1, &mScratchFBO);
}
glBindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, level, layer);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
}
// Access texture contents by rendering them into FBO 0, rather than just grabbing them with
// glReadPixels.
void renderTextureToDefaultFramebuffer(GLuint tex)
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Reset the framebuffer contents to some value that might help debugging.
glClearColor(.1f, .4f, .6f, .9f);
glClear(GL_COLOR_BUFFER_BIT);
GLint linked = 0;
glGetProgramiv(mRenderTextureProgram, GL_LINK_STATUS, &linked);
if (!linked)
{
constexpr char kVS[] =
R"(#version 300 es
precision highp float;
out vec2 texcoord;
void main()
{
texcoord.x = (gl_VertexID & 1) == 0 ? 0.0 : 1.0;
texcoord.y = (gl_VertexID & 2) == 0 ? 0.0 : 1.0;
gl_Position = vec4(texcoord * 2.0 - 1.0, 0, 1);
})";
constexpr char kFS[] =
R"(#version 300 es
precision highp float;
uniform highp sampler2D tex; // FIXME! layout(binding=0) causes an ANGLE crash!
in vec2 texcoord;
out vec4 fragcolor;
void main()
{
fragcolor = texture(tex, texcoord);
})";
mRenderTextureProgram.makeRaster(kVS, kFS);
ASSERT_TRUE(mRenderTextureProgram.valid());
glUseProgram(mRenderTextureProgram);
glUniform1i(glGetUniformLocation(mRenderTextureProgram, "tex"), 0);
}
glUseProgram(mRenderTextureProgram);
glBindTexture(GL_TEXTURE_2D, tex);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
// Implemented as a class members so we can run the test on ES3 and ES31 both.
void doStateRestorationTest();
void doDrawStateTest();
void doImplicitDisablesTest_Framebuffer();
void doImplicitDisablesTest_TextureAttachments();
void doImplicitDisablesTest_RenderbufferAttachments();
GLint MAX_PIXEL_LOCAL_STORAGE_PLANES = 0;
GLint MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES = 0;
GLint MAX_COLOR_ATTACHMENTS = 0;
GLint MAX_DRAW_BUFFERS = 0;
PLSProgram mProgram{PLSProgram::VertexArray::Default};
GLuint mScratchFBO = 0;
GLProgram mRenderTextureProgram;
};
// Verify conformant implementation-dependent PLS limits.
TEST_P(PixelLocalStorageTest, ImplementationDependentLimits)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
// Table 6.X: Impementation Dependent Pixel Local Storage Limits.
EXPECT_TRUE(MAX_PIXEL_LOCAL_STORAGE_PLANES >= 4);
EXPECT_TRUE(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >= 4);
// Logical deductions based on 6.X.
EXPECT_TRUE(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_PIXEL_LOCAL_STORAGE_PLANES);
EXPECT_TRUE(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >= MAX_DRAW_BUFFERS);
EXPECT_TRUE(MAX_COLOR_ATTACHMENTS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
EXPECT_TRUE(MAX_DRAW_BUFFERS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
}
// Verify that rgba8, rgba8i, and rgba8ui pixel local storage behaves as specified.
TEST_P(PixelLocalStorageTest, RGBA8)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane1;
layout(rgba8i, binding=1) uniform lowp ipixelLocalANGLE plane2;
layout(binding=2, rgba8ui) uniform lowp upixelLocalANGLE plane3;
void main()
{
pixelLocalStoreANGLE(plane1, color + pixelLocalLoadANGLE(plane1));
pixelLocalStoreANGLE(plane2, ivec4(aux1) + pixelLocalLoadANGLE(plane2));
pixelLocalStoreANGLE(plane3, uvec4(aux2) + pixelLocalLoadANGLE(plane3));
})");
PLSTestTexture tex1(GL_RGBA8);
PLSTestTexture tex2(GL_RGBA8I);
PLSTestTexture tex3(GL_RGBA8UI);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
// Accumulate R, G, B, A in 4 separate passes.
// Store out-of-range values to ensure they are properly clamped upon storage.
mProgram.drawBoxes({{FULLSCREEN, {2, -1, -2, -3}, {-500, 0, 0, 0}, {1, 0, 0, 0}},
{FULLSCREEN, {0, 1, 0, 100}, {0, -129, 0, 0}, {0, 50, 0, 0}},
{FULLSCREEN, {0, 0, 1, 0}, {0, 0, -70, 0}, {0, 0, 100, 0}},
{FULLSCREEN, {0, 0, 0, -1}, {128, 0, 0, 500}, {0, 0, 0, 300}}});
glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 255, 255, 0));
attachTexture2DToScratchFBO(tex2);
EXPECT_PIXEL_RECT32I_EQ(0, 0, W, H, GLColor32I(0, -128, -70, 127));
attachTexture2DToScratchFBO(tex3);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(1, 50, 100, 255));
ASSERT_GL_NO_ERROR();
}
// Verify that r32f and r32ui pixel local storage behaves as specified.
TEST_P(PixelLocalStorageTest, R32)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(r32f, binding=0) uniform highp pixelLocalANGLE plane1;
layout(binding=1, r32ui) uniform highp upixelLocalANGLE plane2;
void main()
{
pixelLocalStoreANGLE(plane1, color + pixelLocalLoadANGLE(plane1));
pixelLocalStoreANGLE(plane2, uvec4(aux1) + pixelLocalLoadANGLE(plane2));
})");
PLSTestTexture tex1(GL_R32F);
PLSTestTexture tex2(GL_R32UI);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
// Accumulate R in 4 separate passes.
mProgram.drawBoxes({{FULLSCREEN, {-1.5, 0, 0, 0}, {0x000000ff, 0, 0, 0}},
{FULLSCREEN, {-10.25, 0, 0, 0}, {0x0000ff00, 0, 0, 0}},
{FULLSCREEN, {-100, 0, 0, 0}, {0x00ff0000, 0, 0, 0}},
{FULLSCREEN, {.25, 0, 0, 0}, {0xff000000, 0, 0, 22}}});
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
// These values should be exact matches.
//
// GL_R32F is spec'd as a 32-bit IEEE float, and GL_R32UI is a 32-bit unsigned integer.
// There is some affordance for fp32 fused operations, but "a + b" is required to be
// correctly rounded.
//
// From the GLSL ES 3.0 spec:
//
// "Highp unsigned integers have exactly 32 bits of precision. Highp signed integers use
// 32 bits, including a sign bit, in two's complement form."
//
// "Highp floating-point variables within a shader are encoded according to the IEEE 754
// specification for single-precision floating-point values (logically, not necessarily
// physically)."
//
// "Operation: a + b, a - b, a * b
// Precision: Correctly rounded."
attachTexture2DToScratchFBO(tex1);
EXPECT_PIXEL_RECT32F_EQ(0, 0, W, H, GLColor32F(-111.5, 0, 0, 1));
attachTexture2DToScratchFBO(tex2);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0xffffffff, 0, 0, 1));
ASSERT_GL_NO_ERROR();
}
// Check proper functioning of the clear value state.
TEST_P(PixelLocalStorageTest, ClearValues_rgba8)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex8f(GL_RGBA8, 1, 1);
PLSTestTexture tex8i(GL_RGBA8I, 1, 1);
PLSTestTexture tex8ui(GL_RGBA8UI, 1, 1);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex8f, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex8i, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex8ui, 0, 0);
auto clearLoads =
GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE});
auto storeStores =
GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE});
// Clear values are initially zero.
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({0, 0, 0, 0}));
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(3, clearLoads);
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
// Test custom RGBA8 clear values.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(100.5, 0, 0, 0));
glFramebufferPixelLocalClearValueivANGLE(1, ClearI(-1, 2, -3, 4));
glFramebufferPixelLocalClearValueuivANGLE(2, ClearUI(5, 6, 7, 8));
glBeginPixelLocalStorageANGLE(3, clearLoads);
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({100.5, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({-1, 2, -3, 4}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({5, 6, 7, 8}));
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(255, 0, 0, 0));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-1, 2, -3, 4));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(5, 6, 7, 8));
// Rotate and test again.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex8ui, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex8f, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex8i, 0, 0);
glBeginPixelLocalStorageANGLE(3, clearLoads);
// Since each clear value type is separate state, these should all be zero again.
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(0, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(1, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(2, ({0, 0, 0, 0}));
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
// If any component of the clear value is larger than can be represented in plane's
// internalformat, it is clamped.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferPixelLocalClearValueuivANGLE(0, ClearUI(254, 255, 256, 257));
glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(-1, 0, 1, 2));
glFramebufferPixelLocalClearValueivANGLE(2, ClearI(-129, -128, 127, 128));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(0, ({254, 255, 256, 257}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(1, ({-1, 0, 1, 2}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(2, ({-129, -128, 127, 128}));
glBeginPixelLocalStorageANGLE(3, clearLoads);
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(254, 255, 255, 255));
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 255, 255));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-128, -128, 127, 127));
// Final rotation.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex8i, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex8ui, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex8f, 0, 0);
// Since each clear value type is separate state, these should all be zero yet again.
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(0, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(1, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(2, ({0, 0, 0, 0}));
glBeginPixelLocalStorageANGLE(3, clearLoads);
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
// If any component of the clear value is larger than can be represented in plane's
// internalformat, it is clamped.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferPixelLocalClearValueivANGLE(0, ClearI(999, 999, -999, -999));
glFramebufferPixelLocalClearValueuivANGLE(1, ClearUI(0, 0, 999, 999));
glFramebufferPixelLocalClearValuefvANGLE(2, ClearF(999, 999, -999, -999));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(0, ({999, 999, -999, -999}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(1, ({0, 0, 999, 999}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(2, ({999, 999, -999, -999}));
glBeginPixelLocalStorageANGLE(3, clearLoads);
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(127, 127, -128, -128));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 255, 255));
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(255, 255, 0, 0));
// GL_LOAD_OP_ZERO_ANGLE shouldn't be affected by previous clear colors.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
// Cycle back to the original configuration and ensure that clear state hasn't changed.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex8f, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex8i, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex8ui, 0, 0);
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({100.5f, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({-1, 2, -3, 4}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({5, 6, 7, 8}));
glBeginPixelLocalStorageANGLE(3, clearLoads);
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(255, 0, 0, 0));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-1, 2, -3, 4));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(5, 6, 7, 8));
// Clear state is specific to the draw framebuffer; clear values on one framebuffer do not
// affect clear values on another.
GLFramebuffer fbo2;
glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
glFramebufferTexturePixelLocalStorageANGLE(0, tex8f, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex8i, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex8ui, 0, 0);
glBeginPixelLocalStorageANGLE(3, clearLoads);
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({0, 0, 0, 0}));
glEndPixelLocalStorageANGLE(3, storeStores);
attachTexture2DToScratchFBO(tex8f);
EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8i);
EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
attachTexture2DToScratchFBO(tex8ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
}
// Check clear values for r32f and r32ui PLS format.
TEST_P(PixelLocalStorageTest, ClearValues_r32)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Test custom R32 clear values.
PLSTestTexture tex32f(GL_R32F);
PLSTestTexture tex32ui(GL_R32UI);
glFramebufferTexturePixelLocalStorageANGLE(0, tex32f, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex32ui, 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(100.5, 0, 0, 0));
glFramebufferPixelLocalClearValueuivANGLE(1, ClearUI(0xbaadbeef, 1, 1, 0));
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({100.5f, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(1, ({0xbaadbeef, 1, 1, 0}));
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex32f);
EXPECT_PIXEL_RECT32F_EQ(0, 0, 1, 1, GLColor32F(100.5f, 0, 0, 1));
attachTexture2DToScratchFBO(tex32ui);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0xbaadbeef, 0, 0, 1));
}
// Check proper support of GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_LOAD_ANGLE, and GL_LOAD_OP_CLEAR_ANGLE
// loadOps. Also verify that it works do draw with GL_MAX_LOCAL_STORAGE_PLANES_ANGLE planes.
TEST_P(PixelLocalStorageTest, LoadOps)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
std::stringstream fs;
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
fs << "layout(binding=" << i << ", rgba8) uniform highp pixelLocalANGLE pls" << i << ";\n";
}
fs << "void main() {\n";
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
fs << "pixelLocalStoreANGLE(pls" << i << ", color + pixelLocalLoadANGLE(pls" << i
<< "));\n";
}
fs << "}";
mProgram.compile(fs.str().c_str());
// Create pls textures and clear them to red.
glClearColor(1, 0, 0, 1);
std::vector<PLSTestTexture> texs;
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
texs.emplace_back(GL_RGBA8);
attachTexture2DToScratchFBO(texs[i]);
glClear(GL_COLOR_BUFFER_BIT);
}
// Turn on scissor to try and confuse the local storage clear step.
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, 20, H);
// Set up pls color planes with a clear color of black. Odd units load with
// GL_LOAD_OP_CLEAR_ANGLE (cleared to black) and even load with GL_LOAD_OP_LOAD_ANGLE (preserved
// red).
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
std::vector<GLenum> loadOps(MAX_PIXEL_LOCAL_STORAGE_PLANES);
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
glFramebufferTexturePixelLocalStorageANGLE(i, texs[i], 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(i, ClearF(0, 0, 0, 1));
loadOps[i] = (i & 1) ? GL_LOAD_OP_CLEAR_ANGLE : GL_LOAD_OP_LOAD_ANGLE;
}
std::vector<GLenum> storeOps(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_STORE_OP_STORE_ANGLE);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
// Draw transparent green into all pls attachments.
glBeginPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, loadOps.data());
mProgram.drawBoxes({{{FULLSCREEN}, {0, 1, 0, 0}}});
glEndPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, storeOps.data());
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texs[i], 0);
// Check that the draw buffers didn't get perturbed by local storage -- GL_COLOR_ATTACHMENT0
// is currently off, so glClear has no effect. This also verifies that local storage planes
// didn't get left attached to the framebuffer somewhere with draw buffers on.
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_RECT_EQ(0, 0, 20, H,
loadOps[i] == GL_LOAD_OP_CLEAR_ANGLE
? GLColor(0, 255, 0, 255)
: /*GL_LOAD_OP_LOAD_ANGLE*/ GLColor(255, 255, 0, 255));
// Check that the scissor didn't get perturbed by local storage.
EXPECT_PIXEL_RECT_EQ(20, 0, W - 20, H,
loadOps[i] == GL_LOAD_OP_CLEAR_ANGLE
? GLColor(0, 0, 0, 255)
: /*GL_LOAD_OP_LOAD_ANGLE*/ GLColor(255, 0, 0, 255));
}
// Detach the last read pls texture from the framebuffer.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
// Now test GL_LOAD_OP_ZERO_ANGLE, and leaving a plane deinitialized.
for (int i = 0; i < 2; ++i)
{
loadOps[i] = GL_LOAD_OP_ZERO_ANGLE;
}
// Execute a pls pass without a draw.
glBeginPixelLocalStorageANGLE(3, loadOps.data());
glEndPixelLocalStorageANGLE(3, storeOps.data());
for (int i = 0; i < 3; ++i)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texs[i], 0);
if (i < 2)
{
ASSERT(loadOps[i] == GL_LOAD_OP_ZERO_ANGLE);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(0, 0, 0, 0));
}
else
{
// Should have been left unchanged.
EXPECT_PIXEL_RECT_EQ(0, 0, 20, H, GLColor(255, 255, 0, 255));
EXPECT_PIXEL_RECT_EQ(20, 0, W - 20, H, GLColor(255, 0, 0, 255));
}
}
// Now turn GL_COLOR_ATTACHMENT0 back on and check that the clear color and scissor didn't get
// perturbed by local storage.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texs[1], 0);
glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_RECT_EQ(0, 0, 20, H, GLColor(255, 0, 0, 255));
EXPECT_PIXEL_RECT_EQ(20, 0, W - 20, H, GLColor(0, 0, 0, 0));
ASSERT_GL_NO_ERROR();
}
// This next series of tests checks that GL utilities for rejecting fragments prevent stores to PLS:
//
// * stencil test
// * depth test
// * viewport
//
// Some utilities are not legal in ANGLE_shader_pixel_local_storage:
//
// * gl_SampleMask is disallowed by the spec
// * discard, after potential calls to pixelLocalLoadANGLE/Store, is disallowed by the spec
// * pixelLocalLoadANGLE/Store after a return from main is disallowed by the spec
//
// To run the tests, bind a FragmentRejectTestFBO and draw {FRAG_REJECT_TEST_BOX}:
//
// * {0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT} should be green
// * Fragments outside should have been rejected, leaving the pixels black
//
struct FragmentRejectTestFBO : GLFramebuffer
{
FragmentRejectTestFBO(GLuint tex)
{
glBindFramebuffer(GL_FRAMEBUFFER, *this);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, MakeArray<float>({0, 0, 0, 1}));
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
}
};
constexpr static int FRAG_REJECT_TEST_WIDTH = 64;
constexpr static int FRAG_REJECT_TEST_HEIGHT = 64;
constexpr static Box FRAG_REJECT_TEST_BOX(FULLSCREEN,
{0, 1, 0, 0}, // draw color
{0, 0, FRAG_REJECT_TEST_WIDTH,
FRAG_REJECT_TEST_HEIGHT}); // reject pixels outside aux1
// Check that the stencil test prevents stores to PLS.
TEST_P(PixelLocalStorageTest, FragmentReject_stencil)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex(GL_RGBA8);
FragmentRejectTestFBO fbo(tex);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, color + pixelLocalLoadANGLE(pls));
})");
GLuint depthStencil;
glGenRenderbuffers(1, &depthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
depthStencil);
// glStencilFunc(GL_NEVER, ...) should not update pls.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NEVER, 1, ~0u);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
GLint zero = 0;
glClearBufferiv(GL_STENCIL, 0, &zero);
mProgram.drawBoxes({{{0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
glDisable(GL_STENCIL_TEST);
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::black);
// Stencil should be preserved after PLS, and only pixels that pass the stencil test should
// update PLS next.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glEnable(GL_STENCIL_TEST);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
glStencilFunc(GL_NOTEQUAL, 0, ~0u);
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
mProgram.drawBoxes({FRAG_REJECT_TEST_BOX});
glDisable(GL_STENCIL_TEST);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
renderTextureToDefaultFramebuffer(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT, GLColor::green);
EXPECT_PIXEL_RECT_EQ(FRAG_REJECT_TEST_WIDTH, 0, W - FRAG_REJECT_TEST_WIDTH,
FRAG_REJECT_TEST_HEIGHT, GLColor::black);
EXPECT_PIXEL_RECT_EQ(0, FRAG_REJECT_TEST_HEIGHT, W, H - FRAG_REJECT_TEST_HEIGHT,
GLColor::black);
ASSERT_GL_NO_ERROR();
}
// Check that the depth test prevents stores to PLS.
TEST_P(PixelLocalStorageTest, FragmentReject_depth)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex(GL_RGBA8);
FragmentRejectTestFBO fbo(tex);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, pixelLocalLoadANGLE(pls) + color);
})");
GLuint depth;
glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, W, H);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
GLfloat zero = 0;
glClearBufferfv(GL_DEPTH, 0, &zero);
glEnable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT);
GLfloat one = 1;
glClearBufferfv(GL_DEPTH, 0, &one);
glDisable(GL_SCISSOR_TEST);
glDepthFunc(GL_LESS);
mProgram.drawBoxes({FRAG_REJECT_TEST_BOX});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
glDisable(GL_DEPTH_TEST);
renderTextureToDefaultFramebuffer(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT, GLColor::green);
EXPECT_PIXEL_RECT_EQ(FRAG_REJECT_TEST_WIDTH, 0, W - FRAG_REJECT_TEST_WIDTH,
FRAG_REJECT_TEST_HEIGHT, GLColor::black);
EXPECT_PIXEL_RECT_EQ(0, FRAG_REJECT_TEST_HEIGHT, W, H - FRAG_REJECT_TEST_HEIGHT,
GLColor::black);
ASSERT_GL_NO_ERROR();
}
// Check that restricting the viewport also restricts stores to PLS.
TEST_P(PixelLocalStorageTest, FragmentReject_viewport)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex(GL_RGBA8);
FragmentRejectTestFBO fbo(tex);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
void main()
{
vec4 dst = pixelLocalLoadANGLE(pls);
pixelLocalStoreANGLE(pls, color + dst);
})");
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
glViewport(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT);
mProgram.drawBoxes({FRAG_REJECT_TEST_BOX});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
glViewport(0, 0, W, H);
renderTextureToDefaultFramebuffer(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT, GLColor::green);
EXPECT_PIXEL_RECT_EQ(FRAG_REJECT_TEST_WIDTH, 0, W - FRAG_REJECT_TEST_WIDTH,
FRAG_REJECT_TEST_HEIGHT, GLColor::black);
EXPECT_PIXEL_RECT_EQ(0, FRAG_REJECT_TEST_HEIGHT, W, H - FRAG_REJECT_TEST_HEIGHT,
GLColor::black);
ASSERT_GL_NO_ERROR();
}
// Check that results are only nondeterministic within predictable constraints, and that no data is
// random or leaked from other contexts when we forget to insert a barrier.
TEST_P(PixelLocalStorageTest, ForgetBarrier)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(binding=0, r32f) uniform highp pixelLocalANGLE framebuffer;
void main()
{
vec4 dst = pixelLocalLoadANGLE(framebuffer);
pixelLocalStoreANGLE(framebuffer, color + dst * 2.0);
})");
// Draw r=100, one pixel at a time, in random order.
constexpr static int NUM_PIXELS = H * W;
std::vector<Box> boxesA_100;
int pixelIdx = 0;
for (int i = 0; i < NUM_PIXELS; ++i)
{
int iy = pixelIdx / W;
float y = iy;
int ix = pixelIdx % W;
float x = ix;
pixelIdx =
(pixelIdx + 69484171) % NUM_PIXELS; // Prime numbers guarantee we hit every pixel once.
boxesA_100.push_back(Box{{x, y, x + 1, y + 1}, {100, 0, 0, 0}});
}
// Draw r=7, one pixel at a time, in random order.
std::vector<Box> boxesB_7;
for (int i = 0; i < NUM_PIXELS; ++i)
{
int iy = pixelIdx / W;
float y = iy;
int ix = pixelIdx % W;
float x = ix;
pixelIdx =
(pixelIdx + 97422697) % NUM_PIXELS; // Prime numbers guarantee we hit every pixel once.
boxesB_7.push_back(Box{{x, y, x + 1, y + 1}, {7, 0, 0, 0}});
}
PLSTestTexture tex(GL_R32F);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 0));
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
// First make sure it works properly with a barrier.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
mProgram.drawBoxes(boxesA_100, UseBarriers::No);
glPixelLocalStorageBarrierANGLE();
mProgram.drawBoxes(boxesB_7, UseBarriers::No);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT32F_EQ(0, 0, W, H, GLColor32F(211, 0, 0, 1));
ASSERT_GL_NO_ERROR();
// Vulkan generates rightful "SYNC-HAZARD-READ_AFTER_WRITE" validation errors when we omit the
// barrier.
ANGLE_SKIP_TEST_IF(IsVulkan() &&
!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
// Now forget to insert the barrier and ensure our nondeterminism still falls within predictable
// constraints.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
mProgram.drawBoxes(boxesA_100, UseBarriers::No);
// OOPS! We forgot to insert a barrier!
mProgram.drawBoxes(boxesB_7, UseBarriers::No);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
float pixels[H * W * 4];
attachTexture2DToScratchFBO(tex);
glReadPixels(0, 0, W, H, GL_RGBA, GL_FLOAT, pixels);
for (int r = 0; r < NUM_PIXELS * 4; r += 4)
{
// When two fragments, A and B, touch a pixel, there are 6 possible orderings of operations:
//
// * Read A, Write A, Read B, Write B
// * Read B, Write B, Read A, Write A
// * Read A, Read B, Write A, Write B
// * Read A, Read B, Write B, Write A
// * Read B, Read A, Write B, Write A
// * Read B, Read A, Write A, Write B
//
// Which (assumimg the read and/or write operations themselves are atomic), is equivalent to
// 1 of 4 potential effects:
bool isAcceptableValue = pixels[r] == 211 || // A, then B ( 7 + (100 + 1 * 2) * 2 == 211)
pixels[r] == 118 || // B, then A (100 + ( 7 + 1 * 2) * 2 == 118)
pixels[r] == 102 || // A only (100 + 1 * 2 == 102)
pixels[r] == 9;
if (!isAcceptableValue)
{
printf(__FILE__ "(%i): UNACCEPTABLE value at pixel location [%i, %i]\n", __LINE__,
(r / 4) % W, (r / 4) / W);
printf(" Got: %f\n", pixels[r]);
printf(" Expected one of: { 211, 118, 102, 9 }\n");
}
ASSERT_TRUE(isAcceptableValue);
}
ASSERT_GL_NO_ERROR();
}
// Check loading and storing from memoryless local storage planes.
TEST_P(PixelLocalStorageTest, MemorylessStorage)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
// Bind the texture, but don't call glTexStorage until after creating the memoryless plane.
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Create a memoryless plane.
glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8);
// Define the persistent texture now, after attaching the memoryless pixel local storage. This
// verifies that the GL_TEXTURE_2D binding doesn't get perturbed by local storage.
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
// Draw into memoryless storage.
// (Also validates that compiling works while PLS is active.)
PLSProgram drawMemorylessProgram;
drawMemorylessProgram.compile(R"(
layout(binding=0, rgba8) uniform highp pixelLocalANGLE framebuffer;
layout(binding=1, rgba8) uniform highp pixelLocalANGLE memoryless;
void main()
{
pixelLocalStoreANGLE(memoryless, color + pixelLocalLoadANGLE(memoryless));
})");
drawMemorylessProgram.drawBoxes({{{0, 20, W, H}, {1, 0, 0, 0}},
{{0, 40, W, H}, {0, 1, 0, 0}},
{{0, 60, W, H}, {0, 0, 1, 0}}});
ASSERT_GL_NO_ERROR();
// Transfer to a texture.
// (Also validates that compiling works while PLS is active.)
PLSProgram transferToTextureProgram;
transferToTextureProgram.compile(R"(
layout(binding=0, rgba8) uniform highp pixelLocalANGLE framebuffer;
layout(binding=1, rgba8) uniform highp pixelLocalANGLE memoryless;
void main()
{
pixelLocalStoreANGLE(framebuffer, vec4(1) - pixelLocalLoadANGLE(memoryless));
})");
transferToTextureProgram.drawBoxes({{FULLSCREEN}});
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 60, W, H - 60, GLColor(0, 0, 0, 255));
EXPECT_PIXEL_RECT_EQ(0, 40, W, 20, GLColor(0, 0, 255, 255));
EXPECT_PIXEL_RECT_EQ(0, 20, W, 20, GLColor(0, 255, 255, 255));
EXPECT_PIXEL_RECT_EQ(0, 0, W, 20, GLColor(255, 255, 255, 255));
// Ensure the GL_TEXTURE_2D binding still hasn't been perturbed by local storage.
GLint textureBinding2D;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding2D);
ASSERT_EQ((GLuint)textureBinding2D, tex);
ASSERT_GL_NO_ERROR();
}
// Check that it works to render with the maximum supported data payload:
//
// GL_MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE
//
TEST_P(PixelLocalStorageTest, MaxCombinedDrawBuffersAndPLSPlanes)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
for (int numDrawBuffers : {0, 1, MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1})
{
numDrawBuffers = std::min(numDrawBuffers, MAX_DRAW_BUFFERS);
int numPLSPlanes =
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numDrawBuffers;
numPLSPlanes = std::min(numPLSPlanes, MAX_PIXEL_LOCAL_STORAGE_PLANES);
std::stringstream fs;
for (int i = 0; i < numPLSPlanes; ++i)
{
fs << "layout(binding=" << i << ", rgba8ui) uniform highp upixelLocalANGLE pls" << i
<< ";\n";
}
for (int i = 0; i < numDrawBuffers; ++i)
{
if (numDrawBuffers > 1)
{
fs << "layout(location=" << i << ") ";
}
fs << "out uvec4 out" << i << ";\n";
}
fs << "void main() {\n";
int c = 0;
for (; c < std::min(numPLSPlanes, numDrawBuffers); ++c)
{
// Nest an expression involving a fragment output, which will have an unspecified
// location when numDrawBuffers is 1, in pixelLocalStoreANGLE().
fs << "pixelLocalStoreANGLE(pls" << c << ", uvec4(color) - (out" << c
<< " = uvec4(aux1) + uvec4(" << c << ")));\n";
}
for (int i = c; i < numPLSPlanes; ++i)
{
fs << "pixelLocalStoreANGLE(pls" << i << ", uvec4(color) - (uvec4(aux1) + uvec4(" << i
<< ")));\n";
}
for (int i = c; i < numDrawBuffers; ++i)
{
fs << "out" << i << " = uvec4(aux1) + uvec4(" << i << ");\n";
}
fs << "}";
mProgram.compile(fs.str().c_str());
glViewport(0, 0, W, H);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
std::vector<PLSTestTexture> localTexs;
localTexs.reserve(numPLSPlanes);
for (int i = 0; i < numPLSPlanes; ++i)
{
localTexs.emplace_back(GL_RGBA8UI);
glFramebufferTexturePixelLocalStorageANGLE(i, localTexs[i], 0, 0);
}
std::vector<PLSTestTexture> renderTexs;
renderTexs.reserve(numDrawBuffers);
std::vector<GLenum> drawBuffers(numDrawBuffers);
for (int i = 0; i < numDrawBuffers; ++i)
{
renderTexs.emplace_back(GL_RGBA32UI);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
renderTexs[i], 0);
drawBuffers[i] = GL_COLOR_ATTACHMENT0 + i;
}
glDrawBuffers(drawBuffers.size(), drawBuffers.data());
glBeginPixelLocalStorageANGLE(
numPLSPlanes, std::vector<GLenum>(numPLSPlanes, GL_LOAD_OP_ZERO_ANGLE).data());
mProgram.drawBoxes({{FULLSCREEN, {255, 254, 253, 252}, {0, 1, 2, 3}}});
glEndPixelLocalStorageANGLE(
numPLSPlanes, std::vector<GLenum>(numPLSPlanes, GL_STORE_OP_STORE_ANGLE).data());
for (int i = 0; i < numPLSPlanes; ++i)
{
attachTexture2DToScratchFBO(localTexs[i]);
EXPECT_PIXEL_RECT32UI_EQ(
0, 0, W, H,
GLColor32UI(255u - i - 0u, 254u - i - 1u, 253u - i - 2u, 252u - i - 3u));
}
for (int i = 0; i < numDrawBuffers; ++i)
{
attachTexture2DToScratchFBO(renderTexs[i]);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0u + i, 1u + i, 2u + i, 3u + i));
}
ASSERT_GL_NO_ERROR();
}
}
// Verifies that program caching works for programs that use pixel local storage.
TEST_P(PixelLocalStorageTest, ProgramCache)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8UI, 1, 1);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
PLSTestTexture pls1(GL_RGBA8UI, 1, 1);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
PLSTestTexture pls3(GL_RGBA8UI, 1, 1);
glFramebufferTexturePixelLocalStorageANGLE(2, pls3, 0, 0);
glDrawBuffers(0, nullptr);
glViewport(0, 0, 1, 1);
// Compile the same program multiple times and verify it works each time.
for (int j = 0; j < 10; ++j)
{
mProgram.compile(R"(
layout(binding=0, rgba8ui) uniform highp upixelLocalANGLE pls0;
layout(binding=1, rgba8ui) uniform highp upixelLocalANGLE pls1;
layout(binding=2, rgba8ui) uniform highp upixelLocalANGLE pls3;
void main()
{
pixelLocalStoreANGLE(pls0, uvec4(color) - uvec4(aux1));
pixelLocalStoreANGLE(pls1, uvec4(color) - uvec4(aux2));
pixelLocalStoreANGLE(pls3, uvec4(color) - uvec4(aux1) - uvec4(aux2));
})");
glUniform1f(mProgram.widthUniform(), 1);
glUniform1f(mProgram.heightUniform(), 1);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
mProgram.drawBoxes({{FULLSCREEN, {255, 254, 253, 252}, {0, 1, 2, 3}, {4, 5, 6, 7}}});
glEndPixelLocalStorageANGLE(
3, GLenumArray(
{GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(pls0);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1,
GLColor32UI(255u - 0u, 254u - 1u, 253u - 2u, 252u - 3u));
attachTexture2DToScratchFBO(pls1);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1,
GLColor32UI(255u - 4u, 254u - 5u, 253u - 6u, 252u - 7u));
attachTexture2DToScratchFBO(pls3);
EXPECT_PIXEL_RECT32UI_EQ(
0, 0, 1, 1,
GLColor32UI(255u - 0u - 4u, 254u - 1u - 5u, 253u - 2u - 6u, 252u - 3u - 7u));
ASSERT_GL_NO_ERROR();
}
}
// Check that pls is preserved when a shader does not call pixelLocalStoreANGLE(). (Whether that's
// because a conditional branch failed or because the shader didn't write to it at all.)
//
// * The framebuffer fetch implementation needs to make sure every active plane's output variable
// gets written during every invocation, or else its value will become undefined.
//
// * The native pixel local storage implementation needs to declare a variable for every active
// plane, even if it is unused in a particular shader invocation.
//
// Also check that a pixelLocalLoadANGLE() of an r32f texture returns (r, 0, 0, 1).
TEST_P(PixelLocalStorageTest, LoadOnly)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F);
glFramebufferTexturePixelLocalStorageANGLE(1, tex, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
// Pass 1: draw to memoryless conditionally.
PLSProgram pass1;
pass1.compile(R"(
layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
void main()
{
// Omit braces on the 'if' to ensure proper insertion of memoryBarriers in the translator.
if (gl_FragCoord.x < 64.0)
pixelLocalStoreANGLE(memoryless, vec4(1, -.1, .2, -.3)); // Only stores r.
})");
// Pass 2: draw to tex conditionally.
// Don't touch memoryless -- make sure it gets preserved!
PLSProgram pass2;
pass2.compile(R"(
layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
void main()
{
// Omit braces on the 'if' to ensure proper insertion of memoryBarriers in the translator.
if (gl_FragCoord.y < 64.0)
pixelLocalStoreANGLE(tex, vec4(0, 1, 1, 0));
})");
// Pass 3: combine memoryless and tex.
PLSProgram pass3;
pass3.compile(R"(
layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
void main()
{
pixelLocalStoreANGLE(tex, pixelLocalLoadANGLE(tex) + pixelLocalLoadANGLE(memoryless));
})");
// Leave unit 0 with the default clear value of zero.
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 0, 0));
glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(0, 1, 0, 0));
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
pass1.bind();
pass1.drawBoxes({{FULLSCREEN}});
pass2.bind();
pass2.drawBoxes({{FULLSCREEN}});
pass3.bind();
pass3.drawBoxes({{FULLSCREEN}});
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, 64, 64, GLColor(255, 255, 255, 255));
EXPECT_PIXEL_RECT_EQ(64, 0, W - 64, 64, GLColor(0, 255, 255, 255));
EXPECT_PIXEL_RECT_EQ(0, 64, 64, H - 64, GLColor(255, 255, 0, 255));
EXPECT_PIXEL_RECT_EQ(64, 64, W - 64, H - 64, GLColor(0, 255, 0, 255));
ASSERT_GL_NO_ERROR();
// Now treat "tex" as entirely readonly for an entire local storage render pass.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture tex2(GL_RGBA8);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2, 0);
glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
glClear(GL_COLOR_BUFFER_BIT);
mProgram.compile(R"(
layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
out vec4 fragcolor;
void main()
{
fragcolor = 1.0 - pixelLocalLoadANGLE(tex);
})");
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_LOAD_OP_LOAD_ANGLE}));
mProgram.drawBoxes({{FULLSCREEN}});
int n;
glGetIntegerv(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, &n);
glEndPixelLocalStorageANGLE(
n, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
// Ensure "tex" was properly read in the shader.
EXPECT_PIXEL_RECT_EQ(0, 0, 64, 64, GLColor(0, 0, 0, 0));
EXPECT_PIXEL_RECT_EQ(64, 0, W - 64, 64, GLColor(255, 0, 0, 0));
EXPECT_PIXEL_RECT_EQ(0, 64, 64, H - 64, GLColor(0, 0, 255, 0));
EXPECT_PIXEL_RECT_EQ(64, 64, W - 64, H - 64, GLColor(255, 0, 255, 0));
// Ensure "tex" was preserved after the shader.
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, 64, 64, GLColor(255, 255, 255, 255));
EXPECT_PIXEL_RECT_EQ(64, 0, W - 64, 64, GLColor(0, 255, 255, 255));
EXPECT_PIXEL_RECT_EQ(0, 64, 64, H - 64, GLColor(255, 255, 0, 255));
EXPECT_PIXEL_RECT_EQ(64, 64, W - 64, H - 64, GLColor(0, 255, 0, 255));
ASSERT_GL_NO_ERROR();
}
// Check that stores and loads in a single shader invocation are coherent.
TEST_P(PixelLocalStorageTest, LoadAfterStore)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
// Run a fibonacci loop that stores and loads the same PLS multiple times.
mProgram.compile(R"(
layout(binding=0, rgba8ui) uniform highp upixelLocalANGLE fibonacci;
void main()
{
pixelLocalStoreANGLE(fibonacci, uvec4(1, 0, 0, 0)); // fib(1, 0, 0, 0)
for (int i = 0; i < 3; ++i)
{
uvec4 fib0 = pixelLocalLoadANGLE(fibonacci);
uvec4 fib1;
fib1.w = fib0.x + fib0.y;
fib1.z = fib1.w + fib0.x;
fib1.y = fib1.z + fib1.w;
fib1.x = fib1.y + fib1.z; // fib(i*4 + (5, 4, 3, 2))
pixelLocalStoreANGLE(fibonacci, fib1);
}
// fib is at indices (13, 12, 11, 10)
})");
PLSTestTexture tex(GL_RGBA8UI);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
mProgram.drawBoxes({{FULLSCREEN}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(233, 144, 89, 55)); // fib(13, 12, 11, 10)
ASSERT_GL_NO_ERROR();
// Now verify that r32f and r32ui still reload as (r, 0, 0, 1), even after an in-shader store.
mProgram.compile(R"(
layout(binding=0, r32f) uniform highp pixelLocalANGLE pls32f;
layout(binding=1, r32ui) uniform highp upixelLocalANGLE pls32ui;
out vec4 fragcolor;
void main()
{
pixelLocalStoreANGLE(pls32f, vec4(1, .5, .5, .5));
pixelLocalStoreANGLE(pls32ui, uvec4(1, 1, 1, 0));
if ((int(floor(gl_FragCoord.x)) & 1) == 0)
fragcolor = pixelLocalLoadANGLE(pls32f);
else
fragcolor = vec4(pixelLocalLoadANGLE(pls32ui));
})");
tex.reset(GL_RGBA8);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F);
glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_R32UI);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
mProgram.drawBoxes({{FULLSCREEN}});
int n;
glGetIntegerv(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, &n);
glEndPixelLocalStorageANGLE(n,
GLenumArray({GL_DONT_CARE, GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 0, 0, 255));
ASSERT_GL_NO_ERROR();
}
// Check that PLS handles can be passed as function arguments.
TEST_P(PixelLocalStorageTest, FunctionArguments)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE dst;
layout(binding=1, rgba8) uniform mediump pixelLocalANGLE src1;
void store2(lowp pixelLocalANGLE d);
void store(highp pixelLocalANGLE d, lowp pixelLocalANGLE s)
{
pixelLocalStoreANGLE(d, pixelLocalLoadANGLE(s));
}
void main()
{
if (gl_FragCoord.x < 25.0)
store(dst, src1);
else
store2(dst);
}
// Ensure inlining still works on a uniform declared after main().
layout(binding=2, r32f) uniform highp pixelLocalANGLE src2;
void store2(lowp pixelLocalANGLE d)
{
pixelLocalStoreANGLE(d, pixelLocalLoadANGLE(src2));
})");
PLSTestTexture dst(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, dst, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8);
glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_R32F);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 1, 1, 1)); // ignored
glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(0, 1, 1, 0));
glFramebufferPixelLocalClearValuefvANGLE(2, ClearF(1, 0, 0, 1));
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
mProgram.drawBoxes({{FULLSCREEN}});
glEndPixelLocalStorageANGLE(3,
GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_DONT_CARE}));
attachTexture2DToScratchFBO(dst);
EXPECT_PIXEL_RECT_EQ(0, 0, 25, H, GLColor(0, 255, 255, 0));
EXPECT_PIXEL_RECT_EQ(25, 0, W - 25, H, GLColor(255, 0, 0, 255));
ASSERT_GL_NO_ERROR();
}
// Check that if the "_coherent" extension is advertised, PLS operations are ordered and coherent.
TEST_P(PixelLocalStorageTest, Coherency)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(binding=0, rgba8ui) uniform lowp upixelLocalANGLE framebuffer;
layout(binding=1, rgba8) uniform lowp pixelLocalANGLE tmp;
// The application shouldn't be able to override internal synchronization functions used by
// the compiler.
//
// If the compiler accidentally calls any of these functions, stomp out the framebuffer to make
// the test fail.
void endInvocationInterlockNV() { pixelLocalStoreANGLE(framebuffer, uvec4(0)); }
void beginFragmentShaderOrderingINTEL() { pixelLocalStoreANGLE(framebuffer, uvec4(0)); }
void beginInvocationInterlockARB() { pixelLocalStoreANGLE(framebuffer, uvec4(0)); }
// Give these functions a side effect so they don't get pruned, then call them from main().
void beginInvocationInterlockNV() { pixelLocalStoreANGLE(tmp, vec4(0)); }
void endInvocationInterlockARB() { pixelLocalStoreANGLE(tmp, vec4(0)); }
void main()
{
highp uvec4 d = pixelLocalLoadANGLE(framebuffer) >> 1;
pixelLocalStoreANGLE(framebuffer, uvec4(color) + d);
beginInvocationInterlockNV();
endInvocationInterlockARB();
})");
PLSTestTexture tex(GL_RGBA8UI);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
std::vector<uint8_t> expected(H * W * 4);
memset(expected.data(), 0, H * W * 4);
// This test times out on Swiftshader and noncoherent backends if we draw anywhere near the
// same number of boxes as we do on coherent, hardware backends.
int boxesPerList = !IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent") ||
strstr((const char *)glGetString(GL_RENDERER), "SwiftShader")
? 200
: H * W * 3;
// Prepare a ton of random sized boxes in various draws.
std::vector<Box> boxesList[5];
srand(17);
uint32_t boxID = 1;
for (auto &boxes : boxesList)
{
for (int i = 0; i < boxesPerList; ++i)
{
// Define a box.
int w = rand() % 10 + 1;
int h = rand() % 10 + 1;
float x = rand() % (W - w);
float y = rand() % (H - h);
uint8_t r = boxID & 0x7f;
uint8_t g = (boxID >> 7) & 0x7f;
uint8_t b = (boxID >> 14) & 0x7f;
uint8_t a = (boxID >> 21) & 0x7f;
++boxID;
// Update expectations.
for (int yy = y; yy < y + h; ++yy)
{
for (int xx = x; xx < x + w; ++xx)
{
int p = (yy * W + xx) * 4;
expected[p] = r + (expected[p] >> 1);
expected[p + 1] = g + (expected[p + 1] >> 1);
expected[p + 2] = b + (expected[p + 2] >> 1);
expected[p + 3] = a + (expected[p + 3] >> 1);
}
}
// Set up the gpu draw.
float x0 = x;
float x1 = x + w;
float y0 = y;
float y1 = y + h;
// Allow boxes to have negative widths and heights. This adds randomness by making the
// diagonals go in different directions.
if (rand() & 1)
std::swap(x0, x1);
if (rand() & 1)
std::swap(y0, y1);
boxes.push_back({{x0, y0, x1, y1}, {(float)r, (float)g, (float)b, (float)a}});
}
}
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
for (const std::vector<Box> &boxes : boxesList)
{
mProgram.drawBoxes(boxes);
}
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE}));
attachTexture2DToScratchFBO(tex);
std::vector<uint8_t> actual(H * W * 4);
glReadPixels(0, 0, W, H, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, actual.data());
EXPECT_EQ(expected, actual);
ASSERT_GL_NO_ERROR();
}
// Check that binding mipmap levels to PLS is supported.
TEST_P(PixelLocalStorageTest, MipMapLevels)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, color + pixelLocalLoadANGLE(pls));
})");
constexpr int LEVELS = 3;
int levelWidth = 179, levelHeight = 313;
std::vector<GLColor> redData(levelHeight * levelWidth, GLColor::black);
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, LEVELS, GL_RGBA8, levelWidth, levelHeight);
GLFramebuffer fbo;
for (int level = 0; level < LEVELS; ++level)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glUniform1f(mProgram.widthUniform(), levelWidth);
glUniform1f(mProgram.heightUniform(), levelHeight);
glViewport(0, 0, levelWidth, levelHeight);
glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, levelWidth, levelHeight, GL_RGBA,
GL_UNSIGNED_BYTE, redData.data());
glFramebufferTexturePixelLocalStorageANGLE(0, tex, level, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
mProgram.drawBoxes({{{0, 0, (float)levelWidth - 3, (float)levelHeight}, {0, 0, 1, 0}},
{{0, 0, (float)levelWidth - 2, (float)levelHeight}, {0, 1, 0, 0}},
{{0, 0, (float)levelWidth - 1, (float)levelHeight}, {1, 0, 0, 0}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex, level);
EXPECT_PIXEL_RECT_EQ(0, 0, levelWidth - 3, levelHeight, GLColor::white);
EXPECT_PIXEL_RECT_EQ(levelWidth - 3, 0, 1, levelHeight, GLColor::yellow);
EXPECT_PIXEL_RECT_EQ(levelWidth - 2, 0, 1, levelHeight, GLColor::red);
EXPECT_PIXEL_RECT_EQ(levelWidth - 1, 0, 1, levelHeight, GLColor::black);
levelWidth >>= 1;
levelHeight >>= 1;
ASSERT_GL_NO_ERROR();
}
// Delete fbo.
// Don't delete tex -- exercise pixel local storage in a way that it has to clean itself up when
// the context is torn down. (It has internal assertions that validate it is torn down
// correctly.)
}
// Check that all supported texture types work at various levels and layers.
TEST_P(PixelLocalStorageTest, TextureLevelsAndLayers)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, vec4(0, 1, 0, 0) + pixelLocalLoadANGLE(pls));
})");
std::array<float, 4> HALFSCREEN = {0, 0, W / 2.f, H};
int D = 5;
std::vector<GLColor> redImg(H * W * D, GLColor::red);
// GL_TEXTURE_2D
{
// Level 2.
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W * 4, H * 4);
glTexSubImage2D(GL_TEXTURE_2D, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, redImg.data());
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 2, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
mProgram.drawBoxes({{HALFSCREEN}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex, 2);
EXPECT_PIXEL_RECT_EQ(0, 0, W / 2.f, H, GLColor::yellow);
EXPECT_PIXEL_RECT_EQ(W / 2.f, 0, W / 2.f, H, GLColor::red);
ASSERT_GL_NO_ERROR();
}
// GL_TEXTURE_2D_ARRAY
{
// Level 1, layer 0.
GLTexture tex;
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, W * 2, H * 2, D);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, W, H, D, GL_RGBA, GL_UNSIGNED_BYTE,
redImg.data());
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 1, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
mProgram.drawBoxes({{HALFSCREEN}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTextureLayerToScratchFBO(tex, 1, 0);
EXPECT_PIXEL_RECT_EQ(0, 0, W / 2.f, H, GLColor::yellow);
EXPECT_PIXEL_RECT_EQ(W / 2.f, 0, W / 2.f, H, GLColor::red);
ASSERT_GL_NO_ERROR();
// Level 1, layer D - 1.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 1, D - 1);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
mProgram.drawBoxes({{HALFSCREEN}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTextureLayerToScratchFBO(tex, 1, D - 1);
EXPECT_PIXEL_RECT_EQ(0, 0, W / 2.f, H, GLColor::yellow);
EXPECT_PIXEL_RECT_EQ(W / 2.f, 0, W / 2.f, H, GLColor::red);
ASSERT_GL_NO_ERROR();
}
}
void PixelLocalStorageTest::doStateRestorationTest()
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
// Setup state.
PLSTestTexture plsTex(GL_RGBA8UI, 32, 33);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(1, 1, 5, 5);
glScissor(2, 2, 4, 4);
glEnable(GL_SCISSOR_TEST);
bool hasDrawBuffersIndexedOES = IsGLExtensionEnabled("GL_OES_draw_buffers_indexed");
if (hasDrawBuffersIndexedOES)
{
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
{
if (i % 2 == 1)
{
glEnableiOES(GL_BLEND, i);
}
glColorMaskiOES(i, i % 3 == 0, i % 3 == 1, i % 3 == 2, i % 2 == 0);
}
}
else
{
glEnable(GL_BLEND);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
}
std::vector<GLenum> drawBuffers(MAX_DRAW_BUFFERS);
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
{
drawBuffers[i] = i % 3 == 0 ? GL_NONE : GL_COLOR_ATTACHMENT0 + i;
}
glDrawBuffers(MAX_DRAW_BUFFERS, drawBuffers.data());
GLenum imageAccesses[] = {GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE};
GLenum imageFormats[] = {GL_RGBA8, GL_R32UI, GL_R32I, GL_R32F};
std::vector<GLTexture> images;
if (isContextVersionAtLeast(3, 1))
{
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
GLuint tex = images.emplace_back();
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, 8, 8, 5);
GLboolean layered = i % 2;
glBindImageTexture(i, images.back(), i % 3, layered, layered == GL_FALSE ? i % 5 : 0,
imageAccesses[i % 3], imageFormats[i % 4]);
}
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 17);
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, 1);
}
PLSTestTexture boundTex(GL_RGBA8, 1, 1);
glBindTexture(GL_TEXTURE_2D, boundTex);
// Run pixel local storage.
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(1, plsTex, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8);
glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8);
glFramebufferPixelLocalClearValuefvANGLE(2, ClearF(.1, .2, .3, .4));
glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_LOAD_ANGLE,
GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE}));
glPixelLocalStorageBarrierANGLE();
glEndPixelLocalStorageANGLE(
4, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_DONT_CARE}));
// Check state.
GLint textureBinding2D;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding2D);
EXPECT_EQ(static_cast<GLuint>(textureBinding2D), boundTex);
if (isContextVersionAtLeast(3, 1))
{
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
GLint name, level, layer, access, format;
GLboolean layered;
glGetIntegeri_v(GL_IMAGE_BINDING_NAME, i, &name);
glGetIntegeri_v(GL_IMAGE_BINDING_LEVEL, i, &level);
glGetBooleani_v(GL_IMAGE_BINDING_LAYERED, i, &layered);
glGetIntegeri_v(GL_IMAGE_BINDING_LAYER, i, &layer);
glGetIntegeri_v(GL_IMAGE_BINDING_ACCESS, i, &access);
glGetIntegeri_v(GL_IMAGE_BINDING_FORMAT, i, &format);
EXPECT_EQ(static_cast<GLuint>(name), images[i]);
EXPECT_EQ(level, i % 3);
EXPECT_EQ(layered, i % 2);
EXPECT_EQ(layer, layered == GL_FALSE ? i % 5 : 0);
EXPECT_EQ(static_cast<GLuint>(access), imageAccesses[i % 3]);
EXPECT_EQ(static_cast<GLuint>(format), imageFormats[i % 4]);
}
GLint defaultWidth, defaultHeight;
glGetFramebufferParameteriv(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
&defaultWidth);
glGetFramebufferParameteriv(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
&defaultHeight);
EXPECT_EQ(defaultWidth, 17);
EXPECT_EQ(defaultHeight, 1);
}
for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i)
{
GLint attachmentType;
glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
&attachmentType);
EXPECT_EQ(attachmentType, GL_NONE);
}
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
{
GLint drawBuffer;
glGetIntegerv(GL_DRAW_BUFFER0 + i, &drawBuffer);
if (i % 3 == 0)
{
EXPECT_EQ(drawBuffer, GL_NONE);
}
else
{
EXPECT_EQ(drawBuffer, GL_COLOR_ATTACHMENT0 + i);
}
}
if (hasDrawBuffersIndexedOES)
{
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
{
EXPECT_EQ(glIsEnablediOES(GL_BLEND, i), i % 2 == 1);
EXPECT_GL_COLOR_MASK_INDEXED(i, i % 3 == 0, i % 3 == 1, i % 3 == 2, i % 2 == 0);
}
}
else
{
EXPECT_TRUE(glIsEnabled(GL_BLEND));
EXPECT_GL_INTEGER(GL_BLEND, GL_TRUE);
EXPECT_GL_COLOR_MASK(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE)
}
EXPECT_TRUE(glIsEnabled(GL_SCISSOR_TEST));
std::array<GLint, 4> scissorBox;
glGetIntegerv(GL_SCISSOR_BOX, scissorBox.data());
EXPECT_EQ(scissorBox, (std::array<GLint, 4>{2, 2, 4, 4}));
std::array<GLint, 4> viewport;
glGetIntegerv(GL_VIEWPORT, viewport.data());
EXPECT_EQ(viewport, (std::array<GLint, 4>{1, 1, 5, 5}));
GLint drawFramebuffer;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFramebuffer);
EXPECT_EQ(static_cast<GLuint>(drawFramebuffer), fbo);
ASSERT_GL_NO_ERROR();
}
// Check that application-facing ES3 state is not perturbed by pixel local storage.
TEST_P(PixelLocalStorageTest, StateRestoration)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doStateRestorationTest();
}
void PixelLocalStorageTest::doDrawStateTest()
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
glEnable(GL_BLEND);
glBlendFunc(GL_ZERO, GL_ONE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT_AND_BACK);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_NEVER);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1e9f, 1e9f);
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, 1, 1);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NEVER, 0, 0);
if (isContextVersionAtLeast(3, 1))
{
glEnable(GL_SAMPLE_MASK);
glSampleMaski(0, 0);
}
glViewport(0, 0, 1, 1);
glClearColor(.1f, .2f, .3f, .4f);
// Issue a draw to ensure GL state gets synced.
PLSTestTexture tex(GL_RGBA8);
renderTextureToDefaultFramebuffer(tex);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLuint depthStencil;
glGenRenderbuffers(1, &depthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
depthStencil);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, vec4(1, 0, 0, 1));
})");
// Clear to green. This should work regardless of draw state.
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
// Draw a red box. This should not go through because the blocking draw state should have been
// restored after the clear.
mProgram.drawBoxes({{FULLSCREEN}});
// Store PLS to the texture. This should work again even though the blocking draw state was
// synced for the previous draw.
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(0, 255, 0, 255));
ASSERT_GL_NO_ERROR();
}
#define SETUP_IMPLICIT_DISABLES_TEST(framebufferTarget) \
GLFramebuffer fbo, readFBO; \
glBindFramebuffer(framebufferTarget, fbo); \
\
PLSTestTexture tex0(GL_RGBA8); \
PLSTestTexture tex1(GL_RGBA8); \
PLSTestTexture tex2(GL_RGBA8); \
PLSTestTexture tex3(GL_RGBA8); \
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0); \
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0); \
glFramebufferTexturePixelLocalStorageANGLE(2, tex2, 0, 0); \
glFramebufferTexturePixelLocalStorageANGLE(3, tex3, 0, 0); \
GLenum loadOps[4] = {GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, \
GL_LOAD_OP_ZERO_ANGLE}; \
\
glDrawBuffers(0, nullptr); \
\
EXPECT_GL_NO_ERROR()
#define CHECK_PLS_ENABLED() \
EXPECT_GL_NO_ERROR(); \
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 4)
#define CHECK_PLS_DISABLED() \
EXPECT_GL_NO_ERROR(); \
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0)
#define BEGIN_PLS() \
glBeginPixelLocalStorageANGLE(4, loadOps); \
CHECK_PLS_ENABLED()
#define CHECK_ENDS_PLS(cmd) \
BEGIN_PLS(); \
cmd; \
CHECK_PLS_DISABLED()
#define CHECK_ENDS_PLS_WITH_READ_FBO(cmd) \
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO); \
BEGIN_PLS(); \
CHECK_PLS_ENABLED(); \
cmd; \
CHECK_PLS_DISABLED();
#define CHECK_DOES_NOT_END_PLS(cmd) \
BEGIN_PLS(); \
cmd; \
CHECK_PLS_ENABLED(); \
glEndPixelLocalStorageANGLE(4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, \
GL_DONT_CARE, GL_DONT_CARE})); \
CHECK_PLS_DISABLED()
void PixelLocalStorageTest::doImplicitDisablesTest_Framebuffer()
{
SETUP_IMPLICIT_DISABLES_TEST(GL_DRAW_FRAMEBUFFER);
// Binding a READ_FRAMEBUFFER does not end PLS.
CHECK_ENDS_PLS(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));
EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, 0);
EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Binding the same framebuffer still ends PLS.
CHECK_ENDS_PLS(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo));
EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, fbo);
EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
CHECK_ENDS_PLS(glBindFramebuffer(GL_FRAMEBUFFER, fbo));
EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, fbo);
EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, fbo);
CHECK_ENDS_PLS(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, 0);
EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
CHECK_ENDS_PLS(glBindFramebuffer(GL_FRAMEBUFFER, 0));
EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, 0);
EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, 0);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
CHECK_ENDS_PLS(glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8));
GLuint tex3ID = tex3;
CHECK_DOES_NOT_END_PLS(glDeleteTextures(1, &tex3ID));
CHECK_ENDS_PLS(glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0})));
glDrawBuffers(0, nullptr);
CHECK_ENDS_PLS(glDrawBuffers(0, nullptr));
if (EnsureGLExtensionEnabled("GL_MESA_framebuffer_flip_y"))
{
CHECK_ENDS_PLS(
glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE);
glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_FALSE);
CHECK_ENDS_PLS(
glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
GL_TRUE);
glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_FALSE);
CHECK_ENDS_PLS(
glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
GL_TRUE);
glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_FALSE);
CHECK_ENDS_PLS_WITH_READ_FBO(
glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
}
if (isContextVersionAtLeast(3, 1))
{
CHECK_ENDS_PLS(glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 23));
EXPECT_FRAMEBUFFER_PARAMETER_INT(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 23);
glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
CHECK_ENDS_PLS(
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 24));
EXPECT_FRAMEBUFFER_PARAMETER_INT(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 24);
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
CHECK_ENDS_PLS(
glFramebufferParameteri(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 25));
EXPECT_FRAMEBUFFER_PARAMETER_INT(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 25);
glFramebufferParameteri(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(
glFramebufferParameteri(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 23));
}
glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8);
CHECK_ENDS_PLS(glFramebufferTexturePixelLocalStorageANGLE(2, tex2, 0, 0););
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex2);
GLuint tex2ID = tex2;
CHECK_ENDS_PLS(glDeleteTextures(1, &tex2ID));
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8);
GLuint fboID = fbo;
CHECK_ENDS_PLS(glDeleteFramebuffers(1, &fboID));
}
void PixelLocalStorageTest::doImplicitDisablesTest_TextureAttachments()
{
SETUP_IMPLICIT_DISABLES_TEST(GL_FRAMEBUFFER);
GLTexture tex2D;
glBindTexture(GL_TEXTURE_2D, tex2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
CHECK_ENDS_PLS(
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
GLTexture tex2DArray;
glBindTexture(GL_TEXTURE_2D_ARRAY, tex2DArray);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, W, H, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
CHECK_ENDS_PLS(
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
GLTexture tex3d;
glBindTexture(GL_TEXTURE_3D, tex3d);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, W, H, 10, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
CHECK_ENDS_PLS(glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
if (EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"))
{
PLSTestTexture texMSAA(GL_RGBA8);
CHECK_ENDS_PLS(glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texMSAA, 0, 4));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texMSAA);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(glFramebufferTexture2DMultisampleEXT(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texMSAA, 0, 4));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texMSAA);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(glFramebufferTexture2DMultisampleEXT(
GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texMSAA, 0, 4));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texMSAA);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferTexture2DMultisampleEXT(
GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texMSAA, 0, 4));
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
ASSERT_GL_NO_ERROR();
}
if (EnsureGLExtensionEnabled("GL_OVR_multiview2"))
{
CHECK_ENDS_PLS(glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex2DArray, 0, 0, 1));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex2DArray, 0, 0, 1));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(glFramebufferTextureMultiviewOVR(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex2DArray, 0, 0, 1));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferTextureMultiviewOVR(
GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 1));
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
}
if (EnsureGLExtensionEnabled("GL_OES_texture_3D"))
{
CHECK_ENDS_PLS(glFramebufferTexture3DOES(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_3D, tex3d, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(glFramebufferTexture3DOES(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_3D, tex3d, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS(glFramebufferTexture3DOES(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_3D, tex3d, 0, 0));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferTexture3DOES(
GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, tex3d, 0, 0));
}
}
void PixelLocalStorageTest::doImplicitDisablesTest_RenderbufferAttachments()
{
SETUP_IMPLICIT_DISABLES_TEST(GL_FRAMEBUFFER);
GLuint colorBuffer;
glGenRenderbuffers(1, &colorBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
colorBuffer));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, colorBuffer));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorBuffer);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, colorBuffer));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorBuffer);
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferRenderbuffer(
GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer));
GLuint depthStencilBuffer;
glGenRenderbuffers(1, &depthStencilBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, depthStencilBuffer));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
depthStencilBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, depthStencilBuffer));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
depthStencilBuffer);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, depthStencilBuffer));
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
depthStencilBuffer);
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferRenderbuffer(
GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilBuffer));
}
#undef CHECK_ENDS_PLS_WITH_READ_FBO
#undef CHECK_DOES_NOT_END_PLS
#undef BEGIN_PLS
#undef CHECK_PLS_DISABLED
#undef CHECK_PLS_ENABLED
#undef CHECK_ENDS_PLS
// Check that draw state does not affect PLS loads and stores, particularly for
// EXT_shader_pixel_local_storage, where they are implemented as fullscreen draws.
TEST_P(PixelLocalStorageTest, DrawStateReset)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doDrawStateTest();
}
// Check that blend & color mask state and glClearBuffers{u,i,ui}v do not affect
// pixel local storage, and that PLS does not affect blend or color mask on the
// application's draw buffers.
TEST_P(PixelLocalStorageTest, BlendColorMaskAndClear)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex1(GL_RGBA8);
PLSTestTexture tex2(GL_RGBA8);
PLSTestTexture tex3(GL_RGBA8);
PLSTestTexture tex4(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, W, H);
const bool drawBuffersIndexed = IsGLExtensionEnabled("GL_OES_draw_buffers_indexed");
GLint firstClearBuffer = 0;
if (!drawBuffersIndexed)
{
// Blend should not affect pixel local storage.
glBlendFunc(GL_ZERO, GL_ZERO);
// Color mask should not affect pixel local storage.
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls1;
layout(binding=1, rgba8) uniform lowp pixelLocalANGLE pls2;
layout(binding=2, rgba8) uniform lowp pixelLocalANGLE pls3;
layout(binding=3, rgba8) uniform lowp pixelLocalANGLE pls4;
void main()
{
pixelLocalStoreANGLE(pls1, vec4(1, 0, 0, 1));
pixelLocalStoreANGLE(pls2, vec4(0, 1, 0, 1));
pixelLocalStoreANGLE(pls3, vec4(0, 0, 1, 1));
pixelLocalStoreANGLE(pls4, vec4(0, 0, 0, 1));
})");
glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(3, tex4, 0, 0);
glBeginPixelLocalStorageANGLE(
4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
GL_LOAD_OP_ZERO_ANGLE}));
// Blend should not affect pixel local storage.
EXPECT_FALSE(glIsEnabled(GL_BLEND));
EXPECT_GL_INTEGER(GL_BLEND, GL_FALSE);
glEnable(GL_BLEND);
// Enabling GL_BLEND works from the client perspective even though PLS is overriding it.
EXPECT_TRUE(glIsEnabled(GL_BLEND));
EXPECT_GL_INTEGER(GL_BLEND, GL_TRUE);
// BlendFunc has no effect as long as GL_BLEND stays disabled on our PLS draw buffers.
glBlendFunc(GL_ZERO, GL_ONE);
EXPECT_GL_INTEGER(GL_BLEND_SRC_RGB, GL_ZERO);
EXPECT_GL_INTEGER(GL_BLEND_SRC_ALPHA, GL_ZERO);
EXPECT_GL_INTEGER(GL_BLEND_DST_RGB, GL_ONE);
EXPECT_GL_INTEGER(GL_BLEND_DST_ALPHA, GL_ONE);
EXPECT_GL_NO_ERROR();
// Color mask should not affect PLS.
EXPECT_GL_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
EXPECT_GL_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
EXPECT_GL_NO_ERROR();
}
else
{
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls1;
layout(binding=1, rgba8) uniform lowp pixelLocalANGLE pls2;
layout(location=0) out lowp vec4 out1;
layout(location=1) out lowp vec4 out2;
void main()
{
out1 = vec4(0, 1, 1, 0);
out2 = vec4(1, 1, 0, 0);
pixelLocalStoreANGLE(pls1, vec4(0, 0, 1, 1));
pixelLocalStoreANGLE(pls2, vec4(0, 0, 0, 1));
})");
std::vector<GLColor> whiteData(H * W, GLColor::white);
glBindTexture(GL_TEXTURE_2D, tex1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, whiteData.data());
// Blend should not affect pixel local storage.
glEnablei(GL_BLEND, 0);
glBlendEquationi(0, GL_FUNC_REVERSE_SUBTRACT);
glBlendFunci(0, GL_ONE, GL_ONE);
glEnablei(GL_BLEND, 2);
std::vector<GLColor> blackData(H * W, GLColor::black);
glBindTexture(GL_TEXTURE_2D, tex2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, blackData.data());
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex1, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex2, 0);
glFramebufferTexturePixelLocalStorageANGLE(0, tex3, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex4, 0, 0);
glDrawBuffers(2, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}));
firstClearBuffer = 2;
glBeginPixelLocalStorageANGLE(2,
GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
// Color mask should not affect pixel local storage.
glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
glColorMaski(2, GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
EXPECT_GL_COLOR_MASK_INDEXED(0, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
EXPECT_GL_COLOR_MASK_INDEXED(1, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
EXPECT_GL_COLOR_MASK_INDEXED(2, GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
EXPECT_TRUE(glIsEnabledi(GL_BLEND, 0));
EXPECT_FALSE(glIsEnabledi(GL_BLEND, 1));
for (int i = 2; i < MAX_DRAW_BUFFERS; ++i)
{
// Enabling blend with PLS active still works from the client perspective, even though
// it's overridden by PLS.
if (i == 2)
{
EXPECT_TRUE(glIsEnabledi(GL_BLEND, i));
}
else
{
EXPECT_FALSE(glIsEnabledi(GL_BLEND, i));
}
glEnablei(GL_BLEND, i);
EXPECT_TRUE(glIsEnabledi(GL_BLEND, i));
glBlendFunci(i, GL_ZERO, GL_ONE);
// Changing the color mask with PLS active still works from the client perspective, even
// though it's overridden by PLS.
if (i == 2)
{
EXPECT_GL_COLOR_MASK_INDEXED(i, GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
}
else
{
EXPECT_GL_COLOR_MASK_INDEXED(i, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
}
glColorMaski(i, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
EXPECT_GL_COLOR_MASK_INDEXED(i, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
}
ASSERT_GL_NO_ERROR();
}
mProgram.drawBoxes({{FULLSCREEN}});
// glClearBuffer{f,i,ui}v are ignored for reserved PLS draw buffers when PLS is active.
for (GLint i = firstClearBuffer; i < MAX_DRAW_BUFFERS; ++i)
{
constexpr static GLfloat zerof[4] = {0, 0, 0, 0};
constexpr static GLint zeroi[4] = {0, 0, 0, 0};
constexpr static GLuint zeroui[4] = {0, 0, 0, 0};
glClearBufferfv(GL_COLOR, i, zerof);
glClearBufferiv(GL_COLOR, i, zeroi);
glClearBufferuiv(GL_COLOR, i, zeroui);
ASSERT_GL_NO_ERROR();
}
int n;
glGetIntegerv(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, &n);
glEndPixelLocalStorageANGLE(n, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
attachTexture2DToScratchFBO(tex2);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
attachTexture2DToScratchFBO(tex3);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
attachTexture2DToScratchFBO(tex4);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::black);
// Blend state that was set on overridden draw buffers during PLS should still be in effect
// after PLS ends.
if (!drawBuffersIndexed)
{
EXPECT_TRUE(glIsEnabled(GL_BLEND));
EXPECT_GL_INTEGER(GL_BLEND, GL_TRUE);
EXPECT_GL_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
}
else
{
EXPECT_TRUE(glIsEnabledi(GL_BLEND, 0));
EXPECT_FALSE(glIsEnabledi(GL_BLEND, 1));
EXPECT_GL_COLOR_MASK_INDEXED(0, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
EXPECT_GL_COLOR_MASK_INDEXED(1, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
for (GLint i = 2; i < MAX_DRAW_BUFFERS; ++i)
{
EXPECT_TRUE(glIsEnabledi(GL_BLEND, i));
// EXPECT_GL_COLOR_MASK_INDEXED(i, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
}
}
// Now ensure that clears *do* occur on non-PLS draw buffers.
if (firstClearBuffer != 0)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(
n, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE,
GL_LOAD_OP_LOAD_ANGLE}));
constexpr static GLfloat one[4] = {1, 1, 1, 1};
glColorMaski(0, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
glClearBufferfv(GL_COLOR, 0, one);
glColorMaski(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
glClearBufferfv(GL_COLOR, 1, one);
ASSERT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(
n, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::magenta);
attachTexture2DToScratchFBO(tex2);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
}
ASSERT_GL_NO_ERROR();
}
// Check that changing framebuffer state implicitly disable pixel local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_Framebuffer)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doImplicitDisablesTest_Framebuffer();
}
// Check that changing framebuffer texture attachments implicitly disable pixel
// local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_TextureAttachments)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doImplicitDisablesTest_TextureAttachments();
}
// Check that changing framebuffer renderbuffer attachments implicitly disable
// pixel local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_RenderbufferAttachments)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doImplicitDisablesTest_RenderbufferAttachments();
}
// Check that gl(Copy)TexSubImage{2,3}D end PLS before running.
TEST_P(PixelLocalStorageTest, CopyTexSubImage)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex0(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
PLSTestTexture color0(GL_RGBA8);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color0, 0);
glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
glClearBufferfv(GL_COLOR, 0, MakeArray<float>({0, 0, 0, 0}));
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::transparentBlack);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex0;
layout(location=0) out lowp vec4 color0;
void main()
{
lowp vec4 plsOut = color + pixelLocalLoadANGLE(tex0);
pixelLocalStoreANGLE(tex0, plsOut);
color0 = vec4(0, 1, 1, 0);
})");
GLTexture tex3D;
glBindTexture(GL_TEXTURE_3D, tex3D);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, W, H, 5);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
glClearBufferfv(GL_COLOR, 0, MakeArray<float>({1, 0, 0, 1}));
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
glBindTexture(GL_TEXTURE_2D, tex0);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H / 2);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
// glTexStorage2D on a PLS plane generates an error since the texture must be immutable.
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W / 2, H / 2);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_NO_ERROR();
mProgram.drawBoxes({{{FULLSCREEN}, {0, 0, 1, 0}}});
EXPECT_GL_NO_ERROR();
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
GLTexture texNonImmutable;
glBindTexture(GL_TEXTURE_2D, texNonImmutable);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
attachTexture2DToScratchFBO(tex0);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H / 2, GLColor::magenta);
EXPECT_PIXEL_RECT_EQ(0, H / 2, W, H - H / 2, GLColor::cyan);
attachTextureLayerToScratchFBO(tex3D, 0, 2);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
attachTexture2DToScratchFBO(texNonImmutable);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
ASSERT_GL_NO_ERROR();
}
// Check that glBlitFramebuffer ends PLS, while also working as expected.
TEST_P(PixelLocalStorageTest, BlitFramebuffer)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex0(GL_RGBA8);
PLSTestTexture tex1(GL_R32UI);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0);
constexpr static int NUM_PLANES = 2;
PLSTestTexture colorAttachment(GL_RGBA8);
bool canHaveColorAttachment =
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES > NUM_PLANES;
if (canHaveColorAttachment)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
colorAttachment, 0);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
// Set the to-be current PLS plane as the read framebuffer.
GLFramebuffer readFBO;
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
EXPECT_GL_NO_ERROR();
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
glFramebufferPixelLocalClearValueuivANGLE(1, ClearUI(123, 0, 0, 0));
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, NUM_PLANES);
// glBlitFramebuffer will implicitly end PLS and store the PLS plane before
// executing.
glBlitFramebuffer(0, 0, W / 2, H, 0, 0, W / 2, H, GL_COLOR_BUFFER_BIT, GL_NEAREST);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
if (canHaveColorAttachment)
{
attachTexture2DToScratchFBO(colorAttachment);
EXPECT_PIXEL_RECT_EQ(0, 0, W / 2, H, GLColor::green);
EXPECT_PIXEL_RECT_EQ(W / 2, 0, W - W / 2, H, GLColor::red);
}
attachTexture2DToScratchFBO(tex0);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
attachTexture2DToScratchFBO(tex1);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(123, 0, 0, 1));
}
// Check that GL_RASTERIZER_DISCARD disables renders to PLS, but not PLS clear operations.
TEST_P(PixelLocalStorageTest, RasterizerDiscard)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
void main()
{
pixelLocalStoreANGLE(tex, vec4(0, 1, 0, 1));
})");
// PLS clears still pass when RASTERIZER_DISCARD is disabled.
glDisable(GL_RASTERIZER_DISCARD);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 1, 1));
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
ASSERT_GL_NO_ERROR();
// PLS clears still pass when RASTERIZER_DISCARD is enabled.
glEnable(GL_RASTERIZER_DISCARD);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::transparentBlack);
ASSERT_GL_NO_ERROR();
// Rasterization passes when RASTERIZER_DISCARD is disabled.
glDisable(GL_RASTERIZER_DISCARD);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
mProgram.drawBoxes({{{FULLSCREEN}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
ASSERT_GL_NO_ERROR();
// Rasterization fails when RASTERIZER_DISCARD is enabled (but PLS still clears).
glEnable(GL_RASTERIZER_DISCARD);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
mProgram.drawBoxes({{{FULLSCREEN}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
ASSERT_GL_NO_ERROR();
// RASTERIZER_DISCARD can be disabled while PLS is active.
glEnable(GL_RASTERIZER_DISCARD);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
glDisable(GL_RASTERIZER_DISCARD);
mProgram.drawBoxes({{{FULLSCREEN}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
ASSERT_GL_NO_ERROR();
// RASTERIZER_DISCARD can be enabled while PLS is active.
glDisable(GL_RASTERIZER_DISCARD);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
glEnable(GL_RASTERIZER_DISCARD);
mProgram.drawBoxes({{{FULLSCREEN}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(tex);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}
// Check that glClear does not clear PLS planes.
TEST_P(PixelLocalStorageTest, ClearWithActivePLS)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture pls0(GL_RGBA8);
PLSTestTexture pls1(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
constexpr static int NUM_PLANES = 2;
int maxColorAttachmentsWith2PLSPlanes =
std::min(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2, MAX_DRAW_BUFFERS);
int numColorAttachments = std::min(maxColorAttachmentsWith2PLSPlanes, 2);
PLSTestTexture colorAttachment0(GL_RGBA8);
PLSTestTexture colorAttachment1(GL_RGBA8);
for (int colorAttachmentMask = 0; colorAttachmentMask < (1 << numColorAttachments);
++colorAttachmentMask)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if (numColorAttachments >= 2)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
colorAttachment1, 0);
}
if (numColorAttachments >= 1)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
colorAttachment0, 0);
}
// Clear color attachments.
glDrawBuffers(2, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}));
if (numColorAttachments >= 2)
{
float green[4] = {0, 1, 0, 1};
glClearBufferfv(GL_COLOR, 1, green);
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
}
if (numColorAttachments >= 1)
{
float red[4] = {1, 0, 0, 1};
glClearBufferfv(GL_COLOR, 0, red);
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}
// Only enable draw buffers that are in colorAttachmentMask.
GLenum drawBuffers[2];
for (int i = 0; i < 2; ++i)
{
drawBuffers[i] = (colorAttachmentMask & (1 << i)) ? GL_COLOR_ATTACHMENT0 + i : GL_NONE;
}
glDrawBuffers(2, drawBuffers);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 1, 1));
glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(0, 1, 1, 1));
glBeginPixelLocalStorageANGLE(
NUM_PLANES, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, NUM_PLANES);
glClearColor(1, 1, 1, 1);
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, 10, H);
if (numColorAttachments != 0)
{
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
}
glDisable(GL_SCISSOR_TEST);
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(
NUM_PLANES, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
// Make sure glClear worked on the enabled color attachments, and did not clear the disabled
// ones.
if (numColorAttachments >= 2)
{
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
(colorAttachmentMask & 2) ? GLColor::white : GLColor::green);
EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::green);
}
if (numColorAttachments >= 1)
{
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
(colorAttachmentMask & 1) ? GLColor::white : GLColor::red);
EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::red);
}
// Make sure glClear did not touch the PLS planes.
attachTexture2DToScratchFBO(pls0);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
attachTexture2DToScratchFBO(pls1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);
// Clear to check that the draw buffers were properly restored after we cleared with PLS
// active.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClearColor(0, 0, 0, 1);
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, 10, H);
if (numColorAttachments >= 1)
{
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
EXPECT_GL_NO_ERROR();
// Make sure glClear worked on the enabled color attachments, and did not clear the disabled
// ones.
if (numColorAttachments >= 2)
{
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
(colorAttachmentMask & 2) ? GLColor::black : GLColor::green);
EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::green);
}
if (numColorAttachments >= 1)
{
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
(colorAttachmentMask & 1) ? GLColor::black : GLColor::red);
EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::red);
}
// Make sure glClear still did not touch the PLS planes.
attachTexture2DToScratchFBO(pls0);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
attachTexture2DToScratchFBO(pls1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);
EXPECT_GL_NO_ERROR();
}
ASSERT_GL_NO_ERROR();
}
// Check the Intel-Mac workaround for GL_COLOR_ATTACHMENT0. On this platform, it has been necessary
// in the past to attach a texture on GL_COLOR_ATTACHMENT0 when rendering PLS only to storage
// textures.
//
// Check that:
// 1) Needed workarounds are applied and PLS draws correctly.
// 2) The workarounds do not affect GL state after PLS ends.
//
TEST_P(PixelLocalStorageTest, ColorAttachment0Workaround)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
ANGLE_SKIP_TEST_IF(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES < 3);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
void main()
{
pixelLocalStoreANGLE(tex, pixelLocalLoadANGLE(tex) + vec4(0, 1, 0, 1));
})");
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(0, pls, 0, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));
GLTexture attachment0;
glBindTexture(GL_TEXTURE_2D_ARRAY, attachment0);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, W * 2, H * 2, 3);
// Use PLS with:
// 1) GL_COLOR_ATTACHMENT0 attached.
// 2) drawbuffer 0 disabled.
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, attachment0, 1, 2);
glDrawBuffers(1, GLenumArray({GL_NONE}));
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
mProgram.drawBoxes({{{FULLSCREEN}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_NO_ERROR();
// Ensure our workaround didn't change the COLOR_ATTACHMENT0 binding.
EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, attachment0);
EXPECT_FRAMEBUFFER_ATTACHMENT_LEVEL(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 1);
EXPECT_FRAMEBUFFER_ATTACHMENT_LAYER(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 2);
EXPECT_GL_NO_ERROR();
attachTexture2DToScratchFBO(pls);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
ASSERT_GL_NO_ERROR();
}
// Check that PLS and EXT_shader_framebuffer_fetch can be used together.
TEST_P(PixelLocalStorageTest, ParallelFramebufferFetch)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));
mProgram.compile("#extension GL_EXT_shader_framebuffer_fetch : require", R"(
layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls;
inout highp vec4 fbfetch;
void main()
{
// Swap pls and fbfetch.
vec4 tmp = pixelLocalLoadANGLE(pls);
pixelLocalStoreANGLE(pls, fbfetch);
fbfetch = tmp;
})");
PLSTestTexture pls(GL_RGBA8), fbfetch(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, pls, 0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbfetch, 0);
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
GLfloat red[] = {1, 0, 0, 1};
glClearBufferfv(GL_COLOR, 0, red);
// Swap pls and fbfetch.
mProgram.drawBoxes({{FULLSCREEN}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
attachTexture2DToScratchFBO(pls);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}
// Check that PLS gets properly cleaned up when its framebuffer and textures are never deleted.
TEST_P(PixelLocalStorageTest, LeakFramebufferAndTexture)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLuint tex0;
glGenTextures(1, &tex0);
glBindTexture(GL_TEXTURE_2D, tex0);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 10, 10);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
PLSTestTexture tex1(GL_R32F);
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I);
// Delete tex1.
// Don't delete tex0.
// Don't delete fbo.
// The PixelLocalStorage frontend implementation has internal assertions that verify all its GL
// context objects are properly disposed of.
}
// Check that sampler, texture, and PLS bindings all work when they are used in the same shader.
TEST_P(PixelLocalStorageTest, PLSWithSamplers)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture tex0(GL_RGBA8);
std::vector<GLColor> redData(H * H, GLColor::red);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, redData.data());
PLSTestTexture tex1(GL_RGBA8);
std::vector<GLColor> blueData(H * H, GLColor::blue);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, blueData.data());
PLSTestTexture pls0(GL_RGBA8);
std::vector<GLColor> greenData(H * W, GLColor::green);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, greenData.data());
PLSTestTexture pls1(GL_RGBA8);
PLSTestTexture pls2(GL_RGBA8);
glViewport(0, 0, W, H);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, pls2, 0, 0);
mProgram.compile(R"(
layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls0;
layout(binding=1, rgba8) uniform mediump pixelLocalANGLE pls1;
layout(binding=2, rgba8) uniform mediump pixelLocalANGLE pls2;
uniform mediump sampler2D tex0;
uniform mediump sampler2D tex1;
void main()
{
vec4 value;
if (gl_FragCoord.y > 50.0)
{
if (gl_FragCoord.x > 50.0)
value = texture(tex1, color.xy);
else
value = texture(tex0, color.xy);
}
else
{
if (gl_FragCoord.x > 50.0)
value = pixelLocalLoadANGLE(pls1);
else
value = pixelLocalLoadANGLE(pls0);
}
pixelLocalStoreANGLE(pls2, value);
pixelLocalStoreANGLE(pls1, vec4(0, 1, 1, 1));
})");
glUniform1i(glGetUniformLocation(mProgram, "tex0"), 0);
glUniform1i(glGetUniformLocation(mProgram, "tex1"), 3);
glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(1, 1, 0, 1));
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE}));
glBindTexture(GL_TEXTURE_2D, tex0);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, tex1);
mProgram.drawBoxes({{FULLSCREEN, {0, 0, 1, 1}}});
glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(pls2);
EXPECT_PIXEL_RECT_EQ(0, 0, 50, 50, GLColor::green);
EXPECT_PIXEL_RECT_EQ(50, 0, W - 50, 50, GLColor::yellow);
EXPECT_PIXEL_RECT_EQ(0, 50, 50, H - 50, GLColor::red);
EXPECT_PIXEL_RECT_EQ(50, 50, W - 50, H - 50, GLColor::blue);
attachTexture2DToScratchFBO(pls1);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);
attachTexture2DToScratchFBO(pls0);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
ASSERT_GL_NO_ERROR();
}
// Check the PLS interruption mechanism.
TEST_P(PixelLocalStorageTest, Interrupt)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture t(GL_RGBA8);
PLSTestTexture u0(GL_R32UI);
PLSTestTexture u1(GL_R32UI);
PLSTestTexture u2(GL_R32UI);
GLFramebuffer f;
glBindFramebuffer(GL_FRAMEBUFFER, f);
glFramebufferTexturePixelLocalStorageANGLE(0, t, 0, 0);
GLFramebuffer g;
glBindFramebuffer(GL_FRAMEBUFFER, g);
glFramebufferTexturePixelLocalStorageANGLE(0, u0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, u1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, u2, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
PLSProgram p;
p.compile(R"(
layout(binding=0, rgba8) uniform highp pixelLocalANGLE t;
void main()
{
pixelLocalStoreANGLE(t, color + pixelLocalLoadANGLE(t));
})");
PLSProgram q;
q.compile(R"(
layout(binding=0, r32ui) uniform highp upixelLocalANGLE u0;
layout(binding=1, r32ui) uniform highp upixelLocalANGLE u1;
layout(binding=2, r32ui) uniform highp upixelLocalANGLE u2;
void main()
{
pixelLocalStoreANGLE(u0, (pixelLocalLoadANGLE(u1) << 8) | 0xau);
pixelLocalStoreANGLE(u1, (pixelLocalLoadANGLE(u2) << 8) | 0xbu);
pixelLocalStoreANGLE(u2, (pixelLocalLoadANGLE(u0) << 8) | 0xcu);
})");
// Interleave 2 PLS rendering passes.
glBindFramebuffer(GL_FRAMEBUFFER, f);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
p.bind();
p.drawBoxes({{{FULLSCREEN}, {1, 0, 0, 0}}});
glFramebufferPixelLocalStorageInterruptANGLE();
glBindFramebuffer(GL_FRAMEBUFFER, g);
q.bind();
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
q.drawBoxes({{{FULLSCREEN}}});
glFramebufferPixelLocalStorageInterruptANGLE();
p.bind();
glBindFramebuffer(GL_FRAMEBUFFER, f);
glFramebufferPixelLocalStorageRestoreANGLE();
p.drawBoxes({{{FULLSCREEN}, {0, 0, 0, 1}}});
glFramebufferPixelLocalStorageInterruptANGLE();
glBindFramebuffer(GL_FRAMEBUFFER, g);
glFramebufferPixelLocalStorageRestoreANGLE();
q.bind();
q.drawBoxes({{{FULLSCREEN}}});
glFramebufferPixelLocalStorageInterruptANGLE();
glBindFramebuffer(GL_FRAMEBUFFER, f);
glFramebufferPixelLocalStorageRestoreANGLE();
p.bind();
p.drawBoxes({{{FULLSCREEN}, {0, 0, 1, 0}}});
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
q.bind();
glBindFramebuffer(GL_FRAMEBUFFER, g);
glFramebufferPixelLocalStorageRestoreANGLE();
q.drawBoxes({{{FULLSCREEN}}});
glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE}));
attachTexture2DToScratchFBO(t);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 0, 255, 255));
ASSERT_GL_NO_ERROR();
attachTexture2DToScratchFBO(u0);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0x0a0c0b0a, 0, 0, 1));
attachTexture2DToScratchFBO(u1);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0x0b0a0c0b, 0, 0, 1));
attachTexture2DToScratchFBO(u2);
EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0x0c0b0a0c, 0, 0, 1));
ASSERT_GL_NO_ERROR();
}
// Check that deleting attachments and PLS bindings on the current draw framebuffer implicitly
// deactivates pixel local storage.
TEST_P(PixelLocalStorageTest, DeleteAttachments_draw_framebuffer)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
GLuint depthStencil;
glGenRenderbuffers(1, &depthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
depthStencil);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthStencil);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthStencil);
PLSTestTexture colorTex(GL_RGBA8);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTex);
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER,
colorRenderbuffer);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, colorRenderbuffer);
PLSTestTexture pls0(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, pls0);
PLSTestTexture pls1(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, pls1);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
ASSERT_GL_NO_ERROR();
// Deleting the depth/stencil will implicitly end pixel local storage.
glDeleteRenderbuffers(1, &depthStencil);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 0);
// Deleting the color texture will implicitly end pixel local storage.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
colorTex.reset();
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0);
// Deleting the color renderbuffer will implicitly end pixel local storage.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
glDeleteRenderbuffers(1, &colorRenderbuffer);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0);
// Pixel local storage can't be ended because it's already deactivated.
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
// Deleting an inactive PLS plane will implicitly end pixel local storage.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
pls1.reset();
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
// Deleting an active PLS plane will implicitly end pixel local storage.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
pls0.reset();
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
// Deleting the textures deinitialized the planes.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
// Check that deleting attachments and PLS bindings on the current read framebuffer does *not*
// deactivate pixel local storage.
TEST_P(PixelLocalStorageTest, DeleteAttachments_read_framebuffer)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
// Deleting attachments on the read framebuffer does not turn off PLS.
GLFramebuffer readFBO;
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
GLuint depthStencil;
glGenRenderbuffers(1, &depthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
depthStencil);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthStencil);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthStencil);
PLSTestTexture colorTex(GL_RGBA8);
colorTex.reset(GL_RGBA8);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTex);
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER,
colorRenderbuffer);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
colorRenderbuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFBO);
PLSTestTexture inactivePLS0(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(0, inactivePLS0, 0, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, inactivePLS0);
PLSTestTexture inactivePLS1(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(1, inactivePLS1, 0, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, inactivePLS1);
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
PLSTestTexture activePLS(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(0, activePLS, 0, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
glDeleteRenderbuffers(1, &depthStencil);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 0);
colorTex.reset();
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0);
glDeleteRenderbuffers(1, &colorRenderbuffer);
EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0);
inactivePLS0.reset();
inactivePLS1.reset();
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFBO);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
}
// Check that glBeginPixelLocalStorageANGLE() is banned while tiled rendering is
// active, and that glStartTilingQCOM() implicitly disables PLS.
TEST_P(PixelLocalStorageTest, TiledRenderingInteractions)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_QCOM_tiled_rendering"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
PLSTestTexture color0(GL_RGBA8, 100, 100);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color0, 0);
glStartTilingQCOM(8, 8, 8, 8, GL_COLOR_BUFFER_BIT0_QCOM);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
// glStartTilingQCOM implicitly disables PLS.
glStartTilingQCOM(8, 8, 8, 8, GL_COLOR_BUFFER_BIT0_QCOM);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
}
// Checks that draw commands validate current PLS state against the shader's PLS uniforms.
class DrawCommandValidationTest
{
public:
DrawCommandValidationTest()
{
mHasDebugKHR = EnsureGLExtensionEnabled("GL_KHR_debug");
if (mHasDebugKHR)
{
glDebugMessageControlKHR(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR,
GL_DEBUG_SEVERITY_HIGH, 0, NULL, GL_TRUE);
glDebugMessageCallbackKHR(&ErrorMessageCallback, this);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
}
void run()
{
PLSProgram p2;
p2.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane0;
layout(binding=2, r32ui) uniform highp upixelLocalANGLE plane2;
void main()
{
pixelLocalStoreANGLE(plane0, vec4(pixelLocalLoadANGLE(plane2).r));
})");
PLSProgram p;
p.compile(R"(
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane0;
layout(binding=1, rgba8) uniform lowp pixelLocalANGLE plane1;
layout(binding=2, r32ui) uniform highp upixelLocalANGLE plane2;
void main()
{
pixelLocalStoreANGLE(plane0, pixelLocalLoadANGLE(plane1) *
vec4(pixelLocalLoadANGLE(plane2).r));
})");
ForAllDrawCalls([this]() {
// INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
// pixel local uniform bound to an inactive pixel local storage plane.
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Draw program references pixel local storage plane(s) that are not currently "
"active.");
});
PLSTestTexture tex0(GL_RGBA8);
PLSTestTexture tex1(GL_RGBA8);
PLSTestTexture tex2(GL_R32UI);
PLSTestTexture tex3(GL_R32UI);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, tex2, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(3, tex3, 0, 0);
glViewport(0, 0, W, H);
glDrawBuffers(0, nullptr);
glBeginPixelLocalStorageANGLE(2,
GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
ASSERT_GL_NO_ERROR();
ForAllDrawCalls([this]() {
// INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
// pixel local uniform bound to an inactive pixel local storage plane.
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Draw program references pixel local storage plane(s) that are not currently "
"active.");
});
glEndPixelLocalStorageANGLE(
2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(
4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
GL_LOAD_OP_ZERO_ANGLE}));
ASSERT_GL_NO_ERROR();
ForAllDrawCalls([this]() {
// INVALID_OPERATION is generated if a draw is issued with a fragment shader that does
// _not_ have a pixel local uniform bound to an _active_ pixel local storage plane
// (i.e., the fragment shader must declare uniforms bound to every single active pixel
// local storage plane).
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Active pixel local storage plane(s) are not referenced by the draw program.");
});
glEndPixelLocalStorageANGLE(
4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
ASSERT_GL_NO_ERROR();
p2.bind();
ForAllDrawCalls([this]() {
// INVALID_OPERATION is generated if a draw is issued with a fragment shader that does
// _not_ have a pixel local uniform bound to an _active_ pixel local storage plane
// (i.e., the fragment shader must declare uniforms bound to every single active pixel
// local storage plane).
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Active pixel local storage plane(s) are not referenced by the draw program.");
});
p.bind();
glEndPixelLocalStorageANGLE(
3, GLenumArray(
{GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
PLSTestTexture texWrongFormat(GL_RGBA8I);
glFramebufferTexturePixelLocalStorageANGLE(2, texWrongFormat, 0, 0);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
ASSERT_GL_NO_ERROR();
ForAllDrawCalls([this]() {
// INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
// pixel local storage uniform whose format layout qualifier does not identically match
// the internalformat of its associated pixel local storage plane on the current draw
// framebuffer.
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Pixel local storage formats in the draw program do not match actively bound "
"planes.");
});
glEndPixelLocalStorageANGLE(
3, GLenumArray(
{GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
}
private:
static void ForAllDrawCalls(std::function<void()> checks)
{
glDrawArrays(GL_TRIANGLES, 0, 3);
checks();
ASSERT_GL_NO_ERROR();
glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_SHORT, nullptr);
checks();
ASSERT_GL_NO_ERROR();
glDrawRangeElements(GL_TRIANGLES, 0, 0, 0, GL_UNSIGNED_SHORT, nullptr);
checks();
ASSERT_GL_NO_ERROR();
glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 1);
checks();
ASSERT_GL_NO_ERROR();
glDrawElementsInstanced(GL_TRIANGLES, 0, GL_UNSIGNED_SHORT, nullptr, 0);
checks();
ASSERT_GL_NO_ERROR();
}
static void GL_APIENTRY ErrorMessageCallback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
const void *userParam)
{
auto test = const_cast<DrawCommandValidationTest *>(
static_cast<const DrawCommandValidationTest *>(userParam));
test->mErrorMessages.emplace_back(message);
}
bool mHasDebugKHR;
std::vector<std::string> mErrorMessages;
};
// Check that draw commands validate current PLS state against the shader's PLS uniforms.
TEST_P(PixelLocalStorageTest, DrawCommandValidation)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
DrawCommandValidationTest().run();
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTest);
#define PLATFORM(API, BACKEND) API##_##BACKEND()
#define PLS_INSTANTIATE_RENDERING_TEST_AND(TEST, API, ...) \
ANGLE_INSTANTIATE_TEST( \
TEST, \
PLATFORM(API, D3D11) /* D3D coherent. */ \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, D3D11) /* D3D noncoherent. */ \
.enable(Feature::DisableRasterizerOrderViews) \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, OPENGL) /* OpenGL coherent. */ \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, OPENGL) /* OpenGL noncoherent. */ \
.enable(Feature::EmulatePixelLocalStorage) \
.disable(Feature::SupportsFragmentShaderInterlockNV) \
.disable(Feature::SupportsFragmentShaderOrderingINTEL) \
.disable(Feature::SupportsFragmentShaderInterlockARB), \
PLATFORM(API, OPENGLES) /* OpenGL ES coherent */ \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, OPENGLES) /* Test PLS not having access to \
glEnablei/glDisablei/glColorMaski. */ \
.enable(Feature::EmulatePixelLocalStorage) \
.enable(Feature::DisableDrawBuffersIndexed), \
PLATFORM(API, OPENGLES) /* OpenGL ES noncoherent \
(EXT_shader_framebuffer_fetch_non_coherent). */ \
.enable(Feature::EmulatePixelLocalStorage) \
.disable(Feature::SupportsShaderFramebufferFetchEXT), \
PLATFORM(API, OPENGLES) /* OpenGL ES noncoherent (shader images). */ \
.enable(Feature::EmulatePixelLocalStorage) \
.disable(Feature::SupportsShaderFramebufferFetchEXT) \
.disable(Feature::SupportsShaderFramebufferFetchNonCoherentEXT), \
PLATFORM(API, VULKAN) /* Vulkan coherent. */ \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, VULKAN) /* Vulkan noncoherent. */ \
.disable(Feature::SupportsShaderFramebufferFetch) \
.disable(Feature::SupportsFragmentShaderPixelInterlock) \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, VULKAN_SWIFTSHADER) /* Swiftshader coherent (framebuffer fetch). */ \
.enable(Feature::EmulatePixelLocalStorage), \
PLATFORM(API, VULKAN_SWIFTSHADER) /* Swiftshader noncoherent. */ \
.disable(Feature::SupportsShaderFramebufferFetch) \
.disable(Feature::SupportsFragmentShaderPixelInterlock) \
.enable(Feature::EmulatePixelLocalStorage), \
__VA_ARGS__)
#define PLS_INSTANTIATE_RENDERING_TEST_ES3(TEST) \
PLS_INSTANTIATE_RENDERING_TEST_AND( \
TEST, ES3, /* Metal, coherent (in tiled memory on Apple Silicon).*/ \
ES3_METAL().enable(Feature::EmulatePixelLocalStorage), /* Metal, coherent via raster order \
groups + read_write textures.*/ \
ES3_METAL() \
.enable(Feature::EmulatePixelLocalStorage) \
.enable(Feature::DisableProgrammableBlending), /* Metal, coherent, r32 packed \
read_write texture formats.*/ \
ES3_METAL() \
.enable(Feature::EmulatePixelLocalStorage) \
.enable(Feature::DisableProgrammableBlending) \
.enable(Feature::DisableRWTextureTier2Support), /* Metal, noncoherent if not on Apple \
Silicon. (Apple GPUs don't support \
fragment-to-fragment memory \
barriers.)*/ \
ES3_METAL() \
.enable(Feature::EmulatePixelLocalStorage) \
.enable(Feature::DisableRasterOrderGroups))
#define PLS_INSTANTIATE_RENDERING_TEST_ES31(TEST) PLS_INSTANTIATE_RENDERING_TEST_AND(TEST, ES31)
PLS_INSTANTIATE_RENDERING_TEST_ES3(PixelLocalStorageTest);
class PixelLocalStorageTestES31 : public PixelLocalStorageTest
{};
// Check that early_fragment_tests are not triggered when PLS uniforms are not declared.
TEST_P(PixelLocalStorageTestES31, EarlyFragmentTests)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
GLuint stencil;
glGenRenderbuffers(1, &stencil);
glBindRenderbuffer(GL_RENDERBUFFER, stencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, W, H);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencil);
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
// Emits a fullscreen quad.
constexpr char kFullscreenVS[] = R"(#version 310 es
precision highp float;
void main()
{
gl_Position.x = (gl_VertexID & 1) == 0 ? -1.0 : 1.0;
gl_Position.y = (gl_VertexID & 2) == 0 ? -1.0 : 1.0;
gl_Position.zw = vec2(0, 1);
})";
// Renders green to the framebuffer.
constexpr char kDrawRed[] = R"(#version 310 es
out mediump vec4 fragColor;
void main()
{
fragColor = vec4(1, 0, 0, 1);
})";
ANGLE_GL_PROGRAM(drawGreen, kFullscreenVS, kDrawRed);
// Render to stencil without PLS uniforms and with a discard. Since we discard, and since the
// shader shouldn't enable early_fragment_tests, stencil should not be affected.
constexpr char kNonPLSDiscard[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : enable
void f(highp ipixelLocalANGLE pls)
{
// Function arguments don't trigger PLS restrictions.
pixelLocalStoreANGLE(pls, ivec4(8));
}
void main()
{
discard;
})";
ANGLE_GL_PROGRAM(lateDiscard, kFullscreenVS, kNonPLSDiscard);
glUseProgram(lateDiscard);
glStencilFunc(GL_ALWAYS, 1, ~0u);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Clear the framebuffer to green.
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Render red to the framebuffer with a stencil test. This should have no effect because the
// stencil buffer should be all zeros.
glUseProgram(drawGreen);
glStencilFunc(GL_NOTEQUAL, 0, ~0u);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
// Now double check that this test would have failed if the shader had enabled
// early_fragment_tests. Render to stencil *with* early_fragment_tests and a discard. Stencil
// should be affected this time even though we discard.
ANGLE_GL_PROGRAM(earlyDiscard, kFullscreenVS,
(std::string(kNonPLSDiscard) + "layout(early_fragment_tests) in;").c_str());
glUseProgram(earlyDiscard);
glStencilFunc(GL_ALWAYS, 1, ~0u);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Clear the framebuffer to green.
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Render red to the framebuffer again. This time the stencil test should pass because the
// stencil buffer should be all ones.
glUseProgram(drawGreen);
glStencilFunc(GL_NOTEQUAL, 0, ~0u);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
ASSERT_GL_NO_ERROR();
}
// Check that application-facing ES31 state is not perturbed by pixel local storage.
TEST_P(PixelLocalStorageTestES31, StateRestoration)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doStateRestorationTest();
}
// Check that draw state does not affect PLS loads and stores, particularly for
// EXT_shader_pixel_local_storage, where they are implemented as fullscreen draws.
TEST_P(PixelLocalStorageTestES31, DrawStateReset)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doDrawStateTest();
}
// Check that changing framebuffer state implicitly disable pixel local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_Framebuffer)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doImplicitDisablesTest_Framebuffer();
}
// Check that changing framebuffer texture attachments implicitly disable pixel
// local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_TextureAttachments)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doImplicitDisablesTest_TextureAttachments();
}
// Check that changing framebuffer renderbuffer attachments implicitly disable
// pixel local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_RenderbufferAttachments)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
doImplicitDisablesTest_RenderbufferAttachments();
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTestES31);
PLS_INSTANTIATE_RENDERING_TEST_ES31(PixelLocalStorageTestES31);
class PixelLocalStorageRequestableExtensionTest : public ANGLETest<>
{
protected:
PixelLocalStorageRequestableExtensionTest() { setExtensionsEnabled(false); }
};
static void do_implicitly_enabled_extensions_test(const char *plsExtensionToRequest)
{
bool hasDrawBuffersIndexedOES = IsGLExtensionRequestable("GL_OES_draw_buffers_indexed");
bool hasDrawBuffersIndexedEXT = IsGLExtensionRequestable("GL_EXT_draw_buffers_indexed");
bool hasColorBufferFloat = IsGLExtensionRequestable("GL_EXT_color_buffer_float");
bool hasColorBufferHalfFloat = IsGLExtensionRequestable("GL_EXT_color_buffer_half_float");
bool hasCoherent = IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage_coherent");
EXPECT_TRUE(!IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_color_buffer_float"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
glRequestExtensionANGLE(plsExtensionToRequest);
EXPECT_GL_NO_ERROR();
if (hasDrawBuffersIndexedOES)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
}
if (hasDrawBuffersIndexedEXT)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
}
if (hasColorBufferFloat)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_color_buffer_float"));
}
if (hasColorBufferHalfFloat)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"));
}
if (hasCoherent)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
}
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
if (hasDrawBuffersIndexedOES)
{
// If OES_draw_buffers_indexed ever becomes disablable, it will have to implicitly disable
// ANGLE_shader_pixel_local_storage.
glDisableExtensionANGLE("GL_OES_draw_buffers_indexed");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
EXPECT_TRUE(IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
}
if (hasDrawBuffersIndexedEXT)
{
// If EXT_draw_buffers_indexed ever becomes disablable, it will have to implicitly disable
// ANGLE_shader_pixel_local_storage.
glDisableExtensionANGLE("GL_EXT_draw_buffers_indexed");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
}
if (hasColorBufferFloat)
{
// If EXT_color_buffer_float ever becomes disablable, it will have to implicitly disable
// ANGLE_shader_pixel_local_storage.
glDisableExtensionANGLE("GL_EXT_color_buffer_float");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_color_buffer_float"));
}
if (hasColorBufferHalfFloat)
{
// If EXT_color_buffer_half_float ever becomes disablable, it will have to implicitly
// disable ANGLE_shader_pixel_local_storage.
glDisableExtensionANGLE("GL_EXT_color_buffer_half_float");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"));
}
if (hasCoherent)
{
// ANGLE_shader_pixel_local_storage_coherent is not disablable.
glDisableExtensionANGLE("GL_ANGLE_shader_pixel_local_storage_coherent");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
}
// ANGLE_shader_pixel_local_storage is not disablable.
glDisableExtensionANGLE("GL_ANGLE_shader_pixel_local_storage");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// All dependency extensions should have remained enabled.
if (hasDrawBuffersIndexedOES)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
}
if (hasDrawBuffersIndexedEXT)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
}
if (hasColorBufferFloat)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_color_buffer_float"));
}
if (hasColorBufferHalfFloat)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"));
}
if (hasCoherent)
{
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
}
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
}
// Check that ANGLE_shader_pixel_local_storage implicitly enables its dependency extensions.
TEST_P(PixelLocalStorageRequestableExtensionTest, ImplicitlyEnabledExtensions)
{
EXPECT_TRUE(IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANGLE_create_context_extensions_enabled"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage"));
do_implicitly_enabled_extensions_test("GL_ANGLE_shader_pixel_local_storage");
}
// Check that ANGLE_shader_pixel_local_storage_coherent implicitly enables its
// dependency extensions.
TEST_P(PixelLocalStorageRequestableExtensionTest, ImplicitlyEnabledExtensionsCoherent)
{
EXPECT_TRUE(IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANGLE_create_context_extensions_enabled"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage"));
if (!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage_coherent"))
{
// Requesting GL_ANGLE_shader_pixel_local_storage_coherent should not implicitly enable any
// other extensions if the extension itself is not supported.
glRequestExtensionANGLE("GL_ANGLE_shader_pixel_local_storage_coherent");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_TRUE(!IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_color_buffer_float"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
}
else
{
do_implicitly_enabled_extensions_test("GL_ANGLE_shader_pixel_local_storage_coherent");
}
}
// Check that the dependency extensions of ANGLE_shader_pixel_local_storage do not enable it.
TEST_P(PixelLocalStorageRequestableExtensionTest, ANGLEShaderPixelLocalStorageNotImplicitlyEnabled)
{
EXPECT_TRUE(IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANGLE_create_context_extensions_enabled"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage"));
EnsureGLExtensionEnabled("GL_OES_draw_buffers_indexed");
EnsureGLExtensionEnabled("GL_EXT_draw_buffers_indexed");
EnsureGLExtensionEnabled("GL_EXT_color_buffer_float");
EnsureGLExtensionEnabled("GL_EXT_color_buffer_half_float");
EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
if (IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage_coherent"))
{
glRequestExtensionANGLE("GL_ANGLE_shader_pixel_local_storage_coherent");
EXPECT_GL_NO_ERROR();
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
}
else
{
glRequestExtensionANGLE("GL_ANGLE_shader_pixel_local_storage");
EXPECT_GL_NO_ERROR();
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
}
}
PLS_INSTANTIATE_RENDERING_TEST_ES3(PixelLocalStorageRequestableExtensionTest);
class PixelLocalStorageValidationTest : public ANGLETest<>
{
public:
PixelLocalStorageValidationTest() { setExtensionsEnabled(false); }
protected:
void testSetUp() override
{
ASSERT(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
glGetIntegerv(GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE, &MAX_PIXEL_LOCAL_STORAGE_PLANES);
glGetIntegerv(GL_MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE,
&MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &MAX_COLOR_ATTACHMENTS);
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &MAX_DRAW_BUFFERS);
mHasDebugKHR = EnsureGLExtensionEnabled("GL_KHR_debug");
if (mHasDebugKHR)
{
glDebugMessageControlKHR(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR,
GL_DEBUG_SEVERITY_HIGH, 0, NULL, GL_TRUE);
glDebugMessageCallbackKHR(&ErrorMessageCallback, this);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
// INVALID_OPERATION is generated if DITHER is enabled.
glDisable(GL_DITHER);
ANGLETest::testSetUp();
}
int isContextVersionAtLeast(int major, int minor)
{
return getClientMajorVersion() > major ||
(getClientMajorVersion() == major && getClientMinorVersion() >= minor);
}
static void GL_APIENTRY ErrorMessageCallback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
const void *userParam)
{
auto test = const_cast<PixelLocalStorageValidationTest *>(
static_cast<const PixelLocalStorageValidationTest *>(userParam));
test->mErrorMessages.emplace_back(message);
}
GLint MAX_PIXEL_LOCAL_STORAGE_PLANES = 0;
GLint MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES = 0;
GLint MAX_COLOR_ATTACHMENTS = 0;
GLint MAX_DRAW_BUFFERS = 0;
bool mHasDebugKHR;
std::vector<std::string> mErrorMessages;
};
class ScopedEnable
{
public:
ScopedEnable(GLenum feature) : mFeature(feature) { glEnable(mFeature); }
~ScopedEnable() { glDisable(mFeature); }
private:
GLenum mFeature;
};
// Check that PLS state has the correct initial values.
TEST_P(PixelLocalStorageValidationTest, InitialValues)
{
// It's valid to query GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE even when fbo 0 is bound.
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_GL_NO_ERROR();
// Table 6.Y: Pixel Local Storage State
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
}
EXPECT_GL_NO_ERROR();
}
// Check that glFramebufferMemorylessPixelLocalStorageANGLE validates as specified.
TEST_P(PixelLocalStorageValidationTest, FramebufferMemorylessPixelLocalStorageANGLE)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_R32F);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, GL_NONE);
// If <internalformat> is NONE, the pixel local storage plane at index <plane> is deinitialized
// and any internal storage is released.
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_NONE);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, GL_NONE);
// Set back to GL_RGBA8I.
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8I);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8I);
// INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
// to DRAW_FRAMEBUFFER.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32UI);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8I);
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// in an interrupted state.
EXPECT_GL_NO_ERROR();
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32UI);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8I);
// INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
glFramebufferMemorylessPixelLocalStorageANGLE(-1, GL_R32UI);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glFramebufferMemorylessPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_R32UI);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glFramebufferMemorylessPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_R32UI);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_R32UI);
// INVALID_ENUM is generated if <internalformat> is not one of the acceptable values in Table
// X.2, or NONE.
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA16F);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA32UI);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8_SNORM);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8I);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, GL_NONE);
ASSERT_GL_NO_ERROR();
}
// Check that glFramebufferTexturePixelLocalStorageANGLE validates as specified.
TEST_P(PixelLocalStorageValidationTest, FramebufferTexturePixelLocalStorageANGLE)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Initially, pixel local storage planes are in a deinitialized state and are unusable.
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8UI, 10, 10);
glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8UI);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
// If <backingtexture> is 0, <level> and <layer> are ignored and the pixel local storage plane
// <plane> is deinitialized.
glFramebufferTexturePixelLocalStorageANGLE(1, 0, 1, 2);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
// Set back to GL_RGBA8I.
glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8UI);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
{
// When a texture object is deleted, any pixel local storage plane to which it was bound is
// automatically deinitialized.
GLFramebuffer keepalive; // Keep the underlying texture alive after deleting its ID by
// binding it to a framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, keepalive);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
ASSERT_GL_NO_ERROR();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
tex.reset();
PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
}
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8UI, 10, 10);
// Same as above, but with orphaning.
glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8UI);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
{
GLFramebuffer keepalive; // Keep the underlying texture alive after deleting its ID by
// binding it to a framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, keepalive);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
ASSERT_GL_NO_ERROR();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
tex.reset();
PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
}
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8UI, 10, 10);
// INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
// to DRAW_FRAMEBUFFER.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// in an interrupted state.
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0);
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferTexturePixelLocalStorageANGLE(1, 0, 0, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
// INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
glFramebufferTexturePixelLocalStorageANGLE(-1, tex, 1, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glFramebufferTexturePixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, tex, 1, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glFramebufferTexturePixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, tex, 2, 0);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
EXPECT_PLS_INTEGER(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 2);
// INVALID_OPERATION is generated if <backingtexture> is not the name of an existing immutable
// texture object, or zero.
GLTexture badTex;
glFramebufferTexturePixelLocalStorageANGLE(2, badTex, 0, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Not a valid texture object name.");
glBindTexture(GL_TEXTURE_2D, badTex);
glFramebufferTexturePixelLocalStorageANGLE(2, badTex, 0, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Texture is not immutable.");
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 10, 10, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(2, badTex, 0, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Texture is not immutable.");
// INVALID_OPERATION is generated if <backingtexture> is nonzero
// and not of type TEXTURE_2D or TEXTURE_2D_ARRAY.
GLTexture texCube;
glBindTexture(GL_TEXTURE_CUBE_MAP, texCube);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, 10, 10);
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(0, texCube, 0, 1);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage texture type.");
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
GLTexture tex2DMultisample;
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex2DMultisample);
glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 1, GL_RGBA8, 10, 10, 1);
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(0, tex2DMultisample, 0, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage texture type.");
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
GLTexture tex3D;
glBindTexture(GL_TEXTURE_3D, tex3D);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 5, 5, 5);
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(0, tex3D, 0, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage texture type.");
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
// INVALID_VALUE is generated if <level> < 0.
tex.reset();
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_R32UI, 10, 10);
glFramebufferTexturePixelLocalStorageANGLE(2, tex, -1, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Negative level.");
// GL_INVALID_VALUE is generated if <backingtexture> is nonzero and <level> >= the immutable
// number of mipmap levels in <backingtexture>.
glFramebufferTexturePixelLocalStorageANGLE(2, tex, 3, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Level is larger than texture level count.");
// INVALID_VALUE is generated if <layer> < 0.
glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, -1);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Negative layer.");
// GL_INVALID_VALUE is generated if <backingtexture> is nonzero and <layer> >= the immutable
// number of texture layers in <backingtexture>.
glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, 1);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Layer is larger than texture depth.");
GLTexture tex2DArray;
glBindTexture(GL_TEXTURE_2D_ARRAY, tex2DArray);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8I, 10, 10, 7);
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(2, tex2DArray, 1, 7);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Layer is larger than texture depth.");
glFramebufferTexturePixelLocalStorageANGLE(2, tex2DArray, 2, 6);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Level is larger than texture level count.");
glFramebufferTexturePixelLocalStorageANGLE(2, tex2DArray, 1, 6);
EXPECT_GL_NO_ERROR();
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_RGBA8I);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex2DArray);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 6);
// When a texture object is deleted, any pixel local storage plane to which it was bound is
// automatically deinitialized.
{
GLFramebuffer keepalive; // Keep the underlying texture alive after deleting its ID by
// binding it to a framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, keepalive);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0);
ASSERT_GL_NO_ERROR();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
tex2DArray.reset();
PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_FORMAT_ANGLE, GL_NONE);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
}
// INVALID_ENUM is generated if <backingtexture> is nonzero and its internalformat is not
// one of the acceptable values in Table X.2.
tex.reset();
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RG32F, 10, 10);
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, 0);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");
ASSERT_GL_NO_ERROR();
}
// Check that FramebufferPixelLocalClearValue{f,i,ui}vANGLE validate as specified.
TEST_P(PixelLocalStorageValidationTest, glFramebufferPixelLocalClearValuesANGLE)
{
// INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
// to DRAW_FRAMEBUFFER.
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glFramebufferPixelLocalClearValueivANGLE(1, ClearI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glFramebufferPixelLocalClearValueuivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1,
ClearUI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
ASSERT_GL_NO_ERROR();
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// in an interrupted state.
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 1, 1, 1));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalClearValueivANGLE(1, ClearI(1, 1, 1, 1));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalClearValueuivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1,
ClearUI(1, 1, 1, 1));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({0, 0, 0, 0}));
EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, ({0, 0, 0, 0}));
ASSERT_GL_NO_ERROR();
// INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
glFramebufferPixelLocalClearValuefvANGLE(-1, ClearF(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glFramebufferPixelLocalClearValueivANGLE(-1, ClearI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glFramebufferPixelLocalClearValueuivANGLE(-1, ClearUI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glFramebufferPixelLocalClearValuefvANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, ClearF(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glFramebufferPixelLocalClearValueivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, ClearI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glFramebufferPixelLocalClearValueuivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, ClearUI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
ASSERT_GL_NO_ERROR();
// INVALID_OPERATION is generated if PIXEL_LOCAL_STORAGE_ACTIVE_ANGLE is TRUE.
PLSTestTexture tex(GL_R32UI);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
ASSERT_GL_NO_ERROR();
glFramebufferPixelLocalClearValuefvANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1,
ClearF(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
glFramebufferPixelLocalClearValuefvANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 2,
ClearF(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
glFramebufferPixelLocalClearValueuivANGLE(0, ClearUI(0, 0, 0, 0));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
ASSERT_GL_NO_ERROR();
}
#define EXPECT_BANNED(cmd, msg) \
cmd; \
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION); \
EXPECT_GL_SINGLE_ERROR_MSG(msg)
#define EXPECT_BANNED_DEFAULT_MSG(cmd) \
EXPECT_BANNED(cmd, "Operation not permitted while pixel local storage is active.")
static std::vector<char> FormatBannedCapMsg(GLenum cap)
{
constexpr char format[] =
"Cap 0x%04X cannot be enabled or disabled while pixel local storage is active.";
std::vector<char> msg(std::snprintf(nullptr, 0, format, cap) + 1);
std::snprintf(msg.data(), msg.size(), format, cap);
return msg;
}
#define EXPECT_ALLOWED_CAP(cap) \
{ \
glEnable(cap); \
glDisable(cap); \
glIsEnabled(cap); \
EXPECT_GL_NO_ERROR(); \
}
#define EXPECT_BANNED_CAP(cap) \
{ \
std::vector<char> msg = FormatBannedCapMsg(cap); \
EXPECT_BANNED(glEnable(cap), msg.data()); \
EXPECT_BANNED(glDisable(cap), msg.data()); \
glIsEnabled(cap); \
EXPECT_GL_NO_ERROR(); \
}
#define EXPECT_BANNED_CAP_INDEXED(cap) \
{ \
std::vector<char> msg = FormatBannedCapMsg(cap); \
EXPECT_BANNED(glEnablei(cap, 0), msg.data()); \
EXPECT_BANNED(glDisablei(cap, 0), msg.data()); \
EXPECT_GL_NO_ERROR(); \
}
// Check that glBeginPixelLocalStorageANGLE validates non-PLS context state as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_context_state)
{
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
// to DRAW_FRAMEBUFFER.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// in an interrupted state.
glFramebufferPixelLocalStorageInterruptANGLE();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalStorageRestoreANGLE();
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
ASSERT_GL_NO_ERROR();
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
// INVALID_OPERATION is generated if PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE is nonzero.
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// INVALID_OPERATION is generated if the value of SAMPLE_BUFFERS is 1 (i.e., if rendering to a
// multisampled framebuffer).
{
GLRenderbuffer msaa;
glBindRenderbuffer(GL_RENDERBUFFER, msaa);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 2, GL_RGBA8, 100, 100);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaa);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with a multisampled framebuffer.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
// INVALID_OPERATION is generated if DITHER is enabled.
{
ScopedEnable scopedEnable(GL_DITHER);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with GL_DITHER enabled.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
}
// Check that glBeginPixelLocalStorageANGLE() is banned while transform feedback
// is active, and that glBeginTransformFeedback() implicitly disables PLS.
TEST_P(PixelLocalStorageValidationTest, TransformFeedbackInteractions)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
// INVALID_OPERATION is generated if TRANSFORM_FEEDBACK_ACTIVE is true.
constexpr char kFS[] = R"(#version 300 es
out mediump vec4 color;
void main()
{
color = vec4(0.6, 0.0, 0.0, 1.0);
})";
std::vector<std::string> xfVaryings;
xfVaryings.push_back("gl_Position");
ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(xfProgram, essl3_shaders::vs::Simple(), kFS, xfVaryings,
GL_INTERLEAVED_ATTRIBS);
glUseProgram(xfProgram);
GLuint xfBuffer;
glGenBuffers(1, &xfBuffer);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfBuffer);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 1024, nullptr, GL_STATIC_DRAW);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfBuffer);
glBeginTransformFeedback(GL_TRIANGLES);
ASSERT_GL_NO_ERROR();
ASSERT_GL_INTEGER(GL_TRANSFORM_FEEDBACK_ACTIVE, 1);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with transform feedback active.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glEndTransformFeedback();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
// glBeginTransformFeedback implicitly disables PLS.
glBeginTransformFeedback(GL_TRIANGLES);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_TRANSFORM_FEEDBACK_ACTIVE, 1);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
glEndTransformFeedback();
EXPECT_GL_NO_ERROR();
glUseProgram(0);
EXPECT_GL_NO_ERROR();
}
// Check that EXT_blend_func_extended is banned when PLS is active.
TEST_P(PixelLocalStorageValidationTest, PLSActive_bans_blend_func_extended)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
// INVALID_OPERATION is generated if BLEND_DST_ALPHA, BLEND_DST_RGB, BLEND_SRC_ALPHA, or
// BLEND_SRC_RGB, for any draw buffer, is a blend function requiring the secondary color input,
// as specified in EXT_blend_func_extended (SRC1_COLOR_EXT, ONE_MINUS_SRC1_COLOR_EXT,
// SRC1_ALPHA_EXT, ONE_MINUS_SRC1_ALPHA_EXT).
for (auto blendFunc : {GL_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_SRC1_ALPHA_EXT,
GL_ONE_MINUS_SRC1_ALPHA_EXT})
{
glBlendFunc(blendFunc, GL_ONE);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with a blend function requiring the secondary "
"color input.");
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBlendFunc(GL_ONE, GL_ZERO);
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
glBlendFunci(MAX_DRAW_BUFFERS - 1, GL_ZERO, blendFunc);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with a blend function requiring the "
"secondary color input.");
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBlendFunc(GL_ONE, GL_ZERO);
}
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, blendFunc);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with a blend function requiring the secondary "
"color input.");
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBlendFunc(GL_ONE, GL_ZERO);
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
glBlendFuncSeparatei(MAX_DRAW_BUFFERS - 1, blendFunc, GL_ONE, GL_ONE, GL_ONE);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with a blend function requiring the "
"secondary color input.");
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBlendFunc(GL_ONE, GL_ZERO);
}
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
ASSERT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
// INVALID_OPERATION is generated by BlendFunc*() if <srcRGB>, <dstRGB>,
// <srcAlpha>, or <dstAlpha> is a blend function requiring the secondary
// color input, as specified in EXT_blend_func_extended (SRC1_COLOR_EXT,
// ONE_MINUS_SRC1_COLOR_EXT, SRC1_ALPHA_EXT, ONE_MINUS_SRC1_ALPHA_EXT).
constexpr char kErrMsg[] =
"Blend functions requiring the secondary color input are not supported when pixel "
"local storage is active.";
glBlendFunc(GL_ONE, blendFunc);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, blendFunc);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
for (GLint i : {0, MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
MAX_DRAW_BUFFERS - 1})
{
glBlendFunci(i, blendFunc, GL_ZERO);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);
glBlendFuncSeparatei(i, GL_ONE, GL_ONE, GL_ONE, blendFunc);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);
}
}
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
}
// GL_SRC_ALPHA_SATURATE_EXT is ok.
glBlendFuncSeparate(GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT,
GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
glBlendFuncSeparate(GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT,
GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT);
EXPECT_GL_NO_ERROR();
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
for (GLint i = 0; i < MAX_DRAW_BUFFERS; ++i)
{
glBlendFuncSeparatei(i, GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT,
GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT);
EXPECT_GL_NO_ERROR();
}
}
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
}
// Check that KHR_blend_equation_advanced is banned when PLS is active.
TEST_P(PixelLocalStorageValidationTest, PLSActive_bans_blend_equation_advanced)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_blend_equation_advanced"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
// INVALID_OPERATION is generated if BLEND_EQUATION_RGB and/or BLEND_EQUATION_ALPHA are one of
// the advanced blend equations defined in KHR_blend_equation_advanced.
for (auto blendEquation : {
GL_MULTIPLY_KHR,
GL_SCREEN_KHR,
GL_OVERLAY_KHR,
GL_DARKEN_KHR,
GL_LIGHTEN_KHR,
GL_COLORDODGE_KHR,
GL_COLORBURN_KHR,
GL_HARDLIGHT_KHR,
GL_SOFTLIGHT_KHR,
GL_DIFFERENCE_KHR,
GL_EXCLUSION_KHR,
GL_HSL_HUE_KHR,
GL_HSL_SATURATION_KHR,
GL_HSL_COLOR_KHR,
GL_HSL_LUMINOSITY_KHR,
})
{
glBlendEquation(blendEquation);
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with an advanced blend equation enabled.");
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBlendEquation(GL_FUNC_ADD);
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
glBlendEquationi(0, blendEquation);
}
else
{
glBlendEquation(blendEquation);
}
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to begin pixel local storage with an advanced blend equation enabled.");
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
glBlendEquationi(0, GL_FUNC_ADD);
}
else
{
glBlendEquation(GL_FUNC_ADD);
}
ASSERT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
// INVALID_OPERATION is generated by BlendEquation*() if <mode> is one of the advanced
// blend equations defined in KHR_blend_equation_advanced.
glBlendEquation(blendEquation);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Advanced blend equations are not supported when pixel local storage is "
"active.");
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
for (GLint i : {0,
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
MAX_DRAW_BUFFERS - 1})
{
glBlendEquationi(i, blendEquation);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Advanced blend equations are not supported when pixel local storage is "
"active.");
}
}
EXPECT_GL_INTEGER(GL_BLEND_EQUATION_RGB, GL_FUNC_ADD);
EXPECT_GL_INTEGER(GL_BLEND_EQUATION_ALPHA, GL_FUNC_ADD);
ASSERT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
}
}
// Check that glBeginPixelLocalStorageANGLE validates the draw framebuffer's state as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_framebuffer_state)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
// INVALID_VALUE is generated if <n> < 1 or <n> > MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
glBeginPixelLocalStorageANGLE(0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Planes must be greater than 0.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBeginPixelLocalStorageANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES + 1,
std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES + 1, GL_DONT_CARE).data());
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG(
"Planes must be less than or equal to GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// INVALID_FRAMEBUFFER_OPERATION is generated if the draw framebuffer has an image attached to
// any color attachment point on or after:
//
// COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE - <n>
//
int maxColorAttachmentsWithMaxPLSPlanes =
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - MAX_PIXEL_LOCAL_STORAGE_PLANES;
for (int reservedAttachment : {maxColorAttachmentsWithMaxPLSPlanes, MAX_DRAW_BUFFERS - 1})
{
PLSTestTexture tmp(GL_RGBA8, 100, 100);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + reservedAttachment,
GL_TEXTURE_2D, tmp, 0);
glBeginPixelLocalStorageANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES,
std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_DONT_CARE).data());
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Framebuffer cannot have images attached to color attachment points on or after "
"COLOR_ATTACHMENT0 + "
"MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE - <n>.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
}
// Check that glBeginPixelLocalStorageANGLE validates its loadops as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_loadops)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
// INVALID_VALUE is generated if <loadops> is NULL.
glBeginPixelLocalStorageANGLE(1, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<loadops> cannot be null.");
// INVALID_ENUM is generated if <loadops>[0..<n>-1] is not one of the Load Operations enumerated
// in Table X.1.
{
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_REPLACE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Load Operation: 0x1E01.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
for (int i = 1; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
std::vector<GLenum> loadops(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_DONT_CARE);
loadops.back() = GL_SCISSOR_BOX;
glBeginPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, loadops.data());
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Load Operation: 0x0C10.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
for (int i = 1; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_NONE);
}
}
// INVALID_OPERATION is generated if <loadops>[0..<n>-1] is not DISABLE_ANGLE, and the pixel
// local storage plane at that same index is is in a deinitialized state.
{
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to enable a pixel local storage plane that is in a deinitialized state.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// If <backingtexture> is 0, <level> and <layer> are ignored and the pixel local storage
// plane <plane> is deinitialized.
glFramebufferTexturePixelLocalStorageANGLE(0, 0, -1, 999);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to enable a pixel local storage plane that is in a deinitialized state.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
// If <internalformat> is NONE, the pixel local storage plane at index <plane> is
// deinitialized and any internal storage is released.
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_NONE);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to enable a pixel local storage plane that is in a deinitialized state.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
// INVALID_OPERATION is generated if <loadops>[0..<n>-1] is LOAD_OP_LOAD_ANGLE and
// the pixel local storage plane at that same index is memoryless.
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Load Operation GL_LOAD_OP_LOAD_ANGLE is invalid for memoryless planes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
// Check that glBeginPixelLocalStorageANGLE validates the pixel local storage planes as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_planes)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
// INVALID_OPERATION is generated if all enabled, texture-backed pixel local storage planes do
// not have the same width and height.
{
PLSTestTexture pls1(GL_RGBA8, 100, 100);
GLTexture pls2;
glBindTexture(GL_TEXTURE_2D, pls2);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 100, 101);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(2, pls2, 0, 0);
// Disabling the mismatched size plane is fine.
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_NO_ERROR();
// Enabling the mismatched size plane errors.
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Mismatched pixel local storage backing texture sizes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Deleting a texture deinitializes the plane.
pls2.reset();
PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to enable a pixel local storage plane that is in a deinitialized state.");
// Converting the mismatched size plane to memoryless also works.
glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(
3, GLenumArray(
{GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_NO_ERROR();
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
{
// pls1 going out of scope deinitialized the PLS plane.
PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
glBeginPixelLocalStorageANGLE(
2, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Attempted to enable a pixel local storage plane that is in a deinitialized state.");
}
// Convert to memoryless.
glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8);
// INVALID_OPERATION is generated if the draw framebuffer has other attachments, and its
// enabled, texture-backed pixel local storage planes do not have identical dimensions with the
// rendering area.
{
PLSTestTexture rt0(GL_RGBA8, 200, 100);
PLSTestTexture rt1(GL_RGBA8, 100, 200);
// rt0 is wider than the PLS extents.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt0, 0);
glBeginPixelLocalStorageANGLE(2,
GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Pixel local storage backing texture dimensions not equal to the rendering area.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// rt1 is taller than the PLS extents.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt1, 0);
glBeginPixelLocalStorageANGLE(2,
GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Pixel local storage backing texture dimensions not equal to the rendering area.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// The intersection of rt0 and rt1 is equal to the PLS extents.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, rt0, 0);
glBeginPixelLocalStorageANGLE(2,
GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE}));
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
// INVALID_OPERATION is generated if the draw framebuffer has no attachments and no enabled,
// texture-backed pixel local storage planes.
{
for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
glBeginPixelLocalStorageANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES,
std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_LOAD_OP_ZERO_ANGLE).data());
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Draw framebuffer has no attachments and no enabled, texture-backed pixel local "
"storage planes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Draw framebuffer has no attachments and no enabled, texture-backed pixel local "
"storage planes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
}
// Check that glBeginPixelLocalStorageANGLE validates GL_TEXTURE_BASE_LEVEL and
// GL_TEXTURE_MAX_LEVEL.
//
// INVALID_OPERATION is generated if, for any active backing texture, the mipmap level bound to
// pixel local storage is outside the effective base/max range of that texture.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_base_max_level)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
ASSERT_GL_NO_ERROR();
PLSTestTexture pls0(GL_RGBA8, 256, 256, 9);
PLSTestTexture pls1(GL_RGBA8, 256, 256, 9);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 2, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 2, 0);
GLenum dontCare[2] = {GL_DONT_CARE, GL_DONT_CARE};
glBeginPixelLocalStorageANGLE(2, dontCare);
glEndPixelLocalStorageANGLE(2, dontCare);
ASSERT_GL_NO_ERROR();
// GL_TEXTURE_BASE_LEVEL == GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: pass.
glBindTexture(GL_TEXTURE_2D, pls0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
glBeginPixelLocalStorageANGLE(2, dontCare);
glEndPixelLocalStorageANGLE(2, dontCare);
ASSERT_GL_NO_ERROR();
// GL_TEXTURE_BASE_LEVEL > GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: fail.
glBindTexture(GL_TEXTURE_2D, pls1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3);
glBeginPixelLocalStorageANGLE(2, dontCare);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Mipmap level for PLS backing texture outside the effective base/max range.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// GL_TEXTURE_BASE_LEVEL > GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, but plane not enabled: pass.
glBindTexture(GL_TEXTURE_2D, pls1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3);
glBeginPixelLocalStorageANGLE(1, dontCare);
glEndPixelLocalStorageANGLE(1, dontCare);
ASSERT_GL_NO_ERROR();
// GL_TEXTURE_BASE_LEVEL == GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: pass again.
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 3, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 3, 0);
glBeginPixelLocalStorageANGLE(2, dontCare);
glEndPixelLocalStorageANGLE(2, dontCare);
ASSERT_GL_NO_ERROR();
// GL_TEXTURE_MAX_LEVEL == GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: pass.
glBindTexture(GL_TEXTURE_2D, pls1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 3);
glBeginPixelLocalStorageANGLE(2, dontCare);
glEndPixelLocalStorageANGLE(2, dontCare);
ASSERT_GL_NO_ERROR();
// GL_TEXTURE_MAX_LEVEL < GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: fail.
glBindTexture(GL_TEXTURE_2D, pls0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
glBeginPixelLocalStorageANGLE(2, dontCare);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Mipmap level for PLS backing texture outside the effective base/max range.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}
// Check that glBeginPixelLocalStorageANGLE validates feedback loops with GL_TEXTURE_2D as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one pixel local
// storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_tex2d)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3}));
ASSERT_GL_NO_ERROR();
// Check for simple tex2D collisions.
PLSTestTexture pls0(GL_RGBA8, W, H);
PLSTestTexture pls1(GL_RGBA8, W, H);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(2, pls0, 0, 0);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is bound to multiple active pixel local storage planes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Now attach it to the framebuffer and check for a collision there.
PLSTestTexture color(GL_RGBA8, W, H);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls0, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is simultaneously bound to an active pixel local storage plane and "
"attached to an enabled drawbuffer.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Turning off the draw buffer removes the collision.
glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_NONE, GL_COLOR_ATTACHMENT2, GL_NONE}));
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3}));
// Double-binding the other plane also causes a collision.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls1, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is simultaneously bound to an active pixel local storage plane and "
"attached to an enabled drawbuffer.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// A PLS collision is fine as long as only one of the planes is active.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
}
// Check that glBeginPixelLocalStorageANGLE validates feedback loops with GL_TEXTURE_2D_ARRAY as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one active pixel
// local storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_tex2darray)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3}));
ASSERT_GL_NO_ERROR();
// Attach different array indices of the same texture to PLS.
GLTexture pls2darray;
glBindTexture(GL_TEXTURE_2D_ARRAY, pls2darray);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, W, H, 4);
glFramebufferTexturePixelLocalStorageANGLE(0, pls2darray, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls2darray, 0, 1);
glFramebufferTexturePixelLocalStorageANGLE(2, pls2darray, 0, 2);
glFramebufferTexturePixelLocalStorageANGLE(3, pls2darray, 0, 3);
glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_NO_ERROR();
// Now attach the same array index twice.
glFramebufferTexturePixelLocalStorageANGLE(2, pls2darray, 0, 3);
glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is bound to multiple active pixel local storage planes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Now attach the same array index to the framebuffer and PLS.
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, pls2darray, 0, 3);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is simultaneously bound to an active pixel local storage plane and "
"attached to an enabled drawbuffer.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Attach the sidelined layer to the framebuffer and use fewer PLS planes so there is no
// longer a collision.
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, pls2darray, 0, 2);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
}
// Check that glBeginPixelLocalStorageANGLE validates feedback loops with mipmap levels as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one active pixel
// local storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_mipmap)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3}));
ASSERT_GL_NO_ERROR();
// Check collisions with mipmaps.
GLTexture pls8;
glBindTexture(GL_TEXTURE_2D, pls8);
glTexStorage2D(GL_TEXTURE_2D, 4, GL_RGBA8, 8, 8);
GLTexture pls16;
glBindTexture(GL_TEXTURE_2D, pls16);
glTexStorage2D(GL_TEXTURE_2D, 5, GL_RGBA8, 16, 16);
ASSERT_GL_NO_ERROR();
glFramebufferTexturePixelLocalStorageANGLE(0, pls8, 2, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls16, 3, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_NO_ERROR();
// Can't have the same mipmap level attached to different PLS planes.
glFramebufferTexturePixelLocalStorageANGLE(2, pls16, 3, 0);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is bound to multiple active pixel local storage planes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Can't attach different levels to PLS because they're different sizes.
glFramebufferTexturePixelLocalStorageANGLE(2, pls16, 2, 0);
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Mismatched pixel local storage backing texture sizes.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Can't have the same mipmap level attached to PLS and the framebuffer.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls16, 3);
glBeginPixelLocalStorageANGLE(
2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"A single texture slice is simultaneously bound to an active pixel local storage plane and "
"attached to an enabled drawbuffer.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Can't attach different levels to the framebuffer because they're different sizes.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls16, 2);
glBeginPixelLocalStorageANGLE(
2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Pixel local storage backing texture dimensions not equal to the rendering area.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
// Removing the attachment causes PLS to pass again.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
ASSERT_GL_NO_ERROR();
}
// Check that glEndPixelLocalStorageANGLE and glPixelLocalStorageBarrierANGLE validate as specified.
TEST_P(PixelLocalStorageValidationTest, EndAndBarrierANGLE)
{
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// in an interrupted state.
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferPixelLocalStorageInterruptANGLE();
glEndPixelLocalStorageANGLE(0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalStorageRestoreANGLE();
ASSERT_GL_NO_ERROR();
// INVALID_OPERATION is generated if PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE is zero.
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glEndPixelLocalStorageANGLE(0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glPixelLocalStorageBarrierANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glEndPixelLocalStorageANGLE(1, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
glPixelLocalStorageBarrierANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
glBeginPixelLocalStorageANGLE(0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Planes must be greater than 0.");
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
glPixelLocalStorageBarrierANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
glEndPixelLocalStorageANGLE(2, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
PLSTestTexture tex(GL_RGBA8);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
for (int i = 1; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
glBeginPixelLocalStorageANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES,
std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_LOAD_OP_ZERO_ANGLE).data());
EXPECT_GL_NO_ERROR();
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, MAX_PIXEL_LOCAL_STORAGE_PLANES);
glPixelLocalStorageBarrierANGLE();
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES,
std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_DONT_CARE).data());
EXPECT_GL_NO_ERROR();
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
EXPECT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
glPixelLocalStorageBarrierANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
ASSERT_GL_NO_ERROR();
// INVALID_VALUE is generated if <n> != PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE.
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
ASSERT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(3, GLenumArray({GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<n> != ACTIVE_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<n> != ACTIVE_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
ASSERT_GL_NO_ERROR();
// INVALID_ENUM is generated if <storeops>[0..PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE-1] is not
// one of the Store Operations enumerated in Table X.2.
glBeginPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_DONT_CARE, GL_LOAD_OP_CLEAR_ANGLE}));
ASSERT_GL_NO_ERROR();
glEndPixelLocalStorageANGLE(
3, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x96E4.");
glEndPixelLocalStorageANGLE(
3, GLenumArray({GL_DONT_CARE, GL_LOAD_OP_LOAD_ANGLE, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x96E5.");
glEndPixelLocalStorageANGLE(
3, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x96E3.");
glEndPixelLocalStorageANGLE(
3, GLenumArray({GL_DONT_CARE, GL_SCISSOR_BOX, GL_STORE_OP_STORE_ANGLE}));
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x0C10.");
ASSERT_GL_NO_ERROR();
}
// Check that FramebufferPixelLocalStorageInterruptANGLE validates as specified.
TEST_P(PixelLocalStorageValidationTest, InterruptMechanism)
{
// These commands are ignored when the default framebuffer object name 0 is bound.
glFramebufferPixelLocalStorageRestoreANGLE();
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferPixelLocalStorageRestoreANGLE();
glFramebufferPixelLocalStorageRestoreANGLE();
glFramebufferPixelLocalStorageRestoreANGLE();
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_GL_NO_ERROR();
for (size_t i = 0; i < 256; ++i)
{
glFramebufferPixelLocalStorageInterruptANGLE();
}
EXPECT_GL_NO_ERROR();
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// not in an interrupted state.
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is not interrupted.");
for (size_t i = 0; i < 255; ++i)
{
glFramebufferPixelLocalStorageInterruptANGLE();
}
EXPECT_GL_NO_ERROR();
// INVALID_FRAMEBUFFER_OPERATION is generated if the current interrupt count on the draw
// framebuffer is greater than or equal to 255.
glFramebufferPixelLocalStorageInterruptANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Pixel local storage does not support more than 255 nested interruptions.");
for (size_t i = 0; i < 255; ++i)
{
glFramebufferPixelLocalStorageRestoreANGLE();
}
EXPECT_GL_NO_ERROR();
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is not interrupted.");
glFramebufferPixelLocalStorageInterruptANGLE();
glFramebufferPixelLocalStorageInterruptANGLE();
ASSERT_GL_NO_ERROR();
glFramebufferPixelLocalStorageRestoreANGLE();
glFramebufferPixelLocalStorageRestoreANGLE();
ASSERT_GL_NO_ERROR();
glFramebufferPixelLocalStorageRestoreANGLE();
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is not interrupted.");
}
// Check that glGetFramebufferPixelLocalStorageParameter(f|i|ui)(Robust)?ANGLE validate as
// specified.
TEST_P(PixelLocalStorageValidationTest, GetFramebufferPixelLocalStorageParametersANGLE)
{
// The "Get.*Robust" variants require ANGLE_robust_client_memory. ANGLE_robust_client_memory is
// not disableable and is therefore supported in every ANGLE context.
//
// If ANGLE ever does find itself in a situation where ANGLE_robust_client_memory is not
// supported, we will need to hide the "Get.*Robust" variants in order to be spec compliant.
EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_robust_client_memory"));
GLfloat floats[5];
GLint ints[5];
GLsizei length;
// INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
// to DRAW_FRAMEBUFFER.
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, 4,
&length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
glGetFramebufferPixelLocalStorageParameterivANGLE(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG(
"Default framebuffer object name 0 does not support pixel local storage.");
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
// INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
// in an interrupted state.
glFramebufferPixelLocalStorageInterruptANGLE();
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, 4,
&length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glGetFramebufferPixelLocalStorageParameterivANGLE(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
glFramebufferPixelLocalStorageRestoreANGLE();
ASSERT_GL_NO_ERROR();
// INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
-1, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(-1, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(-1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE,
4, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glGetFramebufferPixelLocalStorageParameterivANGLE(-1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
glGetFramebufferPixelLocalStorageParameterivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES,
GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
// INVALID_ENUM is generated if the command issued is not the associated "Get Command" for
// <pname> in Table 6.Y.
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96EB is currently not supported.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96EB is currently not supported.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, 4, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x8CD0 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x8CD0 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, 4,
&length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96E7 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(1, GL_PIXEL_LOCAL_FORMAT_ANGLE, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96E7 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 1,
&length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96E8 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96E8 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
1, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96E9 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96E9 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
1, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96EA is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96EA is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(1, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
1, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96EC is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(1, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96EC is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
1, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 1, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96ED is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(
1, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x96ED is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
1, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, 1, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x8CD3 is currently not supported.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(
1, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
EXPECT_GL_SINGLE_ERROR_MSG("Enum 0x8CD3 is currently not supported.");
// INVALID_OPERATION is generated if <bufSize> is not large enough to receive the requested
// parameter.
//
// ... When an error is generated, nothing is written to <length>.
constexpr GLsizei kLengthInitValue = 0xbaadc0de;
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, 0,
&length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0,
&length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
0, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
0, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
3, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 3, &length, ints);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 3, &length, floats);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("More parameters are required than were provided.");
EXPECT_EQ(length, kLengthInitValue);
// Not quite pure validation, but also ensure that <length> gets written properly.
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, 5,
&length, ints);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 1);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 5,
&length, ints);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 1);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
5, &length, ints);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 1);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
5, &length, ints);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 1);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
5, &length, ints);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 4);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 5, &length, ints);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 4);
length = kLengthInitValue;
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 5, &length, floats);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(length, 4);
// INVALID_VALUE is generated if <params> is NULL.
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, 5,
&length, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_FORMAT_ANGLE, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 5,
&length, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE,
nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
5, &length, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
5, &length, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
5, &length, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 5, &length, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
EXPECT_GL_SINGLE_ERROR_MSG("<params> cannot be null.");
}
// Check command-specific errors that go into effect when PLS is active, as well as commands
// specifically called out by EXT_shader_pixel_local_storage for flushing tiled memory.
TEST_P(PixelLocalStorageValidationTest, BannedCommands)
{
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
for (int i = 1; i < numActivePlanes; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
glBeginPixelLocalStorageANGLE(
numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
ASSERT_GL_NO_ERROR();
// Check commands called out by EXT_shader_pixel_local_storage for flushing tiled memory.
EXPECT_BANNED_DEFAULT_MSG(glFlush());
EXPECT_BANNED_DEFAULT_MSG(glFinish());
EXPECT_BANNED_DEFAULT_MSG(glClientWaitSync(nullptr, 0, 0));
// INVALID_OPERATION is generated by Enable(), Disable() if <cap> is not one of: CULL_FACE,
// DEPTH_CLAMP_EXT, DEPTH_TEST, POLYGON_OFFSET_POINT_NV, POLYGON_OFFSET_LINE_NV,
// POLYGON_OFFSET_LINE_ANGLE, POLYGON_OFFSET_FILL, PRIMITIVE_RESTART_FIXED_INDEX,
// SCISSOR_TEST, STENCIL_TEST, CLIP_DISTANCE[0..7]_EXT
EXPECT_ALLOWED_CAP(GL_CULL_FACE);
if (EnsureGLExtensionEnabled("GL_KHR_debug"))
{
EXPECT_ALLOWED_CAP(GL_DEBUG_OUTPUT);
EXPECT_ALLOWED_CAP(GL_DEBUG_OUTPUT_SYNCHRONOUS);
// Now turn them back on since the test actually uses these caps...
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
EXPECT_ALLOWED_CAP(GL_DEPTH_TEST);
if (EnsureGLExtensionEnabled("GL_EXT_depth_clamp"))
{
EXPECT_ALLOWED_CAP(GL_DEPTH_CLAMP_EXT);
}
if (EnsureGLExtensionEnabled("GL_ANGLE_polygon_mode"))
{
EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_LINE_ANGLE);
glPolygonModeANGLE(GL_FRONT_AND_BACK, GL_FILL_ANGLE);
EXPECT_GL_NO_ERROR();
}
if (EnsureGLExtensionEnabled("GL_NV_polygon_mode"))
{
EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_POINT_NV);
EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_LINE_NV);
glPolygonModeNV(GL_FRONT_AND_BACK, GL_FILL_NV);
EXPECT_GL_NO_ERROR();
}
if (EnsureGLExtensionEnabled("GL_EXT_polygon_offset_clamp"))
{
glPolygonOffsetClampEXT(0.0f, 0.0f, 0.0f);
EXPECT_GL_NO_ERROR();
}
EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_FILL);
EXPECT_ALLOWED_CAP(GL_PRIMITIVE_RESTART_FIXED_INDEX);
EXPECT_ALLOWED_CAP(GL_RASTERIZER_DISCARD);
EXPECT_ALLOWED_CAP(GL_SCISSOR_TEST);
EXPECT_ALLOWED_CAP(GL_STENCIL_TEST);
if (EnsureGLExtensionEnabled("GL_EXT_clip_cull_distance"))
{
for (GLenum i = 0; i < 8; ++i)
{
EXPECT_ALLOWED_CAP(GL_CLIP_DISTANCE0_EXT + i);
}
}
EXPECT_BANNED_CAP(GL_SAMPLE_ALPHA_TO_COVERAGE);
EXPECT_BANNED_CAP(GL_SAMPLE_COVERAGE);
EXPECT_BANNED_CAP(GL_DITHER);
if (isContextVersionAtLeast(3, 1))
{
EXPECT_BANNED_CAP(GL_SAMPLE_MASK);
}
if (EnsureGLExtensionEnabled("GL_EXT_multisample_compatibility"))
{
EXPECT_BANNED_CAP(GL_MULTISAMPLE_EXT);
EXPECT_BANNED_CAP(GL_SAMPLE_ALPHA_TO_ONE_EXT);
}
if (EnsureGLExtensionEnabled("GL_OES_sample_shading"))
{
EXPECT_BANNED_CAP(GL_SAMPLE_SHADING);
}
if (EnsureGLExtensionEnabled("GL_QCOM_shading_rate"))
{
EXPECT_BANNED_CAP(GL_SHADING_RATE_PRESERVE_ASPECT_RATIO_QCOM);
}
if (EnsureGLExtensionEnabled("GL_ANGLE_logic_op"))
{
EXPECT_BANNED_CAP(GL_COLOR_LOGIC_OP);
}
// BLEND is the only indexed capability in ANGLE, but we can at least use SHADING_RATE_IMAGE_NV
// to check the debug output and verify that PLS validation is banning it, which ensures it is
// future proof against hypothetical future extensions that add indexed capabilities.
EXPECT_BANNED_CAP_INDEXED(GL_SHADING_RATE_IMAGE_NV);
// The validation tests are expected to run in a configuration where the draw buffers are
// restricted by PLS (e.g., not shader images).
int bannedCombinedAttachment =
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numActivePlanes;
ASSERT(bannedCombinedAttachment < MAX_DRAW_BUFFERS);
// glClearBuffer{f,i,ui}v are ignored for all attachments at or beyond
// MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numActivePlanes.
GLfloat clearf[4]{};
GLint cleari[4]{};
GLuint clearui[4]{};
glClearBufferfv(GL_COLOR, MAX_DRAW_BUFFERS - 1, clearf);
glClearBufferiv(GL_COLOR, MAX_DRAW_BUFFERS - 1, cleari);
glClearBufferuiv(GL_COLOR, MAX_DRAW_BUFFERS - 1, clearui);
EXPECT_GL_NO_ERROR();
glClearBufferfv(GL_COLOR, bannedCombinedAttachment, clearf);
glClearBufferiv(GL_COLOR, bannedCombinedAttachment, cleari);
glClearBufferuiv(GL_COLOR, bannedCombinedAttachment, clearui);
EXPECT_GL_NO_ERROR();
glClearBufferfv(GL_DEPTH, 0, clearf);
EXPECT_GL_NO_ERROR();
glClearBufferiv(GL_STENCIL, 0, cleari);
EXPECT_GL_NO_ERROR();
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
// Enabling and disabling GL_BLEND is valid while PLS is active.
glEnableiOES(GL_BLEND, MAX_DRAW_BUFFERS - 1);
glDisableiOES(GL_BLEND, MAX_DRAW_BUFFERS - 1);
EXPECT_GL_NO_ERROR();
glEnableiOES(GL_BLEND, bannedCombinedAttachment);
glDisableiOES(GL_BLEND, bannedCombinedAttachment);
EXPECT_GL_NO_ERROR();
}
// INVALID_OPERATION is generated if a draw is issued with a fragment shader that accesses a
// texture bound to pixel local storage.
//
// TODO(anglebug.com/40096838).
ASSERT_GL_NO_ERROR();
}
// Check that glBlendFunc and glBlendEquation updates are valid while PLS is active, and still take
// effect after PLS is ended.
TEST_P(PixelLocalStorageValidationTest, BlendFuncDuringPLS)
{
const GLint drawBufferIndexCount =
IsGLExtensionEnabled("GL_OES_draw_buffers_indexed") ? MAX_DRAW_BUFFERS : 1;
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
for (int i = 1; i < numActivePlanes; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
glBeginPixelLocalStorageANGLE(
numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
ASSERT_GL_NO_ERROR();
for (GLint i = 0; i < drawBufferIndexCount; ++i)
{
if (drawBufferIndexCount > 1)
{
glBlendEquationiOES(i, GL_FUNC_SUBTRACT);
glBlendFunciOES(i, GL_ONE, GL_CONSTANT_COLOR);
}
else
{
glBlendEquation(GL_FUNC_SUBTRACT);
glBlendFunc(GL_ONE, GL_CONSTANT_COLOR);
}
ASSERT_GL_NO_ERROR();
}
glEndPixelLocalStorageANGLE(numActivePlanes,
std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
ASSERT_GL_NO_ERROR();
for (GLint i = 0; i < drawBufferIndexCount; ++i)
{
GLint blendEquationRGB, blendEquationAlpha;
if (drawBufferIndexCount > 1)
{
glGetIntegeri_v(GL_BLEND_EQUATION_RGB, i, &blendEquationRGB);
glGetIntegeri_v(GL_BLEND_EQUATION_ALPHA, i, &blendEquationAlpha);
}
else
{
glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB);
glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha);
}
EXPECT_EQ(blendEquationRGB, GL_FUNC_SUBTRACT);
EXPECT_EQ(blendEquationAlpha, GL_FUNC_SUBTRACT);
GLint blendSrcRGB, blendSrcAlpha, blendDstRGB, blendDstAlpha;
if (drawBufferIndexCount > 1)
{
glGetIntegeri_v(GL_BLEND_SRC_RGB, i, &blendSrcRGB);
glGetIntegeri_v(GL_BLEND_SRC_ALPHA, i, &blendSrcAlpha);
glGetIntegeri_v(GL_BLEND_DST_RGB, i, &blendDstRGB);
glGetIntegeri_v(GL_BLEND_DST_ALPHA, i, &blendDstAlpha);
}
else
{
glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB);
glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha);
glGetIntegerv(GL_BLEND_DST_RGB, &blendDstRGB);
glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDstAlpha);
}
EXPECT_EQ(blendSrcRGB, GL_ONE);
EXPECT_EQ(blendSrcAlpha, GL_ONE);
EXPECT_EQ(blendDstRGB, GL_CONSTANT_COLOR);
EXPECT_EQ(blendDstAlpha, GL_CONSTANT_COLOR);
}
glBeginPixelLocalStorageANGLE(
numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
ASSERT_GL_NO_ERROR();
for (GLint i = 0; i < drawBufferIndexCount; ++i)
{
if (drawBufferIndexCount > 1)
{
glBlendEquationSeparateiOES(i, GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD);
glBlendFuncSeparateiOES(i, GL_ZERO, GL_DST_COLOR, GL_DST_ALPHA, GL_SRC_COLOR);
}
else
{
glBlendEquationSeparate(GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD);
glBlendFuncSeparate(GL_ZERO, GL_DST_COLOR, GL_DST_ALPHA, GL_SRC_COLOR);
}
ASSERT_GL_NO_ERROR();
}
glEndPixelLocalStorageANGLE(numActivePlanes,
std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
ASSERT_GL_NO_ERROR();
for (GLint i = 0; i < drawBufferIndexCount; ++i)
{
GLint blendEquationRGB, blendEquationAlpha;
if (drawBufferIndexCount > 1)
{
glGetIntegeri_v(GL_BLEND_EQUATION_RGB, i, &blendEquationRGB);
glGetIntegeri_v(GL_BLEND_EQUATION_ALPHA, i, &blendEquationAlpha);
}
else
{
glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB);
glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha);
}
EXPECT_EQ(blendEquationRGB, GL_FUNC_REVERSE_SUBTRACT);
EXPECT_EQ(blendEquationAlpha, GL_FUNC_ADD);
GLint blendSrcRGB, blendDstRGB, blendSrcAlpha, blendDstAlpha;
if (drawBufferIndexCount > 1)
{
glGetIntegeri_v(GL_BLEND_SRC_RGB, i, &blendSrcRGB);
glGetIntegeri_v(GL_BLEND_SRC_ALPHA, i, &blendSrcAlpha);
glGetIntegeri_v(GL_BLEND_DST_RGB, i, &blendDstRGB);
glGetIntegeri_v(GL_BLEND_DST_ALPHA, i, &blendDstAlpha);
}
else
{
glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB);
glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha);
glGetIntegerv(GL_BLEND_DST_RGB, &blendDstRGB);
glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDstAlpha);
}
EXPECT_EQ(blendSrcRGB, GL_ZERO);
EXPECT_EQ(blendDstRGB, GL_DST_COLOR);
EXPECT_EQ(blendSrcAlpha, GL_DST_ALPHA);
EXPECT_EQ(blendDstAlpha, GL_SRC_COLOR);
}
}
// Check that glGetFramebufferAttachmentParameteriv() generates errors when
// querying overridden PLS attachments.
TEST_P(PixelLocalStorageValidationTest, FramebufferQueries)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture pls0(GL_RGBA8);
PLSTestTexture pls1(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
int firstOverriddenDrawBuffer = MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2;
// The validation tests are expected to run in a configuration where the draw buffers are
// restricted by PLS (e.g., not shader images).
ASSERT(firstOverriddenDrawBuffer < MAX_DRAW_BUFFERS);
GLint intValue;
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + firstOverriddenDrawBuffer,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");
glGetFramebufferAttachmentParameteriv(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");
glGetFramebufferAttachmentParameteriv(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + MAX_COLOR_ATTACHMENTS - 1,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");
if (firstOverriddenDrawBuffer != 0)
{
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_NO_ERROR();
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + firstOverriddenDrawBuffer - 1,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_NO_ERROR();
}
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + firstOverriddenDrawBuffer,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_NO_ERROR();
glGetFramebufferAttachmentParameteriv(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_NO_ERROR();
glGetFramebufferAttachmentParameteriv(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_NO_ERROR();
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + MAX_COLOR_ATTACHMENTS - 1,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
EXPECT_GL_NO_ERROR();
}
// Check that glEnable(GL_BLEND) and glColorMask() do not generate errors, and are ignored for
// overridden draw buffers during PLS.
TEST_P(PixelLocalStorageValidationTest, BlendMaskDuringPLS)
{
// Make sure we're given a context where this test is relevant.
ASSERT(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES <= MAX_DRAW_BUFFERS);
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
for (int i = 1; i < numActivePlanes; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
auto beginPLS = [=]() {
glBeginPixelLocalStorageANGLE(
numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
};
auto endPLS = [=]() {
glEndPixelLocalStorageANGLE(numActivePlanes,
std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
};
#define EXPECT_UNIFORM_BLEND_MASK(ENABLED) \
EXPECT_EQ(glIsEnabled(GL_BLEND), ENABLED); \
EXPECT_GL_INTEGER(GL_BLEND, ENABLED); \
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed")) \
{ \
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i) \
{ \
EXPECT_EQ(glIsEnabledi(GL_BLEND, i), ENABLED); \
} \
}
#define EXPECT_UNIFORM_COLOR_MASK(R, G, B, A) \
EXPECT_GL_COLOR_MASK(R, G, B, A); \
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed")) \
{ \
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i) \
{ \
EXPECT_GL_COLOR_MASK_INDEXED(i, R, G, B, A); \
} \
}
// Set before.
glEnable(GL_BLEND);
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
beginPLS();
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
EXPECT_GL_NO_ERROR();
EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
endPLS();
// Blend & color mask shouldn't change after endPLS().
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
glDisable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
EXPECT_GL_NO_ERROR();
// Set during.
beginPLS();
EXPECT_UNIFORM_BLEND_MASK(GL_FALSE);
EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
EXPECT_GL_NO_ERROR();
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
endPLS();
// Blend & color mask shouldn't change after endPLS().
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
glDisable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
EXPECT_GL_NO_ERROR();
if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
{
// Indexed before, non-indexed during.
glEnablei(GL_BLEND, 0);
glEnablei(GL_BLEND, MAX_DRAW_BUFFERS - 1);
glColorMaski(0, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
glColorMaski(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
beginPLS();
EXPECT_TRUE(glIsEnabledi(GL_BLEND, 0));
for (GLint i = 1; i < MAX_DRAW_BUFFERS - 1; ++i)
{
EXPECT_FALSE(glIsEnabledi(GL_BLEND, i));
}
EXPECT_TRUE(glIsEnabledi(GL_BLEND, MAX_DRAW_BUFFERS - 1));
EXPECT_GL_COLOR_MASK_INDEXED(0, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
for (GLint i = 1; i < MAX_DRAW_BUFFERS - 1; ++i)
{
EXPECT_GL_COLOR_MASK_INDEXED(i, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
EXPECT_GL_COLOR_MASK_INDEXED(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
glEnable(GL_BLEND);
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_UNIFORM_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
endPLS();
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
EXPECT_UNIFORM_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
glDisable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
EXPECT_GL_NO_ERROR();
// Non-indexed before, indexed during.
glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
glEnable(GL_BLEND);
beginPLS();
EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
glDisablei(GL_BLEND, 1);
glDisablei(GL_BLEND, MAX_DRAW_BUFFERS - 1);
EXPECT_TRUE(glIsEnabled(GL_BLEND));
EXPECT_TRUE(glIsEnabledi(GL_BLEND, 0));
EXPECT_FALSE(glIsEnabledi(GL_BLEND, 1));
for (GLint i = 2; i < MAX_DRAW_BUFFERS - 1; ++i)
{
EXPECT_TRUE(glIsEnabledi(GL_BLEND, i));
}
EXPECT_FALSE(glIsEnabledi(GL_BLEND, MAX_DRAW_BUFFERS - 1));
glColorMaski(2, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
glColorMaski(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
EXPECT_GL_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_COLOR_MASK_INDEXED(0, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_COLOR_MASK_INDEXED(2, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
for (GLint i = 3; i < MAX_DRAW_BUFFERS - 1; ++i)
{
EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
}
EXPECT_GL_COLOR_MASK_INDEXED(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
endPLS();
EXPECT_TRUE(glIsEnabled(GL_BLEND));
EXPECT_TRUE(glIsEnabledi(GL_BLEND, 0));
EXPECT_FALSE(glIsEnabledi(GL_BLEND, 1));
for (GLint i = 2; i < MAX_DRAW_BUFFERS - 1; ++i)
{
EXPECT_TRUE(glIsEnabledi(GL_BLEND, i));
}
EXPECT_FALSE(glIsEnabledi(GL_BLEND, MAX_DRAW_BUFFERS - 1));
EXPECT_GL_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_COLOR_MASK_INDEXED(0, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
EXPECT_GL_COLOR_MASK_INDEXED(2, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
for (GLint i = 3; i < MAX_DRAW_BUFFERS - 1; ++i)
{
EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
}
EXPECT_GL_COLOR_MASK_INDEXED(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDisable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
EXPECT_GL_NO_ERROR();
}
EXPECT_GL_NO_ERROR();
#undef EXPECT_NON_OVERRIDDEN_COLOR_MASK
#undef EXPECT_UNIFORM_BLEND_MASK
}
// Check that modifications to PLS textures bounce while it's active.
TEST_P(PixelLocalStorageValidationTest, ModifyTextureDuringPLS)
{
GLTexture pls2d;
glBindTexture(GL_TEXTURE_2D, pls2d);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W, H);
GLTexture nonpls2d;
glBindTexture(GL_TEXTURE_2D, nonpls2d);
glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W, H);
GLTexture pls2darray;
glBindTexture(GL_TEXTURE_2D_ARRAY, pls2darray);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, W, H, 5);
GLTexture nonpls2darray;
glBindTexture(GL_TEXTURE_2D_ARRAY, nonpls2darray);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, W, H, 5);
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, pls2d, 1, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls2darray, 1, 4);
glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
ASSERT_GL_NO_ERROR();
#define CHECK_TEXTURE_2D_MODIFICATION(FN) \
glBindTexture(GL_TEXTURE_2D, pls2d); \
FN; \
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION); \
EXPECT_GL_SINGLE_ERROR_MSG( \
"Operation not permitted on an active pixel local storage backing texture.") \
glBindTexture(GL_TEXTURE_2D, nonpls2d); \
FN; \
EXPECT_GL_NO_ERROR();
#define CHECK_TEXTURE_2D_ARRAY_MODIFICATION(FN) \
glBindTexture(GL_TEXTURE_2D_ARRAY, pls2darray); \
FN; \
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION); \
EXPECT_GL_SINGLE_ERROR_MSG( \
"Operation not permitted on an active pixel local storage backing texture.") \
glBindTexture(GL_TEXTURE_2D_ARRAY, nonpls2darray); \
FN; \
EXPECT_GL_NO_ERROR();
std::vector<uint8_t> imageData(H * W * 4);
CHECK_TEXTURE_2D_MODIFICATION(
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(glTexSubImage3D(
GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()));
CHECK_TEXTURE_2D_MODIFICATION(glGenerateMipmap(GL_TEXTURE_2D));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(glGenerateMipmap(GL_TEXTURE_2D_ARRAY));
if (EnsureGLExtensionEnabled("GL_ANGLE_robust_client_memory"))
{
CHECK_TEXTURE_2D_MODIFICATION(
glTexSubImage2DRobustANGLE(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
imageData.size(), imageData.data()));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexSubImage3DRobustANGLE(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, W, H, 1, GL_RGBA,
GL_UNSIGNED_BYTE, imageData.size(), imageData.data()));
}
if (IsGLExtensionEnabled("GL_OES_texture_3D"))
{
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(glTexSubImage3DOES(
GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()));
}
if (EnsureGLExtensionEnabled("GL_ANGLE_texture_external_update"))
{
CHECK_TEXTURE_2D_MODIFICATION(glInvalidateTextureANGLE(GL_TEXTURE_2D));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(glInvalidateTextureANGLE(GL_TEXTURE_2D_ARRAY));
}
GLfloat zerof[4] = {};
GLint zeroi[4] = {};
GLuint zeroui[4] = {};
CHECK_TEXTURE_2D_MODIFICATION(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
CHECK_TEXTURE_2D_MODIFICATION(glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, zeroi));
CHECK_TEXTURE_2D_MODIFICATION(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0.f));
CHECK_TEXTURE_2D_MODIFICATION(glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, zerof));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, zeroi));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LOD, 0.f));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LOD, zerof));
if (EnsureGLExtensionEnabled("GL_ANGLE_robust_client_memory"))
{
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterivRobustANGLE(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1, zeroi));
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterfvRobustANGLE(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 1, zerof));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterfvRobustANGLE(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LOD, 1, zerof));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterivRobustANGLE(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1, zeroi));
// TODO: Re-enable these tests once glTexParameterI(u?)ivRobustANGLE are implemented.
#if 0
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterIivRobustANGLE(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, 4, zeroi));
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterIuivRobustANGLE(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, 4, zeroui));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterIivRobustANGLE(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, 4, zeroi));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterIuivRobustANGLE(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, 4, zeroui));
#endif
}
if (EnsureGLExtensionEnabled("GL_EXT_texture_border_clamp"))
{
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterIivEXT(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroi));
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterIuivEXT(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroui));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterIivEXT(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroi));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterIuivEXT(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroui));
}
if (EnsureGLExtensionEnabled("GL_OES_texture_border_clamp"))
{
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroi));
CHECK_TEXTURE_2D_MODIFICATION(
glTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroui));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterIivOES(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroi));
CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
glTexParameterIuivOES(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroui));
}
#undef CHECK_TEXTURE_2D_MODIFICATION
#undef CHECK_TEXTURE_2D_ARRAY_MODIFICATION
glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
ASSERT_GL_NO_ERROR();
}
// Check that framebuffer invalidations bounce while PLS is active.
TEST_P(PixelLocalStorageValidationTest, InvalidateFramebufferDuringPLS)
{
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
PLSTestTexture pls0(GL_RGBA8, 100, 100);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
glInvalidateFramebuffer(GL_FRAMEBUFFER, 0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
glInvalidateSubFramebuffer(GL_FRAMEBUFFER, 0, nullptr, 0, 0, 10, 10);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
if (EnsureGLExtensionEnabled("GL_EXT_discard_framebuffer"))
{
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 0, nullptr);
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
}
glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
EXPECT_GL_NO_ERROR();
// Framebuffer invalidations become legal again after PLS is done.
glInvalidateFramebuffer(GL_FRAMEBUFFER, 0, nullptr);
EXPECT_GL_NO_ERROR();
glInvalidateSubFramebuffer(GL_FRAMEBUFFER, 0, nullptr, 0, 0, 10, 10);
EXPECT_GL_NO_ERROR();
if (EnsureGLExtensionEnabled("GL_EXT_discard_framebuffer"))
{
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 0, nullptr);
EXPECT_GL_NO_ERROR();
}
ASSERT_GL_NO_ERROR();
}
// Check that glClear with active PLS doesn't accidentally turn off draw buffers when there is no
// color attachment.
TEST_P(PixelLocalStorageValidationTest, ClearDuringPLSDoesntAffectDrawBuffers)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
PLSTestTexture pls0(GL_RGBA8);
PLSTestTexture pls1(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0);
constexpr static int NUM_PLANES = 2;
int maxColorAttachmentsWith2PLSPlanes =
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - NUM_PLANES;
// The validation tests are expected to run in a configuration where the draw buffers are
// restricted by PLS (e.g., not shader images).
ASSERT(maxColorAttachmentsWith2PLSPlanes < MAX_DRAW_BUFFERS);
int numColorAttachments = std::min(maxColorAttachmentsWith2PLSPlanes, 2);
PLSTestTexture colorAttachment0(GL_RGBA8);
PLSTestTexture colorAttachment1(GL_RGBA8);
for (int colorAttachmentMask = 0; colorAttachmentMask < (1 << numColorAttachments);
++colorAttachmentMask)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if (numColorAttachments >= 2)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
colorAttachment1, 0);
}
if (numColorAttachments >= 1)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
colorAttachment0, 0);
}
// Only enable draw buffers that are in colorAttachmentMask.
GLenum drawBuffers[2];
for (int i = 0; i < 2; ++i)
{
drawBuffers[i] = (colorAttachmentMask & (1 << i)) ? GL_COLOR_ATTACHMENT0 + i : GL_NONE;
}
glDrawBuffers(2, drawBuffers);
if (colorAttachmentMask != 0 && colorAttachmentMask != 3)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffers(2, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}));
if (numColorAttachments >= 2)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
(colorAttachmentMask & 2) ? colorAttachment1.id() : 0, 0);
}
if (numColorAttachments >= 1)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
(colorAttachmentMask & 1) ? colorAttachment0.id() : 0, 0);
}
glBeginPixelLocalStorageANGLE(
NUM_PLANES, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, NUM_PLANES);
EXPECT_GL_INTEGER(GL_DRAW_BUFFER1,
numColorAttachments >= 2 ? GL_COLOR_ATTACHMENT1 : GL_NONE);
EXPECT_GL_INTEGER(GL_DRAW_BUFFER0,
numColorAttachments >= 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE);
// Check that glClear doesn't turn off any draw buffers. We have to do this check now
// because glEndPixelLocalStorageANGLE restores them anyway.
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_INTEGER(GL_DRAW_BUFFER1,
numColorAttachments >= 2 ? GL_COLOR_ATTACHMENT1 : GL_NONE);
EXPECT_GL_INTEGER(GL_DRAW_BUFFER0,
numColorAttachments >= 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE);
glEndPixelLocalStorageANGLE(
NUM_PLANES, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
// Make sure the draw buffers stuck after disabling PLS.
EXPECT_GL_INTEGER(GL_DRAW_BUFFER1,
numColorAttachments >= 2 ? GL_COLOR_ATTACHMENT1 : GL_NONE);
EXPECT_GL_INTEGER(GL_DRAW_BUFFER0,
numColorAttachments >= 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE);
}
}
ASSERT_GL_NO_ERROR();
}
// Check that glReadPixels and glCopyTex*Image* end PLS before running.
TEST_P(PixelLocalStorageValidationTest, ReadPixels)
{
PLSTestTexture tex(GL_RGBA8);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
for (int i = 1; i < numActivePlanes; ++i)
{
glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8);
}
int firstOverriddenDrawBuffer =
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numActivePlanes;
// The validation tests are expected to run in a configuration where the draw buffers are
// restricted by PLS (e.g., not shader images).
ASSERT(firstOverriddenDrawBuffer < MAX_DRAW_BUFFERS);
std::vector<PLSTestTexture> colorAttachments;
std::vector<GLenum> drawBuffers;
for (int i = 0; i < firstOverriddenDrawBuffer; ++i)
{
colorAttachments.emplace_back(GL_RGBA8);
drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i);
glFramebufferTexture2D(GL_FRAMEBUFFER, drawBuffers.back(), GL_TEXTURE_2D,
colorAttachments.back(), 0);
}
for (int i = firstOverriddenDrawBuffer; i < MAX_DRAW_BUFFERS; ++i)
{
colorAttachments.emplace_back(GL_RGBA8);
}
glDrawBuffers(firstOverriddenDrawBuffer, drawBuffers.data());
GLTexture tex2d;
glBindTexture(GL_TEXTURE_2D, tex2d);
GLTexture tex3D;
glBindTexture(GL_TEXTURE_3D, tex3D);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, W, H, 5);
auto beginPLS = [=]() {
glBeginPixelLocalStorageANGLE(
numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
};
beginPLS();
std::vector<uint8_t> pixelData(H * W * 4);
// glReadPixels can't access the overridden PLS draw buffers.
for (int i = 0; i < firstOverriddenDrawBuffer; ++i)
{
glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage3DOES(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
}
for (int i = firstOverriddenDrawBuffer; i < MAX_DRAW_BUFFERS; ++i)
{
#define EXPECT_BLIT_FRAMEBUFFER_ERROR() \
if (firstOverriddenDrawBuffer == 0) \
{ \
EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION); \
EXPECT_GL_SINGLE_ERROR_MSG( \
"Framebuffer is incomplete: No attachments and default size is zero."); \
} \
else \
{ \
EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION); \
EXPECT_GL_SINGLE_ERROR_MSG("Missing read attachment."); \
}
// PLS gets disabled even if there was an error.
glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());
EXPECT_BLIT_FRAMEBUFFER_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
EXPECT_BLIT_FRAMEBUFFER_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H);
EXPECT_BLIT_FRAMEBUFFER_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_BLIT_FRAMEBUFFER_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage3DOES(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_BLIT_FRAMEBUFFER_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
#undef EXPECT_BLIT_FRAMEBUFFER_ERROR
}
// When a different READ_FRAMEBUFFER is bound, glReadPixels can access all the draw buffers.
GLFramebuffer readFBO;
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
{
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
colorAttachments[i], 0);
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
glCopyTexSubImage3DOES(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
EXPECT_GL_NO_ERROR();
EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
beginPLS();
}
glEndPixelLocalStorageANGLE(numActivePlanes,
std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
ASSERT_GL_NO_ERROR();
}
// Check that draw commands validate current PLS state against the shader's PLS uniforms.
TEST_P(PixelLocalStorageValidationTest, DrawCommandValidation)
{
DrawCommandValidationTest().run();
}
// Check that PLS gets properly cleaned up when its framebuffer and textures are never deleted.
TEST_P(PixelLocalStorageValidationTest, LeakFramebufferAndTexture)
{
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLuint tex0;
glGenTextures(1, &tex0);
glBindTexture(GL_TEXTURE_2D, tex0);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 10, 10);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
PLSTestTexture tex1(GL_R32F);
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I);
// Delete tex1.
// Don't delete tex0.
// Don't delete fbo.
// The PixelLocalStorage frontend implementation has internal assertions that verify all its GL
// context objects are properly disposed of.
}
// Check that PLS gets properly cleaned up when the context is lost.
TEST_P(PixelLocalStorageValidationTest, LoseContext)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_CHROMIUM_lose_context"));
GLuint fbo0;
glGenFramebuffers(1, &fbo0);
GLFramebuffer fbo1;
GLuint tex0;
glGenTextures(1, &tex0);
glBindTexture(GL_TEXTURE_2D, tex0);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 10, 10);
PLSTestTexture tex1(GL_R32F);
glBindFramebuffer(GL_FRAMEBUFFER, fbo0);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I);
glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0);
glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glLoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET, GL_INNOCENT_CONTEXT_RESET);
// Delete tex1.
// Don't delete tex0.
// Delete fbo1.
// Don't delete fbo0.
// The PixelLocalStorage frontend implementation has internal assertions that verify all its GL
// context objects are properly disposed of.
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageValidationTest);
ANGLE_INSTANTIATE_TEST(PixelLocalStorageValidationTest,
WithRobustness(ES31_NULL()).enable(Feature::EmulatePixelLocalStorage));
class PixelLocalStorageCompilerTest : public ANGLETest<>
{
public:
PixelLocalStorageCompilerTest() { setExtensionsEnabled(false); }
protected:
void testSetUp() override
{
ASSERT(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
// INVALID_OPERATION is generated if DITHER is enabled.
glDisable(GL_DITHER);
ANGLETest::testSetUp();
}
ShaderInfoLog log;
};
// Check that PLS #extension support is properly implemented.
TEST_P(PixelLocalStorageCompilerTest, Extension)
{
// GL_ANGLE_shader_pixel_local_storage_coherent isn't a shader extension. Shaders must always
// use GL_ANGLE_shader_pixel_local_storage, regardless of coherency.
constexpr char kNonexistentPLSCoherentExtension[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage_coherent : require
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kNonexistentPLSCoherentExtension));
EXPECT_TRUE(log.has(
"ERROR: 0:2: 'GL_ANGLE_shader_pixel_local_storage_coherent' : extension is not supported"));
// PLS type names cannot be used as variable names when the extension is enabled.
constexpr char kPLSEnabledTypesAsNames[] = R"(#version 310 es
#extension all : warn
void main()
{
int pixelLocalANGLE = 0;
int ipixelLocalANGLE = 0;
int upixelLocalANGLE = 0;
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSEnabledTypesAsNames));
EXPECT_TRUE(log.has("ERROR: 0:5: 'pixelLocalANGLE' : syntax error"));
// PLS type names are fair game when the extension is disabled.
constexpr char kPLSDisabledTypesAsNames[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : disable
void main()
{
int pixelLocalANGLE = 0;
int ipixelLocalANGLE = 0;
int upixelLocalANGLE = 0;
})";
EXPECT_TRUE(log.compileFragmentShader(kPLSDisabledTypesAsNames));
// PLS is not allowed in a vertex shader.
constexpr char kPLSInVertexShader[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : enable
layout(binding=0, rgba8) lowp uniform pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, vec4(0));
})";
EXPECT_FALSE(log.compileShader(kPLSInVertexShader, GL_VERTEX_SHADER));
EXPECT_TRUE(
log.has("ERROR: 0:3: 'pixelLocalANGLE' : undefined use of pixel local storage outside a "
"fragment shader"));
// Internal synchronization functions used by the compiler shouldn't be visible in ESSL.
EXPECT_FALSE(log.compileFragmentShader(R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void main()
{
beginInvocationInterlockNV();
endInvocationInterlockNV();
})"));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'beginInvocationInterlockNV' : no matching overloaded function found"));
EXPECT_TRUE(
log.has("ERROR: 0:6: 'endInvocationInterlockNV' : no matching overloaded function found"));
EXPECT_FALSE(log.compileFragmentShader(R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void main()
{
beginFragmentShaderOrderingINTEL();
})"));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'beginFragmentShaderOrderingINTEL' : no matching overloaded function found"));
EXPECT_FALSE(log.compileFragmentShader(R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void main()
{
beginInvocationInterlockARB();
endInvocationInterlockARB();
})"));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'beginInvocationInterlockARB' : no matching overloaded function found"));
EXPECT_TRUE(
log.has("ERROR: 0:6: 'endInvocationInterlockARB' : no matching overloaded function found"));
ASSERT_GL_NO_ERROR();
}
// Check proper validation of PLS handle declarations.
TEST_P(PixelLocalStorageCompilerTest, Declarations)
{
// PLS handles must be uniform.
constexpr char kPLSTypesMustBeUniform[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : enable
layout(binding=0, rgba8) highp pixelLocalANGLE pls1;
void main()
{
highp ipixelLocalANGLE pls2;
highp upixelLocalANGLE pls3;
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSTypesMustBeUniform));
EXPECT_TRUE(log.has("ERROR: 0:3: 'pixelLocalANGLE' : pixelLocalANGLEs must be uniform"));
EXPECT_TRUE(log.has("ERROR: 0:6: 'ipixelLocalANGLE' : ipixelLocalANGLEs must be uniform"));
EXPECT_TRUE(log.has("ERROR: 0:7: 'upixelLocalANGLE' : upixelLocalANGLEs must be uniform"));
// Memory qualifiers are not allowed on PLS handles.
constexpr char kPLSMemoryQualifiers[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0, rgba8) uniform lowp volatile coherent restrict pixelLocalANGLE pls1;
layout(binding=1, rgba8i) uniform mediump readonly ipixelLocalANGLE pls2;
void f(uniform highp writeonly upixelLocalANGLE pls);
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSMemoryQualifiers));
EXPECT_TRUE(log.has("ERROR: 0:3: 'coherent' : "));
EXPECT_TRUE(log.has("ERROR: 0:3: 'restrict' : "));
EXPECT_TRUE(log.has("ERROR: 0:3: 'volatile' : "));
EXPECT_TRUE(log.has("ERROR: 0:4: 'readonly' : "));
EXPECT_TRUE(log.has("ERROR: 0:5: 'writeonly' : "));
// PLS handles must specify precision.
constexpr char kPLSNoPrecision[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : enable
layout(binding=0, rgba8) uniform pixelLocalANGLE pls1;
layout(binding=1, rgba8i) uniform ipixelLocalANGLE pls2;
void f(upixelLocalANGLE pls3)
{
}
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSNoPrecision));
EXPECT_TRUE(log.has("ERROR: 0:3: 'pixelLocalANGLE' : No precision specified"));
EXPECT_TRUE(log.has("ERROR: 0:4: 'ipixelLocalANGLE' : No precision specified"));
EXPECT_TRUE(log.has("ERROR: 0:5: 'upixelLocalANGLE' : No precision specified"));
// PLS handles cannot cannot be aggregated in arrays.
constexpr char kPLSArrays[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls1[1];
layout(binding=1, rgba8i) uniform mediump ipixelLocalANGLE pls2[2];
layout(binding=2, rgba8ui) uniform highp upixelLocalANGLE pls3[3];
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSArrays));
EXPECT_TRUE(log.has(
"ERROR: 0:3: 'array' : pixel local storage handles cannot be aggregated in arrays"));
EXPECT_TRUE(log.has(
"ERROR: 0:4: 'array' : pixel local storage handles cannot be aggregated in arrays"));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'array' : pixel local storage handles cannot be aggregated in arrays"));
// If PLS handles could be used before their declaration, then we would need to update the PLS
// rewriters to make two passes.
constexpr char kPLSUseBeforeDeclaration[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void f()
{
pixelLocalStoreANGLE(pls, vec4(0));
pixelLocalStoreANGLE(pls2, ivec4(0));
}
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
void main()
{
pixelLocalStoreANGLE(pls, vec4(0));
pixelLocalStoreANGLE(pls2, ivec4(0));
}
layout(binding=1, rgba8i) uniform lowp ipixelLocalANGLE pls2;)";
EXPECT_FALSE(log.compileFragmentShader(kPLSUseBeforeDeclaration));
EXPECT_TRUE(log.has("ERROR: 0:5: 'pls' : undeclared identifier"));
EXPECT_TRUE(log.has("ERROR: 0:6: 'pls2' : undeclared identifier"));
EXPECT_TRUE(log.has("ERROR: 0:12: 'pls2' : undeclared identifier"));
// PLS unimorms must be declared at global scope; they cannot be declared in structs or
// interface blocks.
constexpr char kPLSInStruct[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
struct Foo
{
lowp pixelLocalANGLE pls;
};
uniform Foo foo;
uniform PLSBlock
{
lowp pixelLocalANGLE blockpls;
};
void main()
{
pixelLocalStoreANGLE(foo.pls, pixelLocalLoadANGLE(blockpls));
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSInStruct));
EXPECT_TRUE(log.has("ERROR: 0:5: 'pixelLocalANGLE' : disallowed type in struct"));
EXPECT_TRUE(
log.has("ERROR: 0:10: 'PLSBlock' : Opaque types are not allowed in interface blocks"));
ASSERT_GL_NO_ERROR();
}
// Check proper validation of PLS layout qualifiers.
TEST_P(PixelLocalStorageCompilerTest, LayoutQualifiers)
{
// PLS handles must use a supported format and binding.
constexpr char kPLSUnsupportedFormatsAndBindings[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0, rgba32f) highp uniform pixelLocalANGLE pls0;
layout(binding=1, rgba16f) highp uniform pixelLocalANGLE pls1;
layout(binding=2, rgba8_snorm) highp uniform pixelLocalANGLE pls2;
layout(binding=3, rgba32ui) highp uniform upixelLocalANGLE pls3;
layout(binding=4, rgba16ui) highp uniform upixelLocalANGLE pls4;
layout(binding=5, rgba32i) highp uniform ipixelLocalANGLE pls5;
layout(binding=6, rgba16i) highp uniform ipixelLocalANGLE pls6;
layout(binding=7, r32i) highp uniform ipixelLocalANGLE pls7;
layout(binding=999999999, rgba) highp uniform ipixelLocalANGLE pls8;
highp uniform pixelLocalANGLE pls9;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSUnsupportedFormatsAndBindings));
EXPECT_TRUE(log.has("ERROR: 0:3: 'rgba32f' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:4: 'rgba16f' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:5: 'rgba8_snorm' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:6: 'rgba32ui' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:7: 'rgba16ui' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:8: 'rgba32i' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:9: 'rgba16i' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:10: 'r32i' : illegal pixel local storage format"));
EXPECT_TRUE(log.has("ERROR: 0:11: 'rgba' : invalid layout qualifier"));
EXPECT_TRUE(log.has(
"ERROR: 0:11: 'layout qualifier' : pixel local storage requires a format specifier"));
EXPECT_TRUE(log.has(
"ERROR: 0:12: 'layout qualifier' : pixel local storage requires a format specifier"));
EXPECT_TRUE(
log.has("ERROR: 0:12: 'layout qualifier' : pixel local storage requires a binding index"));
// PLS handles must be within MAX_PIXEL_LOCAL_STORAGE_PLANES.
GLint MAX_PIXEL_LOCAL_STORAGE_PLANES;
glGetIntegerv(GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE, &MAX_PIXEL_LOCAL_STORAGE_PLANES);
std::ostringstream bindingTooLarge;
bindingTooLarge << "#version 310 es\n";
bindingTooLarge << "#extension GL_ANGLE_shader_pixel_local_storage : require\n";
bindingTooLarge << "layout(binding=" << MAX_PIXEL_LOCAL_STORAGE_PLANES
<< ", rgba8) highp uniform pixelLocalANGLE pls;\n";
bindingTooLarge << "void main() {}\n";
EXPECT_FALSE(log.compileFragmentShader(bindingTooLarge.str().c_str()));
EXPECT_TRUE(
log.has("ERROR: 0:3: 'layout qualifier' : pixel local storage binding out of range"));
// PLS handles must use the correct type for the given format.
constexpr char kPLSInvalidTypeForFormat[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0) highp uniform pixelLocalANGLE pls0;
layout(binding=1) highp uniform upixelLocalANGLE pls1;
layout(binding=2) highp uniform ipixelLocalANGLE pls2;
layout(binding=3, rgba8) highp uniform ipixelLocalANGLE pls3;
layout(binding=4, rgba8) highp uniform upixelLocalANGLE pls4;
layout(binding=5, rgba8ui) highp uniform pixelLocalANGLE pls5;
layout(binding=6, rgba8ui) highp uniform ipixelLocalANGLE pls6;
layout(binding=7, rgba8i) highp uniform upixelLocalANGLE pls7;
layout(binding=8, rgba8i) highp uniform pixelLocalANGLE pls8;
layout(binding=9, r32f) highp uniform ipixelLocalANGLE pls9;
layout(binding=10, r32f) highp uniform upixelLocalANGLE pls10;
layout(binding=11, r32ui) highp uniform pixelLocalANGLE pls11;
layout(binding=12, r32ui) highp uniform ipixelLocalANGLE pls12;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSInvalidTypeForFormat));
EXPECT_TRUE(log.has(
"ERROR: 0:3: 'layout qualifier' : pixel local storage requires a format specifier"));
EXPECT_TRUE(log.has(
"ERROR: 0:4: 'layout qualifier' : pixel local storage requires a format specifier"));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'layout qualifier' : pixel local storage requires a format specifier"));
EXPECT_TRUE(
log.has("ERROR: 0:6: 'rgba8' : pixel local storage format requires pixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:7: 'rgba8' : pixel local storage format requires pixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:8: 'rgba8ui' : pixel local storage format requires upixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:9: 'rgba8ui' : pixel local storage format requires upixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:10: 'rgba8i' : pixel local storage format requires ipixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:11: 'rgba8i' : pixel local storage format requires ipixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:12: 'r32f' : pixel local storage format requires pixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:13: 'r32f' : pixel local storage format requires pixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:14: 'r32ui' : pixel local storage format requires upixelLocalANGLE"));
EXPECT_TRUE(
log.has("ERROR: 0:15: 'r32ui' : pixel local storage format requires upixelLocalANGLE"));
// PLS handles cannot have duplicate binding indices.
constexpr char kPLSDuplicateBindings[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0, rgba) uniform highp pixelLocalANGLE pls0;
layout(rgba8i, binding=1) uniform highp ipixelLocalANGLE pls1;
layout(binding=2, rgba8ui) uniform highp upixelLocalANGLE pls2;
layout(binding=1, rgba) uniform highp ipixelLocalANGLE pls3;
layout(rgba8i, binding=0) uniform mediump ipixelLocalANGLE pls4;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSDuplicateBindings));
EXPECT_TRUE(log.has("ERROR: 0:6: '1' : duplicate pixel local storage binding index"));
EXPECT_TRUE(log.has("ERROR: 0:7: '0' : duplicate pixel local storage binding index"));
// PLS handles cannot have duplicate binding indices.
constexpr char kPLSIllegalLayoutQualifiers[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(foo) highp uniform pixelLocalANGLE pls1;
layout(binding=0, location=0, rgba8ui) highp uniform upixelLocalANGLE pls2;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSIllegalLayoutQualifiers));
EXPECT_TRUE(log.has("ERROR: 0:3: 'foo' : invalid layout qualifier"));
EXPECT_TRUE(
log.has("ERROR: 0:4: 'location' : location must only be specified for a single input or "
"output variable"));
// Check that binding is not allowed in ES3, other than pixel local storage. ES3 doesn't have
// blocks, and only has one opaque type: samplers. So we just need to make sure binding isn't
// allowed on samplers.
constexpr char kBindingOnSampler[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0) uniform mediump sampler2D sampler;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kBindingOnSampler));
EXPECT_TRUE(
log.has("ERROR: 0:3: 'binding' : invalid layout qualifier: only valid when used with pixel "
"local storage"));
// Binding qualifiers generate different error messages depending on ES3 and ES31.
constexpr char kBindingOnOutput[] = R"(#version 310 es
layout(binding=0) out mediump vec4 color;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kBindingOnOutput));
EXPECT_TRUE(
log.has("ERROR: 0:2: 'binding' : invalid layout qualifier: only valid when used with "
"opaque types or blocks"));
// Check that internalformats are not allowed in ES3 except for PLS.
constexpr char kFormatOnSamplerES3[] = R"(#version 300 es
layout(rgba8) uniform mediump sampler2D sampler1;
layout(rgba8_snorm) uniform mediump sampler2D sampler2;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kFormatOnSamplerES3));
EXPECT_TRUE(
log.has("ERROR: 0:2: 'rgba8' : invalid layout qualifier: not supported before GLSL ES "
"3.10, except pixel local storage"));
EXPECT_TRUE(log.has(
"ERROR: 0:3: 'rgba8_snorm' : invalid layout qualifier: not supported before GLSL ES 3.10"));
// Format qualifiers generate different error messages depending on whether they can be used
// with PLS.
constexpr char kFormatOnSamplerES31[] = R"(#version 310 es
layout(rgba8) uniform mediump sampler2D sampler1;
layout(rgba8_snorm) uniform mediump sampler2D sampler2;
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kFormatOnSamplerES31));
EXPECT_TRUE(
log.has("ERROR: 0:2: 'rgba8' : invalid layout qualifier: only valid when used with images "
"or pixel local storage"));
EXPECT_TRUE(log.has(
"ERROR: 0:3: 'rgba8_snorm' : invalid layout qualifier: only valid when used with images"));
ASSERT_GL_NO_ERROR();
}
// Check proper validation of the discard statement when pixel local storage is(n't) declared.
TEST_P(PixelLocalStorageCompilerTest, Discard)
{
// Discard is not allowed when pixel local storage has been declared. When polyfilled with
// shader images, pixel local storage requires early_fragment_tests, which causes discard to
// interact differently with the depth and stencil tests.
//
// To ensure identical behavior across all backends (some of which may not have access to
// early_fragment_tests), we disallow discard if pixel local storage has been declared.
constexpr char kDiscardWithPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;
void a()
{
discard;
}
void b();
void main()
{
if (gl_FragDepth == 3.14)
discard;
discard;
}
void b()
{
discard;
})";
EXPECT_FALSE(log.compileFragmentShader(kDiscardWithPLS));
EXPECT_TRUE(
log.has("ERROR: 0:6: 'discard' : illegal discard when pixel local storage is declared"));
EXPECT_TRUE(
log.has("ERROR: 0:12: 'discard' : illegal discard when pixel local storage is declared"));
EXPECT_TRUE(
log.has("ERROR: 0:13: 'discard' : illegal discard when pixel local storage is declared"));
EXPECT_TRUE(
log.has("ERROR: 0:17: 'discard' : illegal discard when pixel local storage is declared"));
// Discard is OK when pixel local storage has _not_ been declared.
constexpr char kDiscardNoPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void f(lowp pixelLocalANGLE pls); // Function arguments don't trigger PLS restrictions.
void a()
{
discard;
}
void b();
void main()
{
if (gl_FragDepth == 3.14)
discard;
discard;
}
void b()
{
discard;
})";
EXPECT_TRUE(log.compileFragmentShader(kDiscardNoPLS));
// Ensure discard is caught even if it happens before PLS is declared.
constexpr char kDiscardBeforePLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void a()
{
discard;
}
void main()
{
}
layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;)";
EXPECT_FALSE(log.compileFragmentShader(kDiscardBeforePLS));
EXPECT_TRUE(
log.has("ERROR: 0:5: 'discard' : illegal discard when pixel local storage is declared"));
ASSERT_GL_NO_ERROR();
}
// Check proper validation of the return statement when pixel local storage is(n't) declared.
TEST_P(PixelLocalStorageCompilerTest, Return)
{
// Returning from main isn't allowed when pixel local storage has been declared.
// (ARB_fragment_shader_interlock isn't allowed after return from main.)
constexpr char kReturnFromMainWithPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;
void main()
{
if (gl_FragDepth == 3.14)
return;
return;
})";
EXPECT_FALSE(log.compileFragmentShader(kReturnFromMainWithPLS));
EXPECT_TRUE(log.has(
"ERROR: 0:7: 'return' : illegal return from main when pixel local storage is declared"));
EXPECT_TRUE(log.has(
"ERROR: 0:8: 'return' : illegal return from main when pixel local storage is declared"));
// Returning from main is OK when pixel local storage has _not_ been declared.
constexpr char kReturnFromMainNoPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void main()
{
if (gl_FragDepth == 3.14)
return;
return;
})";
EXPECT_TRUE(log.compileFragmentShader(kReturnFromMainNoPLS));
// Returning from subroutines is OK when pixel local storage has been declared.
constexpr char kReturnFromSubroutinesWithPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(rgba8ui, binding=0) highp uniform upixelLocalANGLE pls;
void a()
{
return;
}
void b();
void main()
{
a();
b();
}
void b()
{
return;
})";
EXPECT_TRUE(log.compileFragmentShader(kReturnFromSubroutinesWithPLS));
// Ensure return from main is caught even if it happens before PLS is declared.
constexpr char kDiscardBeforePLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void main()
{
return;
}
layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;)";
EXPECT_FALSE(log.compileFragmentShader(kDiscardBeforePLS));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'return' : illegal return from main when pixel local storage is declared"));
ASSERT_GL_NO_ERROR();
}
// Check that gl_FragDepth(EXT) and gl_SampleMask are not assignable when PLS is declared.
TEST_P(PixelLocalStorageCompilerTest, FragmentTestVariables)
{
// gl_FragDepth is not assignable when pixel local storage has been declared. When polyfilled
// with shader images, pixel local storage requires early_fragment_tests, which causes
// assignments to gl_FragDepth(EXT) and gl_SampleMask to be ignored.
//
// To ensure identical behavior across all backends, we disallow assignment to these values if
// pixel local storage has been declared.
constexpr char kAssignFragDepthWithPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void set(out mediump float x, mediump float val)
{
x = val;
}
void set2(inout mediump float x, mediump float val)
{
x = val;
}
void main()
{
gl_FragDepth = 0.0;
gl_FragDepth -= 1.0;
set(gl_FragDepth, 0.0);
set2(gl_FragDepth, 0.1);
}
layout(binding=0, rgba8i) lowp uniform ipixelLocalANGLE pls;)";
EXPECT_FALSE(log.compileFragmentShader(kAssignFragDepthWithPLS));
EXPECT_TRUE(log.has(
"ERROR: 0:13: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
EXPECT_TRUE(log.has(
"ERROR: 0:14: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
EXPECT_TRUE(log.has(
"ERROR: 0:15: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
EXPECT_TRUE(log.has(
"ERROR: 0:16: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
// Assigning gl_FragDepth is OK if we don't declare any PLS.
constexpr char kAssignFragDepthNoPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void f(highp ipixelLocalANGLE pls)
{
// Function arguments don't trigger PLS restrictions.
pixelLocalStoreANGLE(pls, ivec4(8));
}
void set(out mediump float x, mediump float val)
{
x = val;
}
void main()
{
gl_FragDepth = 0.0;
gl_FragDepth /= 2.0;
set(gl_FragDepth, 0.0);
})";
EXPECT_TRUE(log.compileFragmentShader(kAssignFragDepthNoPLS));
// Reading gl_FragDepth is OK.
constexpr char kReadFragDepth[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(r32f, binding=0) highp uniform pixelLocalANGLE pls;
highp vec4 get(in mediump float x)
{
return vec4(x);
}
void set(inout mediump float x, mediump float val)
{
x = val;
}
void main()
{
pixelLocalStoreANGLE(pls, get(gl_FragDepth));
// Check when gl_FragDepth is involved in an l-value expression, but not assigned to.
highp float x[2];
x[int(gl_FragDepth)] = 1.0;
set(x[1 - int(gl_FragDepth)], 2.0);
})";
EXPECT_TRUE(log.compileFragmentShader(kReadFragDepth));
if (EnsureGLExtensionEnabled("GL_OES_sample_variables"))
{
// gl_SampleMask is not assignable when pixel local storage has been declared. The shader
// image polyfill requires early_fragment_tests, which causes gl_SampleMask to be ignored.
//
// To ensure identical behavior across all implementations (some of which may not have
// access to early_fragment_tests), we disallow assignment to these values if pixel local
// storage has been declared.
constexpr char kAssignSampleMaskWithPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_OES_sample_variables : require
void set(out highp int x, highp int val)
{
x = val;
}
void set2(inout highp int x, highp int val)
{
x = val;
}
void main()
{
gl_SampleMask[0] = 0;
gl_SampleMask[0] ^= 1;
set(gl_SampleMask[0], 9);
set2(gl_SampleMask[0], 10);
}
layout(binding=0, rgba8i) highp uniform ipixelLocalANGLE pls;)";
EXPECT_FALSE(log.compileFragmentShader(kAssignSampleMaskWithPLS));
EXPECT_TRUE(
log.has("ERROR: 0:14: 'gl_SampleMask' : value not assignable when pixel local storage "
"is declared"));
EXPECT_TRUE(
log.has("ERROR: 0:15: 'gl_SampleMask' : value not assignable when pixel local storage "
"is declared"));
EXPECT_TRUE(
log.has("ERROR: 0:16: 'gl_SampleMask' : value not assignable when pixel local storage "
"is declared"));
EXPECT_TRUE(
log.has("ERROR: 0:17: 'gl_SampleMask' : value not assignable when pixel local storage "
"is declared"));
// Assigning gl_SampleMask is OK if we don't declare any PLS.
constexpr char kAssignSampleMaskNoPLS[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_OES_sample_variables : require
void set(out highp int x, highp int val)
{
x = val;
}
void main()
{
gl_SampleMask[0] = 0;
gl_SampleMask[0] ^= 1;
set(gl_SampleMask[0], 9);
})";
EXPECT_TRUE(log.compileFragmentShader(kAssignSampleMaskNoPLS));
// Reading gl_SampleMask is OK enough (even though it's technically output only).
constexpr char kReadSampleMask[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_OES_sample_variables : require
layout(binding=0, rgba8i) highp uniform ipixelLocalANGLE pls;
highp int get(in highp int x)
{
return x;
}
void set(out highp int x, highp int val)
{
x = val;
}
void main()
{
pixelLocalStoreANGLE(pls, ivec4(get(gl_SampleMask[0]), gl_SampleMaskIn[0], 0, 1));
// Check when gl_SampleMask is involved in an l-value expression, but not assigned to.
highp int x[2];
x[gl_SampleMask[0]] = 1;
set(x[gl_SampleMask[0]], 2);
})";
EXPECT_TRUE(log.compileFragmentShader(kReadSampleMask));
}
ASSERT_GL_NO_ERROR();
}
// Check that the "blend_support" layout qualifiers defined in KHR_blend_equation_advanced are
// illegal when PLS is declared.
TEST_P(PixelLocalStorageCompilerTest, BlendFuncExtended_illegal_with_PLS)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
// Just declaring the extension is ok.
constexpr char kRequireBlendFuncExtended[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_EXT_blend_func_extended : require
void main()
{}
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;)";
EXPECT_TRUE(log.compileFragmentShader(kRequireBlendFuncExtended));
// The <index> layout qualifier from EXT_blend_func_extended is illegal.
constexpr char kBlendFuncExtendedIndex[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_EXT_blend_func_extended : require
layout(location=0, index=1) out lowp vec4 out1;
void main()
{}
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;)";
EXPECT_FALSE(log.compileFragmentShader(kBlendFuncExtendedIndex));
EXPECT_TRUE(
log.has("ERROR: 0:4: 'layout' : illegal nonzero index qualifier when pixel local storage "
"is declared"));
// Multiple unassigned fragment output locations are illegal, even if EXT_blend_func_extended is
// enabled.
constexpr char kBlendFuncExtendedNoLocation[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_EXT_blend_func_extended : require
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
out lowp vec4 out1;
out lowp vec4 out0;
void main()
{})";
EXPECT_FALSE(log.compileFragmentShader(kBlendFuncExtendedNoLocation));
EXPECT_TRUE(log.has(
"ERROR: 0:5: 'out1' : must explicitly specify all locations when using multiple fragment "
"outputs and pixel local storage, even if EXT_blend_func_extended is enabled"));
EXPECT_TRUE(log.has(
"ERROR: 0:6: 'out0' : must explicitly specify all locations when using multiple fragment "
"outputs and pixel local storage, even if EXT_blend_func_extended is enabled"));
// index=0 is ok.
constexpr char kValidFragmentIndex0[] = R"(#version 300 es
#extension all : warn
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane1;
layout(location=0, index=0) out lowp vec4 outColor0;
layout(location=1, index=0) out lowp vec4 outColor1;
layout(location=2, index=0) out lowp vec4 outColor2;
void main()
{})";
EXPECT_TRUE(log.compileFragmentShader(kValidFragmentIndex0));
}
// Check that the "blend_support" layout qualifiers defined in KHR_blend_equation_advanced are
// illegal when PLS is declared.
TEST_P(PixelLocalStorageCompilerTest, BlendEquationAdvanced_illegal_with_PLS)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_blend_equation_advanced"));
// Just declaring the extension is ok.
constexpr char kRequireBlendAdvanced[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_KHR_blend_equation_advanced : require
void main()
{}
layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";
EXPECT_TRUE(log.compileFragmentShader(kRequireBlendAdvanced));
bool before = true;
for (const char *layoutQualifier : {
"blend_support_multiply",
"blend_support_screen",
"blend_support_overlay",
"blend_support_darken",
"blend_support_lighten",
"blend_support_colordodge",
"blend_support_colorburn",
"blend_support_hardlight",
"blend_support_softlight",
"blend_support_difference",
"blend_support_exclusion",
"blend_support_hsl_hue",
"blend_support_hsl_saturation",
"blend_support_hsl_color",
"blend_support_hsl_luminosity",
"blend_support_all_equations",
})
{
constexpr char kRequireBlendAdvancedBeforePLS[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_KHR_blend_equation_advanced : require
layout(%s) out;
void main()
{}
layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";
constexpr char kRequireBlendAdvancedAfterPLS[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
#extension GL_KHR_blend_equation_advanced : require
layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;
layout(%s) out;
void main()
{})";
const char *formatStr =
before ? kRequireBlendAdvancedBeforePLS : kRequireBlendAdvancedAfterPLS;
size_t buffSize =
snprintf(nullptr, 0, formatStr, layoutQualifier) + 1; // Extra space for '\0'
std::unique_ptr<char[]> shader(new char[buffSize]);
std::snprintf(shader.get(), buffSize, formatStr, layoutQualifier);
EXPECT_FALSE(log.compileFragmentShader(shader.get()));
if (before)
{
EXPECT_TRUE(
log.has("ERROR: 0:4: 'layout' : illegal advanced blend equation when pixel local "
"storage is declared"));
}
else
{
EXPECT_TRUE(
log.has("ERROR: 0:5: 'layout' : illegal advanced blend equation when pixel local "
"storage is declared"));
}
before = !before;
}
}
// Check proper validation of PLS function arguments.
TEST_P(PixelLocalStorageCompilerTest, FunctionArguments)
{
// Ensure PLS handles can't be the result of complex expressions.
constexpr char kPLSHandleComplexExpression[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
layout(rgba8, binding=0) mediump uniform pixelLocalANGLE pls0;
layout(rgba8, binding=1) mediump uniform pixelLocalANGLE pls1;
void clear(mediump pixelLocalANGLE pls)
{
pixelLocalStoreANGLE(pls, vec4(0));
}
void main()
{
highp float x = gl_FragDepth;
clear(((x += 50.0) < 100.0) ? pls0 : pls1);
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSHandleComplexExpression));
EXPECT_TRUE(log.has("ERROR: 0:12: '?:' : ternary operator is not allowed for opaque types"));
// As function arguments, PLS handles cannot have layout qualifiers.
constexpr char kPLSFnArgWithLayoutQualifiers[] = R"(#version 310 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void f(layout(rgba8, binding=1) mediump pixelLocalANGLE pls)
{
}
void g(layout(rgba8) lowp pixelLocalANGLE pls);
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kPLSFnArgWithLayoutQualifiers));
EXPECT_TRUE(log.has("ERROR: 0:3: 'layout' : only allowed at global scope"));
EXPECT_TRUE(log.has("ERROR: 0:6: 'layout' : only allowed at global scope"));
ASSERT_GL_NO_ERROR();
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageCompilerTest);
ANGLE_INSTANTIATE_TEST(PixelLocalStorageCompilerTest,
ES31_NULL().enable(Feature::EmulatePixelLocalStorage));
class PixelLocalStorageTestPreES3 : public ANGLETest<>
{
public:
PixelLocalStorageTestPreES3() { setExtensionsEnabled(false); }
};
// Check that GL_ANGLE_shader_pixel_local_storage is not advertised before ES 3.0.
TEST_P(PixelLocalStorageTestPreES3, UnsupportedClientVersion)
{
EXPECT_FALSE(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
EXPECT_FALSE(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
ShaderInfoLog log;
constexpr char kRequireUnsupportedPLS[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
void main()
{
})";
EXPECT_FALSE(log.compileFragmentShader(kRequireUnsupportedPLS));
EXPECT_TRUE(
log.has("ERROR: 0:2: 'GL_ANGLE_shader_pixel_local_storage' : extension is not supported"));
ASSERT_GL_NO_ERROR();
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTestPreES3);
ANGLE_INSTANTIATE_TEST(PixelLocalStorageTestPreES3,
ES1_NULL().enable(Feature::EmulatePixelLocalStorage),
ES2_NULL().enable(Feature::EmulatePixelLocalStorage));