blob: cf07b0b44b3e917f3490e2f54310f897257e7a23 [file] [log] [blame]
//
// Copyright 2015 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.
//
// StateChangeTest:
// Specifically designed for an ANGLE implementation of GL, these tests validate that
// ANGLE's dirty bits systems don't get confused by certain sequences of state changes.
//
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/random_utils.h"
#include <thread>
using namespace angle;
namespace
{
class StateChangeTest : public ANGLETest<>
{
protected:
StateChangeTest()
{
setWindowWidth(64);
setWindowHeight(64);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setConfigDepthBits(24);
setConfigStencilBits(8);
// Enable the no error extension to avoid syncing the FBO state on validation.
setNoErrorEnabled(true);
}
void testSetUp() override
{
glGenFramebuffers(1, &mFramebuffer);
glGenTextures(2, mTextures.data());
glGenRenderbuffers(1, &mRenderbuffer);
ASSERT_GL_NO_ERROR();
}
void testTearDown() override
{
if (mFramebuffer != 0)
{
glDeleteFramebuffers(1, &mFramebuffer);
mFramebuffer = 0;
}
if (!mTextures.empty())
{
glDeleteTextures(static_cast<GLsizei>(mTextures.size()), mTextures.data());
mTextures.clear();
}
glDeleteRenderbuffers(1, &mRenderbuffer);
}
GLuint mFramebuffer = 0;
GLuint mRenderbuffer = 0;
std::vector<GLuint> mTextures = {0, 0};
};
class StateChangeTestES3 : public StateChangeTest
{
protected:
StateChangeTestES3() {}
};
// Ensure that CopyTexImage2D syncs framebuffer changes.
TEST_P(StateChangeTest, CopyTexImage2DSync)
{
// TODO(geofflang): Fix on Linux AMD drivers (http://anglebug.com/1291)
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
// Init first texture to red
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
// Init second texture to green
glBindTexture(GL_TEXTURE_2D, mTextures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1], 0);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 0, 255, 0, 255);
// Copy in the red texture to the green one.
// CopyTexImage should sync the framebuffer attachment change.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 16, 16, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1], 0);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
ASSERT_GL_NO_ERROR();
}
// Ensure that CopyTexSubImage2D syncs framebuffer changes.
TEST_P(StateChangeTest, CopyTexSubImage2DSync)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
// Init first texture to red
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
// Init second texture to green
glBindTexture(GL_TEXTURE_2D, mTextures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1], 0);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 0, 255, 0, 255);
// Copy in the red texture to the green one.
// CopyTexImage should sync the framebuffer attachment change.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 16, 16);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1], 0);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
ASSERT_GL_NO_ERROR();
}
// Test that Framebuffer completeness caching works when color attachments change.
TEST_P(StateChangeTest, FramebufferIncompleteColorAttachment)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Change the texture at color attachment 0 to be non-color-renderable.
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 16, 16, 0, GL_ALPHA, GL_UNSIGNED_BYTE, nullptr);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that caching works when color attachments change with TexStorage.
TEST_P(StateChangeTest, FramebufferIncompleteWithTexStorage)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Change the texture at color attachment 0 to be non-color-renderable.
glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_ALPHA8_EXT, 16, 16);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that caching works when color attachments change with CompressedTexImage2D.
TEST_P(StateChangeTestES3, FramebufferIncompleteWithCompressedTex)
{
// ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Change the texture at color attachment 0 to be non-color-renderable.
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 16, 16, 0, 128, nullptr);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that caching works when color attachments are deleted.
TEST_P(StateChangeTestES3, FramebufferIncompleteWhenAttachmentDeleted)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Delete the texture at color attachment 0.
glDeleteTextures(1, &mTextures[0]);
mTextures[0] = 0;
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that Framebuffer completeness caching works when depth attachments change.
TEST_P(StateChangeTest, FramebufferIncompleteDepthAttachment)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mRenderbuffer);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Change the texture at color attachment 0 to be non-depth-renderable.
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 16, 16);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that Framebuffer completeness caching works when stencil attachments change.
TEST_P(StateChangeTest, FramebufferIncompleteStencilAttachment)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, 16, 16);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
mRenderbuffer);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Change the texture at the stencil attachment to be non-stencil-renderable.
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 16, 16);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that Framebuffer completeness caching works when depth-stencil attachments change.
TEST_P(StateChangeTestES3, FramebufferIncompleteDepthStencilAttachment)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 16, 16);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
mRenderbuffer);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Change the texture the depth-stencil attachment to be non-depth-stencil-renderable.
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 16, 16);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
}
// Test that enabling GL_SAMPLE_ALPHA_TO_COVERAGE doesn't generate errors.
TEST_P(StateChangeTest, AlphaToCoverageEnable)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
// We don't actually care that this does anything, just that it can be enabled without causing
// an error.
glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
glUseProgram(greenProgram);
drawQuad(greenProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
const char kSimpleAttributeVS[] = R"(attribute vec2 position;
attribute vec4 testAttrib;
varying vec4 testVarying;
void main()
{
gl_Position = vec4(position, 0, 1);
testVarying = testAttrib;
})";
const char kSimpleAttributeFS[] = R"(precision mediump float;
varying vec4 testVarying;
void main()
{
gl_FragColor = testVarying;
})";
// Tests that using a buffered attribute, then disabling it and using current value, works.
TEST_P(StateChangeTest, DisablingBufferedVertexAttribute)
{
ANGLE_GL_PROGRAM(program, kSimpleAttributeVS, kSimpleAttributeFS);
glUseProgram(program);
GLint attribLoc = glGetAttribLocation(program, "testAttrib");
GLint positionLoc = glGetAttribLocation(program, "position");
ASSERT_NE(-1, attribLoc);
ASSERT_NE(-1, positionLoc);
// Set up the buffered attribute.
std::vector<GLColor> red(6, GLColor::red);
GLBuffer attribBuffer;
glBindBuffer(GL_ARRAY_BUFFER, attribBuffer);
glBufferData(GL_ARRAY_BUFFER, red.size() * sizeof(GLColor), red.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(attribLoc);
glVertexAttribPointer(attribLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
// Also set the current value to green now.
glVertexAttrib4f(attribLoc, 0.0f, 1.0f, 0.0f, 1.0f);
// Set up the position attribute as well.
setupQuadVertexBuffer(0.5f, 1.0f);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
// Draw with the buffered attribute. Verify red.
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw with the disabled "current value attribute". Verify green.
glDisableVertexAttribArray(attribLoc);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Verify setting buffer data on the disabled buffer doesn't change anything.
std::vector<GLColor> blue(128, GLColor::blue);
glBindBuffer(GL_ARRAY_BUFFER, attribBuffer);
glBufferData(GL_ARRAY_BUFFER, blue.size() * sizeof(GLColor), blue.data(), GL_STATIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Tests that setting value for a subset of default attributes doesn't affect others.
TEST_P(StateChangeTest, SetCurrentAttribute)
{
constexpr char kVS[] = R"(attribute vec4 position;
attribute mat4 testAttrib; // Note that this generates 4 attributes
varying vec4 testVarying;
void main (void)
{
gl_Position = position;
testVarying = position.y < 0.0 ?
position.x < 0.0 ? testAttrib[0] : testAttrib[1] :
position.x < 0.0 ? testAttrib[2] : testAttrib[3];
})";
ANGLE_GL_PROGRAM(program, kVS, kSimpleAttributeFS);
glUseProgram(program);
GLint attribLoc = glGetAttribLocation(program, "testAttrib");
GLint positionLoc = glGetAttribLocation(program, "position");
ASSERT_NE(-1, attribLoc);
ASSERT_NE(-1, positionLoc);
// Set the current value of two of the test attributes, while leaving the other two as default.
glVertexAttrib4f(attribLoc + 1, 0.0f, 1.0f, 0.0f, 1.0f);
glVertexAttrib4f(attribLoc + 2, 0.0f, 0.0f, 1.0f, 1.0f);
// Set up the position attribute.
setupQuadVertexBuffer(0.5f, 1.0f);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
// Draw and verify the four section in the output:
//
// +---------------+
// | Black | Green |
// +-------+-------+
// | Blue | Black |
// +---------------+
//
glDrawArrays(GL_TRIANGLES, 0, 6);
const int w = getWindowWidth();
const int h = getWindowHeight();
constexpr unsigned int kPixelTolerance = 5u;
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::black, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, h - 1, GLColor::blue, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, GLColor::black, kPixelTolerance);
}
// Tests that drawing with transform feedback paused, then lines without transform feedback works
// without Vulkan validation errors.
TEST_P(StateChangeTestES3, DrawPausedXfbThenNonXfbLines)
{
// glTransformFeedbackVaryings for program2 returns GL_INVALID_OPERATION on both Linux and
// windows. http://anglebug.com/4265
ANGLE_SKIP_TEST_IF(IsIntel() && IsOpenGL());
// http://anglebug.com/5388
ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsDesktopOpenGL());
std::vector<std::string> tfVaryings = {"gl_Position"};
ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(program1, essl1_shaders::vs::Simple(),
essl1_shaders::fs::Blue(), tfVaryings, GL_SEPARATE_ATTRIBS);
GLBuffer xfbBuffer;
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 6 * sizeof(float[4]), nullptr, GL_STATIC_DRAW);
GLTransformFeedback xfb;
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
glUseProgram(program1);
glBeginTransformFeedback(GL_TRIANGLES);
glPauseTransformFeedback();
glDrawArrays(GL_TRIANGLES, 0, 6);
ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
glUseProgram(program2);
glDrawArrays(GL_LINES, 0, 6);
glEndTransformFeedback();
ASSERT_GL_NO_ERROR();
}
// Tests that vertex attribute value is preserved across context switches.
TEST_P(StateChangeTest, MultiContextVertexAttribute)
{
EGLWindow *window = getEGLWindow();
EGLDisplay display = window->getDisplay();
EGLConfig config = window->getConfig();
EGLSurface surface = window->getSurface();
EGLContext context1 = window->getContext();
// Set up program in primary context
ANGLE_GL_PROGRAM(program1, kSimpleAttributeVS, kSimpleAttributeFS);
glUseProgram(program1);
GLint attribLoc = glGetAttribLocation(program1, "testAttrib");
GLint positionLoc = glGetAttribLocation(program1, "position");
ASSERT_NE(-1, attribLoc);
ASSERT_NE(-1, positionLoc);
// Set up the position attribute in primary context
setupQuadVertexBuffer(0.5f, 1.0f);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
// Set primary context attribute to green and draw quad
glVertexAttrib4f(attribLoc, 0.0f, 1.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Set up and switch to secondary context
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR,
GetParam().majorVersion,
EGL_CONTEXT_MINOR_VERSION_KHR,
GetParam().minorVersion,
EGL_NONE,
};
EGLContext context2 = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes);
ASSERT_NE(context2, EGL_NO_CONTEXT);
eglMakeCurrent(display, surface, surface, context2);
// Set up program in secondary context
ANGLE_GL_PROGRAM(program2, kSimpleAttributeVS, kSimpleAttributeFS);
glUseProgram(program2);
ASSERT_EQ(attribLoc, glGetAttribLocation(program2, "testAttrib"));
ASSERT_EQ(positionLoc, glGetAttribLocation(program2, "position"));
// Set up the position attribute in secondary context
setupQuadVertexBuffer(0.5f, 1.0f);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
// attribLoc current value should be default - (0,0,0,1)
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
// Restore primary context
eglMakeCurrent(display, surface, surface, context1);
// ReadPixels to ensure context is switched
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
// Switch to secondary context second time
eglMakeCurrent(display, surface, surface, context2);
// Check that it still draws black
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
// Restore primary context second time
eglMakeCurrent(display, surface, surface, context1);
// Check if it still draws green
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Clean up
eglDestroyContext(display, context2);
}
// Ensure that CopyTexSubImage3D syncs framebuffer changes.
TEST_P(StateChangeTestES3, CopyTexSubImage3DSync)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
// Init first texture to red
glBindTexture(GL_TEXTURE_3D, mTextures[0]);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 16, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTextures[0], 0, 0);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
// Init second texture to green
glBindTexture(GL_TEXTURE_3D, mTextures[1]);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 16, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTextures[1], 0, 0);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 0, 255, 0, 255);
// Copy in the red texture to the green one.
// CopyTexImage should sync the framebuffer attachment change.
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTextures[0], 0, 0);
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 16, 16);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTextures[1], 0, 0);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
ASSERT_GL_NO_ERROR();
}
// Ensure that BlitFramebuffer syncs framebuffer changes.
TEST_P(StateChangeTestES3, BlitFramebufferSync)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
// Init first texture to red
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
// Init second texture to green
glBindTexture(GL_TEXTURE_2D, mTextures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1], 0);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_EQ(0, 0, 0, 255, 0, 255);
// Change to the red textures and blit.
// BlitFramebuffer should sync the framebuffer attachment change.
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0],
0);
glBlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
ASSERT_GL_NO_ERROR();
}
// Ensure that ReadBuffer and DrawBuffers sync framebuffer changes.
TEST_P(StateChangeTestES3, ReadBufferAndDrawBuffersSync)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
// Initialize two FBO attachments
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glBindTexture(GL_TEXTURE_2D, mTextures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, mTextures[1], 0);
// Clear first attachment to red
GLenum bufs1[] = {GL_COLOR_ATTACHMENT0, GL_NONE};
glDrawBuffers(2, bufs1);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Clear second texture to green
GLenum bufs2[] = {GL_NONE, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, bufs2);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Verify first attachment is red and second is green
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_EQ(0, 0, 0, 255, 0, 255);
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
ASSERT_GL_NO_ERROR();
}
// Tests calling invalidate on incomplete framebuffers after switching attachments.
// Adapted partially from WebGL 2 test "renderbuffers/invalidate-framebuffer"
TEST_P(StateChangeTestES3, IncompleteRenderbufferAttachmentInvalidateSync)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
GLint samples = 0;
glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, 1, &samples);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mRenderbuffer);
ASSERT_GL_NO_ERROR();
// invalidate the framebuffer when the attachment is incomplete: no storage allocated to the
// attached renderbuffer
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
GLenum attachments1[] = {GL_COLOR_ATTACHMENT0};
glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, attachments1);
ASSERT_GL_NO_ERROR();
glRenderbufferStorageMultisample(GL_RENDERBUFFER, static_cast<GLsizei>(samples), GL_RGBA8,
getWindowWidth(), getWindowHeight());
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_GL_NO_ERROR();
GLRenderbuffer renderbuf;
glBindRenderbuffer(GL_RENDERBUFFER, renderbuf.get());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
renderbuf.get());
ASSERT_GL_NO_ERROR();
// invalidate the framebuffer when the attachment is incomplete: no storage allocated to the
// attached renderbuffer
// Note: the bug will only repro *without* a call to checkStatus before the invalidate.
GLenum attachments2[] = {GL_DEPTH_ATTACHMENT};
glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, attachments2);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, static_cast<GLsizei>(samples),
GL_DEPTH_COMPONENT16, getWindowWidth(), getWindowHeight());
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClear(GL_DEPTH_BUFFER_BIT);
ASSERT_GL_NO_ERROR();
}
class StateChangeRenderTest : public StateChangeTest
{
protected:
StateChangeRenderTest() : mProgram(0), mRenderbuffer(0) {}
void testSetUp() override
{
StateChangeTest::testSetUp();
constexpr char kVS[] =
"attribute vec2 position;\n"
"void main() {\n"
" gl_Position = vec4(position, 0, 1);\n"
"}";
constexpr char kFS[] =
"uniform highp vec4 uniformColor;\n"
"void main() {\n"
" gl_FragColor = uniformColor;\n"
"}";
mProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, mProgram);
glGenRenderbuffers(1, &mRenderbuffer);
}
void testTearDown() override
{
glDeleteProgram(mProgram);
glDeleteRenderbuffers(1, &mRenderbuffer);
StateChangeTest::testTearDown();
}
void setUniformColor(const GLColor &color)
{
glUseProgram(mProgram);
const Vector4 &normalizedColor = color.toNormalizedVector();
GLint uniformLocation = glGetUniformLocation(mProgram, "uniformColor");
ASSERT_NE(-1, uniformLocation);
glUniform4fv(uniformLocation, 1, normalizedColor.data());
}
GLuint mProgram;
GLuint mRenderbuffer;
};
// Test that re-creating a currently attached texture works as expected.
TEST_P(StateChangeRenderTest, RecreateTexture)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
// Explictly check FBO status sync in some versions of ANGLE no_error skips FBO checks.
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Draw with red to the FBO.
GLColor red(255, 0, 0, 255);
setUniformColor(red);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, red);
// Recreate the texture with green.
GLColor green(0, 255, 0, 255);
std::vector<GLColor> greenPixels(32 * 32, green);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE,
greenPixels.data());
EXPECT_PIXEL_COLOR_EQ(0, 0, green);
// Explictly check FBO status sync in some versions of ANGLE no_error skips FBO checks.
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Verify drawing blue gives blue. This covers the FBO sync with D3D dirty bits.
GLColor blue(0, 0, 255, 255);
setUniformColor(blue);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, blue);
EXPECT_GL_NO_ERROR();
}
// Test that re-creating a currently attached renderbuffer works as expected.
TEST_P(StateChangeRenderTest, RecreateRenderbuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 16, 16);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mRenderbuffer);
// Explictly check FBO status sync in some versions of ANGLE no_error skips FBO checks.
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Draw with red to the FBO.
setUniformColor(GLColor::red);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Recreate the renderbuffer and clear to green.
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 32, 32);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Explictly check FBO status sync in some versions of ANGLE no_error skips FBO checks.
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Verify drawing blue gives blue. This covers the FBO sync with D3D dirty bits.
setUniformColor(GLColor::blue);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
EXPECT_GL_NO_ERROR();
}
// Test that recreating a texture with GenerateMipmaps signals the FBO is dirty.
TEST_P(StateChangeRenderTest, GenerateMipmap)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
// Explictly check FBO status sync in some versions of ANGLE no_error skips FBO checks.
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Draw once to set the RenderTarget in D3D11
GLColor red(255, 0, 0, 255);
setUniformColor(red);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, red);
// This may trigger the texture to be re-created internally.
glGenerateMipmap(GL_TEXTURE_2D);
// Explictly check FBO status sync in some versions of ANGLE no_error skips FBO checks.
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Now ensure we don't have a stale render target.
GLColor blue(0, 0, 255, 255);
setUniformColor(blue);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, blue);
EXPECT_GL_NO_ERROR();
}
// Tests that gl_DepthRange syncs correctly after a change.
TEST_P(StateChangeRenderTest, DepthRangeUpdates)
{
constexpr char kFragCoordShader[] = R"(void main()
{
if (gl_DepthRange.near == 0.2)
{
gl_FragColor = vec4(1, 0, 0, 1);
}
else if (gl_DepthRange.near == 0.5)
{
gl_FragColor = vec4(0, 1, 0, 1);
}
else
{
gl_FragColor = vec4(0, 0, 1, 1);
}
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragCoordShader);
glUseProgram(program);
const auto &quadVertices = GetQuadVertices();
ASSERT_EQ(0, glGetAttribLocation(program, essl1_shaders::PositionAttrib()));
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, quadVertices.size() * sizeof(quadVertices[0]),
quadVertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0u, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0u);
// First, clear.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw to left half viewport with a first depth range.
glDepthRangef(0.2f, 1.0f);
glViewport(0, 0, getWindowWidth() / 2, getWindowHeight());
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Draw to right half viewport with a second depth range.
glDepthRangef(0.5f, 1.0f);
glViewport(getWindowWidth() / 2, 0, getWindowWidth() / 2, getWindowHeight());
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Verify left half of the framebuffer is red and right half is green.
EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth() / 2, getWindowHeight(), GLColor::red);
EXPECT_PIXEL_RECT_EQ(getWindowWidth() / 2, 0, getWindowWidth() / 2, getWindowHeight(),
GLColor::green);
}
class StateChangeRenderTestES3 : public StateChangeRenderTest
{};
TEST_P(StateChangeRenderTestES3, InvalidateNonCurrentFramebuffer)
{
glBindTexture(GL_TEXTURE_2D, mTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0], 0);
glBindTexture(GL_TEXTURE_2D, 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
// Draw with red to the FBO.
GLColor red(255, 0, 0, 255);
setUniformColor(red);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, red);
// Go back to default framebuffer, draw green
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLColor green(0, 255, 0, 255);
setUniformColor(green);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, green);
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
// Invalidate color buffer of FBO
GLenum attachments1[] = {GL_COLOR_ATTACHMENT0};
glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, attachments1);
ASSERT_GL_NO_ERROR();
// Verify drawing blue gives blue.
GLColor blue(0, 0, 255, 255);
setUniformColor(blue);
drawQuad(mProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, blue);
}
// Tests that D3D11 dirty bit updates don't forget about BufferSubData attrib updates.
TEST_P(StateChangeTest, VertexBufferUpdatedAfterDraw)
{
constexpr char kVS[] =
"attribute vec2 position;\n"
"attribute vec4 color;\n"
"varying vec4 outcolor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(position, 0, 1);\n"
" outcolor = color;\n"
"}";
constexpr char kFS[] =
"varying mediump vec4 outcolor;\n"
"void main()\n"
"{\n"
" gl_FragColor = outcolor;\n"
"}";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glUseProgram(program);
GLint colorLoc = glGetAttribLocation(program, "color");
ASSERT_NE(-1, colorLoc);
GLint positionLoc = glGetAttribLocation(program, "position");
ASSERT_NE(-1, positionLoc);
setupQuadVertexBuffer(0.5f, 1.0f);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
GLBuffer colorBuf;
glBindBuffer(GL_ARRAY_BUFFER, colorBuf);
glVertexAttribPointer(colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
glEnableVertexAttribArray(colorLoc);
// Fill with green.
std::vector<GLColor> colorData(6, GLColor::green);
glBufferData(GL_ARRAY_BUFFER, colorData.size() * sizeof(GLColor), colorData.data(),
GL_STATIC_DRAW);
// Draw, expect green.
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
ASSERT_GL_NO_ERROR();
// Update buffer with red.
std::fill(colorData.begin(), colorData.end(), GLColor::red);
glBufferSubData(GL_ARRAY_BUFFER, 0, colorData.size() * sizeof(GLColor), colorData.data());
// Draw, expect red.
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
ASSERT_GL_NO_ERROR();
}
// Tests that drawing after flush without any state change works.
TEST_P(StateChangeTestES3, DrawAfterFlushWithNoStateChange)
{
// Draw (0.125, 0.25, 0.5, 0.5) once, using additive blend
ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(drawColor);
GLint colorUniformLocation =
glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
GLint positionLocation = glGetAttribLocation(drawColor, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
// Setup VAO
const auto &quadVertices = GetQuadVertices();
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vector3) * 6, quadVertices.data(), GL_STATIC_DRAW);
GLVertexArray vertexArray;
glBindVertexArray(vertexArray);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
// Clear and draw
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glUniform4f(colorUniformLocation, 0.125f, 0.25f, 0.5f, 0.5f);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Make sure the work is submitted.
glFinish();
// Draw again with no state change
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Make sure the pixels have the correct colors.
const int h = getWindowHeight() - 1;
const int w = getWindowWidth() - 1;
const GLColor kExpected(63, 127, 255, 255);
EXPECT_PIXEL_COLOR_NEAR(0, 0, kExpected, 1);
EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, kExpected, 1);
EXPECT_PIXEL_COLOR_NEAR(0, h - 1, kExpected, 1);
EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, kExpected, 1);
}
// Test that switching VAOs keeps the disabled "current value" attributes up-to-date.
TEST_P(StateChangeTestES3, VertexArrayObjectAndDisabledAttributes)
{
constexpr char kSingleVS[] = "attribute vec4 position; void main() { gl_Position = position; }";
constexpr char kSingleFS[] = "void main() { gl_FragColor = vec4(1, 0, 0, 1); }";
ANGLE_GL_PROGRAM(singleProgram, kSingleVS, kSingleFS);
constexpr char kDualVS[] =
"#version 300 es\n"
"in vec4 position;\n"
"in vec4 color;\n"
"out vec4 varyColor;\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
" varyColor = color;\n"
"}";
constexpr char kDualFS[] =
"#version 300 es\n"
"precision mediump float;\n"
"in vec4 varyColor;\n"
"out vec4 colorOut;\n"
"void main()\n"
"{\n"
" colorOut = varyColor;\n"
"}";
ANGLE_GL_PROGRAM(dualProgram, kDualVS, kDualFS);
// Force consistent attribute locations
constexpr GLint positionLocation = 0;
constexpr GLint colorLocation = 1;
glBindAttribLocation(singleProgram, positionLocation, "position");
glBindAttribLocation(dualProgram, positionLocation, "position");
glBindAttribLocation(dualProgram, colorLocation, "color");
{
glLinkProgram(singleProgram);
GLint linkStatus;
glGetProgramiv(singleProgram, GL_LINK_STATUS, &linkStatus);
ASSERT_NE(linkStatus, 0);
}
{
glLinkProgram(dualProgram);
GLint linkStatus;
glGetProgramiv(dualProgram, GL_LINK_STATUS, &linkStatus);
ASSERT_NE(linkStatus, 0);
}
glUseProgram(singleProgram);
// Initialize position vertex buffer.
const auto &quadVertices = GetQuadVertices();
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vector3) * 6, quadVertices.data(), GL_STATIC_DRAW);
// Initialize a VAO. Draw with single program.
GLVertexArray vertexArray;
glBindVertexArray(vertexArray);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
// Should draw red.
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw with a green buffer attribute, without the VAO.
glBindVertexArray(0);
glUseProgram(dualProgram);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
std::vector<GLColor> greenColors(6, GLColor::green);
GLBuffer greenBuffer;
glBindBuffer(GL_ARRAY_BUFFER, greenBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * 6, greenColors.data(), GL_STATIC_DRAW);
glVertexAttribPointer(colorLocation, 4, GL_UNSIGNED_BYTE, GL_FALSE, 4, nullptr);
glEnableVertexAttribArray(colorLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Re-bind VAO and try to draw with different program, without changing state.
// Should draw black since current value is not initialized.
glBindVertexArray(vertexArray);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
}
const char kSamplerMetadataVertexShader0[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform sampler2D texture;
void main()
{
vec2 size = vec2(textureSize(texture, 0));
color = size.x != 0.0 ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 0.0);
vec2 pos = vec2(0.0);
switch (gl_VertexID) {
case 0: pos = vec2(-1.0, -1.0); break;
case 1: pos = vec2(3.0, -1.0); break;
case 2: pos = vec2(-1.0, 3.0); break;
};
gl_Position = vec4(pos, 0.0, 1.0);
})";
const char kSamplerMetadataVertexShader1[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
vec2 size1 = vec2(textureSize(texture1, 0));
vec2 size2 = vec2(textureSize(texture2, 0));
color = size1.x * size2.x != 0.0 ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 0.0);
vec2 pos = vec2(0.0);
switch (gl_VertexID) {
case 0: pos = vec2(-1.0, -1.0); break;
case 1: pos = vec2(3.0, -1.0); break;
case 2: pos = vec2(-1.0, 3.0); break;
};
gl_Position = vec4(pos, 0.0, 1.0);
})";
const char kSamplerMetadataFragmentShader[] = R"(#version 300 es
precision mediump float;
in vec4 color;
out vec4 result;
void main()
{
result = color;
})";
// Tests that changing an active program invalidates the sampler metadata properly.
TEST_P(StateChangeTestES3, SamplerMetadataUpdateOnSetProgram)
{
// http://anglebug.com/4092
ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
// TODO(anglebug.com/5491) Appears as though there's something wrong with textureSize on iOS
// unrelated to switching programs.
ANGLE_SKIP_TEST_IF(IsIOS() && IsOpenGLES());
GLVertexArray vertexArray;
glBindVertexArray(vertexArray);
// Create a simple framebuffer.
GLTexture texture1, texture2;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 3, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Create 2 shader programs differing only in the number of active samplers.
ANGLE_GL_PROGRAM(program1, kSamplerMetadataVertexShader0, kSamplerMetadataFragmentShader);
glUseProgram(program1);
glUniform1i(glGetUniformLocation(program1, "texture"), 0);
ANGLE_GL_PROGRAM(program2, kSamplerMetadataVertexShader1, kSamplerMetadataFragmentShader);
glUseProgram(program2);
glUniform1i(glGetUniformLocation(program2, "texture1"), 0);
glUniform1i(glGetUniformLocation(program2, "texture2"), 0);
// Draw a solid green color to the framebuffer.
glUseProgram(program1);
glDrawArrays(GL_TRIANGLES, 0, 3);
// Test that our first program is good.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Bind a different program that uses more samplers.
// Draw another quad that depends on the sampler metadata.
glUseProgram(program2);
glDrawArrays(GL_TRIANGLES, 0, 3);
// Flush via ReadPixels and check that it's still green.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
ASSERT_GL_NO_ERROR();
}
// Tests that redefining Buffer storage syncs with the Transform Feedback object.
TEST_P(StateChangeTestES3, RedefineTransformFeedbackBuffer)
{
// Create the most simple program possible - simple a passthrough for a float attribute.
constexpr char kVertexShader[] = R"(#version 300 es
in float valueIn;
out float valueOut;
void main()
{
gl_Position = vec4(0, 0, 0, 0);
valueOut = valueIn;
})";
constexpr char kFragmentShader[] = R"(#version 300 es
out mediump float unused;
void main()
{
unused = 1.0;
})";
std::vector<std::string> tfVaryings = {"valueOut"};
ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(program, kVertexShader, kFragmentShader, tfVaryings,
GL_SEPARATE_ATTRIBS);
glUseProgram(program);
GLint attribLoc = glGetAttribLocation(program, "valueIn");
ASSERT_NE(-1, attribLoc);
// Disable rasterization - we're not interested in the framebuffer.
glEnable(GL_RASTERIZER_DISCARD);
// Initialize a float vertex buffer with 1.0.
std::vector<GLfloat> data1(16, 1.0);
GLsizei size1 = static_cast<GLsizei>(sizeof(GLfloat) * data1.size());
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, size1, data1.data(), GL_STATIC_DRAW);
glVertexAttribPointer(attribLoc, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(attribLoc);
ASSERT_GL_NO_ERROR();
// Initialize a same-sized XFB buffer.
GLBuffer xfbBuffer;
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, size1, nullptr, GL_STATIC_DRAW);
// Draw with XFB enabled.
GLTransformFeedback xfb;
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, 16);
glEndTransformFeedback();
ASSERT_GL_NO_ERROR();
// Verify the XFB stage caught the 1.0 attribute values.
void *mapped1 = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, size1, GL_MAP_READ_BIT);
GLfloat *asFloat1 = reinterpret_cast<GLfloat *>(mapped1);
std::vector<GLfloat> actualData1(asFloat1, asFloat1 + data1.size());
EXPECT_EQ(data1, actualData1);
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
// Now, reinitialize the XFB buffer to a larger size, and draw with 2.0.
std::vector<GLfloat> data2(128, 2.0);
const GLsizei size2 = static_cast<GLsizei>(sizeof(GLfloat) * data2.size());
glBufferData(GL_ARRAY_BUFFER, size2, data2.data(), GL_STATIC_DRAW);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, size2, nullptr, GL_STATIC_DRAW);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, 128);
glEndTransformFeedback();
ASSERT_GL_NO_ERROR();
// Verify the XFB stage caught the 2.0 attribute values.
void *mapped2 = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, size2, GL_MAP_READ_BIT);
GLfloat *asFloat2 = reinterpret_cast<GLfloat *>(mapped2);
std::vector<GLfloat> actualData2(asFloat2, asFloat2 + data2.size());
EXPECT_EQ(data2, actualData2);
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
}
// Variations:
//
// - bool: whether to use WebGL compatibility mode.
using StateChangeTestWebGL2Params = std::tuple<angle::PlatformParameters, bool>;
std::string StateChangeTestWebGL2Print(
const ::testing::TestParamInfo<StateChangeTestWebGL2Params> &paramsInfo)
{
const StateChangeTestWebGL2Params &params = paramsInfo.param;
std::ostringstream out;
out << std::get<0>(params);
if (std::get<1>(params))
{
out << "__WebGLCompatibility";
}
return out.str();
}
// State change test verifying both ES3 and WebGL2 specific behaviors.
// Test is parameterized to allow execution with and without WebGL validation.
// Note that this can not inherit from StateChangeTest due to the need to use ANGLETestWithParam.
class StateChangeTestWebGL2 : public ANGLETest<StateChangeTestWebGL2Params>
{
protected:
StateChangeTestWebGL2()
{
setWindowWidth(kWidth);
setWindowHeight(kHeight);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
if (testing::get<1>(GetParam()))
setWebGLCompatibilityEnabled(true);
}
struct TestResources
{
GLTexture colorTexture;
GLFramebuffer framebuffer;
};
void setupResources(TestResources &resources)
{
glBindTexture(GL_TEXTURE_2D, resources.colorTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
EXPECT_GL_NO_ERROR();
glBindFramebuffer(GL_FRAMEBUFFER, resources.framebuffer);
EXPECT_GL_NO_ERROR();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
resources.colorTexture, 0);
EXPECT_GL_NO_ERROR();
}
// Use a larger window/framebuffer size than 1x1 (though not much larger) to
// increase the chances that random garbage will appear.
static constexpr GLsizei kWidth = 4;
static constexpr GLsizei kHeight = 4;
};
// Note: tested multiple other combinations:
//
// - Clearing/drawing to the framebuffer after invalidating, without using a
// secondary FBO
// - Clearing the framebuffer after invalidating, using a secondary FBO
// - Invalidating after clearing/drawing to the FBO, to verify WebGL's behavior
// that after invalidation, the framebuffer is either unmodified, or cleared
// to transparent black
//
// This combination, drawing after invalidating plus copying from the drawn-to
// texture, was the only one which provoked the original bug in the Metal
// backend with the following command line arguments:
//
// MTL_DEBUG_LAYER=1 MTL_DEBUG_LAYER_VALIDATE_LOAD_ACTIONS=1 \
// MTL_DEBUG_LAYER_VALIDATE_STORE_ACTIONS=1 \
// MTL_DEBUG_LAYER_VALIDATE_UNRETAINED_RESOURCES=4 \
// angle_end2end_tests ...
//
// See anglebug.com/6923.
TEST_P(StateChangeTestWebGL2, InvalidateThenDrawFBO)
{
GLint origFramebuffer = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &origFramebuffer);
TestResources resources;
setupResources(resources);
ANGLE_GL_PROGRAM(drawGreen, essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());
const GLenum attachment = GL_COLOR_ATTACHMENT0;
glBindFramebuffer(GL_FRAMEBUFFER, resources.framebuffer);
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Clear to red to start.
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Invalidate framebuffer.
glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &attachment);
EXPECT_GL_NO_ERROR();
// Draw green.
// Important to use a vertex buffer because WebGL doesn't support client-side arrays.
constexpr bool useVertexBuffer = true;
drawQuad(drawGreen.get(), essl3_shaders::PositionAttrib(), 0.5f, 1.0f, useVertexBuffer);
EXPECT_GL_NO_ERROR();
// Bind original framebuffer.
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, origFramebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, resources.framebuffer);
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);
EXPECT_GL_NO_ERROR();
// Blit from user's framebuffer to the window.
//
// This step is crucial to catch bugs in the Metal backend's use of no-op load/store actions.
glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
EXPECT_GL_NO_ERROR();
// Verify results.
glBindFramebuffer(GL_READ_FRAMEBUFFER, origFramebuffer);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}
// Simple state change tests for line loop drawing. There is some very specific handling of line
// line loops in Vulkan and we need to test switching between drawElements and drawArrays calls to
// validate every edge cases.
class LineLoopStateChangeTest : public StateChangeTest
{
protected:
LineLoopStateChangeTest()
{
setWindowWidth(32);
setWindowHeight(32);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
void validateSquareAndHourglass() const
{
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Bottom left
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight, GLColor::blue);
// Top left
EXPECT_PIXEL_COLOR_EQ(quarterWidth, (quarterHeight * 3), GLColor::blue);
// Top right
// The last pixel isn't filled on a line loop so we check the pixel right before.
EXPECT_PIXEL_COLOR_EQ((quarterWidth * 3), (quarterHeight * 3) - 1, GLColor::blue);
// dead center to validate the hourglass.
EXPECT_PIXEL_COLOR_EQ((quarterWidth * 2), quarterHeight * 2, GLColor::blue);
// Verify line is closed between the 2 last vertices
EXPECT_PIXEL_COLOR_EQ((quarterWidth * 2), quarterHeight, GLColor::blue);
}
};
// Draw an hourglass with a drawElements call followed by a square with drawArrays.
TEST_P(LineLoopStateChangeTest, DrawElementsThenDrawArrays)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
glUseProgram(program);
// We expect to draw a square with these 4 vertices with a drawArray call.
std::vector<Vector3> vertices;
CreatePixelCenterWindowCoords({{8, 8}, {8, 24}, {24, 24}, {24, 8}}, getWindowWidth(),
getWindowHeight(), &vertices);
// If we use these indices to draw however, we should be drawing an hourglass.
auto indices = std::vector<GLushort>{0, 2, 1, 3};
GLint mPositionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, mPositionLocation);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLushort), &indices[0],
GL_STATIC_DRAW);
glVertexAttribPointer(mPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(mPositionLocation);
glClear(GL_COLOR_BUFFER_BIT);
glDrawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_SHORT, nullptr); // hourglass
glDrawArrays(GL_LINE_LOOP, 0, 4); // square
glDisableVertexAttribArray(mPositionLocation);
validateSquareAndHourglass();
}
// Draw line loop using a drawArrays followed by an hourglass with drawElements.
TEST_P(LineLoopStateChangeTest, DrawArraysThenDrawElements)
{
// http://anglebug.com/2856: Seems to fail on older drivers and pass on newer.
// Tested failing on 18.3.3 and passing on 18.9.2.
ANGLE_SKIP_TEST_IF(IsAMD() && IsVulkan() && IsWindows());
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
glUseProgram(program);
// We expect to draw a square with these 4 vertices with a drawArray call.
std::vector<Vector3> vertices;
CreatePixelCenterWindowCoords({{8, 8}, {8, 24}, {24, 24}, {24, 8}}, getWindowWidth(),
getWindowHeight(), &vertices);
// If we use these indices to draw however, we should be drawing an hourglass.
auto indices = std::vector<GLushort>{0, 2, 1, 3};
GLint mPositionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, mPositionLocation);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLushort), &indices[0],
GL_STATIC_DRAW);
glVertexAttribPointer(mPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(mPositionLocation);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_LINE_LOOP, 0, 4); // square
glDrawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_SHORT, nullptr); // hourglass
glDisableVertexAttribArray(mPositionLocation);
validateSquareAndHourglass();
}
// Draw a triangle with a drawElements call and a non-zero offset and draw the same
// triangle with the same offset again followed by a line loop with drawElements.
TEST_P(LineLoopStateChangeTest, DrawElementsThenDrawElements)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
glUseProgram(program);
// Background Red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// We expect to draw a triangle with the last three points on the bottom right,
// draw with LineLoop, and then draw a triangle with the same non-zero offset.
auto vertices = std::vector<Vector3>{
{-1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}};
auto indices = std::vector<GLushort>{0, 1, 2, 1, 2, 3};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLushort), &indices[0],
GL_STATIC_DRAW);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
// Draw a triangle with a non-zero offset on the bottom right.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *)(3 * sizeof(GLushort)));
// Draw with LineLoop.
glDrawElements(GL_LINE_LOOP, 3, GL_UNSIGNED_SHORT, nullptr);
// Draw the triangle again with the same offset.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *)(3 * sizeof(GLushort)));
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Validate the top left point's color.
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::blue);
// Validate the triangle is drawn on the bottom right.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight, GLColor::blue);
// Validate the triangle is NOT on the top left part.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight * 3, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight * 2, GLColor::red);
}
// Simple state change tests, primarily focused on basic object lifetime and dependency management
// with back-ends that don't support that automatically (i.e. Vulkan).
class SimpleStateChangeTest : public ANGLETest<>
{
protected:
static constexpr int kWindowSize = 64;
SimpleStateChangeTest()
{
setWindowWidth(kWindowSize);
setWindowHeight(kWindowSize);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
void simpleDrawWithBuffer(GLBuffer *buffer);
void simpleDrawWithColor(const GLColor &color);
using UpdateFunc = std::function<void(GLenum, GLTexture *, GLint, GLint, const GLColor &)>;
void updateTextureBoundToFramebufferHelper(UpdateFunc updateFunc);
void bindTextureToFbo(GLFramebuffer &fbo, GLTexture &texture);
void drawToFboWithCulling(const GLenum frontFace, bool earlyFrontFaceDirty);
};
class SimpleStateChangeTestES3 : public SimpleStateChangeTest
{
protected:
void blendAndVerifyColor(const GLColor32F blendColor, const GLColor expectedColor)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
EXPECT_GL_NO_ERROR();
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(program);
GLint colorUniformLocation =
glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
glUniform4f(colorUniformLocation, blendColor.R, blendColor.G, blendColor.B, blendColor.A);
EXPECT_GL_NO_ERROR();
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedColor, 1);
}
};
class SimpleStateChangeTestES31 : public SimpleStateChangeTestES3
{};
class SimpleStateChangeTestComputeES31 : public SimpleStateChangeTest
{
protected:
void testSetUp() override
{
glGenFramebuffers(1, &mFramebuffer);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2);
EXPECT_GL_NO_ERROR();
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=2, local_size_y=2) in;
layout (rgba8, binding = 0) readonly uniform highp image2D srcImage;
layout (rgba8, binding = 1) writeonly uniform highp image2D dstImage;
void main()
{
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)));
})";
mProgram = CompileComputeProgram(kCS);
ASSERT_NE(mProgram, 0u);
glBindImageTexture(1, mTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture,
0);
ASSERT_GL_NO_ERROR();
}
void testTearDown() override
{
if (mFramebuffer != 0)
{
glDeleteFramebuffers(1, &mFramebuffer);
mFramebuffer = 0;
}
if (mTexture != 0)
{
glDeleteTextures(1, &mTexture);
mTexture = 0;
}
glDeleteProgram(mProgram);
}
GLuint mProgram;
GLuint mFramebuffer = 0;
GLuint mTexture = 0;
};
class ImageES31PPO
{
protected:
ImageES31PPO() : mComputeProg(0), mPipeline(0) {}
void bindProgramPipeline(const GLchar *computeString)
{
mComputeProg = glCreateShaderProgramv(GL_COMPUTE_SHADER, 1, &computeString);
ASSERT_NE(mComputeProg, 0u);
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &mPipeline);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_COMPUTE_SHADER_BIT, mComputeProg);
EXPECT_GL_NO_ERROR();
glBindProgramPipeline(mPipeline);
EXPECT_GL_NO_ERROR();
glActiveShaderProgram(mPipeline, mComputeProg);
EXPECT_GL_NO_ERROR();
}
GLuint mComputeProg;
GLuint mPipeline;
};
class SimpleStateChangeTestComputeES31PPO : public ImageES31PPO, public SimpleStateChangeTest
{
protected:
SimpleStateChangeTestComputeES31PPO() : ImageES31PPO(), SimpleStateChangeTest() {}
void testTearDown() override
{
if (mFramebuffer != 0)
{
glDeleteFramebuffers(1, &mFramebuffer);
mFramebuffer = 0;
}
if (mTexture != 0)
{
glDeleteTextures(1, &mTexture);
mTexture = 0;
}
glDeleteProgramPipelines(1, &mPipeline);
}
GLuint mFramebuffer = 0;
GLuint mTexture = 0;
};
constexpr char kSimpleVertexShader[] = R"(attribute vec2 position;
attribute vec4 color;
varying vec4 vColor;
void main()
{
gl_Position = vec4(position, 0, 1);
vColor = color;
}
)";
constexpr char kSimpleFragmentShader[] = R"(precision mediump float;
varying vec4 vColor;
void main()
{
gl_FragColor = vColor;
}
)";
void SimpleStateChangeTest::simpleDrawWithBuffer(GLBuffer *buffer)
{
ANGLE_GL_PROGRAM(program, kSimpleVertexShader, kSimpleFragmentShader);
glUseProgram(program);
GLint colorLoc = glGetAttribLocation(program, "color");
ASSERT_NE(-1, colorLoc);
glBindBuffer(GL_ARRAY_BUFFER, *buffer);
glVertexAttribPointer(colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
glEnableVertexAttribArray(colorLoc);
drawQuad(program, "position", 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
}
void SimpleStateChangeTest::simpleDrawWithColor(const GLColor &color)
{
std::vector<GLColor> colors(6, color);
GLBuffer colorBuffer;
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(GLColor), colors.data(), GL_STATIC_DRAW);
simpleDrawWithBuffer(&colorBuffer);
}
// Test that we can do a drawElements call successfully after making a drawArrays call in the same
// frame.
TEST_P(SimpleStateChangeTest, DrawArraysThenDrawElements)
{
// http://anglebug.com/4121
ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGLES());
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
glUseProgram(program);
// We expect to draw a triangle with the first 3 points to the left, then another triangle with
// the last 3 vertices using a drawElements call.
auto vertices = std::vector<Vector3>{{-1.0f, -1.0f, 0.0f},
{-1.0f, 1.0f, 0.0f},
{0.0f, 0.0f, 0.0f},
{1.0f, 1.0f, 0.0f},
{1.0f, -1.0f, 0.0f}};
// If we use these indices to draw we'll be using the last 2 vertex only to draw.
auto indices = std::vector<GLushort>{2, 3, 4};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLushort), &indices[0],
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
for (int i = 0; i < 10; i++)
{
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3); // triangle to the left
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr); // triangle to the right
glFinish();
}
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int halfHeight = getWindowHeight() / 2;
// Validate triangle to the left
EXPECT_PIXEL_COLOR_EQ(quarterWidth, halfHeight, GLColor::blue);
// Validate triangle to the right
EXPECT_PIXEL_COLOR_EQ((quarterWidth * 3), halfHeight, GLColor::blue);
}
// Draw a triangle with drawElements and a non-zero offset and draw the same
// triangle with the same offset followed by binding the same element buffer.
TEST_P(SimpleStateChangeTest, DrawElementsThenDrawElements)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
glUseProgram(program);
// Background Red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// We expect to draw the triangle with the last three points on the bottom right, and
// rebind the same element buffer and draw with the same indices.
auto vertices = std::vector<Vector3>{
{-1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}};
auto indices = std::vector<GLushort>{0, 1, 2, 1, 2, 3};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLushort), &indices[0],
GL_STATIC_DRAW);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *)(3 * sizeof(GLushort)));
// Rebind the same element buffer.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
// Draw the triangle again with the same offset.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *)(3 * sizeof(GLushort)));
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Validate the triangle is drawn on the bottom right.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight, GLColor::blue);
// Validate the triangle is NOT on the top left part.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight * 3, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight * 2, GLColor::red);
}
// Draw a triangle with drawElements then change the index buffer and draw again.
TEST_P(SimpleStateChangeTest, DrawElementsThenDrawElementsNewIndexBuffer)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(program);
// Background Red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// We expect to draw the triangle with the last three points on the bottom right, and
// rebind the same element buffer and draw with the same indices.
auto vertices = std::vector<Vector3>{
{-1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}};
auto indices8 = std::vector<GLubyte>{0, 1, 2, 1, 2, 3};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLint colorUniformLocation =
glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
glUniform4f(colorUniformLocation, 1.0f, 1.0f, 1.0f, 1.0f);
GLBuffer indexBuffer8;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer8);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices8.size() * sizeof(GLubyte), &indices8[0],
GL_STATIC_DRAW);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
auto indices2nd8 = std::vector<GLubyte>{2, 3, 0, 0, 1, 2};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices2nd8.size() * sizeof(GLubyte), &indices2nd8[0],
GL_STATIC_DRAW);
glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
// Draw the triangle again with the same offset.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Validate the triangle is drawn on the bottom left.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight * 2, GLColor::blue);
// Validate the triangle is NOT on the top right part.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight * 3, GLColor::white);
}
// Draw a triangle with drawElements then change the indices and draw again.
TEST_P(SimpleStateChangeTest, DrawElementsThenDrawElementsNewIndices)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(program);
// Background Red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// We expect to draw the triangle with the last three points on the bottom right, and
// rebind the same element buffer and draw with the same indices.
std::vector<Vector3> vertices = {
{-1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}};
std::vector<GLubyte> indices8 = {0, 1, 2, 2, 3, 0};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLint colorUniformLocation =
glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
glUniform4f(colorUniformLocation, 1.0f, 1.0f, 1.0f, 1.0f);
GLBuffer indexBuffer8;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer8);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices8.size() * sizeof(GLubyte), &indices8[0],
GL_DYNAMIC_DRAW);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
std::vector<GLubyte> newIndices8 = {2, 3, 0};
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, newIndices8.size() * sizeof(GLubyte),
&newIndices8[0]);
glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
// Draw the triangle again with the same offset.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Validate the triangle is drawn on the bottom left.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight * 2, GLColor::blue);
// Validate the triangle is NOT on the top right part.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight * 3, GLColor::white);
}
// Draw a triangle with drawElements then change the indices and draw again. Similar to
// DrawElementsThenDrawElementsNewIndices, but changes the whole index buffer (not just half). This
// triggers a different path in the Vulkan backend based on the fact that the majority of the buffer
// is being updated.
TEST_P(SimpleStateChangeTest, DrawElementsThenDrawElementsWholeNewIndices)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(program);
// Background Red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// We expect to draw the triangle with the last three points on the bottom right, and
// rebind the same element buffer and draw with the same indices.
std::vector<Vector3> vertices = {
{-1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}};
std::vector<GLubyte> indices8 = {0, 1, 2, 2, 3, 0};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLint colorUniformLocation =
glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
glUniform4f(colorUniformLocation, 1.0f, 1.0f, 1.0f, 1.0f);
GLBuffer indexBuffer8;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer8);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices8.size() * sizeof(GLubyte), &indices8[0],
GL_DYNAMIC_DRAW);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
std::vector<GLubyte> newIndices8 = {2, 3, 0, 0, 0, 0};
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, newIndices8.size() * sizeof(GLubyte),
&newIndices8[0]);
glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
// Draw the triangle again with the same offset.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Validate the triangle is drawn on the bottom left.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight * 2, GLColor::blue);
// Validate the triangle is NOT on the top right part.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight * 3, GLColor::white);
}
// Draw a triangle with drawElements and a non-zero offset and draw the same
// triangle with the same offset followed by binding a USHORT element buffer.
TEST_P(SimpleStateChangeTest, DrawElementsUBYTEX2ThenDrawElementsUSHORT)
{
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(program);
// Background Red color
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// We expect to draw the triangle with the last three points on the bottom right, and
// rebind the same element buffer and draw with the same indices.
auto vertices = std::vector<Vector3>{
{-1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}};
auto indices8 = std::vector<GLubyte>{0, 1, 2, 1, 2, 3};
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
GLint colorUniformLocation =
glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
glUniform4f(colorUniformLocation, 1.0f, 1.0f, 1.0f, 1.0f);
GLBuffer indexBuffer8;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer8);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices8.size() * sizeof(GLubyte), &indices8[0],
GL_STATIC_DRAW);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
auto indices2nd8 = std::vector<GLubyte>{2, 3, 0, 0, 1, 2};
GLBuffer indexBuffer2nd8;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer2nd8);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices2nd8.size() * sizeof(GLubyte), &indices2nd8[0],
GL_STATIC_DRAW);
glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *)(0 * sizeof(GLubyte)));
// Bind the 16bit element buffer.
auto indices16 = std::vector<GLushort>{0, 1, 3, 1, 2, 3};
GLBuffer indexBuffer16;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer16);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices16.size() * sizeof(GLushort), &indices16[0],
GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer16);
glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
// Draw the triangle again with the same offset.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (void *)(0 * sizeof(GLushort)));
glDisableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
int quarterWidth = getWindowWidth() / 4;
int quarterHeight = getWindowHeight() / 4;
// Validate green triangle is drawn on the bottom.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight, GLColor::green);
// Validate white triangle is drawn on the right.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 3, quarterHeight * 2, GLColor::white);
// Validate blue triangle is on the top left part.
EXPECT_PIXEL_COLOR_EQ(quarterWidth * 2, quarterHeight * 3, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight * 2, GLColor::blue);
}
// Draw a points use multiple unaligned vertex buffer with same data,
// verify all the rendering results are the same.
TEST_P(SimpleStateChangeTest, DrawRepeatUnalignedVboChange)
{
// http://anglebug.com/4470
ANGLE_SKIP_TEST_IF(isSwiftshader() && (IsWindows() || IsLinux()));
const int kRepeat = 2;
// set up VBO, colorVBO is unaligned
GLBuffer positionBuffer;
constexpr size_t posOffset = 0;
const GLfloat posData[] = {0.5f, 0.5f};
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(posData), posData, GL_STATIC_DRAW);
GLBuffer colorBuffers[kRepeat];
constexpr size_t colorOffset = 1;
const GLfloat colorData[] = {0.515f, 0.515f, 0.515f, 1.0f};
constexpr size_t colorBufferSize = colorOffset + sizeof(colorData);
uint8_t colorDataUnaligned[colorBufferSize] = {0};
memcpy(reinterpret_cast<void *>(colorDataUnaligned + colorOffset), colorData,
sizeof(colorData));
for (uint32_t i = 0; i < kRepeat; i++)
{
glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[i]);
glBufferData(GL_ARRAY_BUFFER, colorBufferSize, colorDataUnaligned, GL_STATIC_DRAW);
}
// set up frame buffer
GLFramebuffer framebuffer;
GLTexture framebufferTexture;
bindTextureToFbo(framebuffer, framebufferTexture);
// set up program
ANGLE_GL_PROGRAM(program, kSimpleVertexShader, kSimpleFragmentShader);
glUseProgram(program);
GLuint colorAttrLocation = glGetAttribLocation(program, "color");
glEnableVertexAttribArray(colorAttrLocation);
GLuint posAttrLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(posAttrLocation);
EXPECT_GL_NO_ERROR();
// draw and get drawing results
constexpr size_t kRenderSize = kWindowSize * kWindowSize;
std::array<GLColor, kRenderSize> pixelBufs[kRepeat];
for (uint32_t i = 0; i < kRepeat; i++)
{
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glVertexAttribPointer(posAttrLocation, 2, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const void *>(posOffset));
glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[i]);
glVertexAttribPointer(colorAttrLocation, 4, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const void *>(colorOffset));
glDrawArrays(GL_POINTS, 0, 1);
// read drawing results
glReadPixels(0, 0, kWindowSize, kWindowSize, GL_RGBA, GL_UNSIGNED_BYTE,
pixelBufs[i].data());
EXPECT_GL_NO_ERROR();
}
// verify something is drawn
static_assert(kRepeat >= 2, "More than one repetition required");
std::array<GLColor, kRenderSize> pixelAllBlack{0};
EXPECT_NE(pixelBufs[0], pixelAllBlack);
// verify drawing results are all identical
for (uint32_t i = 1; i < kRepeat; i++)
{
EXPECT_EQ(pixelBufs[i - 1], pixelBufs[i]);
}
}
// Handles deleting a Buffer when it's being used.
TEST_P(SimpleStateChangeTest, DeleteBufferInUse)
{
std::vector<GLColor> colorData(6, GLColor::red);
GLBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * colorData.size(), colorData.data(),
GL_STATIC_DRAW);
simpleDrawWithBuffer(&buffer);
buffer.reset();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
// Tests that resizing a Buffer during a draw works as expected.
TEST_P(SimpleStateChangeTest, RedefineBufferInUse)
{
std::vector<GLColor> redColorData(6, GLColor::red);
GLBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * redColorData.size(), redColorData.data(),
GL_STATIC_DRAW);
// Trigger a pull from the buffer.
simpleDrawWithBuffer(&buffer);
// Redefine the buffer that's in-flight.
std::vector<GLColor> greenColorData(1024, GLColor::green);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * greenColorData.size(), greenColorData.data(),
GL_STATIC_DRAW);
// Trigger the flush and verify the first draw worked.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw again and verify the new data is correct.
simpleDrawWithBuffer(&buffer);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Tests updating a buffer's contents while in use, without redefining it.
TEST_P(SimpleStateChangeTest, UpdateBufferInUse)
{
std::vector<GLColor> redColorData(6, GLColor::red);
GLBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * redColorData.size(), redColorData.data(),
GL_STATIC_DRAW);
// Trigger a pull from the buffer.
simpleDrawWithBuffer(&buffer);
// Update the buffer that's in-flight.
std::vector<GLColor> greenColorData(6, GLColor::green);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLColor) * greenColorData.size(),
greenColorData.data());
// Trigger the flush and verify the first draw worked.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw again and verify the new data is correct.
simpleDrawWithBuffer(&buffer);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Tests that deleting an in-flight Texture does not immediately delete the resource.
TEST_P(SimpleStateChangeTest, DeleteTextureInUse)
{
std::array<GLColor, 4> colors = {
{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
draw2DTexturedQuad(0.5f, 1.0f, true);
tex.reset();
EXPECT_GL_NO_ERROR();
int w = getWindowWidth() - 2;
int h = getWindowHeight() - 2;
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
}
// Tests that modifying a texture parameter in-flight does not cause problems.
TEST_P(SimpleStateChangeTest, ChangeTextureFilterModeBetweenTwoDraws)
{
std::array<GLColor, 4> colors = {
{GLColor::black, GLColor::white, GLColor::black, GLColor::white}};
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw to the left side of the window only with NEAREST.
glViewport(0, 0, getWindowWidth() / 2, getWindowHeight());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
draw2DTexturedQuad(0.5f, 1.0f, true);
// Draw to the right side of the window only with LINEAR.
glViewport(getWindowWidth() / 2, 0, getWindowWidth() / 2, getWindowHeight());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
draw2DTexturedQuad(0.5f, 1.0f, true);
EXPECT_GL_NO_ERROR();
glViewport(0, 0, getWindowWidth(), getWindowHeight());
// The first half (left) should be only black followed by plain white.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::black);
EXPECT_PIXEL_COLOR_EQ((getWindowWidth() / 2) - 3, 0, GLColor::white);
EXPECT_PIXEL_COLOR_EQ((getWindowWidth() / 2) - 4, 0, GLColor::white);
// The second half (right) should be a gradient so we shouldn't find plain black/white in the
// middle.
EXPECT_NE(angle::ReadColor((getWindowWidth() / 4) * 3, 0), GLColor::black);
EXPECT_NE(angle::ReadColor((getWindowWidth() / 4) * 3, 0), GLColor::white);
}
// Tests that bind the same texture all the time between different draw calls.
TEST_P(SimpleStateChangeTest, RebindTextureDrawAgain)
{
GLuint program = get2DTexturedQuadProgram();
glUseProgram(program);
std::array<GLColor, 4> colors = {{GLColor::cyan, GLColor::cyan, GLColor::cyan, GLColor::cyan}};
// Setup the texture
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Setup the vertex array to draw a quad.
GLint positionLocation = glGetAttribLocation(program, "position");
setupQuadVertexBuffer(1.0f, 1.0f);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
// Draw quad
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Bind again
glBindTexture(GL_TEXTURE_2D, tex);
ASSERT_GL_NO_ERROR();
// Draw again, should still work.
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Validate whole surface is filled with cyan.
int h = getWindowHeight() - 1;
int w = getWindowWidth() - 1;
EXPECT_PIXEL_RECT_EQ(0, 0, w, h, GLColor::cyan);
}
// Tests that we can draw with a texture, modify the texture with a texSubImage, and then draw again
// correctly.
TEST_P(SimpleStateChangeTest, DrawWithTextureTexSubImageThenDrawAgain)
{
GLuint program = get2DTexturedQuadProgram();
ASSERT_NE(0u, program);
glUseProgram(program);
std::array<GLColor, 4> colors = {{GLColor::red, GLColor::red, GLColor::red, GLColor::red}};
std::array<GLColor, 4> subColors = {
{GLColor::green, GLColor::green, GLColor::green, GLColor::green}};
// Setup the texture
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Setup the vertex array to draw a quad.
GLint positionLocation = glGetAttribLocation(program, "position");
setupQuadVertexBuffer(1.0f, 1.0f);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
// Draw quad
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Update bottom-half of texture with green.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, subColors.data());
ASSERT_GL_NO_ERROR();
// Draw again, should still work.
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Validate first half of the screen is red and the bottom is green.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 4 * 3, GLColor::red);
}
// Test that we can alternate between textures between different draws.
TEST_P(SimpleStateChangeTest, DrawTextureAThenTextureBThenTextureA)
{
GLuint program = get2DTexturedQuadProgram();
glUseProgram(program);
std::array<GLColor, 4> colorsTex1 = {
{GLColor::cyan, GLColor::cyan, GLColor::cyan, GLColor::cyan}};
std::array<GLColor, 4> colorsTex2 = {
{GLColor::magenta, GLColor::magenta, GLColor::magenta, GLColor::magenta}};
// Setup the texture
GLTexture tex1;
glBindTexture(GL_TEXTURE_2D, tex1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colorsTex1.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLTexture tex2;
glBindTexture(GL_TEXTURE_2D, tex2);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colorsTex2.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Setup the vertex array to draw a quad.
GLint positionLocation = glGetAttribLocation(program, "position");
setupQuadVertexBuffer(1.0f, 1.0f);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
// Draw quad
glBindTexture(GL_TEXTURE_2D, tex1);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Bind again, draw again
glBindTexture(GL_TEXTURE_2D, tex2);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// Bind again, draw again
glBindTexture(GL_TEXTURE_2D, tex1);
glDrawArrays(GL_TRIANGLES, 0, 6);
// Validate whole surface is filled with cyan.
int h = getWindowHeight() - 1;
int w = getWindowWidth() - 1;
EXPECT_PIXEL_RECT_EQ(0, 0, w, h, GLColor::cyan);
}
// Tests that redefining an in-flight Texture does not affect the in-flight resource.
TEST_P(SimpleStateChangeTest, RedefineTextureInUse)
{
std::array<GLColor, 4> colors = {
{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Draw with the first texture.
draw2DTexturedQuad(0.5f, 1.0f, true);
// Redefine the in-flight texture.
constexpr int kBigSize = 32;
std::vector<GLColor> bigColors;
for (int y = 0; y < kBigSize; ++y)
{
for (int x = 0; x < kBigSize; ++x)
{
bool xComp = x < kBigSize / 2;
bool yComp = y < kBigSize / 2;
if (yComp)
{
bigColors.push_back(xComp ? GLColor::cyan : GLColor::magenta);
}
else
{
bigColors.push_back(xComp ? GLColor::yellow : GLColor::white);
}
}
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, bigColors.data());
EXPECT_GL_NO_ERROR();
// Verify the first draw had the correct data via ReadPixels.
int w = getWindowWidth() - 2;
int h = getWindowHeight() - 2;
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
// Draw and verify with the redefined data.
draw2DTexturedQuad(0.5f, 1.0f, true);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::magenta);
EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::white);
}
// Test updating a Texture's contents while in use by GL works as expected.
TEST_P(SimpleStateChangeTest, UpdateTextureInUse)
{
std::array<GLColor, 4> rgby = {{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
// Set up 2D quad resources.
GLuint program = get2DTexturedQuadProgram();
glUseProgram(program);
ASSERT_EQ(0, glGetAttribLocation(program, "position"));
const auto &quadVerts = GetQuadVertices();
GLBuffer vbo;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0);
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgby.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Draw RGBY to the Framebuffer. The texture is now in-use by GL.
const int w = getWindowWidth() - 2;
const int h = getWindowHeight() - 2;
const int w2 = w >> 1;
glViewport(0, 0, w2, h);
glDrawArrays(GL_TRIANGLES, 0, 6);
// Update the texture to be YBGR, while the Texture is in-use. Should not affect the draw.
std::array<GLColor, 4> ybgr = {{GLColor::yellow, GLColor::blue, GLColor::green, GLColor::red}};
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, ybgr.data());
ASSERT_GL_NO_ERROR();
// Draw again to the Framebuffer. The second draw call should use the updated YBGR data.
glViewport(w2, 0, w2, h);
glDrawArrays(GL_TRIANGLES, 0, 6);
// Check the Framebuffer. Both draws should have completed.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(w2 - 1, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, h - 1, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w2 - 1, h - 1, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(w2 + 1, 0, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(w - 1, 0, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(w2 + 1, h - 1, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(w - 1, h - 1, GLColor::red);
ASSERT_GL_NO_ERROR();
}
void SimpleStateChangeTest::updateTextureBoundToFramebufferHelper(UpdateFunc updateFunc)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_framebuffer_blit"));
std::vector<GLColor> red(4, GLColor::red);
std::vector<GLColor> green(4, GLColor::green);
GLTexture renderTarget;
glBindTexture(GL_TEXTURE_2D, renderTarget);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
GLFramebuffer fbo;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTarget,
0);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);
glViewport(0, 0, 2, 2);
ASSERT_GL_NO_ERROR();
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Draw once to flush dirty state bits.
draw2DTexturedQuad(0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
// Update the (0, 1) pixel to be blue
updateFunc(GL_TEXTURE_2D, &renderTarget, 0, 1, GLColor::blue);
// Draw green to the right half of the Framebuffer.
glBindTexture(GL_TEXTURE_2D, tex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, green.data());
glViewport(1, 0, 1, 2);
draw2DTexturedQuad(0.5f, 1.0f, true);
// Update the (1, 1) pixel to be yellow
updateFunc(GL_TEXTURE_2D, &renderTarget, 1, 1, GLColor::yellow);
ASSERT_GL_NO_ERROR();
// Verify we have a quad with the right colors in the FBO.
std::vector<GLColor> expected = {
{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
std::vector<GLColor> actual(4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glReadPixels(0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, actual.data());
EXPECT_EQ(expected, actual);
}
// Tests that TexSubImage updates are flushed before rendering.
TEST_P(SimpleStateChangeTest, TexSubImageOnTextureBoundToFrambuffer)
{
// http://anglebug.com/4092
ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
auto updateFunc = [](GLenum textureBinding, GLTexture *tex, GLint x, GLint y,
const GLColor &color) {
glBindTexture(textureBinding, *tex);
glTexSubImage2D(textureBinding, 0, x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, color.data());
};
updateTextureBoundToFramebufferHelper(updateFunc);
}
// Tests that CopyTexSubImage updates are flushed before rendering.
TEST_P(SimpleStateChangeTest, CopyTexSubImageOnTextureBoundToFrambuffer)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_framebuffer_blit"));
GLTexture copySource;
glBindTexture(GL_TEXTURE_2D, copySource);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer copyFBO;
glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFBO);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copySource, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);
auto updateFunc = [&copySource](GLenum textureBinding, GLTexture *tex, GLint x, GLint y,
const GLColor &color) {
glBindTexture(GL_TEXTURE_2D, copySource);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, color.data());
glBindTexture(textureBinding, *tex);
glCopyTexSubImage2D(textureBinding, 0, x, y, 0, 0, 1, 1);
};
updateTextureBoundToFramebufferHelper(updateFunc);
}
// Tests that the read framebuffer doesn't affect what the draw call thinks the attachments are
// (which is what the draw framebuffer dictates) when a command is issued with the GL_FRAMEBUFFER
// target.
TEST_P(SimpleStateChangeTestES3, ReadFramebufferDrawFramebufferDifferentAttachments)
{
// http://anglebug.com/4092
ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
GLRenderbuffer drawColorBuffer;
glBindRenderbuffer(GL_RENDERBUFFER, drawColorBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1);
GLRenderbuffer drawDepthBuffer;
glBindRenderbuffer(GL_RENDERBUFFER, drawDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1);
GLRenderbuffer readColorBuffer;