// // 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. // // PolygonOffsetClampTest.cpp: Test cases for GL_EXT_polygon_offset_clamp // #include "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" #include "util/EGLWindow.h" #include "util/test_utils.h" using namespace angle; class PolygonOffsetClampTest : public ANGLETest<> { protected: PolygonOffsetClampTest() { setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); setConfigDepthBits(24); setExtensionsEnabled(false); } }; // Test state queries and updates TEST_P(PolygonOffsetClampTest, State) { // New state query fails without the extension { GLfloat clamp = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_CLAMP_EXT, &clamp); EXPECT_GL_ERROR(GL_INVALID_ENUM); EXPECT_EQ(clamp, -1.0f); ASSERT_GL_NO_ERROR(); } // New function does nothing without enabling the extension { glPolygonOffsetClampEXT(1.0f, 2.0f, 3.0f); EXPECT_GL_ERROR(GL_INVALID_OPERATION); GLfloat factor = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); EXPECT_EQ(factor, 0.0f); GLfloat units = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_UNITS, &units); EXPECT_EQ(units, 0.0f); ASSERT_GL_NO_ERROR(); } ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_polygon_offset_clamp")); // Default state { GLfloat factor = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); EXPECT_EQ(factor, 0.0f); GLfloat units = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_UNITS, &units); EXPECT_EQ(units, 0.0f); GLfloat clamp = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_CLAMP_EXT, &clamp); EXPECT_EQ(clamp, 0.0f); ASSERT_GL_NO_ERROR(); } // Full state update using the new function { glPolygonOffsetClampEXT(1.0f, 2.0f, 3.0f); GLfloat factor = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); EXPECT_EQ(factor, 1.0f); GLfloat units = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_UNITS, &units); EXPECT_EQ(units, 2.0f); GLfloat clamp = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_CLAMP_EXT, &clamp); EXPECT_EQ(clamp, 3.0f); ASSERT_GL_NO_ERROR(); } // Core function resets the clamp value to zero { glPolygonOffset(4.0f, 5.0f); GLfloat factor = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); EXPECT_EQ(factor, 4.0f); GLfloat units = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_UNITS, &units); EXPECT_EQ(units, 5.0f); GLfloat clamp = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_CLAMP_EXT, &clamp); EXPECT_EQ(clamp, 0.0f); ASSERT_GL_NO_ERROR(); } // NaNs must be accepted and replaced with zeros { glPolygonOffsetClampEXT(6.0f, 7.0f, 8.0f); float nan = std::numeric_limits::quiet_NaN(); glPolygonOffsetClampEXT(nan, nan, nan); GLfloat factor = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); EXPECT_EQ(factor, 0.0f); GLfloat units = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_UNITS, &units); EXPECT_EQ(units, 0.0f); GLfloat clamp = -1.0f; glGetFloatv(GL_POLYGON_OFFSET_CLAMP_EXT, &clamp); EXPECT_EQ(clamp, 0.0f); ASSERT_GL_NO_ERROR(); } } // Test polygon offset clamping behavior. Ported from dEQP. TEST_P(PolygonOffsetClampTest, Operation) { ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_polygon_offset_clamp")); ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_depth_texture")); GLTexture depthTexture; glBindTexture(GL_TEXTURE_2D, depthTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 64, 64, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); GLTexture colorTexture; glBindTexture(GL_TEXTURE_2D, colorTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); 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); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0); GLTexture colorReadbackTexture; glBindTexture(GL_TEXTURE_2D, colorReadbackTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); GLFramebuffer fboReadback; glBindFramebuffer(GL_FRAMEBUFFER, fboReadback); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorReadbackTexture, 0); ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); constexpr char kFS[] = R"( varying highp vec2 v_texCoord; uniform highp sampler2D tex; void main() { // Store as unorm24 highp float d = floor(texture2D(tex, v_texCoord).r * 16777215.0); highp float r = floor(d / 65536.0); highp float g = floor(mod(d, 65536.0) / 256.0); highp float b = mod(d, 256.0); gl_FragColor = vec4(r, g, b, 1.0) / 255.0; })"; ANGLE_GL_PROGRAM(readDepthProgram, essl1_shaders::vs::Texture2D(), kFS); // Setup depth testing glEnable(GL_DEPTH_TEST); glDepthFunc(GL_ALWAYS); // Bind framebuffer for drawing glBindFramebuffer(GL_FRAMEBUFFER, fbo); // Offset units and clamp values. Should work with all depth buffer formats. const std::vector> testValues = { {-5000.0f, -0.0001f}, {+5000.0f, +0.0001f}, {-5000.0f, 0.0f}, {+5000.0f, 0.0f}, {-5000.0f, -std::numeric_limits::infinity()}, {+5000.0f, +std::numeric_limits::infinity()}}; auto readDepthValue = [&]() { glBindFramebuffer(GL_FRAMEBUFFER, fboReadback); glDisable(GL_DEPTH_TEST); glUseProgram(readDepthProgram); glUniform1i(glGetUniformLocation(readDepthProgram, "tex"), 0); glBindTexture(GL_TEXTURE_2D, depthTexture); drawQuad(readDepthProgram, "a_position", 0.5); GLubyte pixels[4]; glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Convert read depth value to GLfloat normalized GLfloat depthValue = (GLfloat)(pixels[0] * 65536 + pixels[1] * 256 + pixels[2]) / 0xFFFFFF; // Restore state glBindFramebuffer(GL_FRAMEBUFFER, fbo); glEnable(GL_DEPTH_TEST); glUseProgram(testProgram); return depthValue; }; for (auto testValue : testValues) { const GLfloat units = testValue[0]; const GLfloat clamp = testValue[1]; // Draw reference polygon glDisable(GL_POLYGON_OFFSET_FILL); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawQuad(testProgram, "a_position", 0.0); // Get reference depth value const GLfloat depthValue = readDepthValue(); // Draw polygon with depth offset glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(0.0f, units); drawQuad(testProgram, "a_position", 0.0); // Get depth value with offset const GLfloat depthValueOffset = readDepthValue(); // Draw reference polygon glDisable(GL_POLYGON_OFFSET_FILL); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawQuad(testProgram, "a_position", 0.0); // Draw polygon with depth offset glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffsetClampEXT(0.0f, units, clamp); drawQuad(testProgram, "a_position", 0.0); // Get depth value with clamped offset const GLfloat depthValueOffsetClamp = readDepthValue(); // Check depth values { if (clamp == 0.0f || isinf(clamp)) { ASSERT_NE(units, 0.0f); // Ensure that offset works if (units > 0.0f) { EXPECT_LT(depthValue, depthValueOffset); EXPECT_LT(depthValue, depthValueOffsetClamp); } else if (units < 0.0f) { EXPECT_GT(depthValue, depthValueOffset); EXPECT_GT(depthValue, depthValueOffsetClamp); } // Clamping must have no effect EXPECT_EQ(depthValueOffset, depthValueOffsetClamp); } else if (clamp < 0.0f) { ASSERT_LT(units, 0); // Negative clamp value sets effective offset to max(offset, clamp) EXPECT_GT(depthValue, depthValueOffset); EXPECT_GT(depthValue, depthValueOffsetClamp); EXPECT_LT(depthValueOffset, depthValueOffsetClamp); } else if (clamp > 0.0f) { ASSERT_GT(units, 0); // Positive clamp value sets effective offset to min(offset, clamp) EXPECT_LT(depthValue, depthValueOffset); EXPECT_LT(depthValue, depthValueOffsetClamp); EXPECT_GT(depthValueOffset, depthValueOffsetClamp); } } } } ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(PolygonOffsetClampTest);