blob: 123aa81ec8cd2e5863cec63a6c87bab6ce05af10 [file]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#endif
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <GLES3/gl3.h>
#include <stdint.h>
#include <vector>
#include "build/build_config.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "gpu/config/gpu_test_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/extension_set.h"
#include "ui/gl/gl_context.h"
namespace gpu {
// A collection of tests that exercise the glClear workaround.
class GLClearFramebufferTest : public testing::TestWithParam<bool> {
public:
GLClearFramebufferTest() : color_handle_(0u), depth_handle_(0u) {}
protected:
virtual GLManager::Options GetGlManagerOptions() {
return GLManager::Options();
}
void SetUp() override {
if (GetParam()) {
// Force the glClear() workaround so we can test it here.
GpuDriverBugWorkarounds workarounds;
workarounds.gl_clear_broken = true;
gl_.InitializeWithWorkarounds(GetGlManagerOptions(), workarounds);
DCHECK(gl_.workarounds().gl_clear_broken);
} else {
gl_.Initialize(GetGlManagerOptions());
DCHECK(!gl_.workarounds().gl_clear_broken);
}
}
void InitDraw();
void SetDrawColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a);
void SetDrawDepth(GLfloat depth);
void DrawQuad();
void TearDown() override {
GLTestHelper::CheckGLError("no errors", __LINE__);
gl_.Destroy();
}
protected:
GLManager gl_;
GLuint color_handle_;
GLuint depth_handle_;
};
void GLClearFramebufferTest::InitDraw() {
static const char* v_shader_str =
"attribute vec4 a_Position;\n"
"uniform float u_depth;\n"
"void main()\n"
"{\n"
" gl_Position = a_Position;\n"
" gl_Position.z = u_depth;\n"
"}\n";
static const char* f_shader_str =
"precision mediump float;\n"
"uniform vec4 u_draw_color;\n"
"void main()\n"
"{\n"
" gl_FragColor = u_draw_color;\n"
"}\n";
GLuint program = GLTestHelper::LoadProgram(v_shader_str, f_shader_str);
DCHECK(program);
glUseProgram(program);
GLuint position_loc = glGetAttribLocation(program, "a_Position");
GLTestHelper::SetupUnitQuad(position_loc);
color_handle_ = glGetUniformLocation(program, "u_draw_color");
DCHECK(color_handle_ != static_cast<GLuint>(-1));
depth_handle_ = glGetUniformLocation(program, "u_depth");
DCHECK(depth_handle_ != static_cast<GLuint>(-1));
}
void GLClearFramebufferTest::SetDrawColor(GLfloat r,
GLfloat g,
GLfloat b,
GLfloat a) {
glUniform4f(color_handle_, r, g, b, a);
}
void GLClearFramebufferTest::SetDrawDepth(GLfloat depth) {
glUniform1f(depth_handle_, depth);
}
void GLClearFramebufferTest::DrawQuad() {
glDrawArrays(GL_TRIANGLES, 0, 6);
}
INSTANTIATE_TEST_SUITE_P(GLClearFramebufferTestWithParam,
GLClearFramebufferTest,
::testing::Values(true, false));
TEST_P(GLClearFramebufferTest, ClearColor) {
glClearColor(1.0f, 0.5f, 0.25f, 0.5f);
glClear(GL_COLOR_BUFFER_BIT);
// Verify.
const uint8_t expected[] = {255, 128, 64, 128};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 1 /* tolerance */, expected,
nullptr));
}
TEST_P(GLClearFramebufferTest, ClearColorWithMask) {
glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Verify.
const uint8_t expected[] = {255, 0, 0, 0};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, expected,
nullptr));
}
// crbug.com/434094
#if !BUILDFLAG(IS_MAC)
TEST_P(GLClearFramebufferTest, ClearColorWithScissor) {
// TODO(jonahr): Test fails on Linux with ANGLE/passthrough
// (crbug.com/1099770)
gpu::GPUTestBotConfig bot_config;
if (bot_config.LoadCurrentConfig(nullptr) &&
bot_config.Matches("linux passthrough")) {
return;
}
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Verify.
const uint8_t expected[] = {255, 255, 255, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, expected,
nullptr));
glScissor(0, 0, 0, 0);
glEnable(GL_SCISSOR_TEST);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
// Verify - no changes.
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, expected,
nullptr));
}
#endif
TEST_P(GLClearFramebufferTest, ClearDepthStencil) {
// TODO(kainino): https://crbug.com/782317
if (GPUTestBotConfig::CurrentConfigMatches("Intel")) {
return;
}
const GLuint kStencilRef = 1 << 2;
InitDraw();
SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
DrawQuad();
// Verify.
const uint8_t kRed[] = {255, 0, 0, 255};
const uint8_t kGreen[] = {0, 255, 0, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
glClearStencil(kStencilRef);
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_NOTEQUAL, kStencilRef, 0xFFFFFFFF);
SetDrawColor(0.0f, 1.0f, 0.0f, 1.0f);
DrawQuad();
// Verify - stencil should have failed, so still red.
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
glStencilFunc(GL_EQUAL, kStencilRef, 0xFFFFFFFF);
DrawQuad();
// Verify - stencil should have passed, so green.
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kGreen,
nullptr));
glEnable(GL_DEPTH_TEST);
glClearDepthf(0.0f);
glClear(GL_DEPTH_BUFFER_BIT);
SetDrawDepth(0.5f);
SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
DrawQuad();
// Verify - depth test should have failed, so still green.
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kGreen,
nullptr));
glClearDepthf(0.9f);
glClear(GL_DEPTH_BUFFER_BIT);
DrawQuad();
// Verify - depth test should have passed, so red.
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
}
TEST_P(GLClearFramebufferTest, SeparateFramebufferClear) {
const char* extension_string =
reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
gfx::ExtensionSet extensions = gfx::MakeExtensionSet(extension_string);
bool has_separate_framebuffer =
gfx::HasExtension(extensions, "GL_CHROMIUM_framebuffer_multisample");
if (!has_separate_framebuffer) {
return;
}
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
// Bind incomplete read framebuffer, should not affect clear.
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
EXPECT_NE(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER),
static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));
glClearColor(1.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
gl_.BindOffscreenFramebuffer(GL_READ_FRAMEBUFFER);
const uint8_t kRed[] = {255, 0, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr));
// Bind complete, but smaller read framebuffer, should not affect clear.
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture, 0);
EXPECT_EQ(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER),
static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));
glClearColor(0.f, 1.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
gl_.BindOffscreenFramebuffer(GL_READ_FRAMEBUFFER);
const uint8_t kGreen[] = {0, 255, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(3, 3, 1, 1, 0, kGreen, nullptr));
}
class ES3ClearBufferTest : public GLClearFramebufferTest {
protected:
static const GLsizei kCanvasSize = 4;
static const size_t kTfBufferSizeBytes = 1024;
GLManager::Options GetGlManagerOptions() override {
GLManager::Options options;
options.size = gfx::Size(kCanvasSize, kCanvasSize);
options.context_type = CONTEXT_TYPE_OPENGLES3;
return options;
}
bool ShouldSkipTest() const {
// If a driver isn't capable of supporting ES3 context, creating
// ContextGroup will fail.
// See crbug.com/654709.
return (!gl_.decoder() || !gl_.decoder()->GetContextGroup());
}
bool ShouldSkipTransformFeedbackTest() const {
if (!GetParam()) {
return false;
}
#if defined(ARCH_CPU_X86_FAMILY)
std::string renderer =
reinterpret_cast<const char*>(glGetString(GL_RENDERER));
// android-29;google_apis;x86 does not follow OpenGL ES 3.0+ spec. It
// doesn't allow any primitive type to be used in glDrawArrays, etc. while
// transform feedback is paused, which will cause an error in the
// gl_clear_broken workaround.
return renderer ==
"Android Emulator OpenGL ES Translator (Google SwiftShader)";
#else
return false;
#endif
}
void InitDrawWithTF();
protected:
GLuint tf_buffer_ = 0;
};
INSTANTIATE_TEST_SUITE_P(ES3ClearBufferTestWithParam,
ES3ClearBufferTest,
::testing::Values(true, false));
void ES3ClearBufferTest::InitDrawWithTF() {
static const char* vs =
"#version 300 es\n"
"in vec4 a_Position;\n"
"out vec4 v_out;\n"
"uniform float u_depth;\n"
"void main()\n"
"{\n"
" v_out = a_Position;\n"
" gl_Position = a_Position;\n"
" gl_Position.z = u_depth;\n"
"}\n";
static const char* fs =
"#version 300 es\n"
"precision mediump float;\n"
"uniform vec4 u_draw_color;\n"
"out vec4 fragColor;\n"
"void main()\n"
"{\n"
" fragColor = u_draw_color;\n"
"}\n";
GLuint vertex_shader = GLTestHelper::CompileShader(GL_VERTEX_SHADER, vs);
GLuint fragment_shader = GLTestHelper::CompileShader(GL_FRAGMENT_SHADER, fs);
GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
const char* varyings[] = {"v_out"};
glTransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glLinkProgram(program);
GLint linked = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
ASSERT_EQ(GL_TRUE, linked);
DCHECK(program);
glUseProgram(program);
GLuint position_loc = glGetAttribLocation(program, "a_Position");
GLTestHelper::SetupUnitQuad(position_loc);
color_handle_ = glGetUniformLocation(program, "u_draw_color");
DCHECK(color_handle_ != static_cast<GLuint>(-1));
depth_handle_ = glGetUniformLocation(program, "u_depth");
DCHECK(depth_handle_ != static_cast<GLuint>(-1));
// Set up transform feedback outputs
glGenBuffers(1, &tf_buffer_);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tf_buffer_);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, kTfBufferSizeBytes, nullptr,
GL_DYNAMIC_COPY);
GLuint tf;
glGenTransformFeedbacks(1, &tf);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tf_buffer_);
}
TEST_P(ES3ClearBufferTest, ClearBuffersuiv) {
if (ShouldSkipTest())
return;
// This is a regression test for https://crbug.com/908749
GLuint value[1] = {0u};
glClearBufferuiv(GL_STENCIL, 0, value);
// The above call should not crash in ASAN build.
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
GLTestHelper::CheckGLError("no errors", __LINE__);
}
TEST_P(ES3ClearBufferTest,
DrawArraysWithUnclearedAttachmentAndTransformFeedback) {
if (ShouldSkipTest()) {
GTEST_SKIP() << "ES3 not supported";
}
if (ShouldSkipTransformFeedbackTest()) {
GTEST_SKIP() << "Transform feedback not supported in workaround";
}
InitDrawWithTF();
glBeginTransformFeedback(GL_TRIANGLES);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Create framebuffer with an uncleared texture attachment and bind it as
// the draw framebuffer. This will trigger a clear from HandleDrawArrays ->
// DoMultiDrawArrays -> CheckBoundDrawFramebufferValid ->
// CheckFramebufferValid -> ClearUnclearedAttachments when drawing quad
GLuint fbo, texture;
glGenFramebuffers(1, &fbo);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr /* data, nullptr to make it uncleared */);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture, 0);
EXPECT_EQ(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),
static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));
// Draw quad to fbo.
SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
DrawQuad();
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
const uint8_t kRed[] = {255, 0, 0, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
GLint tf_active = 0;
GLint tf_paused = 0;
glGetIntegerv(GL_TRANSFORM_FEEDBACK_ACTIVE, &tf_active);
glGetIntegerv(GL_TRANSFORM_FEEDBACK_PAUSED, &tf_paused);
EXPECT_EQ(1, tf_active);
EXPECT_EQ(0, tf_paused);
glEndTransformFeedback();
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// GLTestHelper::SetupUnitQuad + DrawQuad: 2 triangles (6 vertices) forming a
// unit quad
constexpr size_t kQuadVertexCount = 6;
constexpr size_t kVec4ComponentCount = 4;
constexpr size_t kFeedbackSize =
kQuadVertexCount * kVec4ComponentCount * sizeof(GLfloat);
uint8_t buffer_data[kFeedbackSize] = {0};
glGetBufferSubDataCHROMIUM(GL_TRANSFORM_FEEDBACK_BUFFER, 0, kFeedbackSize,
buffer_data);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// SAFETY: glBufferSubDataCHROMIUM would fail if reading out of bounds of the
// GPU buffer and the kFeedbackSize is a multiple of the span size for bytes
// -> floats.
auto feedback_span =
UNSAFE_BUFFERS(base::span(reinterpret_cast<const GLfloat*>(buffer_data),
kQuadVertexCount * kVec4ComponentCount));
for (size_t vertex_index = 0; vertex_index < kQuadVertexCount;
++vertex_index) {
size_t base_index = vertex_index * kVec4ComponentCount;
GLfloat w = feedback_span[base_index + 3];
GLfloat x = feedback_span[base_index];
GLfloat y = feedback_span[base_index + 1];
// Unit quad vertex component check
EXPECT_FLOAT_EQ(1.0f, std::abs(x))
<< "x component wrong at vertex " << vertex_index;
EXPECT_FLOAT_EQ(1.0f, std::abs(y))
<< "y component wrong at vertex " << vertex_index;
// 1.0f means it was set. SetupUnitQuad vertices are lower dimension than
// vec4 (vec2), but 1.0f should be filled in for it.
EXPECT_FLOAT_EQ(1.0f, w) << "w component wrong at vertex " << vertex_index;
}
}
TEST_P(ES3ClearBufferTest, ClearDoesNotWriteToTransformFeedbackBuffer) {
if (ShouldSkipTest()) {
GTEST_SKIP() << "ES3 not supported";
}
if (ShouldSkipTransformFeedbackTest()) {
GTEST_SKIP() << "Transform feedback not supported in workaround";
}
InitDrawWithTF();
glBeginTransformFeedback(GL_TRIANGLES);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Issue several clears while transform feedback is active. Transform feedback
// should only capture Primitives generated by the Vertex Processing step(s).
// The workaround should have the effect of glClear which is not part of
// Vertex Processing.
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kRed[] = {255, 0, 0, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
GLint tf_active = 0;
GLint tf_paused = 0;
glGetIntegerv(GL_TRANSFORM_FEEDBACK_ACTIVE, &tf_active);
glGetIntegerv(GL_TRANSFORM_FEEDBACK_PAUSED, &tf_paused);
EXPECT_EQ(1, tf_active);
EXPECT_EQ(0, tf_paused);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kGreen[] = {0, 255, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kGreen,
nullptr));
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kBlue[] = {0, 0, 255, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kBlue, nullptr));
glEndTransformFeedback();
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// The TF buffer was initialized with nullptr (all zeros). If glClear
// workaround incorrectly wrote anything to it, we would see non-zero values.
constexpr size_t kBufferFloats = kTfBufferSizeBytes / sizeof(GLfloat);
uint8_t buffer_data[kTfBufferSizeBytes] = {0};
glGetBufferSubDataCHROMIUM(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
kTfBufferSizeBytes, buffer_data);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// SAFETY: glBufferSubDataCHROMIUM would fail if reading out of bounds of the
// GPU buffer and the kFeedbackSizekTfBufferSizeBytes is a multiple of the
// span size for bytes -> floats.
auto feedback_span = UNSAFE_BUFFERS(
base::span(reinterpret_cast<const GLfloat*>(buffer_data), kBufferFloats));
for (size_t float_index = 0; float_index < kBufferFloats; ++float_index) {
EXPECT_EQ(0.0f, feedback_span[float_index])
<< "TF buffer nonzero at float index " << float_index;
}
}
TEST_P(ES3ClearBufferTest, ClearSucceedsWithDifferentTransformFeedbackTypes) {
if (ShouldSkipTest()) {
GTEST_SKIP() << "ES3 not supported";
}
if (ShouldSkipTransformFeedbackTest()) {
GTEST_SKIP() << "Transform feedback not supported in workaround";
}
InitDrawWithTF();
glBeginTransformFeedback(GL_TRIANGLES);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kRed[] = {255, 0, 0, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
glEndTransformFeedback();
glBeginTransformFeedback(GL_LINES);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kGreen[] = {0, 255, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kGreen,
nullptr));
glEndTransformFeedback();
glBeginTransformFeedback(GL_POINTS);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kBlue[] = {0, 0, 255, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kBlue, nullptr));
glEndTransformFeedback();
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
TEST_P(ES3ClearBufferTest,
ClearSucceedsWithPausingAndResumingTransformFeedback) {
if (ShouldSkipTest()) {
GTEST_SKIP() << "ES3 not supported";
}
if (ShouldSkipTransformFeedbackTest()) {
GTEST_SKIP() << "Transform feedback not supported in workaround";
}
InitDrawWithTF();
glBeginTransformFeedback(GL_TRIANGLES);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kRed[] = {255, 0, 0, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kRed, nullptr));
glPauseTransformFeedback();
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kGreen[] = {0, 255, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kGreen,
nullptr));
glResumeTransformFeedback();
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
const uint8_t kBlue[] = {0, 0, 255, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(0, 0, 1, 1, 0 /* tolerance */, kBlue, nullptr));
glEndTransformFeedback();
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
TEST_P(ES3ClearBufferTest, RasterizerDiscardIntegerClearBypass) {
if (ShouldSkipTest()) {
return;
}
const GLsizei kDirtyWidth = 256;
const GLsizei kDirtyHeight = 256;
// Step 0: Dirty VRAM using a separate context.
{
GLManager gl2;
gl2.Initialize(GetGlManagerOptions());
gl2.MakeCurrent();
for (int i = 0; i < 8; ++i) {
GLuint rb = 0;
glGenRenderbuffers(1, &rb);
glBindRenderbuffer(GL_RENDERBUFFER, rb);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kDirtyWidth,
kDirtyHeight);
GLuint fb = 0;
glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, rb);
EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(0.8f, 0.2f, 0.6f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDeleteFramebuffers(1, &fb);
glDeleteRenderbuffers(1, &rb);
}
glFinish();
gl2.Destroy();
}
// Restore main context.
gl_.MakeCurrent();
// Step 1: Trigger the bug.
GLuint rb = 0;
glGenRenderbuffers(1, &rb);
glBindRenderbuffer(GL_RENDERBUFFER, rb);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8UI, kDirtyWidth, kDirtyHeight);
GLuint fb = 0;
glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, rb);
EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
glCheckFramebufferStatus(GL_FRAMEBUFFER));
glEnable(GL_RASTERIZER_DISCARD);
ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// readPixels to trigger lazy clear.
std::vector<GLuint> pixels(kDirtyWidth * kDirtyHeight * 4, 0xAAAAAAAAu);
glReadPixels(0, 0, kDirtyWidth, kDirtyHeight, GL_RGBA_INTEGER,
GL_UNSIGNED_INT, pixels.data());
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Inspect results.
uint32_t nonzero_components = 0;
for (GLuint val : pixels) {
if (val != 0) {
nonzero_components++;
}
}
// If bug is present, we expect non-zero components (leak from dirty VRAM).
// If fixed, we expect ALL zero.
EXPECT_EQ(0u, nonzero_components);
// Cleanup.
glDisable(GL_RASTERIZER_DISCARD);
glDeleteFramebuffers(1, &fb);
glDeleteRenderbuffers(1, &rb);
}
} // namespace gpu