blob: 9714891f821e4c1ca01b9e6eebd70690a2f85a8f [file] [log] [blame] [edit]
//
// Copyright 2017 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.
//
// SamplerTest.cpp : Tests for samplers.
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include "gtest/gtest.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/angle_test_configs.h"
#include "test_utils/gl_raii.h"
#include "util/gles_loader_autogen.h"
#include "util/shader_utils.h"
namespace angle
{
using BasicSamplersTest = ANGLETest<>;
// Basic sampler test.
TEST_P(BasicSamplersTest, SampleATexture)
{
constexpr int kWidth = 2;
constexpr int kHeight = 2;
const GLchar *vertString = R"(precision highp float;
attribute vec2 a_position;
varying vec2 texCoord;
void main()
{
gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);
texCoord = a_position * 0.5 + vec2(0.5);
})";
const GLchar *fragString = R"(precision highp float;
varying vec2 texCoord;
uniform sampler2D tex;
void main()
{
gl_FragColor = texture2D(tex, texCoord);
})";
std::array<GLColor, kWidth * kHeight> redColor = {
{GLColor::red, GLColor::red, GLColor::red, GLColor::red}};
std::array<GLColor, kWidth * kHeight> greenColor = {
{GLColor::green, GLColor::green, GLColor::green, GLColor::green}};
// Create a red texture and bind to texture unit 0
GLTexture redTex;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, redTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
redColor.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
ASSERT_GL_NO_ERROR();
// Create a green texture and bind to texture unit 1
GLTexture greenTex;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, greenTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
greenColor.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glActiveTexture(GL_TEXTURE0);
ASSERT_GL_NO_ERROR();
GLProgram program;
program.makeRaster(vertString, fragString);
ASSERT_NE(0u, program);
glUseProgram(program);
GLint location = glGetUniformLocation(program, "tex");
ASSERT_NE(location, -1);
ASSERT_GL_NO_ERROR();
// Draw red
glUniform1i(location, 0);
ASSERT_GL_NO_ERROR();
drawQuad(program, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
// Draw green
glUniform1i(location, 1);
ASSERT_GL_NO_ERROR();
drawQuad(program, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}
class SampleFromRenderedTextureTest : public ANGLETest<>
{
protected:
const GLchar *vertString = R"(precision highp float;
attribute vec2 a_position;
varying vec2 texCoord;
void main()
{
gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);
texCoord = a_position * 0.5 + vec2(0.5);
})";
const GLchar *vertString2 = R"(precision highp float;
attribute vec2 a_position;
varying vec2 texCoord;
void main()
{
gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);
texCoord = a_position * 0.25 + vec2(0.5);
})";
const GLchar *fragString = R"(precision highp float;
varying vec2 texCoord;
uniform sampler2D tex;
void main()
{
gl_FragColor = texture2D(tex, texCoord);
})";
virtual GLsizei getTextureWidth() = 0;
virtual GLsizei getTextureHeight() = 0;
virtual GLsizei getViewportOriginX() = 0;
virtual GLsizei getViewportOriginY() = 0;
void testSetUp() override
{
glViewport(getViewportOriginX(), getViewportOriginY(), getTextureWidth(),
getTextureHeight());
ANGLETest<>::testSetUp();
}
GLuint createGradientTexture()
{
GLuint gradientTex;
glGenTextures(1, &gradientTex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gradientTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
std::vector<GLubyte> gradientPixels(getTextureWidth() * getTextureHeight() * 4);
for (GLubyte y = 0; y < getTextureHeight(); y++)
{
for (GLubyte x = 0; x < getTextureWidth(); x++)
{
GLubyte *pixel = &gradientPixels[0] + ((y * getTextureWidth() + x) * 4);
// Draw a gradient, red in x direction, green in y direction
pixel[0] = x;
pixel[1] = y;
pixel[2] = 0u;
pixel[3] = 255u;
}
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getTextureWidth(), getTextureHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, gradientPixels.data());
EXPECT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, 0);
return gradientTex;
}
void installProgram(bool halfScreen)
{
if (halfScreen)
{
mProgram.makeRaster(vertString2, fragString);
ASSERT_NE(0u, mProgram);
glUseProgram(mProgram);
}
else
{
mProgram.makeRaster(vertString, fragString);
ASSERT_NE(0u, mProgram);
glUseProgram(mProgram);
}
}
void createBoundFramebufferWithColorAttachment(GLuint *fboId, GLuint *colorAttachment)
{
// Create a texture to use as the non-default FBO's color attachment.
glGenTextures(1, colorAttachment);
glBindTexture(GL_TEXTURE_2D, *colorAttachment);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, NULL);
EXPECT_GL_NO_ERROR();
// Create the non-default fbo
if (fboId)
{
glGenFramebuffers(1, fboId);
glBindFramebuffer(GL_FRAMEBUFFER, *fboId);
EXPECT_GL_NO_ERROR();
}
// Attach the texture to the fbo
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
*colorAttachment, 0);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
ASSERT_GL_NO_ERROR();
glBindTexture(GL_TEXTURE_2D, 0);
}
void bindActiveTextureToProgram(GLuint activeTextureUnit, GLuint tex)
{
GLint texLocation = glGetUniformLocation(mProgram, "tex");
ASSERT_NE(texLocation, -1);
ASSERT_GL_NO_ERROR();
glActiveTexture(GL_TEXTURE0 + activeTextureUnit);
glBindTexture(GL_TEXTURE_2D, tex);
ASSERT_GL_NO_ERROR();
glUniform1i(texLocation, activeTextureUnit);
ASSERT_GL_NO_ERROR();
}
void drawAndCheckGradient(bool strict)
{
drawQuad(mProgram, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
std::vector<GLubyte> pixels(getTextureWidth() * getTextureHeight() * 4);
glReadPixels(getViewportOriginX(), getViewportOriginY(), getTextureWidth(),
getTextureHeight(), GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
ASSERT_GL_NO_ERROR();
// Check the pixels match the gradient.
size_t checkWidth = getTextureWidth();
size_t checkHeight = getTextureHeight();
if (!strict)
{
// Don't check the last row or column if not strict.
checkWidth--;
checkHeight--;
}
for (size_t y = 1; y < checkHeight; y++)
{
for (size_t x = 1; x < checkWidth; x++)
{
const GLubyte *prevPixel =
pixels.data() + (((y - 1) * getTextureWidth() + (x - 1)) * 4);
const GLubyte *curPixel = pixels.data() + ((y * getTextureWidth() + x) * 4);
if (strict)
{
EXPECT_EQ(curPixel[0], prevPixel[0] + 1)
<< " failed at (" << x << ", " << y << ")";
EXPECT_EQ(curPixel[1], prevPixel[1] + 1)
<< " failed at (" << x << ", " << y << ")";
}
else
{
EXPECT_GE(curPixel[0], prevPixel[0]) << " failed at (" << x << ", " << y << ")";
EXPECT_GE(curPixel[1], prevPixel[1]) << " failed at (" << x << ", " << y << ")";
}
EXPECT_EQ(curPixel[2], prevPixel[2]);
EXPECT_EQ(curPixel[3], prevPixel[3]);
}
}
}
GLProgram mProgram;
};
class SampleFromRenderedTextureTestHalfWindow : public SampleFromRenderedTextureTest
{
protected:
static constexpr GLsizei kTextureWidth = 255;
static constexpr GLsizei kTextureHeight = 255;
static constexpr GLsizei kViewportOriginX = kTextureWidth / 2;
static constexpr GLsizei kViewportOriginY = kTextureHeight / 2;
static constexpr GLsizei kWindowWidth = kTextureWidth * 2;
static constexpr GLsizei kWindowHeight = kTextureHeight * 2;
GLsizei getTextureWidth() override { return kTextureWidth; }
GLsizei getTextureHeight() override { return kTextureHeight; }
GLsizei getViewportOriginX() override { return kViewportOriginX; }
GLsizei getViewportOriginY() override { return kViewportOriginY; }
SampleFromRenderedTextureTestHalfWindow()
{
setWindowWidth(kWindowWidth);
setWindowHeight(kWindowHeight);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
};
// Renders a gradient to a texture (twice the size) attached to an FBO, then samples from that
// texture in a second pass, effectively copying the gradient to the middle of the default
// framebuffer. Tests that the gradient remains intact.
TEST_P(SampleFromRenderedTextureTestHalfWindow, RenderToTextureAndSampleFromIt)
{
// Create a gradient texture to use as the original source texture.
GLuint gradientTex = createGradientTexture();
installProgram(/*halfScreen=*/false);
GLuint fboId;
GLuint fboTextureAttachment;
createBoundFramebufferWithColorAttachment(&fboId, &fboTextureAttachment);
// The source texture used by the fragment shader should be the gradient texture.
bindActiveTextureToProgram(0, gradientTex);
drawAndCheckGradient(/*strict=*/true);
// Sample from the texture only in the current viewport (half the screen).
installProgram(/*halfScreen=*/true);
// Now bind the default framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ASSERT_GL_NO_ERROR();
// Use the texture attached to the first framebuffer as the source texture for this draw call.
bindActiveTextureToProgram(0, fboTextureAttachment);
// Draw and check the pixels, but in the default framebuffer
drawAndCheckGradient(/*strict=*/false);
}
class SampleFromRenderedTextureTestFullWindow : public SampleFromRenderedTextureTest
{
protected:
static constexpr GLsizei kTextureWidth = 255;
static constexpr GLsizei kTextureHeight = 255;
static constexpr GLsizei kViewportOriginX = 0;
static constexpr GLsizei kViewportOriginY = 0;
static constexpr GLsizei kWindowWidth = kTextureWidth;
static constexpr GLsizei kWindowHeight = kTextureHeight;
GLsizei getTextureWidth() override { return kTextureWidth; }
GLsizei getTextureHeight() override { return kTextureHeight; }
GLsizei getViewportOriginX() override { return kViewportOriginX; }
GLsizei getViewportOriginY() override { return kViewportOriginY; }
SampleFromRenderedTextureTestFullWindow()
{
setWindowWidth(kWindowWidth);
setWindowHeight(kWindowHeight);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
};
// Renders a gradient to a texture attached to an FBO, then samples from that texture in a second
// pass, effectively copying the gradient to the default framebuffer. Tests that the gradient
// remains intact.
TEST_P(SampleFromRenderedTextureTestFullWindow, RenderToTextureAndSampleFromIt)
{
// Setup the program.
installProgram(/*halfScreen=*/false);
// Create a gradient texture to use as the original source texture.
GLuint gradientTex = createGradientTexture();
// Create a texture to use as the non-default FBO's color attachment.
GLuint fboId;
GLuint fboTextureAttachment;
createBoundFramebufferWithColorAttachment(&fboId, &fboTextureAttachment);
bindActiveTextureToProgram(0, gradientTex);
drawAndCheckGradient(/*strict=*/true);
// Now bind the default framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ASSERT_GL_NO_ERROR();
// Use the texture attached to the first framebuffer as the source texture for this draw call.
bindActiveTextureToProgram(0, fboTextureAttachment);
drawAndCheckGradient(/*strict=*/true);
}
// Renders a gradient to a texture attached to an FBO, then samples from that texture in a second
// pass rendering to another texture attached to the FBO. Finally that texture is rendered to the
// default framebuffer. Tests that the gradient remains intact.
TEST_P(SampleFromRenderedTextureTestFullWindow, RenderToTextureTwiceAndSampleFromIt)
{
// Setup the program.
installProgram(/*halfScreen=*/false);
// Create a gradient texture to use as the original source texture.
GLuint gradientTex = createGradientTexture();
// Create a texture to use as the non-default FBO's color attachment.
GLuint fboId;
GLuint fboTextureAttachment;
createBoundFramebufferWithColorAttachment(&fboId, &fboTextureAttachment);
bindActiveTextureToProgram(0, gradientTex);
drawAndCheckGradient(/*strict=*/true);
// Create another texture to use as the non-default FBO's color attachment.
GLuint fboTextureAttachment2;
createBoundFramebufferWithColorAttachment(nullptr, &fboTextureAttachment2);
// Use the texture attached to the first framebuffer as the source texture for this draw call.
bindActiveTextureToProgram(0, fboTextureAttachment);
drawAndCheckGradient(/*strict=*/true);
// Now bind the default framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ASSERT_GL_NO_ERROR();
// Use the second texture attached to the first framebuffer as the source texture for this draw
// call.
bindActiveTextureToProgram(0, fboTextureAttachment2);
drawAndCheckGradient(/*strict=*/true);
}
class SamplersTest : public ANGLETest<>
{
protected:
SamplersTest() {}
// Sets a value for GL_TEXTURE_MAX_ANISOTROPY_EXT and expects it to fail.
void validateInvalidAnisotropy(GLSampler &sampler, float invalidValue)
{
glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, invalidValue);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
}
// Sets a value for GL_TEXTURE_MAX_ANISOTROPY_EXT and expects it to work.
void validateValidAnisotropy(GLSampler &sampler, float validValue)
{
glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, validValue);
EXPECT_GL_NO_ERROR();
GLfloat valueToVerify = 0.0f;
glGetSamplerParameterfv(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, &valueToVerify);
ASSERT_EQ(valueToVerify, validValue);
}
};
class SamplersTest31 : public SamplersTest
{};
// Verify that samplerParameterf supports TEXTURE_MAX_ANISOTROPY_EXT valid values.
TEST_P(SamplersTest, ValidTextureSamplerMaxAnisotropyExt)
{
GLSampler sampler;
// Exact min
validateValidAnisotropy(sampler, 1.0f);
GLfloat maxValue = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxValue);
// Max value
validateValidAnisotropy(sampler, maxValue - 1);
// In-between
GLfloat between = (1.0f + maxValue) / 2;
validateValidAnisotropy(sampler, between);
}
// Verify an error is thrown if we try to go under the minimum value for
// GL_TEXTURE_MAX_ANISOTROPY_EXT
TEST_P(SamplersTest, InvalidUnderTextureSamplerMaxAnisotropyExt)
{
GLSampler sampler;
// Under min
validateInvalidAnisotropy(sampler, 0.0f);
}
// Verify an error is thrown if we try to go over the max value for
// GL_TEXTURE_MAX_ANISOTROPY_EXT
TEST_P(SamplersTest, InvalidOverTextureSamplerMaxAnisotropyExt)
{
GLSampler sampler;
GLfloat maxValue = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxValue);
maxValue += 1;
validateInvalidAnisotropy(sampler, maxValue);
}
// Test that updating a sampler uniform in a program behaves correctly.
TEST_P(SamplersTest31, SampleTextureAThenTextureB)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
constexpr int kWidth = 2;
constexpr int kHeight = 2;
const GLchar *vertString = R"(#version 310 es
precision highp float;
in vec2 a_position;
out vec2 texCoord;
void main()
{
gl_Position = vec4(a_position, 0, 1);
texCoord = a_position * 0.5 + vec2(0.5);
})";
const GLchar *fragString = R"(#version 310 es
precision highp float;
in vec2 texCoord;
uniform sampler2D tex;
out vec4 my_FragColor;
void main()
{
my_FragColor = texture(tex, texCoord);
})";
std::array<GLColor, kWidth * kHeight> redColor = {
{GLColor::red, GLColor::red, GLColor::red, GLColor::red}};
std::array<GLColor, kWidth * kHeight> greenColor = {
{GLColor::green, GLColor::green, GLColor::green, GLColor::green}};
// Create a red texture and bind to texture unit 0
GLTexture redTex;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, redTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
redColor.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
ASSERT_GL_NO_ERROR();
// Create a green texture and bind to texture unit 1
GLTexture greenTex;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, greenTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
greenColor.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glActiveTexture(GL_TEXTURE0);
ASSERT_GL_NO_ERROR();
GLProgram program;
program.makeRaster(vertString, fragString);
ASSERT_NE(0u, program);
glUseProgram(program);
GLint location = glGetUniformLocation(program, "tex");
ASSERT_NE(location, -1);
ASSERT_GL_NO_ERROR();
// Draw red
glUniform1i(location, 0);
ASSERT_GL_NO_ERROR();
drawQuad(program, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
// Draw green
glUniform1i(location, 1);
ASSERT_GL_NO_ERROR();
drawQuad(program, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
// Draw red
glUniform1i(location, 0);
ASSERT_GL_NO_ERROR();
drawQuad(program, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::yellow);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BasicSamplersTest);
ANGLE_INSTANTIATE_TEST_ES2_AND(BasicSamplersTest, ES2_WEBGPU());
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SampleFromRenderedTextureTestHalfWindow);
ANGLE_INSTANTIATE_TEST_ES2_AND(SampleFromRenderedTextureTestHalfWindow, ES2_WEBGPU());
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SampleFromRenderedTextureTestFullWindow);
ANGLE_INSTANTIATE_TEST_ES2_AND(SampleFromRenderedTextureTestFullWindow, ES2_WEBGPU());
// Samplers are only supported on ES3.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SamplersTest);
ANGLE_INSTANTIATE_TEST_ES3(SamplersTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SamplersTest31);
ANGLE_INSTANTIATE_TEST_ES31(SamplersTest31);
} // namespace angle