Commit 6cc845bb by Brandon Schade Committed by Commit Bot

Vulkan: Add support for EXT_blend_func_extended

This implementation utilizes vulkan's dualSrcBlend feature. Expose this extension if the underlying vulkan backend allows the use of this feature. Test: angle_end2end_tests --gtest_filter=EXTBlendFuncExtendedDrawTest* Bug: angleproject:5074 Change-Id: I7d2f611df89d65e5cac35158cb5f41a0ebd58aae Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2593151 Commit-Queue: Brandon Schade <b.schade@samsung.com> Commit-Queue: Mohan Maiya <m.maiya@samsung.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org>
parent 926d1cea
......@@ -1009,6 +1009,8 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root,
bool hasGLFragData = false;
bool usePreRotation = (compileOptions & SH_ADD_PRE_ROTATION) != 0;
bool hasGLSampleMask = false;
bool hasGLSecondaryFragColor = false;
bool hasGLSecondaryFragData = false;
for (const ShaderVariable &outputVar : mOutputVariables)
{
......@@ -1030,11 +1032,24 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root,
hasGLSampleMask = true;
continue;
}
else if (outputVar.name == "gl_SecondaryFragColorEXT")
{
ASSERT(!hasGLSecondaryFragColor);
hasGLSecondaryFragColor = true;
continue;
}
else if (outputVar.name == "gl_SecondaryFragDataEXT")
{
ASSERT(!hasGLSecondaryFragData);
hasGLSecondaryFragData = true;
continue;
}
}
// Declare gl_FragColor and glFragData as webgl_FragColor and webgl_FragData
// if it's core profile shaders and they are used.
ASSERT(!(hasGLFragColor && hasGLFragData));
ASSERT(!((hasGLFragColor || hasGLSecondaryFragColor) &&
(hasGLFragData || hasGLSecondaryFragData)));
if (hasGLFragColor)
{
sink << "layout(location = 0) out vec4 webgl_FragColor;\n";
......@@ -1043,6 +1058,15 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root,
{
sink << "layout(location = 0) out vec4 webgl_FragData[gl_MaxDrawBuffers];\n";
}
if (hasGLSecondaryFragColor)
{
sink << "layout(location = 0, index = 1) out vec4 angle_SecondaryFragColor;\n";
}
if (hasGLSecondaryFragData)
{
sink << "layout(location = 0, index = 1) out vec4 angle_SecondaryFragData["
<< getResources().MaxDualSourceDrawBuffers << "];\n";
}
if (usesPointCoord)
{
......
......@@ -108,6 +108,12 @@ void LoadBlockMemberInfo(BinaryInputStream *stream, sh::BlockMemberInfo *var);
void WriteShaderVar(BinaryOutputStream *stream, const sh::ShaderVariable &var);
void LoadShaderVar(BinaryInputStream *stream, sh::ShaderVariable *var);
void WriteInterfaceBlock(BinaryOutputStream *stream, const InterfaceBlock &block);
void LoadInterfaceBlock(BinaryInputStream *stream, InterfaceBlock *block);
void WriteShaderVariableBuffer(BinaryOutputStream *stream, const ShaderVariableBuffer &var);
void LoadShaderVariableBuffer(BinaryInputStream *stream, ShaderVariableBuffer *var);
// Struct used for correlating uniforms/elements of uniform arrays to handles
struct VariableLocation
{
......@@ -233,7 +239,7 @@ class ProgramState final : angle::NonCopyable
}
const UniformBlockBindingMask &getActiveUniformBlockBindingsMask() const
{
return mActiveUniformBlockBindings;
return mExecutable->getActiveUniformBlockBindings();
}
const std::vector<sh::ShaderVariable> &getProgramInputs() const
{
......@@ -250,7 +256,7 @@ class ProgramState final : angle::NonCopyable
}
const std::vector<VariableLocation> &getSecondaryOutputLocations() const
{
return mSecondaryOutputLocations;
return mExecutable->getSecondaryOutputLocations();
}
const std::vector<LinkedUniform> &getUniforms() const { return mExecutable->getUniforms(); }
const std::vector<VariableLocation> &getUniformLocations() const { return mUniformLocations; }
......@@ -386,16 +392,10 @@ class ProgramState final : angle::NonCopyable
uint32_t mLocationsUsedForXfbExtension;
std::vector<std::string> mTransformFeedbackVaryingNames;
// For faster iteration on the blocks currently being bound.
UniformBlockBindingMask mActiveUniformBlockBindings;
std::vector<VariableLocation> mUniformLocations;
std::vector<BufferVariable> mBufferVariables;
RangeUI mAtomicCounterUniformRange;
// EXT_blend_func_extended secondary outputs (ones with index 1) in ESSL 3.00 shaders.
std::vector<VariableLocation> mSecondaryOutputLocations;
DrawBufferMask mActiveOutputVariables;
// Fragment output variable base types: FLOAT, INT, or UINT. Ordered by location.
......
......@@ -233,8 +233,16 @@ class ProgramExecutable final : public angle::Subject
const std::vector<sh::ShaderVariable> &getProgramInputs() const { return mProgramInputs; }
const std::vector<sh::ShaderVariable> &getOutputVariables() const { return mOutputVariables; }
const std::vector<VariableLocation> &getOutputLocations() const { return mOutputLocations; }
const std::vector<VariableLocation> &getSecondaryOutputLocations() const
{
return mSecondaryOutputLocations;
}
const std::vector<LinkedUniform> &getUniforms() const { return mUniforms; }
const std::vector<InterfaceBlock> &getUniformBlocks() const { return mUniformBlocks; }
const UniformBlockBindingMask &getActiveUniformBlockBindings() const
{
return mActiveUniformBlockBindings;
}
const std::vector<SamplerBinding> &getSamplerBindings() const { return mSamplerBindings; }
const std::vector<ImageBinding> &getImageBindings() const
{
......@@ -392,6 +400,8 @@ class ProgramExecutable final : public angle::Subject
// to uniforms.
std::vector<sh::ShaderVariable> mOutputVariables;
std::vector<VariableLocation> mOutputLocations;
// EXT_blend_func_extended secondary outputs (ones with index 1)
std::vector<VariableLocation> mSecondaryOutputLocations;
bool mYUVOutput;
// Vertex attributes, Fragment input varyings, etc.
std::vector<sh::ShaderVariable> mProgramInputs;
......@@ -414,6 +424,10 @@ class ProgramExecutable final : public angle::Subject
RangeUI mDefaultUniformRange;
RangeUI mSamplerUniformRange;
std::vector<InterfaceBlock> mUniformBlocks;
// For faster iteration on the blocks currently being bound.
UniformBlockBindingMask mActiveUniformBlockBindings;
std::vector<AtomicCounterBuffer> mAtomicCounterBuffers;
RangeUI mImageUniformRange;
std::vector<InterfaceBlock> mComputeShaderStorageBlocks;
......
......@@ -539,13 +539,70 @@ void AssignAttributeLocations(const gl::ProgramExecutable &programExecutable,
}
}
void AssignOutputLocations(const gl::ProgramExecutable &programExecutable,
void AssignSecondaryOutputLocations(const gl::ProgramState &programState,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const auto &secondaryOutputLocations =
programState.getExecutable().getSecondaryOutputLocations();
const auto &outputVariables = programState.getExecutable().getOutputVariables();
// Handle EXT_blend_func_extended secondary outputs (ones with index=1)
for (const gl::VariableLocation &outputLocation : secondaryOutputLocations)
{
if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored)
{
const sh::ShaderVariable &outputVar = outputVariables[outputLocation.index];
uint32_t location = 0;
if (outputVar.location != -1)
{
location = outputVar.location;
}
ShaderInterfaceVariableInfo *info =
AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.mappedName,
location, ShaderInterfaceVariableInfo::kInvalid, 0, 0);
// If the shader source has not specified the index, specify it here.
if (outputVar.index == -1)
{
// Index 1 is used to specify that the color be used as the second color input to
// the blend equation
info->index = 1;
}
}
}
// Handle secondary outputs for ESSL version less than 3.00
gl::Shader *fragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment);
if (fragmentShader && fragmentShader->getShaderVersion() == 100)
{
const auto &shaderOutputs = fragmentShader->getActiveOutputVariables();
for (const auto &outputVar : shaderOutputs)
{
if (outputVar.name == "gl_SecondaryFragColorEXT")
{
AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment,
"angle_SecondaryFragColor", 0,
ShaderInterfaceVariableInfo::kInvalid, 0, 0);
}
else if (outputVar.name == "gl_SecondaryFragDataEXT")
{
AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment,
"angle_SecondaryFragData", 0, ShaderInterfaceVariableInfo::kInvalid,
0, 0);
}
}
}
}
void AssignOutputLocations(const gl::ProgramState &programState,
const gl::ShaderType shaderType,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// Assign output locations for the fragment shader.
ASSERT(shaderType == gl::ShaderType::Fragment);
// TODO(syoussefi): Add support for EXT_blend_func_extended. http://anglebug.com/3385
const gl::ProgramExecutable &programExecutable = programState.getExecutable();
const auto &outputLocations = programExecutable.getOutputLocations();
const auto &outputVariables = programExecutable.getOutputVariables();
const std::array<std::string, 3> implicitOutputs = {"gl_FragDepth", "gl_SampleMask",
......@@ -576,6 +633,8 @@ void AssignOutputLocations(const gl::ProgramExecutable &programExecutable,
}
}
AssignSecondaryOutputLocations(programState, variableInfoMapOut);
// When no fragment output is specified by the shader, the translator outputs webgl_FragColor or
// webgl_FragData. Add an entry for these. Even though the translator is already assigning
// location 0 to these entries, adding an entry for them here allows us to ASSERT that every
......@@ -2223,6 +2282,13 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction)
{spirv::LiteralInteger(info->component)});
}
// Add index decoration, if any.
if (info->index != ShaderInterfaceVariableInfo::kInvalid)
{
spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationIndex,
{spirv::LiteralInteger(info->index)});
}
// Add Xfb decorations, if any.
if (mOptions.isTransformFeedbackStage &&
info->xfb.buffer != ShaderInterfaceVariableXfbInfo::kInvalid)
......@@ -3743,7 +3809,7 @@ void GlslangAssignLocations(const GlslangSourceOptions &options,
if ((shaderType == gl::ShaderType::Fragment) &&
programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment))
{
AssignOutputLocations(programExecutable, gl::ShaderType::Fragment, variableInfoMapOut);
AssignOutputLocations(programState, gl::ShaderType::Fragment, variableInfoMapOut);
}
// Assign attributes to the vertex shader, if any.
......
......@@ -97,6 +97,7 @@ struct ShaderInterfaceVariableInfo
// locations in their respective slots.
uint32_t location = kInvalid;
uint32_t component = kInvalid;
uint32_t index = kInvalid;
// The stages this shader interface variable is active.
gl::ShaderBitSet activeStages;
// Used for transform feedback extension to decorate vertex shader output.
......
......@@ -1545,6 +1545,8 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF
enabledFeatures.features.shaderCullDistance = mPhysicalDeviceFeatures.shaderCullDistance;
// Used to support tessellation Shader:
enabledFeatures.features.tessellationShader = mPhysicalDeviceFeatures.tessellationShader;
// Used to support EXT_blend_func_extended
enabledFeatures.features.dualSrcBlend = mPhysicalDeviceFeatures.dualSrcBlend;
if (!vk::CommandBuffer::ExecutesInline())
{
......
......@@ -90,6 +90,14 @@ uint8_t PackGLBlendFactor(GLenum blendFactor)
return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR);
case GL_ONE_MINUS_CONSTANT_ALPHA:
return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA);
case GL_SRC1_COLOR_EXT:
return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC1_COLOR);
case GL_SRC1_ALPHA_EXT:
return static_cast<uint8_t>(VK_BLEND_FACTOR_SRC1_ALPHA);
case GL_ONE_MINUS_SRC1_COLOR_EXT:
return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR);
case GL_ONE_MINUS_SRC1_ALPHA_EXT:
return static_cast<uint8_t>(VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA);
default:
UNREACHABLE();
return 0;
......
......@@ -997,6 +997,10 @@ void RendererVk::ensureCapsInitialized() const
mNativeCaps.maxCombinedClipAndCullDistances = limitsVk.maxCombinedClipAndCullDistances;
}
}
// GL_EXT_blend_func_extended
mNativeExtensions.blendFuncExtended = (mPhysicalDeviceFeatures.dualSrcBlend == VK_TRUE);
mNativeExtensions.maxDualSourceDrawBuffers = LimitToInt(limitsVk.maxFragmentDualSrcAttachments);
}
namespace vk
......
......@@ -7,6 +7,7 @@
// Test EXT_blend_func_extended
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/shader_utils.h"
......@@ -134,13 +135,28 @@ class EXTBlendFuncExtendedDrawTest : public ANGLETest
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 = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib());
GLint src0 = glGetUniformLocation(mProgram, "src0");
GLint src1 = glGetUniformLocation(mProgram, "src1");
GLint position = getVertexAttribLocation(essl1_shaders::PositionAttrib());
GLint src0 = getFragmentUniformLocation("src0");
GLint src1 = getFragmentUniformLocation("src1");
ASSERT_GL_NO_ERROR();
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
......@@ -152,8 +168,8 @@ class EXTBlendFuncExtendedDrawTest : public ANGLETest
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};
glUniform4f(src0, kSrc0[0], kSrc0[1], kSrc0[2], kSrc0[3]);
glUniform4f(src1, kSrc1[0], kSrc1[1], kSrc1[2], kSrc1[3]);
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);
......@@ -216,7 +232,8 @@ class EXTBlendFuncExtendedDrawTestES3 : public EXTBlendFuncExtendedDrawTest
mIsES31OrNewer = true;
}
}
void checkOutputIndexQuery(const char *name, GLint expectedIndex)
virtual void checkOutputIndexQuery(const char *name, GLint expectedIndex)
{
GLint index = glGetFragDataIndexEXT(mProgram, name);
EXPECT_EQ(expectedIndex, index);
......@@ -246,6 +263,109 @@ class EXTBlendFuncExtendedDrawTestES3 : public EXTBlendFuncExtendedDrawTest
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.
......@@ -287,6 +407,12 @@ TEST_P(EXTBlendFuncExtendedDrawTest, FragData)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
// Suspected VK driver bug http://anglebug.com/5523
ANGLE_SKIP_TEST_IF(IsVulkan() && (IsNVIDIA() || IsPixel2()));
// Suspected AMD VK driver bug http://anglebug.com/5537
ANGLE_SKIP_TEST_IF(IsVulkan() && IsWindows() && IsAMD());
const char *kFragColorShader =
"#extension GL_EXT_blend_func_extended : require\n"
"precision mediump float;\n"
......@@ -396,6 +522,9 @@ TEST_P(EXTBlendFuncExtendedDrawTestES3, FragmentArrayOutputLocationsAPI)
// TODO: Investigate this mac-only failure. http://angleproject.com/1085
ANGLE_SKIP_TEST_IF(IsOSX());
// Suspected VK driver bug http://anglebug.com/5523
ANGLE_SKIP_TEST_IF(IsVulkan() && (IsNVIDIA() || IsPixel2()));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
......@@ -708,8 +837,103 @@ void main() {
glDeleteProgram(program);
}
// 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);
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedTestES3);
ANGLE_INSTANTIATE_TEST_ES2(EXTBlendFuncExtendedDrawTest);
ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(EXTBlendFuncExtendedDrawTestES3);
ANGLE_INSTANTIATE_TEST_ES31(EXTBlendFuncExtendedDrawTestES31);
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