// // 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(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