blob: 1f0bc01de0e25040b9bfb5558b42f70c66bba879 [file] [log] [blame]
//
// 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<GLColor> 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<GLColor> 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