// // Copyright 2019 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. // // MultisampleTest: Tests of multisampled default framebuffer #include "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" #include "util/OSWindow.h" #include "util/shader_utils.h" #include "util/test_utils.h" using namespace angle; namespace { class MultisampleTest : public ANGLETest<> { protected: MultisampleTest() { setWindowWidth(kWindowWidth); setWindowHeight(kWindowHeight); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); setConfigDepthBits(24); setConfigStencilBits(8); setSamples(4); setMultisampleEnabled(true); } void prepareVertexBuffer(GLBuffer &vertexBuffer, const Vector3 *vertices, size_t vertexCount, GLint positionLocation) { glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(*vertices) * vertexCount, vertices, GL_STATIC_DRAW); glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glEnableVertexAttribArray(positionLocation); } protected: static constexpr int kWindowWidth = 16; static constexpr int kWindowHeight = 8; }; class MultisampleTestES3 : public MultisampleTest {}; class MultisampleTestES32 : public MultisampleTest {}; // Test point rendering on a multisampled surface. GLES2 section 3.3.1. TEST_P(MultisampleTest, Point) { // http://anglebug.com/42262135 ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES()); // http://anglebug.com/42264264 ANGLE_SKIP_TEST_IF(IsOzone()); constexpr char kPointsVS[] = R"(precision highp float; attribute vec4 a_position; void main() { gl_PointSize = 3.0; gl_Position = a_position; })"; ANGLE_GL_PROGRAM(program, kPointsVS, essl1_shaders::fs::Red()); glUseProgram(program); const GLint positionLocation = glGetAttribLocation(program, "a_position"); GLBuffer vertexBuffer; const Vector3 vertices[1] = {{0.0f, 0.0f, 0.0f}}; prepareVertexBuffer(vertexBuffer, vertices, 1, positionLocation); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_POINTS, 0, 1); ASSERT_GL_NO_ERROR(); // The center pixels should be all red. EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2, kWindowHeight / 2, GLColor::red); EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2 - 1, kWindowHeight / 2, GLColor::red); EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2, kWindowHeight / 2 - 1, GLColor::red); EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2 - 1, kWindowHeight / 2 - 1, GLColor::red); // Border pixels should be between red and black, and not exactly either; corners are darker and // sides are brighter. const GLColor kSideColor = {128, 0, 0, 128}; const GLColor kCornerColor = {64, 0, 0, 64}; constexpr int kErrorMargin = 16; EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2 - 2, kCornerColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2 + 1, kCornerColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2 - 2, kCornerColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2 + 1, kCornerColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2 - 1, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 1, kWindowHeight / 2 - 2, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 1, kWindowHeight / 2 + 1, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2, kWindowHeight / 2 - 2, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2, kWindowHeight / 2 + 1, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2 - 1, kSideColor, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2, kSideColor, kErrorMargin); } // Test line rendering on a multisampled surface. GLES2 section 3.4.4. TEST_P(MultisampleTest, Line) { ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D()); // http://anglebug.com/42264264 ANGLE_SKIP_TEST_IF(IsOzone()); ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); glUseProgram(program); const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib()); GLBuffer vertexBuffer; const Vector3 vertices[2] = {{-1.0f, -0.3f, 0.0f}, {1.0f, 0.3f, 0.0f}}; prepareVertexBuffer(vertexBuffer, vertices, 2, positionLocation); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_LINES, 0, 2); ASSERT_GL_NO_ERROR(); // The line goes from left to right at about -17 degrees slope. It renders as such (captured // with renderdoc): // // D D = Dark Red (0.25) or (0.5) // BRA R = Red (1.0) // ARB M = Middle Red (0.75) // D B = Bright Red (1.0 or 0.75) // A = Any red (0.5, 0.75 or 1.0) // // Verify that rendering is done as above. const GLColor kDarkRed = {128, 0, 0, 128}; const GLColor kMidRed = {192, 0, 0, 192}; constexpr int kErrorMargin = 16; constexpr int kLargeMargin = 80; static_assert(kWindowWidth == 16, "Verification code written for 16x8 window"); EXPECT_PIXEL_COLOR_NEAR(0, 2, kDarkRed, kLargeMargin); EXPECT_PIXEL_COLOR_NEAR(3, 3, GLColor::red, kLargeMargin); EXPECT_PIXEL_COLOR_NEAR(4, 3, GLColor::red, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(6, 3, kMidRed, kLargeMargin); EXPECT_PIXEL_COLOR_NEAR(8, 4, kMidRed, kLargeMargin); EXPECT_PIXEL_COLOR_NEAR(11, 4, GLColor::red, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(12, 4, GLColor::red, kLargeMargin); EXPECT_PIXEL_COLOR_NEAR(15, 5, kDarkRed, kLargeMargin); } // Test polygon rendering on a multisampled surface. GLES2 section 3.5.3. TEST_P(MultisampleTest, Triangle) { // http://anglebug.com/42262135 ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES()); // http://anglebug.com/42264264 ANGLE_SKIP_TEST_IF(IsOzone()); ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); glUseProgram(program); const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib()); GLBuffer vertexBuffer; const Vector3 vertices[3] = {{-1.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}}; prepareVertexBuffer(vertexBuffer, vertices, 3, positionLocation); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); ASSERT_GL_NO_ERROR(); // Top-left pixels should be all red. EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 4, kWindowHeight / 4, GLColor::red); // Diagonal pixels from bottom-left to top-right are between red and black. Pixels above the // diagonal are red and pixels below it are black. const GLColor kMidRed = {128, 0, 0, 128}; // D3D11 is off by 63 for red (191 instead of 128), where other back-ends get 128 constexpr int kErrorMargin = 64; for (int i = 2; i + 2 < kWindowWidth; i += 2) { int j = kWindowHeight - 1 - (i / 2); EXPECT_PIXEL_COLOR_NEAR(i, j, kMidRed, kErrorMargin); EXPECT_PIXEL_COLOR_EQ(i, j - 1, GLColor::red); EXPECT_PIXEL_COLOR_EQ(i, j + 1, GLColor::transparentBlack); } } // Test polygon rendering on a multisampled surface. And rendering is interrupted by a compute pass // that converts the index buffer. Make sure the rendering's multisample result is preserved after // interruption. TEST_P(MultisampleTest, ContentPresevedAfterInterruption) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_rgb8_rgba8")); // http://anglebug.com/42262135 ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES()); // http://anglebug.com/42263216 ANGLE_SKIP_TEST_IF(IsD3D11()); // http://anglebug.com/42264264 ANGLE_SKIP_TEST_IF(IsOzone()); ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); glUseProgram(program); const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib()); if (IsGLExtensionEnabled("GL_EXT_discard_framebuffer")) { GLenum attachments[] = {GL_COLOR_EXT, GL_DEPTH_EXT, GL_STENCIL_EXT}; glDiscardFramebufferEXT(GL_FRAMEBUFFER, 3, attachments); } // Draw triangle GLBuffer vertexBuffer; const Vector3 vertices[3] = {{-1.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}}; prepareVertexBuffer(vertexBuffer, vertices, 3, positionLocation); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); ASSERT_GL_NO_ERROR(); // Draw a line GLBuffer vertexBuffer2; GLBuffer indexBuffer2; const Vector3 vertices2[2] = {{-1.0f, -0.3f, 0.0f}, {1.0f, 0.3f, 0.0f}}; const GLubyte indices2[] = {0, 1}; prepareVertexBuffer(vertexBuffer2, vertices2, 2, positionLocation); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer2); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW); glDrawElements(GL_LINES, 2, GL_UNSIGNED_BYTE, 0); ASSERT_GL_NO_ERROR(); // Top-left pixels should be all red. EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 4, kWindowHeight / 4, GLColor::red); // Triangle edge: // Diagonal pixels from bottom-left to top-right are between red and black. Pixels above the // diagonal are red and pixels below it are black. { const GLColor kMidRed = {128, 0, 0, 128}; constexpr int kErrorMargin = 16; for (int i = 2; i + 2 < kWindowWidth; i += 2) { // Exclude the middle pixel where the triangle and line cross each other. if (abs(kWindowHeight / 2 - (i / 2)) <= 1) { continue; } int j = kWindowHeight - 1 - (i / 2); EXPECT_PIXEL_COLOR_NEAR(i, j, kMidRed, kErrorMargin); EXPECT_PIXEL_COLOR_EQ(i, j - 1, GLColor::red); EXPECT_PIXEL_COLOR_EQ(i, j + 1, GLColor::transparentBlack); } } // Line edge: { const GLColor kDarkRed = {128, 0, 0, 128}; constexpr int kErrorMargin = 16; constexpr int kLargeMargin = 80; static_assert(kWindowWidth == 16, "Verification code written for 16x8 window"); // Exclude the triangle region. EXPECT_PIXEL_COLOR_NEAR(11, 4, GLColor::red, kErrorMargin); EXPECT_PIXEL_COLOR_NEAR(12, 4, GLColor::red, kLargeMargin); EXPECT_PIXEL_COLOR_NEAR(15, 5, kDarkRed, kLargeMargin); } } // Test that alpha to coverage is enabled works properly along with early fragment test. TEST_P(MultisampleTest, AlphaToSampleCoverage) { // http://anglebug.com/42264264 ANGLE_SKIP_TEST_IF(IsOzone()); constexpr char kFS[] = "precision highp float;\n" "void main()\n" "{\n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0);\n" "}\n"; ANGLE_GL_PROGRAM(transparentRedProgram, essl1_shaders::vs::Simple(), kFS); glUseProgram(transparentRedProgram); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glClearDepthf(1.0f); glClearColor(0.0f, 1.0f, 0.0f, 1.0f); // clear to green glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // This should pass depth test, but because of the alpha to coverage enabled, and alpha is 0, // the fragment should be discarded. If early fragment test is disabled, no depth will be // written. depth buffer should be 1.0. glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); // There was a bug in ANGLE that we are checking sampler coverage enabled or not instead of // alpha to sample coverage enabled or not. This is specically try to trick ANGLE so that it // will enable early fragment test. When early fragment test is accidentally enabled, then the // depth test will occur before fragment shader, and depth buffer maybe written with value // (0.0+1.0)/2.0=0.5. glEnable(GL_SAMPLE_COVERAGE); drawQuad(transparentRedProgram, essl1_shaders::PositionAttrib(), 0.0f); // Now draw with blue color but to test against 0.0f. This should fail depth test glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); glDisable(GL_SAMPLE_COVERAGE); glDepthFunc(GL_GREATER); ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue()); // Zd = 0.5f means (0.5+1.0)/2.0=0.75. Depends on early fragment on or off this will pass or // fail depth test. drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f); EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::green); ASSERT_GL_NO_ERROR(); } // Test that resolve from multisample default framebuffer works. TEST_P(MultisampleTestES3, ResolveToFBO) { GLTexture resolveTexture; glBindTexture(GL_TEXTURE_2D, resolveTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWindowWidth, kWindowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); GLFramebuffer resolveFBO; glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0); // Clear the default framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearColor(0.25, 0.5, 0.75, 0.25); glClear(GL_COLOR_BUFFER_BIT); // Resolve into FBO glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO); glClearColor(1, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glBlitFramebuffer(0, 0, kWindowWidth, kWindowHeight, 0, 0, kWindowWidth, kWindowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); ASSERT_GL_NO_ERROR(); const GLColor kResult = GLColor(63, 127, 191, 63); glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO); EXPECT_PIXEL_COLOR_NEAR(0, 0, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth - 1, 0, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(0, kWindowHeight - 1, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth - 1, kWindowHeight - 1, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2, kWindowHeight / 2, kResult, 1); } // Test that resolve from multisample default framebuffer after an open render pass works when the // framebuffer is also immediately implicitly resolved due to swap afterwards. TEST_P(MultisampleTestES3, RenderPassResolveToFBOThenSwap) { GLTexture resolveTexture; glBindTexture(GL_TEXTURE_2D, resolveTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWindowWidth, kWindowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); GLFramebuffer resolveFBO; glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0); auto runTest = [&](bool flipY) { // Open a render pass by drawing to the default framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); ANGLE_GL_PROGRAM(red, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); drawQuad(red, essl1_shaders::PositionAttrib(), 0.5f); // Resolve into FBO glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO); if (flipY) { glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1); } glBlitFramebuffer(0, 0, kWindowWidth, kWindowHeight, 0, 0, kWindowWidth, kWindowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); ASSERT_GL_NO_ERROR(); // Immediately swap so that an implicit resolve to the backbuffer happens right away. swapBuffers(); glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO); EXPECT_PIXEL_RECT_EQ(0, 0, kWindowWidth, kWindowHeight, GLColor::red); }; runTest(false); if (IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y")) { // With multiple backends, the default framebuffer is flipped w.r.t GL's coordinates. As a // result, the glBlitFramebuffer may need to take a different path from a direct multisample // resolve. This test ensures a direct resolve is also tested where possible. runTest(true); } } // Test that CopyTexImage2D from an MSAA default fbo works TEST_P(MultisampleTestES3, CopyTexImage2DFromMsaaDefaultFbo) { constexpr char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 my_FragColor; void main() { switch (uint(gl_SampleID)) { case 0u: my_FragColor = vec4(1.0, 0.9, 0.8, 0.7); break; case 1u: my_FragColor = vec4(0.0, 0.1, 0.2, 0.3); break; case 2u: my_FragColor = vec4(0.5, 0.25, 0.75, 1.0); break; case 3u: my_FragColor = vec4(0.4, 0.6, 0.2, 0.8); break; default: my_FragColor = vec4(0.0); break; } } )"; ANGLE_GL_PROGRAM(programMs, essl3_shaders::vs::Simple(), kFS); glUseProgram(programMs); // Clear the default framebuffer and draw glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearColor(0.25, 0.5, 0.75, 0.25); glClear(GL_COLOR_BUFFER_BIT); drawQuad(programMs, essl3_shaders::PositionAttrib(), 0.0); // Create a texture for copy GLTexture copyTexture; glBindTexture(GL_TEXTURE_2D, copyTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWindowWidth, kWindowHeight, 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); constexpr int kCopyWidth = 10; constexpr int kCopyHeight = 5; // Copy MSAA default framebuffer into the texture glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, kCopyWidth, kCopyHeight, 0); ASSERT_GL_NO_ERROR(); GLFramebuffer readFbo; glBindFramebuffer(GL_FRAMEBUFFER, readFbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copyTexture, 0); const GLColor kResult = GLColor(121, 118, 124, 178); EXPECT_PIXEL_COLOR_NEAR(0, 0, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(kCopyWidth - 1, 0, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(0, kCopyHeight - 1, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(kCopyWidth - 1, kCopyHeight - 1, kResult, 1); EXPECT_PIXEL_COLOR_NEAR(kCopyWidth / 2, kCopyHeight / 2, kResult, 1); } class MultisampleResolveTest : public ANGLETest<> { protected: static const GLColor kEXPECTED_R8; static const GLColor kEXPECTED_RG8; static const GLColor kEXPECTED_RGB8; static const GLColor kEXPECTED_RGBA8; static const GLColor32F kEXPECTED_RF; static const GLColor32F kEXPECTED_RGF; static const GLColor32F kEXPECTED_RGBF; static const GLColor32F kEXPECTED_RGBAF; static constexpr GLint kWidth = 13; static constexpr GLint kHeight = 11; MultisampleResolveTest() {} struct GLResources { GLFramebuffer fb; GLRenderbuffer rb; }; void resolveToFBO(GLenum format, GLint samples, GLint width, GLint height, GLResources &resources) { constexpr char kVS[] = R"(#version 300 es layout(location = 0) in vec4 position; void main() { gl_Position = position; } )"; constexpr char kFS[] = R"(#version 300 es precision highp float; out vec4 color; void main() { color = vec4(0.5, 0.6, 0.7, 0.8); } )"; ANGLE_GL_PROGRAM(program, kVS, kFS); // Make samples = 4 multi-sample framebuffer. GLFramebuffer fb0; glBindFramebuffer(GL_FRAMEBUFFER, fb0); GLRenderbuffer rb0; glBindRenderbuffer(GL_RENDERBUFFER, rb0); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb0); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Make samples = 0 multi-sample framebuffer. glBindFramebuffer(GL_FRAMEBUFFER, resources.fb); glBindRenderbuffer(GL_RENDERBUFFER, resources.rb); glRenderbufferStorageMultisample(GL_RENDERBUFFER, 0, format, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, resources.rb); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Draw quad to fb0. glBindFramebuffer(GL_FRAMEBUFFER, fb0); glViewport(0, 0, width, height); glUseProgram(program); GLBuffer buf; glBindBuffer(GL_ARRAY_BUFFER, buf); constexpr float vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, }; glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glDrawArrays(GL_TRIANGLES, 0, 6); // Blit fb0 to fb1. glBindFramebuffer(GL_READ_FRAMEBUFFER, fb0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resources.fb); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); ASSERT_GL_NO_ERROR(); // Prep for read pixels. glBindFramebuffer(GL_READ_FRAMEBUFFER, resources.fb); } void testResolveToUNormFBO(GLenum format, const GLColor &expected_color, GLint samples, GLint width, GLint height) { GLResources resources; resolveToFBO(format, samples, width, height, resources); // Check the results for (GLint y = 0; y < kHeight; ++y) { for (GLint x = 0; x < kWidth; ++x) { EXPECT_PIXEL_COLOR_NEAR(x, y, expected_color, 2); } } ASSERT_GL_NO_ERROR(); } void testResolveToHalfFBO(GLenum format, const GLColor32F &expected_color, GLint samples, GLint width, GLint height) { if (!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float")) { return; } GLResources resources; resolveToFBO(format, samples, width, height, resources); // Check the results for (GLint y = 0; y < kHeight; ++y) { for (GLint x = 0; x < kWidth; ++x) { EXPECT_PIXEL_COLOR32F_NEAR(x, y, expected_color, 2.0f / 255.0f); } } ASSERT_GL_NO_ERROR(); } void testResolveToFloatFBO(GLenum format, const GLColor32F &expected_color, GLint samples, GLint width, GLint height) { if (!IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgba")) { return; } GLResources resources; resolveToFBO(format, samples, width, height, resources); // Check the results for (GLint y = 0; y < kHeight; ++y) { for (GLint x = 0; x < kWidth; ++x) { EXPECT_PIXEL_COLOR32F_NEAR(x, y, expected_color, 2.0f / 255.0f); } } ASSERT_GL_NO_ERROR(); } void testResolveToRGBFloatFBO(GLenum format, const GLColor32F &expected_color, GLint samples, GLint width, GLint height) { if (!IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgb")) { return; } GLResources resources; resolveToFBO(format, samples, width, height, resources); // Check the results for (GLint y = 0; y < kHeight; ++y) { for (GLint x = 0; x < kWidth; ++x) { EXPECT_PIXEL_COLOR32F_NEAR(x, y, expected_color, 2.0f / 255.0f); } } ASSERT_GL_NO_ERROR(); } void peelDepth(GLint colorLoc) { // Draw full quads from front to back and increasing depths // with depth test = less. glDepthMask(GL_FALSE); constexpr int steps = 64; for (int i = 0; i < steps; ++i) { float l = float(i) / float(steps); float c = l; float z = c * 2.0f - 1.0f; glVertexAttrib4f(1, 0, 0, z, 0); glUniform4f(colorLoc, c, c, c, c); glDrawArrays(GL_TRIANGLES, 0, 6); } glDepthMask(GL_TRUE); } void testResolveDepthToFBO(GLenum format, GLenum attachment, GLint samples, GLint width, GLint height) { constexpr char kVS[] = R"(#version 300 es layout(location = 0) in vec4 position; void main() { gl_Position = position; } )"; constexpr char kFS[] = R"(#version 300 es precision highp float; out vec4 color; void main() { color = vec4(0.5, 0.6, 0.7, 0.8); } )"; constexpr char kDepthVS[] = R"(#version 300 es layout(location = 0) in vec4 position; layout(location = 1) in vec4 offset; void main() { gl_Position = position + offset; } )"; constexpr char kDepthFS[] = R"(#version 300 es precision highp float; uniform vec4 color; out vec4 outColor; void main() { outColor = color; } )"; ANGLE_GL_PROGRAM(program, kVS, kFS); ANGLE_GL_PROGRAM(depthProgram, kDepthVS, kDepthFS); // Make samples = 4 multi-sample framebuffer. GLFramebuffer fb0; glBindFramebuffer(GL_FRAMEBUFFER, fb0); GLRenderbuffer rb0; glBindRenderbuffer(GL_RENDERBUFFER, rb0); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb0); GLRenderbuffer db0; glBindRenderbuffer(GL_RENDERBUFFER, db0); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, db0); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Make samples = 0 multi-sample framebuffer. GLFramebuffer fb1; glBindFramebuffer(GL_FRAMEBUFFER, fb1); GLRenderbuffer rb1; glBindRenderbuffer(GL_RENDERBUFFER, rb1); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb1); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); GLRenderbuffer db1; glBindRenderbuffer(GL_RENDERBUFFER, db1); glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, db1); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Draw quad to fb0. glBindFramebuffer(GL_FRAMEBUFFER, fb0); glViewport(0, 0, width, height); glClearColor(1, 1, 1, 1); glUseProgram(program); GLVertexArray va0; glBindVertexArray(va0); GLBuffer buf0; glBindBuffer(GL_ARRAY_BUFFER, buf0); // clang-format off constexpr float vertices[] = { -1.0f, -1.0f, -1.0, 1.0f, -1.0f, 0.0, -1.0f, 1.0f, 0.0, -1.0f, 1.0f, 0.0, 1.0f, -1.0f, 0.0, 1.0f, 1.0f, 1.0, }; // clang-format on glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_ALWAYS); glDrawArrays(GL_TRIANGLES, 0, 6); // Blit fb0 to fb1. glBindFramebuffer(GL_READ_FRAMEBUFFER, fb0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb1); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_DEPTH_BUFFER_BIT, GL_NEAREST); ASSERT_GL_NO_ERROR(); GLVertexArray va1; glBindVertexArray(va1); // clang-format off constexpr float depthVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, }; // clang-format on GLBuffer buf1; glBindBuffer(GL_ARRAY_BUFFER, buf1); glBufferData(GL_ARRAY_BUFFER, sizeof(depthVertices), depthVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glUseProgram(depthProgram); GLint colorLoc = glGetUniformLocation(depthProgram, "color"); // Extract the depth results. glBindFramebuffer(GL_FRAMEBUFFER, fb1); glClear(GL_COLOR_BUFFER_BIT); glDepthFunc(GL_LESS); peelDepth(colorLoc); std::vector actual(width * height); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); // Render what should be a similar result to the non-multi-sampled fb glBindVertexArray(va0); glDepthFunc(GL_ALWAYS); glUseProgram(program); glDrawArrays(GL_TRIANGLES, 0, 6); // Extract the expected depth results. glBindVertexArray(va1); glUseProgram(depthProgram); glClear(GL_COLOR_BUFFER_BIT); glDepthFunc(GL_LESS); peelDepth(colorLoc); std::vector expected(width * height); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, expected.data()); for (size_t i = 0; i < expected.size(); ++i) { EXPECT_NEAR(expected[i].R, actual[i].R, 8) << "at " << (i % width) << "," << (i / width); } // Verify we read the depth buffer. const GLint minDimension = std::min(width, height); for (GLint i = 1; i < minDimension; ++i) { const GLColor &c1 = expected[i - 1]; const GLColor &c2 = expected[i * width + i]; EXPECT_LT(c1.R, c2.R); } ASSERT_GL_NO_ERROR(); } }; // Test the multisampled optimized resolve subpass TEST_P(MultisampleResolveTest, DISABLED_ResolveSubpassMSImage) { ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); // Draw green. drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f); swapBuffers(); // Wait for visual verification. angle::Sleep(2000); } // This is a test that must be verified visually. // // Tests that clear of the default framebuffer with multisample applies to the window. TEST_P(MultisampleTestES3, DISABLED_ClearMSAAReachesWindow) { ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue()); // Draw blue. drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f); swapBuffers(); // Use glClear to clear to red. Regression test for the Vulkan backend where this clear // remained "deferred" and didn't make it to the window on swap. glClearColor(1.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); swapBuffers(); // Wait for visual verification. angle::Sleep(2000); } // According to the spec, the minimum value for the multisample line width range limits is one. TEST_P(MultisampleTestES32, MultisampleLineWidthRangeCheck) { GLfloat range[2] = {0, 0}; glGetFloatv(GL_MULTISAMPLE_LINE_WIDTH_RANGE, range); EXPECT_GL_NO_ERROR(); EXPECT_GE(range[0], 1.0f); EXPECT_GE(range[1], 1.0f); } // The multisample line width granularity should not be negative. TEST_P(MultisampleTestES32, MultisampleLineWidthGranularityCheck) { GLfloat granularity = -1.0f; glGetFloatv(GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY, &granularity); EXPECT_GL_NO_ERROR(); EXPECT_GE(granularity, 0.0f); } // These colors match the shader in resolveToFBO which returns (0.5, 0.6, 0.7, 0.8). const GLColor MultisampleResolveTest::kEXPECTED_R8(128, 0, 0, 255); const GLColor MultisampleResolveTest::kEXPECTED_RG8(128, 153, 0, 255); const GLColor MultisampleResolveTest::kEXPECTED_RGB8(128, 153, 178, 255); const GLColor MultisampleResolveTest::kEXPECTED_RGBA8(128, 153, 178, 204); const GLColor32F MultisampleResolveTest::kEXPECTED_RF(0.5f, 0.0f, 0.0f, 1.0f); const GLColor32F MultisampleResolveTest::kEXPECTED_RGF(0.5f, 0.6f, 0.0f, 1.0f); const GLColor32F MultisampleResolveTest::kEXPECTED_RGBF(0.5f, 0.6f, 0.7f, 1.0f); const GLColor32F MultisampleResolveTest::kEXPECTED_RGBAF(0.5f, 0.6f, 0.7f, 0.8f); // Test we can render to and resolve an RGBA8 renderbuffer TEST_P(MultisampleResolveTest, ResolveRGBA8ToFBO4Samples) { testResolveToUNormFBO(GL_RGBA8, kEXPECTED_RGBA8, 4, kWidth, kHeight); } // Test we can render to and resolve an RGB8 renderbuffer TEST_P(MultisampleResolveTest, ResolveRGB8ToFBO4Samples) { testResolveToUNormFBO(GL_RGB8, kEXPECTED_RGB8, 4, kWidth, kHeight); } // Test we can render to and resolve an RG8 renderbuffer TEST_P(MultisampleResolveTest, ResolveRG8ToFBO4Samples) { testResolveToUNormFBO(GL_RG8, kEXPECTED_RG8, 4, kWidth, kHeight); } // Test we can render to and resolve an R8 renderbuffer TEST_P(MultisampleResolveTest, ResolveR8ToFBO4Samples) { testResolveToUNormFBO(GL_R8, kEXPECTED_R8, 4, kWidth, kHeight); } // Test we can render to and resolve an R16F renderbuffer TEST_P(MultisampleResolveTest, ResolveR16FToFBO4Samples) { testResolveToHalfFBO(GL_R16F, kEXPECTED_RF, 4, kWidth, kHeight); } // Test we can render to and resolve an RG16F renderbuffer TEST_P(MultisampleResolveTest, ResolveRG16FToFBO4Samples) { testResolveToHalfFBO(GL_RG16F, kEXPECTED_RGF, 4, kWidth, kHeight); } // Test we can render to and resolve an RGB16F renderbuffer TEST_P(MultisampleResolveTest, ResolveRGB16FToFBO4Samples) { testResolveToHalfFBO(GL_RGB16F, kEXPECTED_RGBF, 4, kWidth, kHeight); } // Test we can render to and resolve an RGBA16F renderbuffer TEST_P(MultisampleResolveTest, ResolveRGBA16FToFBO4Samples) { testResolveToHalfFBO(GL_RGBA16F, kEXPECTED_RGBAF, 4, kWidth, kHeight); } // Test we can render to and resolve an R32F renderbuffer TEST_P(MultisampleResolveTest, ResolveR32FToFBO4Samples) { testResolveToFloatFBO(GL_R32F, kEXPECTED_RF, 4, kWidth, kHeight); } // Test we can render to and resolve an RG32F renderbuffer TEST_P(MultisampleResolveTest, ResolveRG32FToFBO4Samples) { testResolveToFloatFBO(GL_RG32F, kEXPECTED_RGF, 4, kWidth, kHeight); } // Test we can render to and resolve an RGB32F renderbuffer TEST_P(MultisampleResolveTest, ResolveRGB32FToFBO4Samples) { testResolveToRGBFloatFBO(GL_RGB32F, kEXPECTED_RGBF, 4, kWidth, kHeight); } // Test we can render to and resolve an RGBA32F renderbuffer TEST_P(MultisampleResolveTest, ResolveRGBA32FToFBO4Samples) { testResolveToFloatFBO(GL_RGBA32F, kEXPECTED_RGBAF, 4, kWidth, kHeight); } TEST_P(MultisampleResolveTest, ResolveD32FS8F4Samples) { testResolveDepthToFBO(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, 4, kWidth, kHeight); } TEST_P(MultisampleResolveTest, ResolveD24S8Samples) { testResolveDepthToFBO(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, 4, kWidth, kHeight); } TEST_P(MultisampleResolveTest, ResolveD32FSamples) { testResolveDepthToFBO(GL_DEPTH_COMPONENT32F, GL_DEPTH_ATTACHMENT, 4, kWidth, kHeight); } TEST_P(MultisampleResolveTest, ResolveD24Samples) { testResolveDepthToFBO(GL_DEPTH_COMPONENT24, GL_DEPTH_ATTACHMENT, 4, kWidth, kHeight); } TEST_P(MultisampleResolveTest, ResolveD16Samples) { testResolveDepthToFBO(GL_DEPTH_COMPONENT16, GL_DEPTH_ATTACHMENT, 4, kWidth, kHeight); } void drawRectAndBlit(GLuint msFramebuffer, GLuint resolveFramebuffer, GLint width, GLint height, GLint matLoc, GLint colorLoc, float x, float y, float w, float h, const GLColor &color) { glBindFramebuffer(GL_FRAMEBUFFER, msFramebuffer); float matrix[16] = { w, 0, 0, 0, 0, h, 0, 0, 0, 0, 1, 0, x, y, 0, 1, }; glUniformMatrix4fv(matLoc, 1, false, matrix); angle::Vector4 c(color.toNormalizedVector()); glUniform4f(colorLoc, c[0], c[1], c[2], c[3]); glDrawArrays(GL_TRIANGLES, 0, 6); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFramebuffer); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); } // Tests if we resolve(blit) a multisample renderbuffer that it // does not lose its contents. TEST_P(MultisampleResolveTest, DrawAndResolveMultipleTimes) { constexpr GLint samples = 4; constexpr GLenum format = GL_RGBA8; constexpr GLint width = 16; constexpr GLint height = 16; constexpr char kVS[] = R"(#version 300 es layout(location = 0) in vec4 position; uniform mat4 mat; void main() { gl_Position = mat * position; } )"; constexpr char kFS[] = R"(#version 300 es precision highp float; uniform vec4 color; out vec4 outColor; void main() { outColor = color; } )"; glViewport(0, 0, width, height); ANGLE_GL_PROGRAM(program, kVS, kFS); GLint matLoc = glGetUniformLocation(program, "mat"); GLint colorLoc = glGetUniformLocation(program, "color"); glUseProgram(program); GLBuffer buf; glBindBuffer(GL_ARRAY_BUFFER, buf); constexpr float vertices[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); // Make samples = 4 multi-sample framebuffer. GLFramebuffer msFB; glBindFramebuffer(GL_FRAMEBUFFER, msFB); GLRenderbuffer msRB; glBindRenderbuffer(GL_RENDERBUFFER, msRB); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msRB); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Make non-multi-sample framebuffer. GLFramebuffer drawFB; glBindFramebuffer(GL_FRAMEBUFFER, drawFB); GLRenderbuffer drawRB; glBindRenderbuffer(GL_RENDERBUFFER, drawRB); glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, drawRB); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, -1, -1, 2, 2, GLColor::red); drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, 0, -1, 1, 1, GLColor::green); drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, -1, 0, 1, 1, GLColor::blue); drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, 0, 0, 1, 1, GLColor::yellow); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, -0.5, -0.5, 1, 1, GLColor(0x80, 0x80, 0x80, 0x80)); glDisable(GL_BLEND); ASSERT_GL_NO_ERROR(); /* expected +-------------+--------------+ | blue | yellow | | +---------+----------+ | | |.5,.5,1,1| 1,1,.5,1 | | +---+---------+----------+---+ | |1,.5,.5,1| .5,1,.5,1| | | +---------+----------+ | | red | green | +-------------+--------------+ 0,0 */ glBindFramebuffer(GL_FRAMEBUFFER, drawFB); EXPECT_PIXEL_RECT_EQ(0, 0, width / 2, height / 4, GLColor::red); EXPECT_PIXEL_RECT_EQ(width / 2, 0, width / 2, height / 4, GLColor::green); EXPECT_PIXEL_RECT_EQ(0, height * 3 / 4, width / 2, height / 4, GLColor::blue); EXPECT_PIXEL_RECT_EQ(width / 2, height * 3 / 4, width / 2, height / 4, GLColor::yellow); EXPECT_PIXEL_RECT_EQ(width / 4, height / 4, width / 4, height / 4, GLColor(255, 128, 128, 255)); EXPECT_PIXEL_RECT_EQ(width / 2, height / 4, width / 4, height / 4, GLColor(128, 255, 128, 255)); EXPECT_PIXEL_RECT_EQ(width / 4, height / 2, width / 4, height / 4, GLColor(128, 128, 255, 255)); EXPECT_PIXEL_RECT_EQ(width / 2, height / 2, width / 4, height / 4, GLColor(255, 255, 128, 255)); } // Tests resolve after the read framebuffer's attachment has been swapped out. TEST_P(MultisampleResolveTest, SwitchAttachmentsBeforeResolve) { constexpr GLuint kWidth = 16; constexpr GLuint kHeight = 24; GLRenderbuffer msaaColor0, msaaColor1; glBindRenderbuffer(GL_RENDERBUFFER, msaaColor0); glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, kWidth, kHeight); glBindRenderbuffer(GL_RENDERBUFFER, msaaColor1); glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, kWidth, kHeight); { // First, make one of the MSAA color buffers green. GLFramebuffer clearFbo; glBindFramebuffer(GL_FRAMEBUFFER, clearFbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColor1); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); glClearColor(0, 1, 0, 1); glClear(GL_COLOR_BUFFER_BIT); // Make sure clear is performed. GLTexture resolveTexture; glBindTexture(GL_TEXTURE_2D, resolveTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); GLFramebuffer verifyFBO; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, verifyFBO); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0); glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, verifyFBO); EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green); } // Set up the msaa framebuffer with the other MSAA color buffer. GLFramebuffer msaaFBO; glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColor0); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); // Draw into it to start a render pass ANGLE_GL_PROGRAM(red, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); drawQuad(red, essl1_shaders::PositionAttrib(), 0.5f); // Set up the resolve framebuffer GLRenderbuffer resolveColor; glBindRenderbuffer(GL_RENDERBUFFER, resolveColor); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kWidth, kHeight); GLFramebuffer resolveFBO; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, resolveColor); EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER); // Before resolve the MSAA framebuffer, switch its attachment. Regression test for a bug where // the resolve used the previous attachment. glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColor1); glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); // Verify that the resolve happened on the pre-cleared attachment, not the rendered one. glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO); EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green); } ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31_AND( MultisampleTest, ES3_VULKAN().enable(Feature::EmulatedPrerotation90), ES3_VULKAN().enable(Feature::EmulatedPrerotation180), ES3_VULKAN().enable(Feature::EmulatedPrerotation270), // Simulate missing msaa auto resolve feature in Metal. ES2_METAL().disable(Feature::AllowMultisampleStoreAndResolve)); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultisampleTestES3); ANGLE_INSTANTIATE_TEST_ES3_AND_ES31_AND(MultisampleTestES3, ES3_VULKAN().enable(Feature::EmulatedPrerotation90), ES3_VULKAN().enable(Feature::EmulatedPrerotation180), ES3_VULKAN().enable(Feature::EmulatedPrerotation270)); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultisampleTestES32); ANGLE_INSTANTIATE_TEST_ES32_AND(MultisampleTestES32, ES32_VULKAN().enable(Feature::EmulatedPrerotation90), ES32_VULKAN().enable(Feature::EmulatedPrerotation180), ES32_VULKAN().enable(Feature::EmulatedPrerotation270)); ANGLE_INSTANTIATE_TEST_ES3_AND( MultisampleResolveTest, ES3_VULKAN().enable(Feature::EmulatedPrerotation90), ES3_VULKAN().enable(Feature::EmulatedPrerotation180), ES3_VULKAN().enable(Feature::EmulatedPrerotation270), ES3_VULKAN().enable(Feature::PreferDrawClearOverVkCmdClearAttachments)); } // anonymous namespace