GL_EXT_multisampled_render_to_texture extension. Part 2.
For textures that use this extension, a multisampled texture is
implicitly created for the texture.
Upon write or read, the multisampled texture is either return
to be drawn to or resolved and returned as a single sampled texture.
This is the functionality change with end2end tests.
Bug: angleproject:980428
Change-Id: I5776875a132fed7a3f4f00fb02f9e8e250684630
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1773717
Commit-Queue: Rafael Cintron <rafael.cintron@microsoft.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/tests/gl_tests/MultisampledRenderToTextureTest.cpp b/src/tests/gl_tests/MultisampledRenderToTextureTest.cpp
new file mode 100644
index 0000000..cb90c5b
--- /dev/null
+++ b/src/tests/gl_tests/MultisampledRenderToTextureTest.cpp
@@ -0,0 +1,738 @@
+//
+// 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.
+//
+
+// MultisampledRenderToTextureTest: Tests of EXT_multisampled_render_to_texture extension
+
+#include "test_utils/ANGLETest.h"
+#include "test_utils/gl_raii.h"
+
+using namespace angle;
+
+namespace
+{
+constexpr char kBasicVertexShader[] =
+ R"(attribute vec3 position;
+void main()
+{
+ gl_Position = vec4(position, 1);
+})";
+
+constexpr char kGreenFragmentShader[] =
+ R"(void main()
+{
+ gl_FragColor = vec4(0, 1, 0, 1);
+})";
+
+constexpr char kRedFragmentShader[] =
+ R"(void main()
+{
+ gl_FragColor = vec4(1, 0, 0, 1);
+})";
+
+constexpr char kVS[] =
+ "precision highp float;\n"
+ "attribute vec4 position;\n"
+ "varying vec2 texcoord;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = position;\n"
+ " texcoord = (position.xy * 0.5) + 0.5;\n"
+ "}\n";
+
+constexpr char kFS[] =
+ "precision highp float;\n"
+ "uniform sampler2D tex;\n"
+ "varying vec2 texcoord;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " gl_FragColor = texture2D(tex, texcoord);\n"
+ "}\n";
+
+class MultisampledRenderToTextureTest : public ANGLETest
+{
+ protected:
+ MultisampledRenderToTextureTest()
+ {
+ setWindowWidth(64);
+ setWindowHeight(64);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ }
+
+ void testSetUp() override {}
+
+ void testTearDown() override {}
+
+ void setupCopyTexProgram()
+ {
+ mCopyTextureProgram.makeRaster(kVS, kFS);
+ ASSERT_GL_TRUE(mCopyTextureProgram.valid());
+
+ mCopyTextureUniformLocation = glGetUniformLocation(mCopyTextureProgram, "tex");
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void verifyResults(GLuint texture,
+ GLubyte data[4],
+ GLint fboSize,
+ GLint xs,
+ GLint ys,
+ GLint xe,
+ GLint ye)
+ {
+ glViewport(0, 0, fboSize, fboSize);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ // Draw a quad with the target texture
+ glUseProgram(mCopyTextureProgram);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glUniform1i(mCopyTextureUniformLocation, 0);
+
+ drawQuad(mCopyTextureProgram, "position", 0.5f);
+
+ // Expect that the rendered quad has the same color as the source texture
+ EXPECT_PIXEL_NEAR(xs, ys, data[0], data[1], data[2], data[3], 1.0);
+ EXPECT_PIXEL_NEAR(xs, ye - 1, data[0], data[1], data[2], data[3], 1.0);
+ EXPECT_PIXEL_NEAR(xe - 1, ys, data[0], data[1], data[2], data[3], 1.0);
+ EXPECT_PIXEL_NEAR(xe - 1, ye - 1, data[0], data[1], data[2], data[3], 1.0);
+ EXPECT_PIXEL_NEAR((xs + xe) / 2, (ys + ye) / 2, data[0], data[1], data[2], data[3], 1.0);
+ }
+
+ void clearAndDrawQuad(GLuint program, GLsizei viewportWidth, GLsizei viewportHeight)
+ {
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, viewportWidth, viewportHeight);
+ ASSERT_GL_NO_ERROR();
+
+ drawQuad(program, "position", 0.0f);
+ }
+
+ GLProgram mCopyTextureProgram;
+ GLint mCopyTextureUniformLocation = -1;
+};
+
+class MultisampledRenderToTextureES3Test : public MultisampledRenderToTextureTest
+{};
+
+// Checking against invalid parameters for RenderbufferStorageMultisampleEXT.
+TEST_P(MultisampledRenderToTextureTest, RenderbufferParameterCheck)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ GLRenderbuffer renderbuffer;
+ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+
+ // Positive test case
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, 64, 64);
+ ASSERT_GL_NO_ERROR();
+
+ GLint samples;
+ glGetIntegerv(GL_MAX_SAMPLES_EXT, &samples);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_GE(samples, 1);
+
+ // Samples too large
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples + 1, GL_DEPTH_COMPONENT16, 64, 64);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Renderbuffer size too large
+ GLint maxSize;
+ glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 2, GL_DEPTH_COMPONENT16, maxSize + 1,
+ maxSize);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 2, GL_DEPTH_COMPONENT16, maxSize,
+ maxSize + 1);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Retrieving samples
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, 64, 64);
+ GLint param = 0;
+ glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES_EXT, ¶m);
+ // GE because samples may vary base on implementation. Spec says "the resulting value for
+ // RENDERBUFFER_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more
+ // than the next larger sample count supported by the implementation"
+ EXPECT_GE(param, 4);
+}
+
+// Checking against invalid parameters for FramebufferTexture2DMultisampleEXT.
+TEST_P(MultisampledRenderToTextureTest, Texture2DParameterCheck)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ // Positive test case
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Attachment not COLOR_ATTACHMENT0
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Target not framebuffer
+ glFramebufferTexture2DMultisampleEXT(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ GLint samples;
+ glGetIntegerv(GL_MAX_SAMPLES_EXT, &samples);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_GE(samples, 1);
+
+ // Samples too large
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, samples + 1);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Retrieving samples
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ GLint param = 0;
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT, ¶m);
+ // GE because samples may vary base on implementation. Spec says "the resulting value for
+ // TEXTURE_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more than the
+ // next larger sample count supported by the implementation"
+ EXPECT_GE(param, 4);
+}
+
+// Checking against invalid parameters for FramebufferTexture2DMultisampleEXT (cubemap).
+TEST_P(MultisampledRenderToTextureTest, TextureCubeMapParameterCheck)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+ for (GLenum face = 0; face < 6; face++)
+ {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, 64, 64, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+ }
+
+ GLint samples;
+ glGetIntegerv(GL_MAX_SAMPLES_EXT, &samples);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_GE(samples, 1);
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ for (GLenum face = 0; face < 6; face++)
+ {
+ // Positive test case
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Attachment not COLOR_ATTACHMENT0
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Target not framebuffer
+ glFramebufferTexture2DMultisampleEXT(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Samples too large
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0,
+ samples + 1);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Retrieving samples
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ GLint param = 0;
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT,
+ ¶m);
+ // GE because samples may vary base on implementation. Spec says "the resulting value for
+ // TEXTURE_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more than
+ // the next larger sample count supported by the implementation"
+ EXPECT_GE(param, 4);
+ }
+}
+
+// Checking for framebuffer completeness using extension methods.
+TEST_P(MultisampledRenderToTextureTest, FramebufferCompleteness)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ // Checking that Renderbuffer and texture2d having different number of samples results
+ // in a FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ GLRenderbuffer renderbuffer;
+ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 8, GL_DEPTH_COMPONENT16, 64, 64);
+ ASSERT_GL_NO_ERROR();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
+ EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
+ glCheckFramebufferStatus(GL_FRAMEBUFFER));
+}
+
+// Draw test with color attachment only.
+TEST_P(MultisampledRenderToTextureTest, 2DColorAttachmentMultisampleDrawTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ // Set up texture and bind to FBO
+ GLsizei size = 6;
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set viewport and clear to black
+ glViewport(0, 0, size, size);
+ glClearColor(0.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Set up Green square program
+ ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
+ glUseProgram(program);
+ GLint positionLocation = glGetAttribLocation(program, "position");
+ ASSERT_NE(-1, positionLocation);
+
+ setupQuadVertexBuffer(0.5f, 0.5f);
+ glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(positionLocation);
+
+ // Draw green square
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
+
+ // Set up Red square program
+ ANGLE_GL_PROGRAM(program2, kBasicVertexShader, kRedFragmentShader);
+ glUseProgram(program2);
+ GLint positionLocation2 = glGetAttribLocation(program2, "position");
+ ASSERT_NE(-1, positionLocation2);
+
+ setupQuadVertexBuffer(0.5f, 0.75f);
+ glVertexAttribPointer(positionLocation2, 3, GL_FLOAT, GL_FALSE, 0, 0);
+
+ // Draw red square
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::red);
+
+ glDisableVertexAttribArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+// Draw test using both color and depth attachments.
+TEST_P(MultisampledRenderToTextureTest, 2DColorDepthMultisampleDrawTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 6;
+ // create complete framebuffer with depth buffer
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+
+ GLRenderbuffer renderbuffer;
+ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, size, size);
+ ASSERT_GL_NO_ERROR();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set viewport and clear framebuffer
+ glViewport(0, 0, size, size);
+ glClearColor(0.0, 0.0, 0.0, 1.0);
+ glClearDepthf(0.5f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // Draw first green square
+ ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_GREATER);
+ glUseProgram(program);
+ GLint positionLocation = glGetAttribLocation(program, "position");
+ ASSERT_NE(-1, positionLocation);
+
+ setupQuadVertexBuffer(0.8f, 0.5f);
+ glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(positionLocation);
+
+ // Tests that TRIANGLES works.
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
+
+ // Draw red square behind green square
+ ANGLE_GL_PROGRAM(program2, kBasicVertexShader, kRedFragmentShader);
+ glUseProgram(program2);
+ GLint positionLocation2 = glGetAttribLocation(program2, "position");
+ ASSERT_NE(-1, positionLocation2);
+
+ setupQuadVertexBuffer(0.7f, 1.0f);
+ glVertexAttribPointer(positionLocation2, 3, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+ glDisable(GL_DEPTH_TEST);
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
+
+ glDisableVertexAttribArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+// Read pixels with pack buffer. ES3+.
+TEST_P(MultisampledRenderToTextureES3Test, MultisampleReadPixelsTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ // PBO only available ES3 and above
+ ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+ GLsizei size = 6;
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size, size);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set viewport and clear to red
+ glViewport(0, 0, size, size);
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ // Bind Pack Pixel Buffer and read to it
+ GLBuffer PBO;
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, PBO);
+ glBufferData(GL_PIXEL_PACK_BUFFER, 4 * size * size, nullptr, GL_STATIC_DRAW);
+ glReadPixels(0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ ASSERT_GL_NO_ERROR();
+
+ // Retrieving pixel color
+ void *mappedPtr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, 32, GL_MAP_READ_BIT);
+ GLColor *dataColor = static_cast<GLColor *>(mappedPtr);
+ EXPECT_GL_NO_ERROR();
+
+ EXPECT_EQ(GLColor::red, dataColor[0]);
+
+ glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+ EXPECT_GL_NO_ERROR();
+}
+
+// CopyTexImage from a multisampled texture functionality test.
+TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexImageTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 16;
+
+ setupCopyTexProgram();
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+
+ // Set color for framebuffer
+ glClearColor(0.25f, 1.0f, 0.75f, 0.5f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ GLTexture copyToTex;
+ glBindTexture(GL_TEXTURE_2D, copyToTex);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, size, size, 0);
+ ASSERT_GL_NO_ERROR();
+
+ GLubyte expected[4] = {64, 255, 191, 255};
+ verifyResults(copyToTex, expected, size, 0, 0, size, size);
+}
+
+// CopyTexSubImage from a multisampled texture functionality test.
+TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexSubImageTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 16;
+
+ setupCopyTexProgram();
+
+ GLTexture texture;
+ // Create texture in copyFBO0 with color (.25, 1, .75, .5)
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLFramebuffer copyFBO0;
+ glBindFramebuffer(GL_FRAMEBUFFER, copyFBO0);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+
+ // Set color for
+ glClearColor(0.25f, 1.0f, 0.75f, 0.5f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ // Create texture in copyFBO[1] with color (1, .75, .5, .25)
+ GLTexture texture1;
+ glBindTexture(GL_TEXTURE_2D, texture1);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLFramebuffer copyFBO1;
+ glBindFramebuffer(GL_FRAMEBUFFER, copyFBO1);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture1, 0, 4);
+
+ // Set color for
+ glClearColor(1.0f, 0.75f, 0.5f, 0.25f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ GLTexture copyToTex;
+ glBindTexture(GL_TEXTURE_2D, copyToTex);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // copyFBO0 -> copyToTex
+ // copyToTex should hold what was originally in copyFBO0 : (.25, 1, .75, .5)
+ glBindFramebuffer(GL_FRAMEBUFFER, copyFBO0);
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, size, size, 0);
+ ASSERT_GL_NO_ERROR();
+
+ GLubyte expected0[4] = {64, 255, 191, 255};
+ verifyResults(copyToTex, expected0, size, 0, 0, size, size);
+
+ // copyFBO[1] - copySubImage -> copyToTex
+ // copyToTex should have subportion what was in copyFBO[1] : (1, .75, .5, .25)
+ // The rest should still be untouched: (.25, 1, .75, .5)
+ GLint half = size / 2;
+ glBindFramebuffer(GL_FRAMEBUFFER, copyFBO1);
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, half, half, half, half, half, half);
+ ASSERT_GL_NO_ERROR();
+
+ GLubyte expected1[4] = {255, 191, 127, 255};
+ verifyResults(copyToTex, expected1, size, half, half, size, size);
+
+ // Verify rest is untouched
+ verifyResults(copyToTex, expected0, size, 0, 0, half, half);
+ verifyResults(copyToTex, expected0, size, 0, half, half, size);
+ verifyResults(copyToTex, expected0, size, half, 0, size, half);
+}
+
+// BlitFramebuffer functionality test. ES3+.
+TEST_P(MultisampledRenderToTextureES3Test, MultisampleBlitFramebufferTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ // blitFramebuffer only available ES3 and above
+ ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+
+ GLsizei size = 16;
+
+ // Create multisampled framebuffer to use as source.
+ GLRenderbuffer depthMS;
+ glBindRenderbuffer(GL_RENDERBUFFER, depthMS.get());
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT24, size, size);
+
+ GLTexture colorMS;
+ glBindTexture(GL_TEXTURE_2D, colorMS);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ GLFramebuffer fboMS;
+ glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthMS.get());
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ colorMS, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Clear depth to 0.5 and color to green.
+ glClearDepthf(0.5f);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ glFlush();
+ ASSERT_GL_NO_ERROR();
+
+ // Draw red into the multisampled color buffer.
+ ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_EQUAL);
+ drawQuad(drawRed.get(), essl1_shaders::PositionAttrib(), 0.0f);
+ ASSERT_GL_NO_ERROR();
+
+ // Create single sampled framebuffer to use as dest.
+ GLFramebuffer fboSS;
+ glBindFramebuffer(GL_FRAMEBUFFER, fboSS);
+ GLTexture colorSS;
+ glBindTexture(GL_TEXTURE_2D, colorSS);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorSS, 0);
+ ASSERT_GL_NO_ERROR();
+
+ // Bind MS to READ as SS is already bound to DRAW.
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, fboMS.get());
+ glBlitFramebuffer(0, 0, size, size, 0, 0, size, size, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ ASSERT_GL_NO_ERROR();
+
+ // Bind SS to READ so we can readPixels from it
+ glBindFramebuffer(GL_FRAMEBUFFER, fboSS.get());
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(size - 1, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(0, size - 1, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(size - 1, size - 1, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::red);
+ ASSERT_GL_NO_ERROR();
+}
+
+// GenerateMipmap functionality test
+TEST_P(MultisampledRenderToTextureTest, MultisampleGenerateMipmapTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 64;
+ // Vertex Shader source
+ constexpr char kVS[] = R"(attribute vec4 position;
+varying vec2 vTexCoord;
+
+void main()
+{
+ gl_Position = position;
+ vTexCoord = (position.xy * 0.5) + 0.5;
+})";
+
+ // Fragment Shader source
+ constexpr char kFS[] = R"(precision mediump float;
+uniform sampler2D uTexture;
+varying vec2 vTexCoord;
+
+void main()
+{
+ gl_FragColor = texture2D(uTexture, vTexCoord);
+})";
+
+ GLProgram m2DProgram;
+ m2DProgram.makeRaster(kVS, kFS);
+ ASSERT_GL_TRUE(m2DProgram.valid());
+
+ ASSERT_GL_NO_ERROR();
+
+ // Initialize texture with blue
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size, size, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, size, size);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ ASSERT_GL_NO_ERROR();
+
+ // Generate mipmap
+ glGenerateMipmap(GL_TEXTURE_2D);
+ ASSERT_GL_NO_ERROR();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+
+ // Now draw the texture to various different sized areas.
+ clearAndDrawQuad(m2DProgram, size, size);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::blue);
+
+ // Use mip level 1
+ clearAndDrawQuad(m2DProgram, size / 2, size / 2);
+ EXPECT_PIXEL_COLOR_EQ(size / 4, size / 4, GLColor::blue);
+
+ // Use mip level 2
+ clearAndDrawQuad(m2DProgram, size / 4, size / 4);
+ EXPECT_PIXEL_COLOR_EQ(size / 8, size / 8, GLColor::blue);
+
+ ASSERT_GL_NO_ERROR();
+}
+ANGLE_INSTANTIATE_TEST(MultisampledRenderToTextureTest,
+ ES2_D3D9(),
+ ES2_D3D11(),
+ ES3_D3D11(),
+ ES2_OPENGL(),
+ ES3_OPENGL(),
+ ES2_OPENGLES(),
+ ES3_OPENGLES(),
+ ES2_VULKAN(),
+ ES3_VULKAN());
+ANGLE_INSTANTIATE_TEST(MultisampledRenderToTextureES3Test,
+ ES3_D3D11(),
+ ES3_OPENGL(),
+ ES3_OPENGLES(),
+ ES3_VULKAN());
+} // namespace