blob: 1cb19a17a896603fca31b3d7aa0f6255f0349fe4 [file] [log] [blame]
//
// Copyright 2024 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.
//
// These tests verify shadow sampler functions and their options.
#include "common/gl_enum_utils.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
enum class FunctionType
{
Texture,
TextureBias,
TextureOffset,
TextureOffsetBias,
TextureLod,
TextureLodOffset,
TextureGrad,
TextureGradOffset,
TextureProj,
TextureProjBias,
TextureProjOffset,
TextureProjOffsetBias,
TextureProjLod,
TextureProjLodOffset,
TextureProjGrad,
TextureProjGradOffset,
};
const char *FunctionName(FunctionType function)
{
switch (function)
{
case FunctionType::Texture:
case FunctionType::TextureBias:
return "texture";
case FunctionType::TextureOffset:
case FunctionType::TextureOffsetBias:
return "textureOffset";
case FunctionType::TextureLod:
return "textureLod";
case FunctionType::TextureLodOffset:
return "textureLodOffset";
case FunctionType::TextureGrad:
return "textureGrad";
case FunctionType::TextureGradOffset:
return "textureGradOffset";
case FunctionType::TextureProj:
case FunctionType::TextureProjBias:
return "textureProj";
case FunctionType::TextureProjOffset:
case FunctionType::TextureProjOffsetBias:
return "textureProjOffset";
case FunctionType::TextureProjLod:
return "textureProjLod";
case FunctionType::TextureProjLodOffset:
return "textureProjLodOffset";
case FunctionType::TextureProjGrad:
return "textureProjGrad";
case FunctionType::TextureProjGradOffset:
return "textureProjGradOffset";
}
}
constexpr bool IsProj(FunctionType function)
{
switch (function)
{
case FunctionType::TextureProj:
case FunctionType::TextureProjBias:
case FunctionType::TextureProjOffset:
case FunctionType::TextureProjOffsetBias:
case FunctionType::TextureProjLod:
case FunctionType::TextureProjLodOffset:
case FunctionType::TextureProjGrad:
case FunctionType::TextureProjGradOffset:
return true;
default:
return false;
}
}
constexpr bool HasBias(FunctionType function)
{
switch (function)
{
case FunctionType::TextureBias:
case FunctionType::TextureOffsetBias:
case FunctionType::TextureProjBias:
case FunctionType::TextureProjOffsetBias:
return true;
default:
return false;
}
}
constexpr bool HasLOD(FunctionType function)
{
switch (function)
{
case FunctionType::TextureLod:
case FunctionType::TextureLodOffset:
case FunctionType::TextureProjLod:
case FunctionType::TextureProjLodOffset:
return true;
default:
return false;
}
}
constexpr bool HasGrad(FunctionType function)
{
switch (function)
{
case FunctionType::TextureGrad:
case FunctionType::TextureGradOffset:
case FunctionType::TextureProjGrad:
case FunctionType::TextureProjGradOffset:
return true;
default:
return false;
}
}
constexpr bool HasOffset(FunctionType function)
{
switch (function)
{
case FunctionType::TextureOffset:
case FunctionType::TextureOffsetBias:
case FunctionType::TextureLodOffset:
case FunctionType::TextureGradOffset:
case FunctionType::TextureProjOffset:
case FunctionType::TextureProjOffsetBias:
case FunctionType::TextureProjLodOffset:
case FunctionType::TextureProjGradOffset:
return true;
default:
return false;
}
}
constexpr bool RequiresExtensionFor2DArray(FunctionType function)
{
switch (function)
{
case FunctionType::TextureBias:
case FunctionType::TextureOffset:
case FunctionType::TextureOffsetBias:
case FunctionType::TextureLod:
case FunctionType::TextureLodOffset:
return true;
default:
return false;
}
}
constexpr bool RequiresExtensionForCube(FunctionType function)
{
switch (function)
{
case FunctionType::TextureLod:
return true;
default:
return false;
}
}
constexpr bool RequiresExtensionForCubeArray(FunctionType function)
{
switch (function)
{
case FunctionType::TextureBias:
case FunctionType::TextureLod:
return true;
default:
return false;
}
}
bool Compare(float reference, float sampled, GLenum op)
{
switch (op)
{
case GL_NEVER:
return false;
case GL_LESS:
return reference < sampled;
case GL_EQUAL:
return reference == sampled;
case GL_LEQUAL:
return reference <= sampled;
case GL_GREATER:
return reference > sampled;
case GL_NOTEQUAL:
return reference != sampled;
case GL_GEQUAL:
return reference >= sampled;
case GL_ALWAYS:
return true;
default:
UNREACHABLE();
return false;
}
}
// Variations corresponding to enums above.
using ShadowSamplerFunctionVariationsTestParams =
std::tuple<angle::PlatformParameters, FunctionType, bool>;
std::ostream &operator<<(std::ostream &out, FunctionType function)
{
switch (function)
{
case FunctionType::Texture:
out << "Texture";
break;
case FunctionType::TextureBias:
out << "TextureBias";
break;
case FunctionType::TextureOffset:
out << "TextureOffset";
break;
case FunctionType::TextureOffsetBias:
out << "TextureOffsetBias";
break;
case FunctionType::TextureLod:
out << "TextureLod";
break;
case FunctionType::TextureLodOffset:
out << "TextureLodOffset";
break;
case FunctionType::TextureGrad:
out << "TextureGrad";
break;
case FunctionType::TextureGradOffset:
out << "TextureGradOffset";
break;
case FunctionType::TextureProj:
out << "TextureProj";
break;
case FunctionType::TextureProjBias:
out << "TextureProjBias";
break;
case FunctionType::TextureProjOffset:
out << "TextureProjOffset";
break;
case FunctionType::TextureProjOffsetBias:
out << "TextureProjOffsetBias";
break;
case FunctionType::TextureProjLod:
out << "TextureProjLod";
break;
case FunctionType::TextureProjLodOffset:
out << "TextureProjLodOffset";
break;
case FunctionType::TextureProjGrad:
out << "TextureProjGrad";
break;
case FunctionType::TextureProjGradOffset:
out << "TextureProjGradOffset";
break;
}
return out;
}
void ParseShadowSamplerFunctionVariationsTestParams(
const ShadowSamplerFunctionVariationsTestParams &params,
FunctionType *functionOut,
bool *mipmappedOut)
{
*functionOut = std::get<1>(params);
*mipmappedOut = std::get<2>(params);
}
std::string ShadowSamplerFunctionVariationsTestPrint(
const ::testing::TestParamInfo<ShadowSamplerFunctionVariationsTestParams> &paramsInfo)
{
const ShadowSamplerFunctionVariationsTestParams &params = paramsInfo.param;
std::ostringstream out;
out << std::get<0>(params);
FunctionType function;
bool mipmapped;
ParseShadowSamplerFunctionVariationsTestParams(params, &function, &mipmapped);
out << "__" << function << "_" << (mipmapped ? "Mipmapped" : "NonMipmapped");
return out.str();
}
class ShadowSamplerFunctionTestBase : public ANGLETest<ShadowSamplerFunctionVariationsTestParams>
{
protected:
ShadowSamplerFunctionTestBase()
{
setWindowWidth(16);
setWindowHeight(16);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
GLuint mPrg = 0;
};
class ShadowSamplerFunctionTexture2DTest : public ShadowSamplerFunctionTestBase
{
protected:
void setupProgram2D(FunctionType function, bool useShadowSampler)
{
std::stringstream fragmentSource;
fragmentSource << "#version 300 es\n"
<< "precision mediump float;\n"
<< "precision mediump sampler2D;\n"
<< "precision mediump sampler2DShadow;\n"
<< "uniform float dRef;\n"
<< "uniform sampler2D" << (useShadowSampler ? "Shadow" : "") << " tex;\n"
<< "in vec4 v_position;\n"
<< "out vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " vec2 texcoord = v_position.xy * 0.5 + 0.5;\n"
<< " float r = " << FunctionName(function) << "(tex, ";
if (IsProj(function))
{
if (useShadowSampler)
{
fragmentSource << "vec4(texcoord * 2.0, dRef * 2.0, 2.0)";
}
else
{
fragmentSource << "vec3(texcoord * 2.0, 2.0)";
}
}
else
{
if (useShadowSampler)
{
fragmentSource << "vec3(texcoord, dRef)";
}
else
{
fragmentSource << "vec2(texcoord)";
}
}
if (HasLOD(function))
{
fragmentSource << ", 2.0";
}
else if (HasGrad(function))
{
fragmentSource << ", vec2(0.17), vec2(0.17)";
}
if (HasOffset(function))
{
// Does not affect LOD selection, added to try all overloads.
fragmentSource << ", ivec2(1, 1)";
}
if (HasBias(function))
{
fragmentSource << ", 3.0";
}
fragmentSource << ")" << (useShadowSampler ? "" : ".r") << ";\n";
if (useShadowSampler)
{
fragmentSource << " my_FragColor = vec4(0.0, r, 1.0 - r, 1.0);\n";
}
else
{
fragmentSource << " my_FragColor = vec4(r, 0.0, 0.0, 1.0);\n";
}
fragmentSource << "}";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Passthrough(), fragmentSource.str().c_str());
glUseProgram(program);
mPrg = program;
}
};
class ShadowSamplerFunctionTexture2DArrayTest : public ShadowSamplerFunctionTestBase
{
protected:
void setupProgram2DArray(FunctionType function, bool useShadowSampler)
{
ASSERT_FALSE(IsProj(function));
std::stringstream fragmentSource;
fragmentSource << "#version 300 es\n"
<< "#extension GL_EXT_texture_shadow_lod : enable\n"
<< "precision mediump float;\n"
<< "precision mediump sampler2DArray;\n"
<< "precision mediump sampler2DArrayShadow;\n"
<< "uniform float dRef;\n"
<< "uniform sampler2DArray" << (useShadowSampler ? "Shadow" : "")
<< " tex;\n"
<< "in vec4 v_position;\n"
<< "out vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " vec2 texcoord = v_position.xy * 0.5 + 0.5;\n"
<< " float r = " << FunctionName(function) << "(tex, ";
if (useShadowSampler)
{
fragmentSource << "vec4(texcoord, 1.0, dRef)";
}
else
{
fragmentSource << "vec3(texcoord, 1.0)";
}
if (HasLOD(function))
{
fragmentSource << ", 2.0";
}
else if (HasGrad(function))
{
fragmentSource << ", vec2(0.17), vec2(0.17)";
}
if (HasOffset(function))
{
// Does not affect LOD selection, added to try all overloads.
fragmentSource << ", ivec2(1, 1)";
}
if (HasBias(function))
{
fragmentSource << ", 3.0";
}
fragmentSource << ")" << (useShadowSampler ? "" : ".r") << ";\n";
if (useShadowSampler)
{
fragmentSource << " my_FragColor = vec4(0.0, r, 1.0 - r, 1.0);\n";
}
else
{
fragmentSource << " my_FragColor = vec4(r, 0.0, 0.0, 1.0);\n";
}
fragmentSource << "}";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Passthrough(), fragmentSource.str().c_str());
glUseProgram(program);
mPrg = program;
}
};
class ShadowSamplerFunctionTextureCubeTest : public ShadowSamplerFunctionTestBase
{
protected:
void setupProgramCube(FunctionType function, bool useShadowSampler)
{
ASSERT_FALSE(IsProj(function));
ASSERT_FALSE(HasOffset(function));
std::stringstream fragmentSource;
fragmentSource << "#version 300 es\n"
<< "#extension GL_EXT_texture_shadow_lod : enable\n"
<< "precision mediump float;\n"
<< "precision mediump samplerCube;\n"
<< "precision mediump samplerCubeShadow;\n"
<< "uniform float dRef;\n"
<< "uniform samplerCube" << (useShadowSampler ? "Shadow" : "") << " tex;\n"
<< "in vec4 v_position;\n"
<< "out vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " vec3 texcoord = vec3(1.0, v_position.xy);\n"
<< " float r = " << FunctionName(function) << "(tex, ";
if (useShadowSampler)
{
fragmentSource << "vec4(texcoord, dRef)";
}
else
{
fragmentSource << "vec3(texcoord)";
}
if (HasLOD(function))
{
fragmentSource << ", 2.0";
}
else if (HasGrad(function))
{
fragmentSource << ", vec3(0.0, 0.34, 0.34), vec3(0.0)";
}
if (HasBias(function))
{
fragmentSource << ", 3.0";
}
fragmentSource << ")" << (useShadowSampler ? "" : ".r") << ";\n";
if (useShadowSampler)
{
fragmentSource << " my_FragColor = vec4(0.0, r, 1.0 - r, 1.0);\n";
}
else
{
fragmentSource << " my_FragColor = vec4(r, 0.0, 0.0, 1.0);\n";
}
fragmentSource << "}";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Passthrough(), fragmentSource.str().c_str());
glUseProgram(program);
mPrg = program;
}
};
class ShadowSamplerFunctionTextureCubeArrayTest : public ShadowSamplerFunctionTestBase
{
protected:
void setupProgramCubeArray(FunctionType function, bool useShadowSampler)
{
ASSERT_FALSE(IsProj(function));
ASSERT_FALSE(HasOffset(function));
ASSERT_FALSE(HasGrad(function));
std::stringstream fragmentSource;
fragmentSource << "#version 310 es\n"
<< "#extension GL_EXT_texture_cube_map_array : require\n"
<< "#extension GL_EXT_texture_shadow_lod : enable\n"
<< "precision mediump float;\n"
<< "precision mediump samplerCubeArray;\n"
<< "precision mediump samplerCubeArrayShadow;\n"
<< "uniform float dRef;\n"
<< "uniform samplerCubeArray" << (useShadowSampler ? "Shadow" : "")
<< " tex;\n"
<< "in vec4 v_position;\n"
<< "out vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " vec4 texcoord = vec4(1.0, v_position.xy, 1.0);\n"
<< " float r = " << FunctionName(function) << "(tex, texcoord";
if (useShadowSampler)
{
fragmentSource << ", dRef";
}
if (HasLOD(function))
{
fragmentSource << ", 2.0";
}
else if (HasBias(function))
{
fragmentSource << ", 3.0";
}
fragmentSource << ")" << (useShadowSampler ? "" : ".r") << ";\n";
if (useShadowSampler)
{
fragmentSource << " my_FragColor = vec4(0.0, r, 1.0 - r, 1.0);\n";
}
else
{
fragmentSource << " my_FragColor = vec4(r, 0.0, 0.0, 1.0);\n";
}
fragmentSource << "}";
ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Passthrough(), fragmentSource.str().c_str());
glUseProgram(program);
mPrg = program;
}
};
constexpr GLenum kCompareFuncs[] = {
GL_LEQUAL, GL_GEQUAL, GL_LESS, GL_GREATER, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS, GL_NEVER,
};
constexpr float kRefValues[] = {
0.0f, 0.25f, 0.5f, 0.75f, 1.0f,
};
// Test TEXTURE_2D with shadow samplers
TEST_P(ShadowSamplerFunctionTexture2DTest, Test)
{
FunctionType function;
bool mipmapped;
ParseShadowSamplerFunctionVariationsTestParams(GetParam(), &function, &mipmapped);
GLTexture tex;
const std::vector<GLfloat> level0(64, 0.125f);
const std::vector<GLfloat> level1(16, 0.5f);
const std::vector<GLfloat> level2(4, 0.25f);
const std::vector<GLfloat> level3(1, 0.75f);
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 4, GL_DEPTH_COMPONENT32F, 8, 8);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_DEPTH_COMPONENT, GL_FLOAT, level0.data());
glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 4, 4, GL_DEPTH_COMPONENT, GL_FLOAT, level1.data());
glTexSubImage2D(GL_TEXTURE_2D, 2, 0, 0, 2, 2, GL_DEPTH_COMPONENT, GL_FLOAT, level2.data());
glTexSubImage2D(GL_TEXTURE_2D, 3, 0, 0, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, level3.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmapped ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
ASSERT_GL_NO_ERROR();
float expectedSample;
if (mipmapped)
{
if (HasBias(function))
{
// Base level 8x8, viewport 8x8, bias 3.0
expectedSample = level3[0];
}
else if (HasLOD(function))
{
// Explicitly requested level 2
expectedSample = level2[0];
}
else if (HasGrad(function))
{
// Screen space derivatives of 0.17 for a 8x8 texture should resolve to level 1
expectedSample = level1[0];
}
else // implicit LOD
{
// Base level 8x8, viewport 8x8, no bias
expectedSample = level0[0];
}
}
else
{
// LOD options must have no effect when the texture is not mipmapped.
expectedSample = level0[0];
}
glViewport(0, 0, 8, 8);
glClearColor(1.0, 0.0, 1.0, 1.0);
// First sample the texture directly for easier debugging
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
setupProgram2D(function, false);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl3_shaders::PositionAttrib(), 0.0f);
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(expectedSample * 255.0, 0, 0, 255), 1);
// Try shadow samplers
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
setupProgram2D(function, true);
const GLint loc = glGetUniformLocation(mPrg, "dRef");
for (const float refValue : kRefValues)
{
glUniform1f(loc, refValue);
for (const GLenum compareFunc : kCompareFuncs)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, compareFunc);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl3_shaders::PositionAttrib(), 0.0f);
if (Compare(refValue, expectedSample, compareFunc))
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::green)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
else
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::blue)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
}
}
}
// Test TEXTURE_2D_ARRAY with shadow samplers
TEST_P(ShadowSamplerFunctionTexture2DArrayTest, Test)
{
FunctionType function;
bool mipmapped;
ParseShadowSamplerFunctionVariationsTestParams(GetParam(), &function, &mipmapped);
if (RequiresExtensionFor2DArray(function))
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_shadow_lod"));
}
GLTexture tex;
const std::vector<GLfloat> unused(64, 0.875);
const std::vector<GLfloat> level0(64, 0.125f);
const std::vector<GLfloat> level1(16, 0.5f);
const std::vector<GLfloat> level2(4, 0.25f);
const std::vector<GLfloat> level3(1, 0.75f);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 4, GL_DEPTH_COMPONENT32F, 8, 8, 2);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
unused.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
unused.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 2, 0, 0, 0, 2, 2, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
unused.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 3, 0, 0, 0, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
unused.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 8, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
level0.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 1, 4, 4, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
level1.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 2, 0, 0, 1, 2, 2, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
level2.data());
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 3, 0, 0, 1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
level3.data());
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,
mipmapped ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
ASSERT_GL_NO_ERROR();
float expectedSample;
if (mipmapped)
{
if (HasBias(function))
{
// Base level 8x8, viewport 8x8, bias 3.0
expectedSample = level3[0];
}
else if (HasLOD(function))
{
// Explicitly requested level 2
expectedSample = level2[0];
}
else if (HasGrad(function))
{
// Screen space derivatives of 0.17 for a 8x8 texture should resolve to level 1
expectedSample = level1[0];
}
else // implicit LOD
{
// Base level 8x8, viewport 8x8, no bias
expectedSample = level0[0];
}
}
else
{
// LOD options must have no effect when the texture is not mipmapped.
expectedSample = level0[0];
}
glViewport(0, 0, 8, 8);
glClearColor(1.0, 0.0, 1.0, 1.0);
// First sample the texture directly for easier debugging
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE);
setupProgram2DArray(function, false);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl3_shaders::PositionAttrib(), 0.0f);
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(expectedSample * 255.0, 0, 0, 255), 1);
// Try shadow samplers
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
setupProgram2DArray(function, true);
const GLint loc = glGetUniformLocation(mPrg, "dRef");
for (const float refValue : kRefValues)
{
glUniform1f(loc, refValue);
for (const GLenum compareFunc : kCompareFuncs)
{
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, compareFunc);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl3_shaders::PositionAttrib(), 0.0f);
if (Compare(refValue, expectedSample, compareFunc))
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::green)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
else
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::blue)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
}
}
}
// Test TEXTURE_CUBE_MAP with shadow samplers
TEST_P(ShadowSamplerFunctionTextureCubeTest, Test)
{
FunctionType function;
bool mipmapped;
ParseShadowSamplerFunctionVariationsTestParams(GetParam(), &function, &mipmapped);
if (RequiresExtensionForCube(function))
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_shadow_lod"));
}
GLTexture tex;
const std::vector<GLfloat> level0(64, 0.125f);
const std::vector<GLfloat> level1(16, 0.5f);
const std::vector<GLfloat> level2(4, 0.25f);
const std::vector<GLfloat> level3(1, 0.75f);
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 4, GL_DEPTH_COMPONENT32F, 8, 8);
for (const GLenum face : {GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z})
{
glTexSubImage2D(face, 0, 0, 0, 8, 8, GL_DEPTH_COMPONENT, GL_FLOAT, level0.data());
glTexSubImage2D(face, 1, 0, 0, 4, 4, GL_DEPTH_COMPONENT, GL_FLOAT, level1.data());
glTexSubImage2D(face, 2, 0, 0, 2, 2, GL_DEPTH_COMPONENT, GL_FLOAT, level2.data());
glTexSubImage2D(face, 3, 0, 0, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, level3.data());
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,
mipmapped ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
ASSERT_GL_NO_ERROR();
float expectedSample;
if (mipmapped)
{
if (HasBias(function))
{
// Base level 8x8, viewport 8x8, bias 3.0
expectedSample = level3[0];
}
else if (HasLOD(function))
{
// Explicitly requested level 2
expectedSample = level2[0];
}
else if (HasGrad(function))
{
// Cube screen space derivatives should be projected as 0.17
// on the +X face and resolved to level 1 for a 8x8 texture
expectedSample = level1[0];
}
else // implicit LOD
{
// Base level 8x8, viewport 8x8, no bias
expectedSample = level0[0];
}
}
else
{
// LOD options must have no effect when the texture is not mipmapped.
expectedSample = level0[0];
}
glViewport(0, 0, 8, 8);
glClearColor(1.0, 0.0, 1.0, 1.0);
// First sample the texture directly for easier debugging
// This step is skipped on Apple GPUs when the PreTransformTextureCubeGradDerivatives feature
// is disabled for testing because native cubemap sampling with explicit derivatives does not
// work on that platform without this feature.
// Since the AllowSamplerCompareGradient feature is also disabled in this case, the next steps,
// which use shadow samplers, rely on emulation and thus they are not affected.
const auto &disabledFeatures = std::get<0>(GetParam()).eglParameters.disabledFeatureOverrides;
if (!IsAppleGPU() ||
std::find(disabledFeatures.begin(), disabledFeatures.end(),
Feature::PreTransformTextureCubeGradDerivatives) == disabledFeatures.end())
{
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_MODE, GL_NONE);
setupProgramCube(function, false);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl3_shaders::PositionAttrib(), 0.0f);
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(expectedSample * 255.0, 0, 0, 255), 1);
}
// Try shadow samplers
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
setupProgramCube(function, true);
const GLint loc = glGetUniformLocation(mPrg, "dRef");
for (const float refValue : kRefValues)
{
glUniform1f(loc, refValue);
for (const GLenum compareFunc : kCompareFuncs)
{
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_FUNC, compareFunc);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl3_shaders::PositionAttrib(), 0.0f);
if (Compare(refValue, expectedSample, compareFunc))
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::green)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
else
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::blue)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
}
}
}
// Test TEXTURE_CUBE_MAP_ARRAY with shadow samplers
TEST_P(ShadowSamplerFunctionTextureCubeArrayTest, Test)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_cube_map_array"));
FunctionType function;
bool mipmapped;
ParseShadowSamplerFunctionVariationsTestParams(GetParam(), &function, &mipmapped);
if (RequiresExtensionForCubeArray(function))
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_shadow_lod"));
}
GLTexture tex;
const std::vector<GLfloat> unused(64, 0.875);
const std::vector<GLfloat> level0(64, 0.125f);
const std::vector<GLfloat> level1(16, 0.5f);
const std::vector<GLfloat> level2(4, 0.25f);
const std::vector<GLfloat> level3(1, 0.75f);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, tex);
glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, 4, GL_DEPTH_COMPONENT32F, 8, 8, 12);
for (size_t k = 0; k < 6 * 2; ++k)
{
glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, 0, 0, 0, k, 8, 8, 1, GL_DEPTH_COMPONENT,
GL_FLOAT, k > 5 ? level0.data() : unused.data());
glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, 1, 0, 0, k, 4, 4, 1, GL_DEPTH_COMPONENT,
GL_FLOAT, k > 5 ? level1.data() : unused.data());
glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, 2, 0, 0, k, 2, 2, 1, GL_DEPTH_COMPONENT,
GL_FLOAT, k > 5 ? level2.data() : unused.data());
glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, 3, 0, 0, k, 1, 1, 1, GL_DEPTH_COMPONENT,
GL_FLOAT, k > 5 ? level3.data() : unused.data());
}
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, GL_TEXTURE_MIN_FILTER,
mipmapped ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
ASSERT_GL_NO_ERROR();
float expectedSample;
if (mipmapped)
{
if (HasBias(function))
{
// Base level 8x8, viewport 8x8, bias 3.0
expectedSample = level3[0];
}
else if (HasLOD(function))
{
// Explicitly requested level 2
expectedSample = level2[0];
}
else // implicit LOD
{
// Base level 8x8, viewport 8x8, no bias
expectedSample = level0[0];
}
}
else
{
// LOD options must have no effect when the texture is not mipmapped.
expectedSample = level0[0];
}
glViewport(0, 0, 8, 8);
glClearColor(1.0, 0.0, 1.0, 1.0);
// First sample the texture directly for easier debugging
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, GL_TEXTURE_COMPARE_MODE, GL_NONE);
setupProgramCubeArray(function, false);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl31_shaders::PositionAttrib(), 0.0f);
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(expectedSample * 255.0, 0, 0, 255), 1);
// Try shadow samplers
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, GL_TEXTURE_COMPARE_MODE,
GL_COMPARE_REF_TO_TEXTURE);
setupProgramCubeArray(function, true);
const GLint loc = glGetUniformLocation(mPrg, "dRef");
for (const float refValue : kRefValues)
{
glUniform1f(loc, refValue);
for (const GLenum compareFunc : kCompareFuncs)
{
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY_EXT, GL_TEXTURE_COMPARE_FUNC, compareFunc);
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mPrg, essl31_shaders::PositionAttrib(), 0.0f);
if (Compare(refValue, expectedSample, compareFunc))
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::green)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
else
{
EXPECT_PIXEL_COLOR_EQ(4, 4, GLColor::blue)
<< gl::GLenumToString(gl::GLESEnum::DepthFunction, compareFunc)
<< ", reference " << refValue << ", expected sample " << expectedSample;
}
}
}
}
constexpr FunctionType kTexture2DFunctionTypes[] = {
FunctionType::Texture, FunctionType::TextureBias,
FunctionType::TextureOffset, FunctionType::TextureOffsetBias,
FunctionType::TextureLod, FunctionType::TextureLodOffset,
FunctionType::TextureGrad, FunctionType::TextureGradOffset,
FunctionType::TextureProj, FunctionType::TextureProjBias,
FunctionType::TextureProjOffset, FunctionType::TextureProjOffsetBias,
FunctionType::TextureProjLod, FunctionType::TextureProjLodOffset,
FunctionType::TextureProjGrad, FunctionType::TextureProjGradOffset,
};
constexpr FunctionType kTexture2DArrayFunctionTypes[] = {
FunctionType::Texture, FunctionType::TextureBias,
FunctionType::TextureOffset, FunctionType::TextureOffsetBias,
FunctionType::TextureLod, FunctionType::TextureLodOffset,
FunctionType::TextureGrad, FunctionType::TextureGradOffset,
};
constexpr FunctionType kTextureCubeFunctionTypes[] = {
FunctionType::Texture,
FunctionType::TextureBias,
FunctionType::TextureLod,
FunctionType::TextureGrad,
};
constexpr FunctionType kTextureCubeArrayFunctionTypes[] = {
FunctionType::Texture,
FunctionType::TextureBias,
FunctionType::TextureLod,
};
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ShadowSamplerFunctionTexture2DTest);
ANGLE_INSTANTIATE_TEST_COMBINE_2(ShadowSamplerFunctionTexture2DTest,
ShadowSamplerFunctionVariationsTestPrint,
testing::ValuesIn(kTexture2DFunctionTypes),
testing::Bool(),
ANGLE_ALL_TEST_PLATFORMS_ES3,
ES3_METAL()
.disable(Feature::AllowSamplerCompareGradient)
.disable(Feature::PreTransformTextureCubeGradDerivatives));
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ShadowSamplerFunctionTexture2DArrayTest);
ANGLE_INSTANTIATE_TEST_COMBINE_2(ShadowSamplerFunctionTexture2DArrayTest,
ShadowSamplerFunctionVariationsTestPrint,
testing::ValuesIn(kTexture2DArrayFunctionTypes),
testing::Bool(),
ANGLE_ALL_TEST_PLATFORMS_ES3,
ES3_METAL()
.disable(Feature::AllowSamplerCompareGradient)
.disable(Feature::PreTransformTextureCubeGradDerivatives));
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ShadowSamplerFunctionTextureCubeTest);
ANGLE_INSTANTIATE_TEST_COMBINE_2(ShadowSamplerFunctionTextureCubeTest,
ShadowSamplerFunctionVariationsTestPrint,
testing::ValuesIn(kTextureCubeFunctionTypes),
testing::Bool(),
ANGLE_ALL_TEST_PLATFORMS_ES3,
ES3_METAL()
.disable(Feature::AllowSamplerCompareGradient)
.disable(Feature::PreTransformTextureCubeGradDerivatives));
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ShadowSamplerFunctionTextureCubeArrayTest);
ANGLE_INSTANTIATE_TEST_COMBINE_2(ShadowSamplerFunctionTextureCubeArrayTest,
ShadowSamplerFunctionVariationsTestPrint,
testing::ValuesIn(kTextureCubeArrayFunctionTypes),
testing::Bool(),
ANGLE_ALL_TEST_PLATFORMS_ES31);
} // anonymous namespace