blob: e335734f9f8a27be9dcfffe8f945a9bbee37f8e4 [file] [log] [blame]
//
// Copyright 2018 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.
//
// BlendFuncExtendedTest
// Test EXT_blend_func_extended
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/shader_utils.h"
#include <algorithm>
#include <cmath>
#include <fstream>
using namespace angle;
namespace
{
// Partial implementation of weight function for GLES 2 blend equation that
// is dual-source aware.
template <int factor, int index>
float Weight(const float /*dst*/[4], const float src[4], const float src1[4])
{
if (factor == GL_SRC_COLOR)
return src[index];
if (factor == GL_SRC_ALPHA)
return src[3];
if (factor == GL_SRC1_COLOR_EXT)
return src1[index];
if (factor == GL_SRC1_ALPHA_EXT)
return src1[3];
if (factor == GL_ONE_MINUS_SRC1_COLOR_EXT)
return 1.0f - src1[index];
if (factor == GL_ONE_MINUS_SRC1_ALPHA_EXT)
return 1.0f - src1[3];
return 0.0f;
}
GLubyte ScaleChannel(float weight)
{
return static_cast<GLubyte>(std::floor(std::max(0.0f, std::min(1.0f, weight)) * 255.0f));
}
// Implementation of GLES 2 blend equation that is dual-source aware.
template <int RGBs, int RGBd, int As, int Ad>
void BlendEquationFuncAdd(const float dst[4],
const float src[4],
const float src1[4],
angle::GLColor *result)
{
float r[4];
r[0] = src[0] * Weight<RGBs, 0>(dst, src, src1) + dst[0] * Weight<RGBd, 0>(dst, src, src1);
r[1] = src[1] * Weight<RGBs, 1>(dst, src, src1) + dst[1] * Weight<RGBd, 1>(dst, src, src1);
r[2] = src[2] * Weight<RGBs, 2>(dst, src, src1) + dst[2] * Weight<RGBd, 2>(dst, src, src1);
r[3] = src[3] * Weight<As, 3>(dst, src, src1) + dst[3] * Weight<Ad, 3>(dst, src, src1);
result->R = ScaleChannel(r[0]);
result->G = ScaleChannel(r[1]);
result->B = ScaleChannel(r[2]);
result->A = ScaleChannel(r[3]);
}
void CheckPixels(GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLint tolerance,
const angle::GLColor &color)
{
for (GLint yy = 0; yy < height; ++yy)
{
for (GLint xx = 0; xx < width; ++xx)
{
const auto px = x + xx;
const auto py = y + yy;
EXPECT_PIXEL_COLOR_NEAR(px, py, color, 2);
}
}
}
const GLuint kWidth = 100;
const GLuint kHeight = 100;
class EXTBlendFuncExtendedTest : public ANGLETest<>
{};
class EXTBlendFuncExtendedTestES3 : public ANGLETest<>
{};
class EXTBlendFuncExtendedDrawTest : public ANGLETest<>
{
protected:
EXTBlendFuncExtendedDrawTest() : mProgram(0)
{
setWindowWidth(kWidth);
setWindowHeight(kHeight);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
void testSetUp() override
{
glGenBuffers(1, &mVBO);
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
static const float vertices[] = {
1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
}
void testTearDown() override
{
glDeleteBuffers(1, &mVBO);
if (mProgram)
{
glDeleteProgram(mProgram);
}
ASSERT_GL_NO_ERROR();
}
void makeProgram(const char *vertSource, const char *fragSource)
{
mProgram = CompileProgram(vertSource, fragSource);
ASSERT_NE(0u, mProgram);
}
virtual GLint getVertexAttribLocation(const char *name)
{
return glGetAttribLocation(mProgram, name);
}
virtual GLint getFragmentUniformLocation(const char *name)
{
return glGetUniformLocation(mProgram, name);
}
virtual void setUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)
{
glUniform4f(location, v0, v1, v2, v3);
}
void drawTest()
{
glUseProgram(mProgram);
GLint position = getVertexAttribLocation(essl1_shaders::PositionAttrib());
GLint src0 = getFragmentUniformLocation("src0");
GLint src1 = getFragmentUniformLocation("src1");
ASSERT_GL_NO_ERROR();
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, 0);
ASSERT_GL_NO_ERROR();
static const float kDst[4] = {0.5f, 0.5f, 0.5f, 0.5f};
static const float kSrc0[4] = {1.0f, 1.0f, 1.0f, 1.0f};
static const float kSrc1[4] = {0.3f, 0.6f, 0.9f, 0.7f};
setUniform4f(src0, kSrc0[0], kSrc0[1], kSrc0[2], kSrc0[3]);
setUniform4f(src1, kSrc1[0], kSrc1[1], kSrc1[2], kSrc1[3]);
ASSERT_GL_NO_ERROR();
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glViewport(0, 0, kWidth, kHeight);
glClearColor(kDst[0], kDst[1], kDst[2], kDst[3]);
ASSERT_GL_NO_ERROR();
{
glBlendFuncSeparate(GL_SRC1_COLOR_EXT, GL_SRC_ALPHA, GL_ONE_MINUS_SRC1_COLOR_EXT,
GL_ONE_MINUS_SRC1_ALPHA_EXT);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// verify
angle::GLColor color;
BlendEquationFuncAdd<GL_SRC1_COLOR_EXT, GL_SRC_ALPHA, GL_ONE_MINUS_SRC1_COLOR_EXT,
GL_ONE_MINUS_SRC1_ALPHA_EXT>(kDst, kSrc0, kSrc1, &color);
CheckPixels(kWidth / 4, (3 * kHeight) / 4, 1, 1, 1, color);
CheckPixels(kWidth - 1, 0, 1, 1, 1, color);
}
{
glBlendFuncSeparate(GL_ONE_MINUS_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE_MINUS_SRC_COLOR, GL_SRC1_ALPHA_EXT);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// verify
angle::GLColor color;
BlendEquationFuncAdd<GL_ONE_MINUS_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE_MINUS_SRC_COLOR, GL_SRC1_ALPHA_EXT>(kDst, kSrc0, kSrc1,
&color);
CheckPixels(kWidth / 4, (3 * kHeight) / 4, 1, 1, 1, color);
CheckPixels(kWidth - 1, 0, 1, 1, 1, color);
}
}
GLuint mVBO;
GLuint mProgram;
};
class EXTBlendFuncExtendedDrawTestES3 : public EXTBlendFuncExtendedDrawTest
{
protected:
EXTBlendFuncExtendedDrawTestES3() : EXTBlendFuncExtendedDrawTest(), mIsES31OrNewer(false) {}
void testSetUp() override
{
EXTBlendFuncExtendedDrawTest::testSetUp();
if (getClientMajorVersion() > 3 ||
(getClientMajorVersion() == 3 && getClientMinorVersion() >= 1))
{
mIsES31OrNewer = true;
}
}
virtual void checkOutputIndexQuery(const char *name, GLint expectedIndex)
{
GLint index = glGetFragDataIndexEXT(mProgram, name);
EXPECT_EQ(expectedIndex, index);
if (mIsES31OrNewer)
{
index = glGetProgramResourceLocationIndexEXT(mProgram, GL_PROGRAM_OUTPUT, name);
EXPECT_EQ(expectedIndex, index);
}
else
{
glGetProgramResourceLocationIndexEXT(mProgram, GL_PROGRAM_OUTPUT, name);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
}
void LinkProgram()
{
glLinkProgram(mProgram);
GLint linked = 0;
glGetProgramiv(mProgram, GL_LINK_STATUS, &linked);
EXPECT_NE(0, linked);
glUseProgram(mProgram);
return;
}
private:
bool mIsES31OrNewer;
};
class EXTBlendFuncExtendedDrawTestES31 : public EXTBlendFuncExtendedDrawTestES3
{
protected:
EXTBlendFuncExtendedDrawTestES31()
: EXTBlendFuncExtendedDrawTestES3(), mPipeline(0), mVertexProgram(0), mFragProgram(0)
{}
GLint getVertexAttribLocation(const char *name) override
{
return glGetAttribLocation(mVertexProgram, name);
}
GLint getFragmentUniformLocation(const char *name) override
{
return glGetUniformLocation(mFragProgram, name);
}
void setUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) override
{
glActiveShaderProgram(mPipeline, mFragProgram);
EXTBlendFuncExtendedDrawTest::setUniform4f(location, v0, v1, v2, v3);
}
void checkOutputIndexQuery(const char *name, GLint expectedIndex) override
{
GLint index = glGetFragDataIndexEXT(mFragProgram, name);
EXPECT_EQ(expectedIndex, index);
index = glGetProgramResourceLocationIndexEXT(mFragProgram, GL_PROGRAM_OUTPUT, name);
EXPECT_EQ(expectedIndex, index);
}
void setupProgramPipeline(const char *vertexSource, const char *fragmentSource)
{
mVertexProgram = createShaderProgram(GL_VERTEX_SHADER, vertexSource);
ASSERT_NE(mVertexProgram, 0u);
mFragProgram = createShaderProgram(GL_FRAGMENT_SHADER, fragmentSource);
ASSERT_NE(mFragProgram, 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, mVertexProgram);
EXPECT_GL_NO_ERROR();
glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProgram);
EXPECT_GL_NO_ERROR();
glBindProgramPipeline(mPipeline);
EXPECT_GL_NO_ERROR();
}
GLuint createShaderProgram(GLenum type, const GLchar *shaderString)
{
GLShader shader(type);
if (!shader.get())
{
return 0;
}
glShaderSource(shader, 1, &shaderString, nullptr);
glCompileShader(shader);
GLuint program = glCreateProgram();
if (program)
{
GLint compiled;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
if (compiled)
{
glAttachShader(program, shader);
glLinkProgram(program);
glDetachShader(program, shader);
}
}
EXPECT_GL_NO_ERROR();
return program;
}
void testTearDown() override
{
EXTBlendFuncExtendedDrawTest::testTearDown();
if (mVertexProgram)
{
glDeleteProgram(mVertexProgram);
}
if (mFragProgram)
{
glDeleteProgram(mFragProgram);
}
if (mPipeline)
{
glDeleteProgramPipelines(1, &mPipeline);
}
ASSERT_GL_NO_ERROR();
}
GLuint mPipeline;
GLuint mVertexProgram;
GLuint mFragProgram;
};
} // namespace
// Test EXT_blend_func_extended related gets.
TEST_P(EXTBlendFuncExtendedTest, TestMaxDualSourceDrawBuffers)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
GLint maxDualSourceDrawBuffers = 0;
glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
EXPECT_GT(maxDualSourceDrawBuffers, 0);
ASSERT_GL_NO_ERROR();
}
// Test that SRC1 factors limit the number of allowed draw buffers.
TEST_P(EXTBlendFuncExtendedTest, MaxDualSourceDrawBuffersError)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_rgb8_rgba8"));
GLint maxDualSourceDrawBuffers = 0;
glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
ANGLE_SKIP_TEST_IF(maxDualSourceDrawBuffers != 1);
ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLRenderbuffer rb0;
glBindRenderbuffer(GL_RENDERBUFFER, rb0);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, 1, 1);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER, rb0);
GLRenderbuffer rb1;
glBindRenderbuffer(GL_RENDERBUFFER, rb1);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, 1, 1);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT, GL_RENDERBUFFER, rb1);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
const GLenum bufs[] = {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT};
glDrawBuffersEXT(2, bufs);
ASSERT_GL_NO_ERROR();
for (const GLenum func : {GL_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_SRC1_ALPHA_EXT,
GL_ONE_MINUS_SRC1_ALPHA_EXT})
{
for (size_t slot = 0; slot < 4; slot++)
{
switch (slot)
{
case 0:
glBlendFuncSeparate(func, GL_ONE, GL_ONE, GL_ONE);
break;
case 1:
glBlendFuncSeparate(GL_ONE, func, GL_ONE, GL_ONE);
break;
case 2:
glBlendFuncSeparate(GL_ONE, GL_ONE, func, GL_ONE);
break;
case 3:
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, func);
break;
}
// Limit must be applied even with blending disabled
glDisable(GL_BLEND);
drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
glEnable(GL_BLEND);
drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// Limit must be applied even when an attachment is missing
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT, GL_RENDERBUFFER, 0);
drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// Restore the attachment for the next iteration
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT, GL_RENDERBUFFER,
rb1);
// Limit is not applied when non-SRC1 funcs are used
glBlendFunc(GL_ONE, GL_ONE);
drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0);
EXPECT_GL_NO_ERROR();
}
}
}
// Test a shader with EXT_blend_func_extended and gl_SecondaryFragColorEXT.
// Outputs to primary color buffer using primary and secondary colors.
TEST_P(EXTBlendFuncExtendedDrawTest, FragColor)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const char *kFragColorShader =
"#extension GL_EXT_blend_func_extended : require\n"
"precision mediump float;\n"
"uniform vec4 src0;\n"
"uniform vec4 src1;\n"
"void main() {\n"
" gl_FragColor = src0;\n"
" gl_SecondaryFragColorEXT = src1;\n"
"}\n";
makeProgram(essl1_shaders::vs::Simple(), kFragColorShader);
drawTest();
}
// Test a shader with EXT_blend_func_extended and EXT_draw_buffers enabled at the same time.
TEST_P(EXTBlendFuncExtendedDrawTest, FragColorBroadcast)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
const char *kFragColorShader =
"#extension GL_EXT_blend_func_extended : require\n"
"#extension GL_EXT_draw_buffers : require\n"
"precision mediump float;\n"
"uniform vec4 src0;\n"
"uniform vec4 src1;\n"
"void main() {\n"
" gl_FragColor = src0;\n"
" gl_SecondaryFragColorEXT = src1;\n"
"}\n";
makeProgram(essl1_shaders::vs::Simple(), kFragColorShader);
drawTest();
}
// Test a shader with EXT_blend_func_extended and gl_FragData.
// Outputs to a color buffer using primary and secondary frag data.
TEST_P(EXTBlendFuncExtendedDrawTest, FragData)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const char *kFragColorShader =
"#extension GL_EXT_blend_func_extended : require\n"
"precision mediump float;\n"
"uniform vec4 src0;\n"
"uniform vec4 src1;\n"
"void main() {\n"
" gl_FragData[0] = src0;\n"
" gl_SecondaryFragDataEXT[0] = src1;\n"
"}\n";
makeProgram(essl1_shaders::vs::Simple(), kFragColorShader);
drawTest();
}
// Test that min/max blending works correctly with SRC1 factors.
TEST_P(EXTBlendFuncExtendedDrawTest, MinMax)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_minmax"));
const char *kFragColorShader = R"(#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
gl_FragColor = vec4(0.125, 0.25, 0.75, 0.875);
gl_SecondaryFragColorEXT = vec4(0.0, 0.0, 0.0, 0.0);
})";
makeProgram(essl1_shaders::vs::Simple(), kFragColorShader);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_SRC1_ALPHA_EXT,
GL_ONE_MINUS_SRC1_ALPHA_EXT);
glClearColor(0.5, 0.5, 0.5, 0.5);
auto test = [&](GLenum colorOp, GLenum alphaOp, GLColor color) {
glBlendEquationSeparate(colorOp, alphaOp);
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0);
EXPECT_PIXEL_COLOR_NEAR(0, 0, color, 2);
};
test(GL_MIN_EXT, GL_MIN_EXT, GLColor(32, 64, 128, 128));
test(GL_MIN_EXT, GL_MAX_EXT, GLColor(32, 64, 128, 224));
test(GL_MAX_EXT, GL_MIN_EXT, GLColor(128, 128, 192, 128));
test(GL_MAX_EXT, GL_MAX_EXT, GLColor(128, 128, 192, 224));
}
// Test an ESSL 3.00 shader that uses two fragment outputs with locations specified in the shader.
TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentOutputLocationsInShader)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const char *kFragColorShader = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
layout(location = 0, index = 1) out vec4 outSrc1;
layout(location = 0, index = 0) out vec4 outSrc0;
void main() {
outSrc0 = src0;
outSrc1 = src1;
})";
makeProgram(essl3_shaders::vs::Simple(), kFragColorShader);
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
drawTest();
}
// Test an ESSL 3.00 shader that uses two fragment outputs with locations specified through the API.
TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentOutputLocationsAPI)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
out vec4 outSrc1;
out vec4 outSrc0;
void main() {
outSrc0 = src0;
outSrc1 = src1;
})";
mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) {
glBindFragDataLocationIndexedEXT(program, 0, 0, "outSrc0");
glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1");
});
ASSERT_NE(0u, mProgram);
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
drawTest();
}
// Test an ESSL 3.00 shader that uses two fragment outputs, with location for one specified through
// the API and location for another being set automatically.
TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentOutputLocationsAPIAndAutomatic)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
out vec4 outSrc1;
out vec4 outSrc0;
void main() {
outSrc0 = src0;
outSrc1 = src1;
})";
mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) {
glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1");
});
ASSERT_NE(0u, mProgram);
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
drawTest();
}
// Test an ESSL 3.00 shader that uses two array fragment outputs with locations
// specified in the shader.
TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentArrayOutputLocationsInShader)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const char *kFragColorShader = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
layout(location = 0, index = 1) out vec4 outSrc1[1];
layout(location = 0, index = 0) out vec4 outSrc0[1];
void main() {
outSrc0[0] = src0;
outSrc1[0] = src1;
})";
makeProgram(essl3_shaders::vs::Simple(), kFragColorShader);
checkOutputIndexQuery("outSrc0[0]", 0);
checkOutputIndexQuery("outSrc1[0]", 1);
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
// These queries use an out of range array index so they should return -1.
checkOutputIndexQuery("outSrc0[1]", -1);
checkOutputIndexQuery("outSrc1[1]", -1);
drawTest();
}
// Test an ESSL 3.00 shader that uses two array fragment outputs with locations specified through
// the API.
TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentArrayOutputLocationsAPI)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
out vec4 outSrc1[1];
out vec4 outSrc0[1];
void main() {
outSrc0[0] = src0;
outSrc1[0] = src1;
})";
mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) {
// Specs aren't very clear on what kind of name should be used when binding location for
// array variables. We only allow names that do include the "[0]" suffix.
glBindFragDataLocationIndexedEXT(program, 0, 0, "outSrc0[0]");
glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1[0]");
});
ASSERT_NE(0u, mProgram);
// The extension spec is not very clear on what name can be used for the queries for array
// variables. We're checking that the queries work in the same way as specified in OpenGL 4.4
// page 107.
checkOutputIndexQuery("outSrc0[0]", 0);
checkOutputIndexQuery("outSrc1[0]", 1);
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
// These queries use an out of range array index so they should return -1.
checkOutputIndexQuery("outSrc0[1]", -1);
checkOutputIndexQuery("outSrc1[1]", -1);
drawTest();
}
// Ported from TranslatorVariants/EXTBlendFuncExtendedES3DrawTest
// Test that tests glBindFragDataLocationEXT, glBindFragDataLocationIndexedEXT,
// glGetFragDataLocation, glGetFragDataIndexEXT work correctly with
// GLSL array output variables. The output variable can be bound by
// referring to the variable name with or without the first element array
// accessor. The getters can query location of the individual elements in
// the array. The test does not actually use the base test drawing,
// since the drivers at the time of writing do not support multiple
// buffers and dual source blending.
TEST_P(EXTBlendFuncExtendedDrawTestES3, ES3GettersArray)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const GLint kTestArraySize = 2;
const GLint kFragData0Location = 2;
const GLint kFragData1Location = 1;
const GLint kUnusedLocation = 5;
// The test binds kTestArraySize -sized array to location 1 for test purposes.
// The GL_MAX_DRAW_BUFFERS must be > kTestArraySize, since an
// array will be bound to continuous locations, starting from the first
// location.
GLint maxDrawBuffers = 0;
glGetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &maxDrawBuffers);
EXPECT_LT(kTestArraySize, maxDrawBuffers);
constexpr char kFragColorShader[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src;
uniform vec4 src1;
out vec4 FragData[2];
void main() {
FragData[0] = src;
FragData[1] = src1;
})";
struct testCase
{
std::string unusedLocationName;
std::string fragData0LocationName;
std::string fragData1LocationName;
};
testCase testCases[4]{{"FragData[0]", "FragData", "FragData[1]"},
{"FragData", "FragData[0]", "FragData[1]"},
{"FragData[0]", "FragData", "FragData[1]"},
{"FragData", "FragData[0]", "FragData[1]"}};
for (const testCase &test : testCases)
{
mProgram =
CompileProgram(essl3_shaders::vs::Simple(), kFragColorShader, [&](GLuint program) {
glBindFragDataLocationEXT(program, kUnusedLocation,
test.unusedLocationName.c_str());
glBindFragDataLocationEXT(program, kFragData0Location,
test.fragData0LocationName.c_str());
glBindFragDataLocationEXT(program, kFragData1Location,
test.fragData1LocationName.c_str());
});
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
LinkProgram();
EXPECT_EQ(kFragData0Location, glGetFragDataLocation(mProgram, "FragData"));
EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "FragData"));
EXPECT_EQ(kFragData0Location, glGetFragDataLocation(mProgram, "FragData[0]"));
EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "FragData[0]"));
EXPECT_EQ(kFragData1Location, glGetFragDataLocation(mProgram, "FragData[1]"));
EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "FragData[1]"));
// Index bigger than the GLSL variable array length does not find anything.
EXPECT_EQ(-1, glGetFragDataLocation(mProgram, "FragData[3]"));
}
}
// Ported from TranslatorVariants/EXTBlendFuncExtendedES3DrawTest
TEST_P(EXTBlendFuncExtendedDrawTestES3, ESSL3BindSimpleVarAsArrayNoBind)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFragDataShader[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src;
uniform vec4 src1;
out vec4 FragData;
out vec4 SecondaryFragData;
void main() {
FragData = src;
SecondaryFragData = src1;
})";
mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFragDataShader, [](GLuint program) {
glBindFragDataLocationEXT(program, 0, "FragData[0]");
glBindFragDataLocationIndexedEXT(program, 0, 1, "SecondaryFragData[0]");
});
LinkProgram();
EXPECT_EQ(-1, glGetFragDataLocation(mProgram, "FragData[0]"));
EXPECT_EQ(0, glGetFragDataLocation(mProgram, "FragData"));
EXPECT_EQ(1, glGetFragDataLocation(mProgram, "SecondaryFragData"));
// Did not bind index.
EXPECT_EQ(0, glGetFragDataIndexEXT(mProgram, "SecondaryFragData"));
glBindFragDataLocationEXT(mProgram, 0, "FragData");
glBindFragDataLocationIndexedEXT(mProgram, 0, 1, "SecondaryFragData");
LinkProgram();
}
// Test an ESSL 3.00 program with a link-time fragment output location conflict.
TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputLocationConflict)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
out vec4 out0;
out vec4 out1;
void main() {
out0 = src0;
out1 = src1;
})";
GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
ASSERT_NE(0u, vs);
ASSERT_NE(0u, fs);
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glBindFragDataLocationIndexedEXT(program, 0, 0, "out0");
glBindFragDataLocationIndexedEXT(program, 0, 0, "out1");
// The program should fail to link.
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_EQ(0, linkStatus);
glDeleteProgram(program);
}
// Test an ESSL 3.00 program with some bindings set for nonexistent variables. These should not
// create link-time conflicts.
TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputLocationForNonexistentOutput)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
out vec4 out0;
void main() {
out0 = src0;
})";
GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
ASSERT_NE(0u, vs);
ASSERT_NE(0u, fs);
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glBindFragDataLocationIndexedEXT(program, 0, 0, "out0");
glBindFragDataLocationIndexedEXT(program, 0, 0, "out1");
glBindFragDataLocationIndexedEXT(program, 0, 0, "out2[0]");
// The program should link successfully - conflicting location for nonexistent variables out1 or
// out2 should not be an issue.
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_NE(0, linkStatus);
glDeleteProgram(program);
}
// Test mixing shader-assigned and automatic output locations.
TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputLocationsPartiallyAutomatic)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
GLint maxDrawBuffers;
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
ANGLE_SKIP_TEST_IF(maxDrawBuffers < 4);
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
uniform vec4 src2;
uniform vec4 src3;
layout(location=0) out vec4 out0;
layout(location=3) out vec4 out3;
out vec4 out12[2];
void main() {
out0 = src0;
out12[0] = src1;
out12[1] = src2;
out3 = src3;
})";
GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFS);
ASSERT_NE(0u, program);
GLint location = glGetFragDataLocation(program, "out0");
EXPECT_EQ(0, location);
location = glGetFragDataLocation(program, "out12");
EXPECT_EQ(1, location);
location = glGetFragDataLocation(program, "out3");
EXPECT_EQ(3, location);
glDeleteProgram(program);
}
// Test a fragment output array that doesn't fit because contiguous locations are not available.
TEST_P(EXTBlendFuncExtendedTestES3, FragmentOutputArrayDoesntFit)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
GLint maxDrawBuffers;
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
ANGLE_SKIP_TEST_IF(maxDrawBuffers < 4);
std::stringstream fragShader;
fragShader << R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(location=2) out vec4 out0;
out vec4 outArray[)"
<< (maxDrawBuffers - 1) << R"(];
void main() {
out0 = vec4(1.0);
outArray[0] = vec4(1.0);
})";
GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragShader.str().c_str());
ASSERT_NE(0u, vs);
ASSERT_NE(0u, fs);
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
// The program should not link - there's no way to fit "outArray" into available output
// locations.
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_EQ(0, linkStatus);
glDeleteProgram(program);
}
// Test that a secondary blending source limits the number of primary outputs.
TEST_P(EXTBlendFuncExtendedTestES3, TooManyFragmentOutputsForDualSourceBlending)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
GLint maxDualSourceDrawBuffers;
glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
ASSERT_GE(maxDualSourceDrawBuffers, 1);
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
out vec4 outSrc0;
out vec4 outSrc1;
void main() {
outSrc0 = vec4(0.5);
outSrc1 = vec4(1.0);
})";
GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
ASSERT_NE(0u, vs);
ASSERT_NE(0u, fs);
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glBindFragDataLocationIndexedEXT(program, maxDualSourceDrawBuffers, 0, "outSrc0");
glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1");
ASSERT_GL_NO_ERROR();
GLint linkStatus;
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_EQ(0, linkStatus);
glDeleteProgram(program);
}
// Test that fragment outputs bound to the same location must have the same type.
TEST_P(EXTBlendFuncExtendedTestES3, InconsistentTypesForLocationAPI)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
out vec4 outSrc0;
out ivec4 outSrc1;
void main() {
outSrc0 = vec4(0.5);
outSrc1 = ivec4(1.0);
})";
GLuint vs = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
ASSERT_NE(0u, vs);
ASSERT_NE(0u, fs);
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glDeleteShader(vs);
glAttachShader(program, fs);
glDeleteShader(fs);
glBindFragDataLocationIndexedEXT(program, 0, 0, "outSrc0");
glBindFragDataLocationIndexedEXT(program, 0, 1, "outSrc1");
ASSERT_GL_NO_ERROR();
GLint linkStatus;
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_EQ(0, linkStatus);
glDeleteProgram(program);
}
// Test that rendering to multiple fragment outputs bound via API works.
TEST_P(EXTBlendFuncExtendedDrawTestES3, MultipleDrawBuffersAPI)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
out vec4 outSrc0;
out ivec4 outSrc1;
void main() {
outSrc0 = vec4(0.0, 1.0, 0.0, 1.0);
outSrc1 = ivec4(1, 2, 3, 4);
})";
mProgram = CompileProgram(essl3_shaders::vs::Simple(), kFS, [](GLuint program) {
glBindFragDataLocationEXT(program, 0, "outSrc0");
glBindFragDataLocationEXT(program, 1, "outSrc1");
});
ASSERT_NE(0u, mProgram);
GLRenderbuffer rb0;
glBindRenderbuffer(GL_RENDERBUFFER, rb0);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1);
GLRenderbuffer rb1;
glBindRenderbuffer(GL_RENDERBUFFER, rb1);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8I, 1, 1);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
const GLenum bufs[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, bufs);
GLfloat clearF[] = {0.0, 0.0, 0.0, 0.0};
GLint clearI[] = {0, 0, 0, 0};
// FBO: rb0 (rgba8), rb1 (rgba8i)
glBindRenderbuffer(GL_RENDERBUFFER, rb0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb0);
glBindRenderbuffer(GL_RENDERBUFFER, rb1);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rb1);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
glClearBufferfv(GL_COLOR, 0, clearF);
glClearBufferiv(GL_COLOR, 1, clearI);
ASSERT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0);
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_8I(0, 0, 0, 0, 0, 0);
drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.0);
ASSERT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_8I(0, 0, 1, 2, 3, 4);
// FBO: rb1 (rgba8i), rb0 (rgba8)
glBindRenderbuffer(GL_RENDERBUFFER, rb0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rb0);
glBindRenderbuffer(GL_RENDERBUFFER, rb1);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb1);
ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Rebind fragment outputs
glBindFragDataLocationEXT(mProgram, 0, "outSrc1");
glBindFragDataLocationEXT(mProgram, 1, "outSrc0");
glLinkProgram(mProgram);
glClearBufferfv(GL_COLOR, 1, clearF);
glClearBufferiv(GL_COLOR, 0, clearI);
ASSERT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0);
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_8I(0, 0, 0, 0, 0, 0);
drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.0);
ASSERT_GL_NO_ERROR();
glReadBuffer(GL_COLOR_ATTACHMENT1);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
glReadBuffer(GL_COLOR_ATTACHMENT0);
EXPECT_PIXEL_8I(0, 0, 1, 2, 3, 4);
}
// Use a program pipeline with EXT_blend_func_extended
TEST_P(EXTBlendFuncExtendedDrawTestES31, UseProgramPipeline)
{
// Only the Vulkan backend supports PPO
ANGLE_SKIP_TEST_IF(!IsVulkan());
// Create two separable program objects from a
// single source string respectively (vertSrc and fragSrc)
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const char *kFragColorShader = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
layout(location = 0, index = 1) out vec4 outSrc1;
layout(location = 0, index = 0) out vec4 outSrc0;
void main() {
outSrc0 = src0;
outSrc1 = src1;
})";
setupProgramPipeline(essl3_shaders::vs::Simple(), kFragColorShader);
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
ASSERT_EQ(mProgram, 0u);
drawTest();
ASSERT_GL_NO_ERROR();
}
// Use program pipeline where the fragment program is changed
TEST_P(EXTBlendFuncExtendedDrawTestES31, UseTwoProgramStages)
{
// Only the Vulkan backend supports PPO
ANGLE_SKIP_TEST_IF(!IsVulkan());
// Create two separable program objects from a
// single source string respectively (vertSrc and fragSrc)
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const char *kFragColorShaderFlipped = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
layout(location = 0, index = 0) out vec4 outSrc1;
layout(location = 0, index = 1) out vec4 outSrc0;
void main() {
outSrc0 = src0;
outSrc1 = src1;
})";
const char *kFragColorShader = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
uniform vec4 src0;
uniform vec4 src1;
layout(location = 0, index = 1) out vec4 outSrc1;
layout(location = 0, index = 0) out vec4 outSrc0;
void main() {
outSrc0 = src0;
outSrc1 = src1;
})";
setupProgramPipeline(essl3_shaders::vs::Simple(), kFragColorShaderFlipped);
// Check index values frag shader with the "flipped" index values
checkOutputIndexQuery("outSrc0", 1);
checkOutputIndexQuery("outSrc1", 0);
GLuint previousProgram = mFragProgram;
mFragProgram = createShaderProgram(GL_FRAGMENT_SHADER, kFragColorShader);
ASSERT_NE(mFragProgram, 0u);
// Change the Fragment program of the pipeline
glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProgram);
EXPECT_GL_NO_ERROR();
checkOutputIndexQuery("outSrc0", 0);
checkOutputIndexQuery("outSrc1", 1);
ASSERT_EQ(mProgram, 0u);
drawTest();
if (previousProgram)
{
glDeleteProgram(previousProgram);
}
ASSERT_GL_NO_ERROR();
}
ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedTestES3);
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedTestES3);
ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedDrawTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedDrawTestES3);
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedDrawTestES3);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EXTBlendFuncExtendedDrawTestES31);
ANGLE_INSTANTIATE_TEST_ES31(EXTBlendFuncExtendedDrawTestES31);