| // |
| // 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. |
| // |
| // The tests assert OpenGL behavior for all combinations of the following states |
| // - Depth Range |
| // - Full (0, 1) |
| // - Reduced |
| // - Depth Clamp |
| // - Enabled (if supported) |
| // - Disabled |
| // - Vertex Depth |
| // - Inside clip volume |
| // - Less than -1 |
| // - Greater than +1 |
| // - gl_FragDepth |
| // - Unused |
| // - Passthrough (gl_FragCoord.z) |
| // - Within the depth range |
| // - Between 0 and near clipping plane |
| // - Between 1 and far clipping plane |
| // - Negative |
| // - Greater than 1 |
| // - Depth buffer format |
| // - DEPTH_COMPONENT16 |
| // - DEPTH_COMPONENT32F (ES 3.x only) |
| |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/gl_raii.h" |
| |
| using namespace angle; |
| |
| namespace |
| { |
| |
| enum class DepthRange |
| { |
| Full, |
| Reduced, |
| }; |
| |
| enum class VertexDepth |
| { |
| InsideClipVolume, |
| LessThanMinusOne, |
| GreaterThanOne, |
| }; |
| |
| enum class FragmentDepth |
| { |
| Unused, |
| Passthrough, |
| WithinDepthRange, |
| BetweenZeroAndNearPlane, |
| BetweenFarPlaneAndOne, |
| Negative, |
| GreaterThanOne, |
| }; |
| |
| // Variations corresponding to enums above. |
| using DepthWriteVariationsTestParams = |
| std::tuple<angle::PlatformParameters, DepthRange, bool, VertexDepth, FragmentDepth, GLenum>; |
| |
| std::ostream &operator<<(std::ostream &out, DepthRange depthRange) |
| { |
| switch (depthRange) |
| { |
| case DepthRange::Full: |
| out << "Full"; |
| break; |
| case DepthRange::Reduced: |
| out << "Reduced"; |
| break; |
| } |
| |
| return out; |
| } |
| |
| std::ostream &operator<<(std::ostream &out, VertexDepth vertexDepth) |
| { |
| switch (vertexDepth) |
| { |
| case VertexDepth::InsideClipVolume: |
| out << "InsideClipVolume"; |
| break; |
| case VertexDepth::LessThanMinusOne: |
| out << "LessThanMinusOne"; |
| break; |
| case VertexDepth::GreaterThanOne: |
| out << "GreaterThanOne"; |
| break; |
| } |
| |
| return out; |
| } |
| |
| std::ostream &operator<<(std::ostream &out, FragmentDepth fragmentDepth) |
| { |
| switch (fragmentDepth) |
| { |
| case FragmentDepth::Unused: |
| out << "Unused"; |
| break; |
| case FragmentDepth::Passthrough: |
| out << "Passthrough"; |
| break; |
| case FragmentDepth::WithinDepthRange: |
| out << "WithinDepthRange"; |
| break; |
| case FragmentDepth::BetweenZeroAndNearPlane: |
| out << "BetweenZeroAndNearPlane"; |
| break; |
| case FragmentDepth::BetweenFarPlaneAndOne: |
| out << "BetweenFarPlaneAndOne"; |
| break; |
| case FragmentDepth::Negative: |
| out << "Negative"; |
| break; |
| case FragmentDepth::GreaterThanOne: |
| out << "GreaterThanOne"; |
| break; |
| } |
| |
| return out; |
| } |
| |
| std::string BufferFormatToString(GLenum format) |
| { |
| switch (format) |
| { |
| case GL_DEPTH_COMPONENT16: |
| return "Depth16Unorm"; |
| case GL_DEPTH_COMPONENT32F: |
| return "Depth32Float"; |
| default: |
| return ""; |
| } |
| } |
| |
| void ParseDepthWriteVariationsTestParams(const DepthWriteVariationsTestParams ¶ms, |
| DepthRange *depthRangeOut, |
| bool *depthClampEnabledOut, |
| VertexDepth *vertexDepthOut, |
| FragmentDepth *fragmentDepthOut, |
| GLenum *depthBufferFormatOut) |
| { |
| *depthRangeOut = std::get<1>(params); |
| *depthClampEnabledOut = std::get<2>(params); |
| *vertexDepthOut = std::get<3>(params); |
| *fragmentDepthOut = std::get<4>(params); |
| *depthBufferFormatOut = std::get<5>(params); |
| } |
| |
| std::string DepthWriteVariationsTestPrint( |
| const ::testing::TestParamInfo<DepthWriteVariationsTestParams> ¶msInfo) |
| { |
| const DepthWriteVariationsTestParams ¶ms = paramsInfo.param; |
| std::ostringstream out; |
| |
| out << std::get<0>(params); |
| |
| DepthRange depthRange; |
| bool depthClampEnabled; |
| VertexDepth vertexDepth; |
| FragmentDepth fragmentDepth; |
| GLenum depthBufferFormat; |
| ParseDepthWriteVariationsTestParams(params, &depthRange, &depthClampEnabled, &vertexDepth, |
| &fragmentDepth, &depthBufferFormat); |
| |
| out << "__" |
| << "DepthRange" << depthRange << "_" << (depthClampEnabled ? "Clamped" : "Clipped") << "_" |
| << "VertexDepth" << vertexDepth << "_" |
| << "FragmentDepth" << fragmentDepth << "_" << BufferFormatToString(depthBufferFormat); |
| return out.str(); |
| } |
| |
| class DepthWriteTest : public ANGLETest<DepthWriteVariationsTestParams> |
| {}; |
| |
| // Test correctness of depth writes |
| TEST_P(DepthWriteTest, Test) |
| { |
| if (getClientMajorVersion() < 3) |
| { |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_depth_texture")); |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float")); |
| } |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float") && |
| !IsGLExtensionEnabled("GL_EXT_color_buffer_float")); |
| |
| DepthRange depthRange; |
| bool depthClampEnabled; |
| VertexDepth vertexDepth; |
| FragmentDepth fragmentDepth; |
| GLenum depthBufferFormat; |
| ParseDepthWriteVariationsTestParams(GetParam(), &depthRange, &depthClampEnabled, &vertexDepth, |
| &fragmentDepth, &depthBufferFormat); |
| |
| if (getClientMajorVersion() < 3) |
| { |
| ANGLE_SKIP_TEST_IF(fragmentDepth != FragmentDepth::Unused && |
| !IsGLExtensionEnabled("GL_EXT_frag_depth")); |
| ANGLE_SKIP_TEST_IF(depthBufferFormat == GL_DEPTH_COMPONENT32F); |
| } |
| ANGLE_SKIP_TEST_IF(depthClampEnabled && !IsGLExtensionEnabled("GL_EXT_depth_clamp")); |
| |
| const float near = depthRange == DepthRange::Full ? 0.0 : 0.25; |
| const float far = depthRange == DepthRange::Full ? 1.0 : 0.75; |
| glDepthRangef(near, far); |
| |
| if (depthClampEnabled) |
| { |
| glEnable(GL_DEPTH_CLAMP_EXT); |
| } |
| |
| float vertexDepthValue = 0.0; |
| switch (vertexDepth) |
| { |
| case VertexDepth::InsideClipVolume: |
| vertexDepthValue = 0.25; // maps to 0.625 |
| break; |
| case VertexDepth::LessThanMinusOne: |
| vertexDepthValue = -1.5; |
| break; |
| case VertexDepth::GreaterThanOne: |
| vertexDepthValue = 1.5; |
| break; |
| } |
| |
| float fragmentDepthValue = 0.0; |
| switch (fragmentDepth) |
| { |
| case FragmentDepth::Unused: |
| case FragmentDepth::Passthrough: |
| break; |
| case FragmentDepth::WithinDepthRange: |
| fragmentDepthValue = 0.375; |
| break; |
| case FragmentDepth::BetweenZeroAndNearPlane: |
| fragmentDepthValue = depthRange == DepthRange::Reduced ? 0.125 : 0.0; |
| break; |
| case FragmentDepth::BetweenFarPlaneAndOne: |
| fragmentDepthValue = depthRange == DepthRange::Reduced ? 0.875 : 1.0; |
| break; |
| case FragmentDepth::Negative: |
| fragmentDepthValue = -0.25; |
| break; |
| case FragmentDepth::GreaterThanOne: |
| fragmentDepthValue = 1.25; |
| break; |
| } |
| |
| glEnable(GL_DEPTH_TEST); |
| glDepthFunc(GL_ALWAYS); |
| |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| |
| const bool es2 = getClientMajorVersion() < 3; |
| const bool fragCoord = fragmentDepth == FragmentDepth::Passthrough; |
| std::stringstream fragmentSource; |
| fragmentSource << (es2 ? "#extension GL_EXT_frag_depth : require\n" : "#version 300 es\n") |
| << (es2 ? "" : "out mediump vec4 fragColor;\n") |
| << (fragCoord ? "" : "uniform mediump float u_depth;\n") << "void main()\n" |
| << "{\n" |
| << (es2 ? " gl_FragColor" : " fragColor") |
| << " = vec4(1.0, 0.0, 0.0, 1.0);\n" |
| << " gl_FragDepth" << (es2 ? "EXT" : "") |
| << (fragCoord ? " = gl_FragCoord.z;\n" : " = u_depth;\n") << "}"; |
| |
| ANGLE_GL_PROGRAM(program, es2 ? essl1_shaders::vs::Simple() : essl3_shaders::vs::Simple(), |
| fragmentDepth == FragmentDepth::Unused |
| ? (es2 ? essl1_shaders::fs::Red() : essl3_shaders::fs::Red()) |
| : fragmentSource.str().c_str()); |
| glUseProgram(program); |
| if (fragmentDepth != FragmentDepth::Unused && fragmentDepth != FragmentDepth::Passthrough) |
| { |
| glUniform1f(glGetUniformLocation(program, "u_depth"), fragmentDepthValue); |
| } |
| ASSERT_GL_NO_ERROR(); |
| |
| GLFramebuffer fb; |
| glBindFramebuffer(GL_FRAMEBUFFER, fb); |
| |
| GLRenderbuffer rb; |
| glBindRenderbuffer(GL_RENDERBUFFER, rb); |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, w, h); |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb); |
| |
| GLTexture texDepth; |
| glBindTexture(GL_TEXTURE_2D, texDepth); |
| 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, |
| depthBufferFormat == GL_DEPTH_COMPONENT32F ? GL_DEPTH_COMPONENT32F : GL_DEPTH_COMPONENT, w, |
| h, 0, GL_DEPTH_COMPONENT, |
| depthBufferFormat == GL_DEPTH_COMPONENT32F ? GL_FLOAT : GL_UNSIGNED_SHORT, nullptr); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texDepth, 0); |
| |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| glEnable(GL_DEPTH_TEST); |
| glDepthFunc(GL_ALWAYS); |
| |
| glClearDepthf(0.33333333); |
| glClear(GL_DEPTH_BUFFER_BIT); |
| |
| drawQuad(program, essl1_shaders::PositionAttrib(), vertexDepthValue); |
| ASSERT_GL_NO_ERROR(); |
| |
| auto getExpectedValue = [&]() { |
| auto clamp = [](float x, float min, float max) { |
| return x < min ? min : (x > max ? max : x); |
| }; |
| |
| if (depthClampEnabled) |
| { |
| if (fragmentDepth != FragmentDepth::Unused && |
| fragmentDepth != FragmentDepth::Passthrough) |
| { |
| // Fragment value clamped to the depth range |
| return clamp(fragmentDepthValue, near, far); |
| } |
| else |
| { |
| // Vertex value transformed to window coordinates and clamped to the depth range |
| return clamp(0.5f * ((far - near) * vertexDepthValue + (far + near)), near, far); |
| } |
| } |
| else if (vertexDepthValue >= -1.0f && vertexDepthValue <= 1.0f) |
| { |
| if (fragmentDepth != FragmentDepth::Unused && |
| fragmentDepth != FragmentDepth::Passthrough) |
| { |
| // Fragment value clamped to [0, 1] |
| return clamp(fragmentDepthValue, 0.0f, 1.0f); |
| } |
| else |
| { |
| // Vertex value transformed to window coordinates |
| return 0.5f * ((far - near) * vertexDepthValue + (far + near)); |
| } |
| } |
| return 0.33333333f; |
| }; |
| |
| // Second pass to read written depth value |
| ANGLE_GL_PROGRAM(readProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); |
| glUseProgram(readProgram); |
| glUniform1i(glGetUniformLocation(readProgram, essl1_shaders::Texture2DUniform()), 0); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLFramebuffer readFb; |
| glBindFramebuffer(GL_FRAMEBUFFER, readFb); |
| |
| GLRenderbuffer readRb; |
| glBindRenderbuffer(GL_RENDERBUFFER, readRb); |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA16F, w, h); |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, readRb); |
| |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| glClear(GL_COLOR_BUFFER_BIT); |
| drawQuad(readProgram, essl1_shaders::PositionAttrib(), 0.0f); |
| |
| float writtenValue[4] = {std::numeric_limits<float>::quiet_NaN()}; |
| glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, writtenValue); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_NEAR(getExpectedValue(), writtenValue[0], 0.001); |
| } |
| |
| constexpr DepthRange kDepthRanges[] = { |
| DepthRange::Full, |
| DepthRange::Reduced, |
| }; |
| constexpr VertexDepth kVertexDepths[] = { |
| VertexDepth::InsideClipVolume, |
| VertexDepth::LessThanMinusOne, |
| VertexDepth::GreaterThanOne, |
| }; |
| constexpr FragmentDepth kFragmentDepths[] = { |
| FragmentDepth::Unused, |
| FragmentDepth::Passthrough, |
| FragmentDepth::WithinDepthRange, |
| FragmentDepth::BetweenZeroAndNearPlane, |
| FragmentDepth::BetweenFarPlaneAndOne, |
| FragmentDepth::Negative, |
| FragmentDepth::GreaterThanOne, |
| }; |
| constexpr GLenum kDepthBufferFormats[] = { |
| GL_DEPTH_COMPONENT16, |
| GL_DEPTH_COMPONENT32F, |
| }; |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DepthWriteTest); |
| ANGLE_INSTANTIATE_TEST_COMBINE_5(DepthWriteTest, |
| DepthWriteVariationsTestPrint, |
| testing::ValuesIn(kDepthRanges), |
| testing::Bool(), |
| testing::ValuesIn(kVertexDepths), |
| testing::ValuesIn(kFragmentDepths), |
| testing::ValuesIn(kDepthBufferFormats), |
| ANGLE_ALL_TEST_PLATFORMS_ES2, |
| ANGLE_ALL_TEST_PLATFORMS_ES3); |
| |
| } // anonymous namespace |