// // Copyright 2023 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. // // Test built-in variables added by OES_sample_variables #include #include "common/mathutil.h" #include "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" using namespace angle; namespace { class SampleVariablesTest : public ANGLETest<> { protected: SampleVariablesTest() { setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); } }; // Test gl_MaxSamples == GL_MAX_SAMPLES TEST_P(SampleVariablesTest, MaxSamples) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); GLint maxSamples = -1; glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); ASSERT_GT(maxSamples, 0); ASSERT_LE(maxSamples, 32); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 color; void main() { color = vec4(float(gl_MaxSamples * 4) / 255.0, 0.0, 0.0, 1.0); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); EXPECT_PIXEL_NEAR(0, 0, maxSamples * 4, 0, 0, 255, 1.0); } // Test gl_NumSamples == GL_SAMPLES TEST_P(SampleVariablesTest, NumSamples) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 color; void main() { color = vec4(float(gl_NumSamples * 4) / 255.0, 0.0, 0.0, 1.0); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); GLint numSampleCounts = 0; glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts); ASSERT_GT(numSampleCounts, 0); std::vector sampleCounts; sampleCounts.resize(numSampleCounts); glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, numSampleCounts, sampleCounts.data()); sampleCounts.push_back(0); for (GLint sampleCount : sampleCounts) { GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rbo; glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_RGBA8, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); GLint samples = -1; glGetIntegerv(GL_SAMPLES, &samples); EXPECT_EQ(samples, sampleCount); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); GLubyte pixel[4]; glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); EXPECT_NEAR(std::max(samples, 1) * 4, pixel[0], 1.0) << "Samples: " << sampleCount; } } // Test gl_SampleID values TEST_P(SampleVariablesTest, SampleID) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float")); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 color; uniform int id; void main() { // 1.0 when the selected sample is processed, 0.0 otherwise float r = float(gl_SampleID == id); // Must always be 0.0 float g = float(gl_SampleID < 0 || gl_SampleID >= gl_NumSamples); color = vec4(r, g, 0.0, 1.0); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); glUseProgram(program); GLint numSampleCounts = 0; glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA16F, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts); ASSERT_GT(numSampleCounts, 0); std::vector sampleCounts; sampleCounts.resize(numSampleCounts); glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA16F, GL_SAMPLES, numSampleCounts, sampleCounts.data()); sampleCounts.push_back(0); GLFramebuffer fboResolve; glBindFramebuffer(GL_FRAMEBUFFER, fboResolve); GLRenderbuffer rboResolve; glBindRenderbuffer(GL_RENDERBUFFER, rboResolve); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA16F, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboResolve); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); ASSERT_GL_NO_ERROR(); for (GLint sampleCount : sampleCounts) { GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rbo; glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_RGBA16F, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); for (int sample = 0; sample < std::max(sampleCount, 1); sample++) { glUniform1i(glGetUniformLocation(program, "id"), sample); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboResolve); glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); GLfloat pixel[4]; glBindFramebuffer(GL_READ_FRAMEBUFFER, fboResolve); glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, pixel); EXPECT_GL_NO_ERROR(); // Only one sample has 1.0 value EXPECT_NEAR(1.0 / std::max(sampleCount, 1), pixel[0], 0.001) << "Samples: " << sampleCount << ", SampleID: " << sample; EXPECT_EQ(0.0, pixel[1]); } } } // Test gl_SamplePosition values TEST_P(SampleVariablesTest, SamplePosition) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float")); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 color; uniform int id; void main() { vec2 rg = (gl_SampleID == id) ? gl_SamplePosition : vec2(0.0, 0.0); // Must always be 0.0 float b = float(gl_SamplePosition.x < 0.0 || gl_SamplePosition.x > 1.0); float a = float(gl_SamplePosition.y < 0.0 || gl_SamplePosition.y > 1.0); color = vec4(rg, b, a); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); glUseProgram(program); GLint numSampleCounts = 0; glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA16F, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts); ASSERT_GT(numSampleCounts, 0); std::vector sampleCounts; sampleCounts.resize(numSampleCounts); glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA16F, GL_SAMPLES, numSampleCounts, sampleCounts.data()); sampleCounts.push_back(0); GLFramebuffer fboResolve; glBindFramebuffer(GL_FRAMEBUFFER, fboResolve); GLRenderbuffer rboResolve; glBindRenderbuffer(GL_RENDERBUFFER, rboResolve); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA16F, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboResolve); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); for (GLint sampleCount : sampleCounts) { if (sampleCount > 16) { // Sample positions for MSAAx32 are not defined. continue; } GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rbo; glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_RGBA16F, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); std::unordered_set positionX; std::unordered_set positionY; for (int sample = 0; sample < std::max(sampleCount, 1); sample++) { glUniform1i(glGetUniformLocation(program, "id"), sample); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboResolve); glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); GLfloat pixel[4]; glBindFramebuffer(GL_READ_FRAMEBUFFER, fboResolve); glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, pixel); EXPECT_GL_NO_ERROR(); // Check that positions are unique for each sample const int realSampleCount = std::max(sampleCount, 1); EXPECT_TRUE(positionX.insert(pixel[0]).second) << "Samples: " << realSampleCount << " SampleID: " << sample << " X: " << (pixel[0] * realSampleCount); EXPECT_TRUE(positionY.insert(pixel[1]).second) << "Samples: " << realSampleCount << " SampleID: " << sample << " Y: " << (pixel[1] * realSampleCount); // Check that sample positions are in the normalized range EXPECT_EQ(0, pixel[2]); EXPECT_EQ(0, pixel[3]); } } } // Test gl_SampleMaskIn values TEST_P(SampleVariablesTest, SampleMaskIn) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 color; uint popcount(uint v) { uint c = 0u; for (; v != 0u; v >>= 1) c += v & 1u; return c; } void main() { float r = 0.0; r = float(popcount(uint(gl_SampleMaskIn[0]))); color = vec4(r * 4.0 / 255.0, 0, 0, 1); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); glUseProgram(program); GLint numSampleCounts = 0; glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts); ASSERT_GT(numSampleCounts, 0); std::vector sampleCounts; sampleCounts.resize(numSampleCounts); glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, numSampleCounts, sampleCounts.data()); sampleCounts.push_back(0); auto test = [&](GLint sampleCount, bool sampleCoverageEnabled, GLfloat coverage) { if (sampleCoverageEnabled) { glEnable(GL_SAMPLE_COVERAGE); } else { glDisable(GL_SAMPLE_COVERAGE); } glSampleCoverage(coverage, false); GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rbo; glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_RGBA8, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glClear(GL_COLOR_BUFFER_BIT); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); GLubyte pixel[4]; glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); EXPECT_GL_NO_ERROR(); // Shader scales up the number of input samples to increase precision in unorm8 space. int expected = std::max(sampleCount, 1) * 4; // Sample coverage must not affect single sampled buffers if (sampleCoverageEnabled && sampleCount > 0) { // The number of samples in gl_SampleMaskIn must be affected by the sample // coverage GL state and then the resolved value must be scaled down again. expected *= coverage * coverage; } EXPECT_NEAR(expected, pixel[0], 1.0) << "Samples: " << sampleCount << ", Sample Coverage: " << (sampleCoverageEnabled ? "Enabled" : "Disabled") << ", Coverage: " << coverage; }; for (GLint sampleCount : sampleCounts) { if (sampleCount > 32) { // The test shader will not work with MSAAx64. continue; } for (bool sampleCoverageEnabled : {false, true}) { for (GLfloat coverage : {0.0, 0.5, 1.0}) { if (sampleCount == 1 && coverage != 0.0 && coverage != 1.0) { continue; } test(sampleCount, sampleCoverageEnabled, coverage); } } } } // Test gl_SampleMaskIn values with per-sample shading TEST_P(SampleVariablesTest, SampleMaskInPerSample) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; out vec4 color; void main() { float r = float(gl_SampleMaskIn[0] == (1 << gl_SampleID)); color = vec4(r, 0, 0, 1); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); glUseProgram(program); GLint numSampleCounts = 0; glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts); ASSERT_GT(numSampleCounts, 0); std::vector sampleCounts; sampleCounts.resize(numSampleCounts); glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, numSampleCounts, sampleCounts.data()); sampleCounts.push_back(0); for (GLint sampleCount : sampleCounts) { if (sampleCount > 32) { // The test shader will not work with MSAAx64. continue; } GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rbo; glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_RGBA8, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red) << "Samples: " << sampleCount; } } // Test writing gl_SampleMask TEST_P(SampleVariablesTest, SampleMask) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables")); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float")); const char kFS[] = R"(#version 300 es #extension GL_OES_sample_variables : require precision highp float; uniform highp int sampleMask; out vec4 color; void main() { gl_SampleMask[0] = sampleMask; color = vec4(1, 0, 0, 1); })"; ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS); glUseProgram(program); GLint numSampleCounts = 0; glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA16F, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts); ASSERT_GT(numSampleCounts, 0); std::vector sampleCounts; sampleCounts.resize(numSampleCounts); glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA16F, GL_SAMPLES, numSampleCounts, sampleCounts.data()); sampleCounts.push_back(0); GLFramebuffer fboResolve; glBindFramebuffer(GL_FRAMEBUFFER, fboResolve); GLRenderbuffer rboResolve; glBindRenderbuffer(GL_RENDERBUFFER, rboResolve); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA16F, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboResolve); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); auto test = [&](GLint sampleCount, GLint sampleMask, bool sampleCoverageEnabled, GLfloat coverage) { if (sampleCoverageEnabled) { glEnable(GL_SAMPLE_COVERAGE); } else { glDisable(GL_SAMPLE_COVERAGE); } ASSERT(coverage == 0.0 || coverage == 1.0); glSampleCoverage(coverage, false); GLFramebuffer fbo; glBindFramebuffer(GL_FRAMEBUFFER, fbo); GLRenderbuffer rbo; glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_RGBA16F, 1, 1); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glClear(GL_COLOR_BUFFER_BIT); glUniform1i(glGetUniformLocation(program, "sampleMask"), sampleMask); drawQuad(program, essl3_shaders::PositionAttrib(), 0.0); ASSERT_GL_NO_ERROR(); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboResolve); glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); GLfloat pixel[4]; glBindFramebuffer(GL_READ_FRAMEBUFFER, fboResolve); glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, pixel); EXPECT_GL_NO_ERROR(); float expected = 1.0; if (sampleCount > 0) { if (sampleCoverageEnabled && coverage == 0.0) { expected = 0.0; } else { uint32_t fullSampleCount = std::max(sampleCount, 1); uint32_t realSampleMask = sampleMask & (0xFFFFFFFFu >> (32 - fullSampleCount)); expected = static_cast(gl::BitCount(realSampleMask)) / fullSampleCount; } } EXPECT_EQ(expected, pixel[0]) << "Samples: " << sampleCount << ", gl_SampleMask[0]: " << sampleMask << ", Sample Coverage: " << (sampleCoverageEnabled ? "Enabled" : "Disabled") << ", Coverage: " << coverage; }; for (GLint sampleCount : sampleCounts) { if (sampleCount > 32) { // The test shader will not work with MSAAx64. continue; } for (bool sampleCoverageEnabled : {false, true}) { for (GLfloat coverage : {0.0, 1.0}) { for (GLint sampleMask : {0xFFFFFFFFu, 0x55555555u, 0xAAAAAAAAu, 0x00000000u}) { test(sampleCount, sampleMask, sampleCoverageEnabled, coverage); } } } } } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SampleVariablesTest); ANGLE_INSTANTIATE_TEST_ES3(SampleVariablesTest); } // anonymous namespace