From 317b0d88f00eb31306077afc256fab2b08fd2c84 Mon Sep 17 00:00:00 2001 From: Andy Fang Date: Tue, 27 Oct 2026 12:07:48 -0400 Subject: [PATCH] Metal: Preserve reversed depth ranges after viewport updates Reapply depth-range normalization after rebuilding the Metal viewport. Otherwise viewport or framebuffer changes restore reversed endpoints while shader depth inversion remains active. Add GLES 1/2/3 regression coverage for viewport changes, framebuffer switches, attachment resizing, and fixed-point depth ranges. Isolate viewport-only changes between draws and probe near a known depth value. Test: angle_end2end_tests (28 Metal cases pass) Test: All 9 regression cases fail without the renderer fix Bug: angleproject:578080497 Change-Id: I3f63a8615191ee9bb7d4ca92eb2f9afef1ebe5dc --- AUTHORS | 1 + src/libANGLE/renderer/metal/ContextMtl.mm | 5 +- src/tests/gl_tests/ViewportTest.cpp | 181 +++++++++++++++++++++ src/tests/gl_tests/gles1/BasicDrawTest.cpp | 38 +++++ 4 files changed, 222 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 86bf8bd9526..59a3de347fd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -98,3 +98,4 @@ Daniel Wedzicha Vlad Zahorodnii Thomas Portal Birk Skyum +Andy Fang diff --git a/src/libANGLE/renderer/metal/ContextMtl.mm b/src/libANGLE/renderer/metal/ContextMtl.mm index 3b2b89e68f1..342395a9b5d 100644 --- a/src/libANGLE/renderer/metal/ContextMtl.mm +++ b/src/libANGLE/renderer/metal/ContextMtl.mm @@ -2072,9 +2072,8 @@ GLint GetOwnershipIdentity(const egl::AttributeMap &attribs) { mViewport = mtl::GetViewport(viewport, framebufferMtl->getState().getDimensions().height, framebufferMtl->getFlipY(), nearPlane, farPlane); - mDirtyBits.set(DIRTY_BIT_VIEWPORT); - - invalidateDriverUniforms(); + // Normalize reversed depth ranges. + updateDepthRange(nearPlane, farPlane); } void ContextMtl::updateDepthRange(float nearPlane, float farPlane) diff --git a/src/tests/gl_tests/ViewportTest.cpp b/src/tests/gl_tests/ViewportTest.cpp index 8c58a7af093..30ad013c8f4 100644 --- a/src/tests/gl_tests/ViewportTest.cpp +++ b/src/tests/gl_tests/ViewportTest.cpp @@ -433,6 +433,187 @@ TEST_P(ViewportTest, ClampOnStore) ASSERT_EQ(viewport[3], maxDims[1]); } +class ViewportDepthRangeTest : public ANGLETest<> +{ + protected: + ViewportDepthRangeTest() + { + setWindowWidth(64); + setWindowHeight(64); + setConfigRedBits(8); + setConfigGreenBits(8); + setConfigBlueBits(8); + setConfigAlphaBits(8); + setConfigDepthBits(24); + } + + void testSetUp() override + { + mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor()); + ASSERT_NE(0u, mProgram); + glUseProgram(mProgram); + mColorLocation = glGetUniformLocation(mProgram, essl1_shaders::ColorUniform()); + ASSERT_NE(-1, mColorLocation); + glEnable(GL_DEPTH_TEST); + glClearColor(0, 0, 0, 1); + } + + void testTearDown() override { glDeleteProgram(mProgram); } + + void setDepthRange(GLfloat nearPlane, GLfloat farPlane) + { + glDepthRangef(nearPlane, farPlane); + glDepthFunc(nearPlane > farPlane ? GL_GREATER : GL_LESS); + glClearDepthf(nearPlane > farPlane ? 0.0f : 1.0f); + } + + void checkDepthRange(GLfloat nearPlane, GLfloat farPlane) + { + GLfloat range[2]; + glGetFloatv(GL_DEPTH_RANGE, range); + EXPECT_FLOAT_EQ(nearPlane, range[0]); + EXPECT_FLOAT_EQ(farPlane, range[1]); + + // Probe around NDC z = -0.5 against a fixed depth reference. + glClearDepthf(nearPlane + (farPlane - nearPlane) * 0.25f); + glDepthMask(GL_TRUE); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDepthMask(GL_FALSE); + glUniform4f(mColorLocation, 0, 1, 0, 1); + drawQuad(mProgram, essl1_shaders::PositionAttrib(), -0.51f); + glUniform4f(mColorLocation, 1, 0, 0, 1); + drawQuad(mProgram, essl1_shaders::PositionAttrib(), -0.49f); + glDepthMask(GL_TRUE); + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::green); + } + + void allocateAttachments(GLuint color, GLuint depth, GLsizei size) + { + glBindRenderbuffer(GL_RENDERBUFFER, color); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, size, size); + glBindRenderbuffer(GL_RENDERBUFFER, depth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size, size); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + } + + static constexpr std::array, 4> kDepthRanges = { + {{1.0f, 0.0f}, {0.75f, 0.25f}, {0.0f, 1.0f}, {0.25f, 0.75f}}}; + + GLuint mProgram = 0; + GLint mColorLocation = -1; +}; + +// Preserve depth range when only the viewport is dirty. +TEST_P(ViewportDepthRangeTest, ViewportChangeOnly) +{ + GLint positionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); + ASSERT_NE(-1, positionLocation); + setupQuadVertexBuffer(-0.5f, 1.0f); + glEnableVertexAttribArray(positionLocation); + glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + glUniform4f(mColorLocation, 0, 0.5f, 0, 1); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + + for (const auto &range : kDepthRanges) + { + SCOPED_TRACE(::testing::Message() << range[0] << ", " << range[1]); + for (const auto &viewport : {std::array{0, 0, 63, 64}, {1, 1, 63, 63}}) + { + SCOPED_TRACE(::testing::Message() << viewport[0] << ", " << viewport[1]); + glViewport(0, 0, 64, 64); + setDepthRange(range[0], range[1]); + glDepthFunc(range[0] > range[1] ? GL_GEQUAL : GL_LEQUAL); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Both draws must pass; additive blending yields full green. + glDrawArrays(GL_TRIANGLES, 0, 6); + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + glDrawArrays(GL_TRIANGLES, 0, 6); + + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::green); + } + } +} + +// Preserve depth range across viewport changes. +TEST_P(ViewportDepthRangeTest, ViewportChanges) +{ + for (const auto &range : kDepthRanges) + { + SCOPED_TRACE(::testing::Message() << range[0] << ", " << range[1]); + glViewport(0, 0, 64, 64); + setDepthRange(range[0], range[1]); + checkDepthRange(range[0], range[1]); + glViewport(0, 0, 63, 64); + checkDepthRange(range[0], range[1]); + glViewport(1, 0, 63, 64); + checkDepthRange(range[0], range[1]); + glViewport(1, 1, 63, 63); + // An unchanged range must not dirty state. + glDepthRangef(range[0], range[1]); + checkDepthRange(range[0], range[1]); + glViewport(0, 0, 64, 64); + checkDepthRange(range[0], range[1]); + } +} + +// Preserve depth range across framebuffer switches. +TEST_P(ViewportDepthRangeTest, FramebufferChanges) +{ + GLFramebuffer framebuffers[2]; + GLRenderbuffer colors[2]; + GLRenderbuffer depths[2]; + for (size_t index = 0; index < 2; ++index) + { + glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[index]); + allocateAttachments(colors[index], depths[index], 64 + static_cast(index)); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, 64, 64); + for (const auto &range : kDepthRanges) + { + SCOPED_TRACE(::testing::Message() << range[0] << ", " << range[1]); + setDepthRange(range[0], range[1]); + checkDepthRange(range[0], range[1]); + for (size_t index : {0u, 1u, 0u}) + { + glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[index]); + checkDepthRange(range[0], range[1]); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + checkDepthRange(range[0], range[1]); + } +} + +// Preserve depth range when attachments resize. +TEST_P(ViewportDepthRangeTest, FramebufferResize) +{ + GLFramebuffer framebuffer; + GLRenderbuffer color; + GLRenderbuffer depth; + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + allocateAttachments(color, depth, 64); + glViewport(0, 0, 64, 64); + for (const auto &range : kDepthRanges) + { + SCOPED_TRACE(::testing::Message() << range[0] << ", " << range[1]); + setDepthRange(range[0], range[1]); + checkDepthRange(range[0], range[1]); + for (GLsizei size : {65, 64}) + { + allocateAttachments(color, depth, size); + checkDepthRange(range[0], range[1]); + } + } +} + +ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ViewportDepthRangeTest); + // Use this to select which configurations (e.g. which renderer, which GLES major version) these // tests should be run against. ANGLE_INSTANTIATE_TEST_ES2_AND(ViewportTest, ES2_D3D11_PRESENT_PATH_FAST()); diff --git a/src/tests/gl_tests/gles1/BasicDrawTest.cpp b/src/tests/gl_tests/gles1/BasicDrawTest.cpp index 8eab12d8811..9bcd1e4de6c 100644 --- a/src/tests/gl_tests/gles1/BasicDrawTest.cpp +++ b/src/tests/gl_tests/gles1/BasicDrawTest.cpp @@ -129,6 +129,44 @@ TEST_P(BasicDrawTest, SetDepthRangex) EXPECT_EQ(0x10000, depth_range[1]); } +// Preserve fixed-point depth ranges across viewport changes. +TEST_P(BasicDrawTest, DepthRangeAfterViewportChange) +{ + const GLfloat quad[] = {-0.75f, -0.75f, -0.5f, 0.75f, -0.75f, -0.5f, + -0.75f, 0.75f, -0.5f, 0.75f, 0.75f, -0.5f}; + constexpr std::array, 4> kRanges = { + {{0x10000, 0}, {0xC000, 0x4000}, {0, 0x10000}, {0x4000, 0xC000}}}; + glEnable(GL_DEPTH_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, quad); + glColor4f(0, 0.5f, 0, 1); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + glClearColor(0, 0, 0, 1); + for (const auto &range : kRanges) + { + SCOPED_TRACE(::testing::Message() << range[0] << ", " << range[1]); + for (GLsizei width : {31, 32}) + { + glViewport(0, 0, width == 31 ? 32 : 31, 32); + glDepthRangex(range[0], range[1]); + glDepthFunc(range[0] > range[1] ? GL_GEQUAL : GL_LEQUAL); + glClearDepthf(range[0] > range[1] ? 0.0f : 1.0f); + GLfixed actual[2]; + glGetFixedv(GL_DEPTH_RANGE, actual); + EXPECT_EQ(range[0], actual[0]); + EXPECT_EQ(range[1], actual[1]); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glViewport(0, 0, width, 32); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_COLOR_EQ(16, 16, GLColor::green); + } + } +} + // Check that sample coverage parameters can be set by glSampleCoveragex. TEST_P(BasicDrawTest, SetSampleCoveragex) {