blob: fd5ba60146cbe452ba661a5f275b2e54abd39e69 [file] [log] [blame]
//
// 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 &params,
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> &paramsInfo)
{
const DepthWriteVariationsTestParams &params = 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