| // |
| // Copyright 2016 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // CHROMIUMFramebufferMixedSamplesTest |
| // Test CHROMIUM subset of NV_framebuffer_mixed_samples. |
| // This extension allows rendering to a framebuffer that has different |
| // sample counts for different render buffers (stencil, depth, color) |
| |
| #include "test_utils/ANGLETest.h" |
| #include "shader_utils.h" |
| |
| using namespace angle; |
| |
| namespace |
| { |
| |
| const GLuint kWidth = 100; |
| const GLuint kHeight = 100; |
| |
| class CHROMIUMFramebufferMixedSamplesTest : public ANGLETest |
| { |
| protected: |
| enum SetupFBOType |
| { |
| MixedSampleFBO, // 1 color sample, N stencil samples. |
| SingleSampleFBO, // 1 color sample, 1 stencil sample. |
| }; |
| |
| bool isApplicable() const |
| { |
| return extensionEnabled("GL_CHROMIUM_framebuffer_mixed_samples") && |
| extensionEnabled("GL_OES_rgb8_rgba8"); |
| } |
| |
| void SetUp() override |
| { |
| ANGLETest::SetUp(); |
| |
| // clang-format off |
| static const char* kVertexShaderSource = |
| "attribute mediump vec4 position;\n" |
| "void main() {\n" |
| " gl_Position = position;\n" |
| "}\n"; |
| |
| static const char* kFragmentShaderSource = |
| "uniform mediump vec4 color;\n" |
| "void main() {\n" |
| " gl_FragColor = color;\n" |
| "}\n"; |
| |
| // clang-format on |
| mProgram = CompileProgram(kVertexShaderSource, kFragmentShaderSource); |
| |
| GLuint position_loc = glGetAttribLocation(mProgram, "position"); |
| mColorLoc = glGetUniformLocation(mProgram, "color"); |
| |
| glGenBuffers(1, &mVBO); |
| glBindBuffer(GL_ARRAY_BUFFER, mVBO); |
| |
| static 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, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, |
| }; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
| glEnableVertexAttribArray(position_loc); |
| glVertexAttribPointer(position_loc, 2, GL_FLOAT, GL_FALSE, 0, 0); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void TearDown() override |
| { |
| glDeleteBuffers(1, &mVBO); |
| glDeleteProgram(mProgram); |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| ANGLETest::TearDown(); |
| } |
| |
| void prepareForDraw(SetupFBOType fbo_type) |
| { |
| glActiveTexture(GL_TEXTURE0); |
| glGenTextures(1, &mTexture); |
| glBindTexture(GL_TEXTURE_2D, mTexture); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| nullptr); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| |
| glGenRenderbuffers(1, &mStencilRB); |
| glBindRenderbuffer(GL_RENDERBUFFER, mStencilRB); |
| |
| if (fbo_type == MixedSampleFBO) |
| { |
| // Create a sample buffer. |
| GLsizei num_samples = 8, max_samples = 0; |
| glGetIntegerv(GL_MAX_SAMPLES, &max_samples); |
| num_samples = std::min(num_samples, max_samples); |
| glRenderbufferStorageMultisampleANGLE(GL_RENDERBUFFER, num_samples, GL_STENCIL_INDEX8, |
| kWidth, kHeight); |
| GLint param = 0; |
| glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, ¶m); |
| EXPECT_GT(param, 1); |
| } |
| else |
| { |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, kWidth, kHeight); |
| } |
| glBindRenderbuffer(GL_RENDERBUFFER, 0); |
| |
| glGenFramebuffers(1, &mSampleFBO); |
| glBindFramebuffer(GL_FRAMEBUFFER, mSampleFBO); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0); |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, |
| mStencilRB); |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), |
| glCheckFramebufferStatus(GL_FRAMEBUFFER)); |
| |
| glUseProgram(mProgram); |
| glBindBuffer(GL_ARRAY_BUFFER, 0); |
| glViewport(0, 0, kWidth, kHeight); |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| glClearStencil(1); |
| glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
| glEnable(GL_STENCIL_TEST); |
| glEnable(GL_BLEND); |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| glStencilMask(0xffffffff); |
| glStencilFunc(GL_EQUAL, 1, 0xffffffff); |
| glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void cleanup() |
| { |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| glDeleteFramebuffers(1, &mSampleFBO); |
| glDeleteTextures(1, &mTexture); |
| glDeleteRenderbuffers(1, &mStencilRB); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| GLuint mSampleFBO; |
| GLuint mStencilRB; |
| GLuint mTexture; |
| |
| GLuint mProgram; |
| GLuint mVBO; |
| GLint mColorLoc; |
| }; |
| |
| } // |
| |
| TEST_P(CHROMIUMFramebufferMixedSamplesTest, StateSettingTest) |
| { |
| if (!isApplicable()) |
| { |
| return; |
| } |
| |
| GLint value = -1; |
| glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value); |
| EXPECT_EQ(GL_NONE, value); |
| GLenum kValues[] = {GL_NONE, GL_RGB, GL_RGBA, GL_ALPHA}; |
| for (auto expect : kValues) |
| { |
| glCoverageModulationCHROMIUM(expect); |
| value = -1; |
| glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value); |
| EXPECT_EQ(expect, static_cast<GLenum>(value)); |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); |
| } |
| |
| glCoverageModulationCHROMIUM(GL_BYTE); |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError()); |
| value = -1; |
| glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value); |
| EXPECT_EQ(static_cast<GLenum>(GL_ALPHA), static_cast<GLenum>(value)); |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); |
| } |
| |
| // The test pattern is as follows: |
| // A green triangle (bottom left, top right, top left). |
| // A blue triangle (top left, bottom right, bottom left). |
| // The triangles will overlap but overlap only contains green pixels, |
| // due to each draw erasing its area from stencil. |
| // The blue triangle will fill only the area (bottom left, center, |
| // bottom right). |
| |
| // The test tests that CoverageModulation call works. |
| // The fractional pixels of both triangles end up being modulated |
| // by the coverage of the fragment. Test that drawing with and without |
| // CoverageModulation causes the result to be different. |
| TEST_P(CHROMIUMFramebufferMixedSamplesTest, CoverageModulation) |
| { |
| if (!isApplicable()) |
| { |
| return; |
| } |
| static const float kBlue[] = {0.0f, 0.0f, 1.0f, 1.0f}; |
| static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f}; |
| std::unique_ptr<uint8_t[]> results[3]; |
| const GLint kResultSize = kWidth * kHeight * 4; |
| |
| for (int pass = 0; pass < 3; ++pass) |
| { |
| prepareForDraw(MixedSampleFBO); |
| if (pass == 1) |
| { |
| glCoverageModulationCHROMIUM(GL_RGBA); |
| } |
| glUniform4fv(mColorLoc, 1, kGreen); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| glUniform4fv(mColorLoc, 1, kBlue); |
| glDrawArrays(GL_TRIANGLES, 3, 3); |
| if (pass == 1) |
| { |
| glCoverageModulationCHROMIUM(GL_NONE); |
| } |
| results[pass].reset(new uint8_t[kResultSize]); |
| memset(results[pass].get(), 123u, kResultSize); |
| glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, results[pass].get()); |
| |
| cleanup(); |
| } |
| |
| EXPECT_NE(0, memcmp(results[0].get(), results[1].get(), kResultSize)); |
| // Verify that rendering is deterministic, so that the pass above does not |
| // come from non-deterministic rendering. |
| EXPECT_EQ(0, memcmp(results[0].get(), results[2].get(), kResultSize)); |
| } |
| |
| // The test tests that the stencil buffer can be multisampled, even though the |
| // color buffer is single-sampled. Draws the same pattern with single-sample |
| // stencil buffer and with multisample stencil buffer. The images should differ. |
| TEST_P(CHROMIUMFramebufferMixedSamplesTest, MultisampleStencilEffective) |
| { |
| if (!isApplicable()) |
| { |
| return; |
| } |
| |
| static const float kBlue[] = {0.0f, 0.0f, 1.0f, 1.0f}; |
| static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f}; |
| |
| std::unique_ptr<uint8_t[]> results[3]; |
| const GLint kResultSize = kWidth * kHeight * 4; |
| |
| for (int pass = 0; pass < 3; ++pass) |
| { |
| if (pass == 1) |
| { |
| prepareForDraw(MixedSampleFBO); |
| } |
| else |
| { |
| prepareForDraw(SingleSampleFBO); |
| } |
| glUniform4fv(mColorLoc, 1, kGreen); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| glUniform4fv(mColorLoc, 1, kBlue); |
| glDrawArrays(GL_TRIANGLES, 3, 3); |
| |
| results[pass].reset(new uint8_t[kResultSize]); |
| memset(results[pass].get(), 12u, kResultSize); |
| glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, results[pass].get()); |
| |
| cleanup(); |
| } |
| |
| EXPECT_NE(0, memcmp(results[0].get(), results[1].get(), kResultSize)); |
| // Verify that rendering is deterministic, so that the pass above does not |
| // come from non-deterministic rendering. |
| EXPECT_EQ(0, memcmp(results[0].get(), results[2].get(), kResultSize)); |
| } |
| |
| ANGLE_INSTANTIATE_TEST(CHROMIUMFramebufferMixedSamplesTest, |
| ES2_OPENGL(), |
| ES2_OPENGLES(), |
| ES3_OPENGL()); |