Commit c39d25ae by Tim Van Patten Committed by Commit Bot

Update State to check mExecutable

A user may be using Program Pipelines, rather than monolithic Programs, so State should check if mExecutable is valid, rather than mProgram, since that indicates the presence of either a PPO or a Program. Exercising these paths requires additional tests: SimpleStateChangeTestComputeES31PPO::DeleteImageTextureInUse() Texture2DTestES31PPO::TexStorage() Texture2DTestES31PPO::SingleTextureMultipleSamplers() These new tests exposed bugs in the PPO implementation where updates to the active Program's ProgramExecutable were not being propagated to the Executables of the PPO's containing that Program. In these particular cases, updates to the active samplers/images/textures were not being copied to the PPO's Executable. Bug: angleproject:3570 Test: end2end tests listed above Change-Id: I297cac2d0367f180dd7fa01a1ee7ba53996867c4 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2225417 Commit-Queue: Tim Van Patten <timvp@google.com> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org>
parent 707868ae
......@@ -157,11 +157,12 @@ class ProgramPipeline final : public RefCountObject<ProgramPipelineID>, public L
angle::Result syncState(const Context *context);
void setDirtyBit(DirtyBitType dirtyBitType) { mDirtyBits.set(dirtyBitType); }
void updateExecutableTextures();
private:
void updateLinkedShaderStages();
void updateExecutableAttributes();
void updateTransformFeedbackMembers();
void updateExecutableTextures();
void updateHasBooleans();
void updateExecutable();
......
......@@ -567,7 +567,9 @@ void State::reset(const Context *context)
mExecutable = nullptr;
if (mTransformFeedback.get())
{
mTransformFeedback->onBindingChanged(context, false);
}
mTransformFeedback.set(context, nullptr);
for (QueryType type : angle::AllEnums<QueryType>())
......@@ -629,7 +631,7 @@ ANGLE_INLINE void State::updateActiveTextureState(const Context *context,
}
}
if (texture && mProgram)
if (texture && mExecutable)
{
const SamplerState &samplerState =
sampler ? sampler->getSamplerState() : texture->getSamplerState();
......@@ -1539,7 +1541,9 @@ void State::invalidateTexture(TextureType type)
void State::setSamplerBinding(const Context *context, GLuint textureUnit, Sampler *sampler)
{
if (mSamplers[textureUnit].get() == sampler)
{
return;
}
mSamplers[textureUnit].set(context, sampler);
mDirtyBits.set(DIRTY_BIT_SAMPLER_BINDINGS);
......@@ -3368,22 +3372,43 @@ void State::setImageUnit(const Context *context,
onImageStateChange(context, unit);
}
void State::updatePPOActiveTextures()
{
// TODO(http://anglebug.com/4559): Use the Subject/Observer pattern for
// Programs in PPOs so we can remove this.
if (!mProgram)
{
// There is no Program bound, so we are updating the textures for a separable Program.
// Only that Program's Executable has been updated so far, so we need to update the
// Executable for each of the PPO's in case the Program is in there as well.
for (ResourceMap<ProgramPipeline, ProgramPipelineID>::Iterator ppoIterator =
mProgramPipelineManager->begin();
ppoIterator != mProgramPipelineManager->end(); ++ppoIterator)
{
ProgramPipeline *pipeline = ppoIterator->second;
pipeline->updateExecutableTextures();
}
}
}
// Handle a dirty texture event.
void State::onActiveTextureChange(const Context *context, size_t textureUnit)
{
if (mProgram)
if (mExecutable)
{
TextureType type = mExecutable->getActiveSamplerTypes()[textureUnit];
Texture *activeTexture = (type != TextureType::InvalidEnum)
? getTextureForActiveSampler(type, textureUnit)
: nullptr;
updateActiveTexture(context, textureUnit, activeTexture);
updatePPOActiveTextures();
}
}
void State::onActiveTextureStateChange(const Context *context, size_t textureUnit)
{
if (mProgram)
if (mExecutable)
{
TextureType type = mExecutable->getActiveSamplerTypes()[textureUnit];
Texture *activeTexture = (type != TextureType::InvalidEnum)
......@@ -3396,7 +3421,7 @@ void State::onActiveTextureStateChange(const Context *context, size_t textureUni
void State::onImageStateChange(const Context *context, size_t unit)
{
if (mProgram)
if (mExecutable)
{
const ImageUnit &image = mImageUnits[unit];
......@@ -3414,6 +3439,8 @@ void State::onImageStateChange(const Context *context, size_t unit)
{
mDirtyObjects.set(DIRTY_OBJECT_IMAGES_INIT);
}
updatePPOActiveTextures();
}
}
......
......@@ -818,6 +818,8 @@ class State : angle::NonCopyable
angle::Result syncProgram(const Context *context);
angle::Result syncProgramPipeline(const Context *context);
void updatePPOActiveTextures();
using DirtyObjectHandler = angle::Result (State::*)(const Context *context);
static constexpr DirtyObjectHandler kDirtyObjectHandlers[DIRTY_OBJECT_MAX] = {
&State::syncTexturesInit, &State::syncImagesInit, &State::syncReadAttachments,
......
......@@ -3592,6 +3592,7 @@ angle::Result ContextVk::updateActiveTextures(const gl::Context *context)
gl::Texture *texture = textures[textureUnit];
gl::Sampler *sampler = mState.getSampler(static_cast<uint32_t>(textureUnit));
gl::TextureType textureType = textureTypes[textureUnit];
ASSERT(textureType != gl::TextureType::InvalidEnum);
// Null textures represent incomplete textures.
if (texture == nullptr)
......
......@@ -1449,6 +1449,56 @@ void main()
GLuint mTexture = 0;
};
class ImageES31PPO
{
protected:
ImageES31PPO() : mComputeProg(0), mPipeline(0) {}
void bindProgramPipeline(const GLchar *computeString)
{
mComputeProg = glCreateShaderProgramv(GL_COMPUTE_SHADER, 1, &computeString);
ASSERT_NE(mComputeProg, 0u);
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &mPipeline);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_COMPUTE_SHADER_BIT, mComputeProg);
EXPECT_GL_NO_ERROR();
glBindProgramPipeline(mPipeline);
EXPECT_GL_NO_ERROR();
glActiveShaderProgram(mPipeline, mComputeProg);
EXPECT_GL_NO_ERROR();
}
GLuint mComputeProg;
GLuint mPipeline;
};
class SimpleStateChangeTestComputeES31PPO : public ImageES31PPO, public SimpleStateChangeTest
{
protected:
SimpleStateChangeTestComputeES31PPO() : ImageES31PPO(), SimpleStateChangeTest() {}
void testTearDown() override
{
if (mFramebuffer != 0)
{
glDeleteFramebuffers(1, &mFramebuffer);
mFramebuffer = 0;
}
if (mTexture != 0)
{
glDeleteTextures(1, &mTexture);
mTexture = 0;
}
glDeleteProgramPipelines(1, &mPipeline);
}
GLuint mFramebuffer = 0;
GLuint mTexture = 0;
};
constexpr char kSimpleVertexShader[] = R"(attribute vec2 position;
attribute vec4 color;
varying vec4 vColor;
......@@ -3425,6 +3475,60 @@ TEST_P(SimpleStateChangeTestComputeES31, DispatchImageTextureAThenTextureBThenTe
ASSERT_GL_NO_ERROR();
}
// Copied from SimpleStateChangeTestComputeES31::DeleteImageTextureInUse
// Tests that deleting an in-flight image texture does not immediately delete the resource.
TEST_P(SimpleStateChangeTestComputeES31PPO, DeleteImageTextureInUse)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
glGenFramebuffers(1, &mFramebuffer);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2);
EXPECT_GL_NO_ERROR();
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=2, local_size_y=2) in;
layout (rgba8, binding = 0) readonly uniform highp image2D srcImage;
layout (rgba8, binding = 1) writeonly uniform highp image2D dstImage;
void main()
{
imageStore(dstImage, ivec2(gl_LocalInvocationID.xy),
imageLoad(srcImage, ivec2(gl_LocalInvocationID.xy)));
})";
bindProgramPipeline(kCS);
glBindImageTexture(1, mTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
ASSERT_GL_NO_ERROR();
std::array<GLColor, 4> colors = {
{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
GLTexture texRead;
glBindTexture(GL_TEXTURE_2D, texRead);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
EXPECT_GL_NO_ERROR();
glBindImageTexture(0, texRead, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
glDispatchCompute(1, 1, 1);
texRead.reset();
std::array<GLColor, 4> results;
glReadPixels(0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, results.data());
EXPECT_GL_NO_ERROR();
for (int i = 0; i < 4; i++)
{
EXPECT_EQ(colors[i], results[i]);
}
}
static constexpr char kColorVS[] = R"(attribute vec2 position;
attribute vec4 color;
varying vec4 vColor;
......@@ -4731,6 +4835,7 @@ ANGLE_INSTANTIATE_TEST_ES2(SimpleStateChangeTest);
ANGLE_INSTANTIATE_TEST_ES3(SimpleStateChangeTestES3);
ANGLE_INSTANTIATE_TEST_ES31(SimpleStateChangeTestES31);
ANGLE_INSTANTIATE_TEST_ES31(SimpleStateChangeTestComputeES31);
ANGLE_INSTANTIATE_TEST_ES31(SimpleStateChangeTestComputeES31PPO);
ANGLE_INSTANTIATE_TEST_ES3(ValidationStateChangeTest);
ANGLE_INSTANTIATE_TEST_ES3(WebGL2ValidationStateChangeTest);
ANGLE_INSTANTIATE_TEST_ES31(ValidationStateChangeTestES31);
......
......@@ -548,6 +548,124 @@ void main()
};
};
class TextureES31PPO
{
protected:
TextureES31PPO() : mVertProg(0), mFragProg(0), mPipeline(0) {}
const char *get2DTexturedVertexShaderSource()
{
return "#version 310 es\n"
"precision mediump float;\n"
"in vec2 position;\n"
"out vec2 texCoord;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(position, 0, 1);\n"
" texCoord = position * 0.5 + vec2(0.5);\n"
"}";
}
const char *get2DTexturedFragmentShaderSource()
{
return "#version 310 es\n"
"precision mediump float;\n"
"in vec2 texCoord;\n"
"uniform sampler2D tex1;\n"
"uniform sampler2D tex2;\n"
"uniform sampler2D tex3;\n"
"uniform sampler2D tex4;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" color = (texture(tex1, texCoord) + texture(tex2, texCoord) \n"
" + texture(tex3, texCoord) + texture(tex4, texCoord)) * 0.25;\n"
"}";
}
void bindProgramPipeline(const GLchar *vertString, const GLchar *fragString)
{
mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString);
ASSERT_NE(mVertProg, 0u);
mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString);
ASSERT_NE(mFragProg, 0u);
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &mPipeline);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg);
EXPECT_GL_NO_ERROR();
glBindProgramPipeline(mPipeline);
EXPECT_GL_NO_ERROR();
}
void bind2DTexturedQuadProgramPipeline()
{
const char *vertexShaderSource = get2DTexturedVertexShaderSource();
const char *fragmentShaderSource = get2DTexturedFragmentShaderSource();
m2DTexturedQuadVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertexShaderSource);
ASSERT_NE(m2DTexturedQuadVertProg, 0u);
m2DTexturedQuadFragProg =
glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragmentShaderSource);
ASSERT_NE(m2DTexturedQuadFragProg, 0u);
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &m2DTexturedQuadPipeline);
EXPECT_GL_NO_ERROR();
glUseProgramStages(m2DTexturedQuadPipeline, GL_VERTEX_SHADER_BIT, m2DTexturedQuadVertProg);
EXPECT_GL_NO_ERROR();
glUseProgramStages(m2DTexturedQuadPipeline, GL_FRAGMENT_SHADER_BIT,
m2DTexturedQuadFragProg);
EXPECT_GL_NO_ERROR();
glBindProgramPipeline(m2DTexturedQuadPipeline);
EXPECT_GL_NO_ERROR();
}
void ppoDrawQuad(std::array<Vector3, 6> &quadVertices,
const std::string &positionAttribName,
const GLfloat positionAttribZ,
const GLfloat positionAttribXYScale)
{
glUseProgram(0);
for (Vector3 &vertex : quadVertices)
{
vertex.x() *= positionAttribXYScale;
vertex.y() *= positionAttribXYScale;
vertex.z() = positionAttribZ;
}
GLint positionLocation = glGetAttribLocation(mVertProg, positionAttribName.c_str());
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
}
GLuint mVertProg;
GLuint mFragProg;
GLuint mPipeline;
GLuint m2DTexturedQuadVertProg;
GLuint m2DTexturedQuadFragProg;
GLuint m2DTexturedQuadPipeline;
};
class Texture2DTestES31PPO : public TextureES31PPO, public Texture2DTest
{
protected:
Texture2DTestES31PPO() : TextureES31PPO(), Texture2DTest() {}
void testSetUp() override { Texture2DTest::testSetUp(); }
};
class Texture2DIntegerAlpha1TestES3 : public Texture2DTest
{
protected:
......@@ -3541,6 +3659,215 @@ TEST_P(Texture2DTestES3, TextureCOMPRESSEDSRGB8ETC2ImplicitAlpha1)
EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
}
// Copied from Texture2DTest::TexStorage
// Test that glTexSubImage2D works properly when glTexStorage2DEXT has initialized the image with a
// default color.
TEST_P(Texture2DTestES31PPO, TexStorage)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
ANGLE_SKIP_TEST_IF((getClientMajorVersion() < 3 && getClientMinorVersion() < 1) &&
!IsGLExtensionEnabled("GL_EXT_texture_storage"));
const char *vertexShaderSource = getVertexShaderSource();
const char *fragmentShaderSource = getFragmentShaderSource();
bindProgramPipeline(vertexShaderSource, fragmentShaderSource);
mTexture2DUniformLocation = glGetUniformLocation(mFragProg, getTextureUniformName());
int width = getWindowWidth();
int height = getWindowHeight();
GLuint tex2D;
glGenTextures(1, &tex2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex2D);
// Fill with red
std::vector<GLubyte> pixels(3 * 16 * 16);
for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
{
pixels[pixelId * 3 + 0] = 255;
pixels[pixelId * 3 + 1] = 0;
pixels[pixelId * 3 + 2] = 0;
}
// ANGLE internally uses RGBA as the internal format for RGB images, therefore glTexStorage2DEXT
// initializes the image to a default color to get a consistent alpha color. The data is kept in
// a CPU-side image and the image is marked as dirty.
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, 16, 16);
// Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
// glTexSubImage2D should take into account that the image is dirty.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glActiveShaderProgram(mPipeline, mFragProg);
glUniform1i(mTexture2DUniformLocation, 0);
std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
glDeleteTextures(1, &tex2D);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
// Validate that the region of the texture without data has an alpha of 1.0
angle::GLColor pixel = ReadColor(3 * width / 4, 3 * height / 4);
EXPECT_EQ(255, pixel.A);
}
// Copied from Texture2DTestES3::SingleTextureMultipleSamplers
// Tests behaviour with a single texture and multiple sampler objects.
TEST_P(Texture2DTestES31PPO, SingleTextureMultipleSamplers)
{
ANGLE_SKIP_TEST_IF(!IsVulkan());
const char *vertexShaderSource = getVertexShaderSource();
const char *fragmentShaderSource = getFragmentShaderSource();
bindProgramPipeline(vertexShaderSource, fragmentShaderSource);
mTexture2DUniformLocation = glGetUniformLocation(mFragProg, getTextureUniformName());
GLint maxTextureUnits = 0;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
ANGLE_SKIP_TEST_IF(maxTextureUnits < 4);
constexpr int kSize = 16;
std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
// Make a single-level texture, fill it with red.
std::vector<GLColor> redColors(kSize * kSize, GLColor::red);
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
redColors.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Simple sanity check.
bind2DTexturedQuadProgramPipeline();
ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Bind texture to unit 1 with a sampler object making it incomplete.
GLSampler sampler;
glBindSampler(0, sampler);
glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Make a mipmap texture, fill it with blue.
std::vector<GLColor> blueColors(kSize * kSize, GLColor::blue);
GLTexture mipmapTex;
glBindTexture(GL_TEXTURE_2D, mipmapTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
blueColors.data());
glGenerateMipmap(GL_TEXTURE_2D);
// Draw with the sampler, expect blue.
draw2DTexturedQuad(0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Simple multitexturing program.
constexpr char kVS[] =
"#version 310 es\n"
"precision mediump float;\n"
"in vec2 position;\n"
"out vec2 texCoord;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(position, 0, 1);\n"
" texCoord = position * 0.5 + vec2(0.5);\n"
"}";
constexpr char kFS[] =
"#version 310 es\n"
"precision mediump float;\n"
"in vec2 texCoord;\n"
"uniform sampler2D tex1;\n"
"uniform sampler2D tex2;\n"
"uniform sampler2D tex3;\n"
"uniform sampler2D tex4;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" color = (texture(tex1, texCoord) + texture(tex2, texCoord) \n"
" + texture(tex3, texCoord) + texture(tex4, texCoord)) * 0.25;\n"
"}";
bindProgramPipeline(kVS, kFS);
std::array<GLint, 4> texLocations = {
{glGetUniformLocation(mFragProg, "tex1"), glGetUniformLocation(mFragProg, "tex2"),
glGetUniformLocation(mFragProg, "tex3"), glGetUniformLocation(mFragProg, "tex4")}};
for (GLint location : texLocations)
{
ASSERT_NE(-1, location);
}
// Init the uniform data.
glActiveShaderProgram(mPipeline, mFragProg);
for (GLint location = 0; location < 4; ++location)
{
glUniform1i(texLocations[location], location);
}
// Initialize four samplers
GLSampler samplers[4];
// 0: non-mipped.
glBindSampler(0, samplers[0]);
glSamplerParameteri(samplers[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(samplers[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 1: mipped.
glBindSampler(1, samplers[1]);
glSamplerParameteri(samplers[1], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glSamplerParameteri(samplers[1], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 2: non-mipped.
glBindSampler(2, samplers[2]);
glSamplerParameteri(samplers[2], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(samplers[2], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 3: mipped.
glBindSampler(3, samplers[3]);
glSamplerParameteri(samplers[3], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glSamplerParameteri(samplers[3], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Bind two blue mipped textures and two single layer textures, should all draw.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, mipmapTex);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, tex);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, mipmapTex);
ASSERT_GL_NO_ERROR();
ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_NEAR(0, 0, 128, 0, 128, 255, 2);
// Bind four single layer textures, two should be incomplete.
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, tex);
ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 2);
}
// Use a sampler in a uniform struct.
TEST_P(SamplerInStructTest, SamplerInStruct)
{
......@@ -6512,6 +6839,7 @@ ANGLE_INSTANTIATE_TEST_ES2(Sampler2DAsFunctionParameterTest);
ANGLE_INSTANTIATE_TEST_ES2(SamplerArrayTest);
ANGLE_INSTANTIATE_TEST_ES2(SamplerArrayAsFunctionParameterTest);
ANGLE_INSTANTIATE_TEST_ES3(Texture2DTestES3);
ANGLE_INSTANTIATE_TEST_ES31(Texture2DTestES31PPO);
ANGLE_INSTANTIATE_TEST_ES3(Texture2DBaseMaxTestES3);
ANGLE_INSTANTIATE_TEST_ES3(Texture3DTestES3);
ANGLE_INSTANTIATE_TEST_ES3(Texture2DIntegerAlpha1TestES3);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment