// // 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" 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 IsGLExtensionEnabled("GL_CHROMIUM_framebuffer_mixed_samples") && IsGLExtensionEnabled("GL_OES_rgb8_rgba8"); } void testSetUp() override { mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor()); GLuint position_loc = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); mColorLoc = glGetUniformLocation(mProgram, essl1_shaders::ColorUniform()); 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 testTearDown() override { glDeleteBuffers(1, &mVBO); glDeleteProgram(mProgram); ASSERT_GL_NO_ERROR(); } 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(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; }; } // namespace 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(value)); EXPECT_EQ(static_cast(GL_NO_ERROR), glGetError()); } glCoverageModulationCHROMIUM(GL_BYTE); EXPECT_EQ(static_cast(GL_INVALID_ENUM), glGetError()); value = -1; glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value); EXPECT_EQ(static_cast(GL_ALPHA), static_cast(value)); EXPECT_EQ(static_cast(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 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 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_ES2(CHROMIUMFramebufferMixedSamplesTest);