| // |
| // Copyright 2016 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| |
| // SRGBFramebufferTest.cpp: Tests of sRGB framebuffer functionality. |
| |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/gl_raii.h" |
| |
| namespace |
| { |
| constexpr angle::GLColor linearColor(64, 127, 191, 255); |
| constexpr angle::GLColor srgbColor(13, 54, 133, 255); |
| } // namespace |
| |
| namespace angle |
| { |
| |
| class SRGBFramebufferTest : public ANGLETest<> |
| { |
| protected: |
| SRGBFramebufferTest() |
| { |
| setWindowWidth(128); |
| setWindowHeight(128); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| } |
| |
| void testSetUp() override |
| { |
| mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor()); |
| ASSERT_NE(0u, mProgram); |
| |
| mColorLocation = glGetUniformLocation(mProgram, essl1_shaders::ColorUniform()); |
| ASSERT_NE(-1, mColorLocation); |
| } |
| |
| void testTearDown() override { glDeleteProgram(mProgram); } |
| |
| GLuint mProgram = 0; |
| GLint mColorLocation = -1; |
| }; |
| |
| class SRGBFramebufferTestES3 : public SRGBFramebufferTest |
| {}; |
| |
| // Test basic validation of GL_EXT_sRGB_write_control |
| TEST_P(SRGBFramebufferTest, Validation) |
| { |
| GLenum expectedError = |
| IsGLExtensionEnabled("GL_EXT_sRGB_write_control") ? GL_NO_ERROR : GL_INVALID_ENUM; |
| |
| GLboolean value = GL_FALSE; |
| glEnable(GL_FRAMEBUFFER_SRGB_EXT); |
| EXPECT_GL_ERROR(expectedError); |
| |
| glGetBooleanv(GL_FRAMEBUFFER_SRGB_EXT, &value); |
| EXPECT_GL_ERROR(expectedError); |
| if (expectedError == GL_NO_ERROR) |
| { |
| EXPECT_GL_TRUE(value); |
| } |
| |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| EXPECT_GL_ERROR(expectedError); |
| |
| glGetBooleanv(GL_FRAMEBUFFER_SRGB_EXT, &value); |
| EXPECT_GL_ERROR(expectedError); |
| if (expectedError == GL_NO_ERROR) |
| { |
| EXPECT_GL_FALSE(value); |
| } |
| } |
| |
| // Test basic functionality of GL_EXT_sRGB_write_control |
| TEST_P(SRGBFramebufferTest, BasicUsage) |
| { |
| if (!IsGLExtensionEnabled("GL_EXT_sRGB_write_control") || |
| (!IsGLExtensionEnabled("GL_EXT_sRGB") && getClientMajorVersion() < 3)) |
| { |
| std::cout |
| << "Test skipped because GL_EXT_sRGB_write_control and GL_EXT_sRGB are not available." |
| << std::endl; |
| return; |
| } |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA_EXT, 1, 1, 0, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE, |
| nullptr); |
| |
| GLFramebuffer framebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| glUseProgram(mProgram); |
| glUniform4fv(mColorLocation, 1, srgbColor.toNormalizedVector().data()); |
| |
| glEnable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0); |
| } |
| |
| // Test that GL_EXT_sRGB_write_control state applies to all framebuffers if multiple are used |
| // 1. disable srgb |
| // 2. draw to both framebuffers |
| // 3. enable srgb |
| // 4. draw to both framebuffers |
| TEST_P(SRGBFramebufferTest, MultipleFramebuffers) |
| { |
| if (!IsGLExtensionEnabled("GL_EXT_sRGB_write_control") || |
| (!IsGLExtensionEnabled("GL_EXT_sRGB") && getClientMajorVersion() < 3)) |
| { |
| std::cout |
| << "Test skipped because GL_EXT_sRGB_write_control and GL_EXT_sRGB are not available." |
| << std::endl; |
| return; |
| } |
| |
| // NVIDIA failures on older drivers |
| // http://anglebug.com/42264177 |
| ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGLES()); |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA_EXT, 1, 1, 0, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE, |
| nullptr); |
| |
| GLFramebuffer framebuffer1; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| glUseProgram(mProgram); |
| glUniform4fv(mColorLocation, 1, srgbColor.toNormalizedVector().data()); |
| |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0); |
| |
| GLFramebuffer framebuffer2; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer2); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0); |
| |
| glEnable(GL_FRAMEBUFFER_SRGB_EXT); |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer2); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| } |
| |
| // Test that we behave correctly when we toggle FRAMEBUFFER_SRGB_EXT on a framebuffer that has an |
| // attachment in linear colorspace |
| TEST_P(SRGBFramebufferTest, NegativeAlreadyLinear) |
| { |
| if (!IsGLExtensionEnabled("GL_EXT_sRGB_write_control") || |
| (!IsGLExtensionEnabled("GL_EXT_sRGB") && getClientMajorVersion() < 3)) |
| { |
| std::cout |
| << "Test skipped because GL_EXT_sRGB_write_control and GL_EXT_sRGB are not available." |
| << std::endl; |
| return; |
| } |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| |
| GLFramebuffer framebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| glUseProgram(mProgram); |
| glUniform4fv(mColorLocation, 1, linearColor.toNormalizedVector().data()); |
| |
| glEnable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| } |
| |
| // Test that lifetimes of internal resources are tracked correctly by deleting a texture and then |
| // attempting to use it. This is expected to produce a non-fatal error. |
| TEST_P(SRGBFramebufferTest, NegativeLifetimeTracking) |
| { |
| if (!IsGLExtensionEnabled("GL_EXT_sRGB_write_control") || |
| (!IsGLExtensionEnabled("GL_EXT_sRGB") && getClientMajorVersion() < 3)) |
| { |
| std::cout |
| << "Test skipped because GL_EXT_sRGB_write_control and GL_EXT_sRGB are not available." |
| << std::endl; |
| return; |
| } |
| |
| // NVIDIA failures |
| // http://anglebug.com/42264177 |
| ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGLES()); |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA_EXT, 1, 1, 0, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE, |
| nullptr); |
| |
| GLFramebuffer framebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| glUseProgram(mProgram); |
| glUniform4fv(mColorLocation, 1, srgbColor.toNormalizedVector().data()); |
| |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0); |
| |
| // Delete the texture |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); |
| texture.reset(); |
| |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION); |
| |
| GLColor throwaway_color; |
| glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &throwaway_color); |
| EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION); |
| } |
| |
| // Test that glBlitFramebuffer correctly converts colorspaces |
| TEST_P(SRGBFramebufferTestES3, BlitFramebuffer) |
| { |
| // http://anglebug.com/42264326 |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| if (!IsGLExtensionEnabled("GL_EXT_sRGB_write_control") || |
| (!IsGLExtensionEnabled("GL_EXT_sRGB") && getClientMajorVersion() < 3)) |
| { |
| std::cout |
| << "Test skipped because GL_EXT_sRGB_write_control and GL_EXT_sRGB are not available." |
| << std::endl; |
| return; |
| } |
| |
| GLTexture dstTexture; |
| glBindTexture(GL_TEXTURE_2D, dstTexture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA_EXT, 1, 1, 0, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE, |
| nullptr); |
| GLFramebuffer dstFramebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, dstFramebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstTexture, 0); |
| |
| GLTexture srcTexture; |
| glBindTexture(GL_TEXTURE_2D, srcTexture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA_EXT, 1, 1, 0, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE, |
| nullptr); |
| |
| GLFramebuffer srcFramebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, srcFramebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, 0); |
| |
| glUseProgram(mProgram); |
| glUniform4fv(mColorLocation, 1, srgbColor.toNormalizedVector().data()); |
| |
| // Draw onto the framebuffer normally |
| glEnable(GL_FRAMEBUFFER_SRGB_EXT); |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| |
| // Blit the framebuffer normally |
| glEnable(GL_FRAMEBUFFER_SRGB_EXT); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFramebuffer); |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFramebuffer); |
| glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, dstFramebuffer); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0); |
| |
| // Blit the framebuffer with forced linear colorspace |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFramebuffer); |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFramebuffer); |
| glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, dstFramebuffer); |
| EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0); |
| } |
| |
| // This test reproduces an issue in the Vulkan backend found in the Chromium CI that |
| // was caused by enabling the VK_KHR_image_format_list extension on SwiftShader |
| // which exposed GL_EXT_sRGB_write_control. |
| TEST_P(SRGBFramebufferTest, DrawToSmallFBOClearLargeFBO) |
| { |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_sRGB_write_control") || |
| (!IsGLExtensionEnabled("GL_EXT_sRGB") && getClientMajorVersion() < 3)); |
| |
| // Disabling GL_FRAMEBUFFER_SRGB_EXT caused the issue |
| glDisable(GL_FRAMEBUFFER_SRGB_EXT); |
| |
| // The issue involved framebuffers of two different sizes. |
| // The smaller needed to be drawn to, while the larger one could be just cleared |
| // to reproduce the issue. These are the smallest tested sizes that generated |
| // the validation error. |
| constexpr GLsizei kDimensionsSmall[] = {1, 1}; |
| constexpr GLsizei kDimensionsLarge[] = {2, 2}; |
| { |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kDimensionsSmall[0], kDimensionsSmall[1]); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| |
| GLFramebuffer framebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| unsigned char vertexData[] = {0}; |
| GLBuffer vertexBuffer; |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(char), vertexData, GL_STATIC_DRAW); |
| |
| unsigned int indexData[] = {0}; |
| GLBuffer indexBuffer; |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int), indexData, GL_STATIC_DRAW); |
| |
| glUseProgram(mProgram); |
| |
| glDrawElements(GL_POINTS, 1, GL_UNSIGNED_INT, nullptr); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| { |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kDimensionsLarge[0], kDimensionsLarge[1]); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| |
| GLFramebuffer framebuffer; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| |
| // Vulkan validation happened to fail here with: |
| // "Cannot execute a render pass with renderArea not within the bound of the framebuffer" |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| } |
| |
| // Use this to select which configurations (e.g. which renderer, which GLES major version) these |
| // tests should be run against. |
| ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(SRGBFramebufferTest); |
| ANGLE_INSTANTIATE_TEST_ES3(SRGBFramebufferTestES3); |
| |
| } // namespace angle |