| // 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/gl2chromium.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); | |
| } | |
| class GLReattachFboDepthStencilTest : public GLClearFramebufferTest { | |
| protected: | |
| void SetUp() override { | |
| GpuDriverBugWorkarounds workarounds; | |
| if (GetParam()) { | |
| workarounds.reattach_fbo_depth_stencil_on_reallocation = true; | |
| } | |
| gl_.InitializeWithWorkarounds(GetGlManagerOptions(), workarounds); | |
| } | |
| void RunReattachFboDepthStencilTest(bool use_texture, | |
| bool bind_fbo_during_reallocation); | |
| }; | |
| INSTANTIATE_TEST_SUITE_P(GLReattachFboDepthStencilTestWithParam, | |
| GLReattachFboDepthStencilTest, | |
| ::testing::Bool()); | |
| void GLReattachFboDepthStencilTest::RunReattachFboDepthStencilTest( | |
| bool use_texture, | |
| bool bind_fbo_during_reallocation) { | |
| GLuint fbo = 0; | |
| glGenFramebuffers(1, &fbo); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| GLuint color_tex = 0; | |
| glGenTextures(1, &color_tex); | |
| glBindTexture(GL_TEXTURE_2D, color_tex); | |
| 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, | |
| color_tex, 0); | |
| GLuint depth_obj = 0; | |
| if (use_texture) { | |
| glGenTextures(1, &depth_obj); | |
| glBindTexture(GL_TEXTURE_2D, depth_obj); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 16, 16, 0, | |
| GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, | |
| depth_obj, 0); | |
| } else { | |
| glGenRenderbuffers(1, &depth_obj); | |
| glBindRenderbuffer(GL_RENDERBUFFER, depth_obj); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, | |
| GL_RENDERBUFFER, depth_obj); | |
| } | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_FRAMEBUFFER)); | |
| // Clear color to Green, depth to 1.0 (far). | |
| glClearColor(0.0f, 1.0f, 0.0f, 1.0f); | |
| glClearDepthf(1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| const uint8_t kGreen[] = {0, 255, 0, 255}; | |
| const uint8_t kRed[] = {255, 0, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr)); | |
| // Enable depth test. Draw Red quad at depth 0.5. Should pass (0.5 < 1.0). | |
| glEnable(GL_DEPTH_TEST); | |
| glDepthFunc(GL_LESS); | |
| InitDraw(); | |
| SetDrawDepth(0.5f); | |
| SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f); | |
| DrawQuad(); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr)); | |
| // Clear color to Green, depth to 0.0 (near). | |
| glClearColor(0.0f, 1.0f, 0.0f, 1.0f); | |
| glClearDepthf(0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr)); | |
| // Draw Red quad at depth 0.5. Should fail (0.5 is not < 0.0). | |
| DrawQuad(); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr)); | |
| if (!bind_fbo_during_reallocation) { | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| } | |
| // Redefine depth attachment (reallocate to 16x16). | |
| if (use_texture) { | |
| glBindTexture(GL_TEXTURE_2D, depth_obj); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 16, 16, 0, | |
| GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr); | |
| } else { | |
| glBindRenderbuffer(GL_RENDERBUFFER, depth_obj); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16); | |
| } | |
| if (!bind_fbo_during_reallocation) { | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| } | |
| // Clear depth to 1.0. | |
| glClearDepthf(1.0f); | |
| glClear(GL_DEPTH_BUFFER_BIT); | |
| // Draw Red quad at depth 0.5. Should pass (0.5 < 1.0). | |
| SetDrawDepth(0.5f); | |
| SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f); | |
| DrawQuad(); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr)); | |
| glDeleteFramebuffers(1, &fbo); | |
| glDeleteTextures(1, &color_tex); | |
| if (use_texture) { | |
| glDeleteTextures(1, &depth_obj); | |
| } else { | |
| glDeleteRenderbuffers(1, &depth_obj); | |
| } | |
| } | |
| TEST_P(GLReattachFboDepthStencilTest, TextureReallocationBound) { | |
| RunReattachFboDepthStencilTest(true, true); | |
| } | |
| TEST_P(GLReattachFboDepthStencilTest, TextureReallocationUnbound) { | |
| RunReattachFboDepthStencilTest(true, false); | |
| } | |
| TEST_P(GLReattachFboDepthStencilTest, RenderbufferReallocationBound) { | |
| RunReattachFboDepthStencilTest(false, true); | |
| } | |
| TEST_P(GLReattachFboDepthStencilTest, RenderbufferReallocationUnbound) { | |
| RunReattachFboDepthStencilTest(false, false); | |
| } | |
| class ES3ReattachFboDepthStencilTest : public GLReattachFboDepthStencilTest { | |
| protected: | |
| GLManager::Options GetGlManagerOptions() override { | |
| GLManager::Options options; | |
| options.context_type = CONTEXT_TYPE_OPENGLES3; | |
| return options; | |
| } | |
| bool ShouldSkipTest() const { | |
| return (!gl_.decoder() || !gl_.decoder()->GetContextGroup()); | |
| } | |
| void RunTexImage2DToTexStorage2DTest(bool bind_fbo_during_reallocation); | |
| }; | |
| INSTANTIATE_TEST_SUITE_P(ES3ReattachFboDepthStencilTestWithParam, | |
| ES3ReattachFboDepthStencilTest, | |
| ::testing::Bool()); | |
| void ES3ReattachFboDepthStencilTest::RunTexImage2DToTexStorage2DTest( | |
| bool bind_fbo_during_reallocation) { | |
| if (ShouldSkipTest()) { | |
| return; | |
| } | |
| GLuint fbo = 0; | |
| glGenFramebuffers(1, &fbo); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| GLuint color_tex = 0; | |
| glGenTextures(1, &color_tex); | |
| glBindTexture(GL_TEXTURE_2D, color_tex); | |
| 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, | |
| color_tex, 0); | |
| GLuint depth_tex = 0; | |
| glGenTextures(1, &depth_tex); | |
| glBindTexture(GL_TEXTURE_2D, depth_tex); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 16, 16, 0, | |
| GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, | |
| depth_tex, 0); | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_FRAMEBUFFER)); | |
| // Clear color to Green, depth to 1.0 (far). | |
| glClearColor(0.0f, 1.0f, 0.0f, 1.0f); | |
| glClearDepthf(1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| const uint8_t kGreen[] = {0, 255, 0, 255}; | |
| const uint8_t kRed[] = {255, 0, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr)); | |
| // Enable depth test. Draw Red quad at depth 0.5. Should pass. | |
| glEnable(GL_DEPTH_TEST); | |
| glDepthFunc(GL_LESS); | |
| InitDraw(); | |
| SetDrawDepth(0.5f); | |
| SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f); | |
| DrawQuad(); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr)); | |
| // Clear color to Green, depth to 0.0 (near). | |
| glClearColor(0.0f, 1.0f, 0.0f, 1.0f); | |
| glClearDepthf(0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr)); | |
| // Draw Red quad at depth 0.5. Should fail. | |
| DrawQuad(); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr)); | |
| if (!bind_fbo_during_reallocation) { | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| } | |
| // Redefine depth attachment via glTexStorage2D. | |
| glBindTexture(GL_TEXTURE_2D, depth_tex); | |
| glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT16, 16, 16); | |
| if (!bind_fbo_during_reallocation) { | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| } | |
| // Clear depth to 1.0. | |
| glClearDepthf(1.0f); | |
| glClear(GL_DEPTH_BUFFER_BIT); | |
| // Draw Red quad at depth 0.5. Should pass. | |
| SetDrawDepth(0.5f); | |
| SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f); | |
| DrawQuad(); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr)); | |
| glDeleteFramebuffers(1, &fbo); | |
| glDeleteTextures(1, &color_tex); | |
| glDeleteTextures(1, &depth_tex); | |
| } | |
| TEST_P(ES3ReattachFboDepthStencilTest, TexImage2DToTexStorage2DBound) { | |
| RunTexImage2DToTexStorage2DTest(true); | |
| } | |
| TEST_P(ES3ReattachFboDepthStencilTest, TexImage2DToTexStorage2DUnbound) { | |
| RunTexImage2DToTexStorage2DTest(false); | |
| } | |
| } // namespace gpu |