Fix fragment output variables validation
* Reject fragment shaders that assign
out-of-range fragment locations
* Reject fragment shaders that assign
output variables of different types
to the same location
* Apply similar validation for fragment
outputs bound via API calls
* Ensure that masks for active output
variables and draw buffer types are
set after processing all fragment
output bindings
Bug: angleproject:1085
Change-Id: If29cbb58be1981279fc97c67739fe4136b0cdc98
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4813656
Commit-Queue: Alexey Knyazev <lexa.knyazev@gmail.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 9ef54ed..eb4d249 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -886,8 +886,8 @@
}
if (mShaderVersion >= 300 && mShaderType == GL_FRAGMENT_SHADER &&
- !ValidateOutputs(root, getExtensionBehavior(), mResources.MaxDrawBuffers,
- hasPixelLocalStorageUniforms(), &mDiagnostics))
+ !ValidateOutputs(root, getExtensionBehavior(), mResources, hasPixelLocalStorageUniforms(),
+ &mDiagnostics))
{
return false;
}
diff --git a/src/compiler/translator/ValidateOutputs.cpp b/src/compiler/translator/ValidateOutputs.cpp
index 66ab3f3..b42cca0 100644
--- a/src/compiler/translator/ValidateOutputs.cpp
+++ b/src/compiler/translator/ValidateOutputs.cpp
@@ -30,7 +30,7 @@
{
public:
ValidateOutputsTraverser(const TExtensionBehavior &extBehavior,
- int maxDrawBuffers,
+ const ShBuiltInResources &resources,
bool usesPixelLocalStorage);
void validate(TDiagnostics *diagnostics) const;
@@ -39,7 +39,9 @@
private:
int mMaxDrawBuffers;
+ int mMaxDualSourceDrawBuffers;
bool mEnablesBlendFuncExtended;
+ bool mUsesIndex1;
bool mUsesPixelLocalStorage;
bool mUsesFragDepth;
@@ -51,12 +53,14 @@
};
ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &extBehavior,
- int maxDrawBuffers,
+ const ShBuiltInResources &resources,
bool usesPixelLocalStorage)
: TIntermTraverser(true, false, false),
- mMaxDrawBuffers(maxDrawBuffers),
+ mMaxDrawBuffers(resources.MaxDrawBuffers),
+ mMaxDualSourceDrawBuffers(resources.MaxDualSourceDrawBuffers),
mEnablesBlendFuncExtended(
IsExtensionEnabled(extBehavior, TExtension::EXT_blend_func_extended)),
+ mUsesIndex1(false),
mUsesPixelLocalStorage(usesPixelLocalStorage),
mUsesFragDepth(false)
{}
@@ -74,11 +78,16 @@
TQualifier qualifier = symbol->getQualifier();
if (qualifier == EvqFragmentOut)
{
- if (symbol->getType().getLayoutQualifier().location != -1)
+ const TLayoutQualifier &layoutQualifier = symbol->getType().getLayoutQualifier();
+ if (layoutQualifier.location != -1)
{
mOutputs.push_back(symbol);
+ if (layoutQualifier.index == 1)
+ {
+ mUsesIndex1 = true;
+ }
}
- else if (symbol->getType().getLayoutQualifier().yuv == true)
+ else if (layoutQualifier.yuv == true)
{
mYuvOutputs.push_back(symbol);
}
@@ -96,8 +105,8 @@
void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const
{
ASSERT(diagnostics);
- OutputVector validOutputs(mMaxDrawBuffers, nullptr);
- OutputVector validSecondaryOutputs(mMaxDrawBuffers, nullptr);
+ OutputVector validOutputs(mUsesIndex1 ? mMaxDualSourceDrawBuffers : mMaxDrawBuffers, nullptr);
+ OutputVector validSecondaryOutputs(mMaxDualSourceDrawBuffers, nullptr);
for (const auto &symbol : mOutputs)
{
@@ -110,11 +119,13 @@
ASSERT(type.getLayoutQualifier().location != -1);
OutputVector *validOutputsToUse = &validOutputs;
+ OutputVector *otherOutputsToUse = &validSecondaryOutputs;
// The default index is 0, so we only assign the output to secondary outputs in case the
// index is explicitly set to 1.
if (type.getLayoutQualifier().index == 1)
{
validOutputsToUse = &validSecondaryOutputs;
+ otherOutputsToUse = &validOutputs;
}
if (location + elementCount <= validOutputsToUse->size())
@@ -132,6 +143,19 @@
else
{
(*validOutputsToUse)[offsetLocation] = symbol;
+ if (offsetLocation < otherOutputsToUse->size())
+ {
+ TIntermSymbol *otherSymbol = (*otherOutputsToUse)[offsetLocation];
+ if (otherSymbol && otherSymbol->getType().getBasicType() !=
+ symbol->getType().getBasicType())
+ {
+ std::stringstream strstr = sh::InitializeStream<std::stringstream>();
+ strstr << "conflicting output types with previously defined output "
+ << "'" << (*otherOutputsToUse)[offsetLocation]->getName() << "'"
+ << " for location " << offsetLocation;
+ error(*symbol, strstr.str().c_str(), diagnostics);
+ }
+ }
}
}
}
@@ -139,10 +163,11 @@
{
if (elementCount > 0)
{
- error(*symbol,
- elementCount > 1 ? "output array locations would exceed MAX_DRAW_BUFFERS"
- : "output location must be < MAX_DRAW_BUFFERS",
- diagnostics);
+ std::stringstream strstr = sh::InitializeStream<std::stringstream>();
+ strstr << (elementCount > 1 ? "output array locations would exceed "
+ : "output location must be < ")
+ << "MAX_" << (mUsesIndex1 ? "DUAL_SOURCE_" : "") << "DRAW_BUFFERS";
+ error(*symbol, strstr.str().c_str(), diagnostics);
}
}
}
@@ -188,11 +213,11 @@
bool ValidateOutputs(TIntermBlock *root,
const TExtensionBehavior &extBehavior,
- int maxDrawBuffers,
+ const ShBuiltInResources &resources,
bool usesPixelLocalStorage,
TDiagnostics *diagnostics)
{
- ValidateOutputsTraverser validateOutputs(extBehavior, maxDrawBuffers, usesPixelLocalStorage);
+ ValidateOutputsTraverser validateOutputs(extBehavior, resources, usesPixelLocalStorage);
root->traverse(&validateOutputs);
int numErrorsBefore = diagnostics->numErrors();
validateOutputs.validate(diagnostics);
diff --git a/src/compiler/translator/ValidateOutputs.h b/src/compiler/translator/ValidateOutputs.h
index 1957450..4201f7c 100644
--- a/src/compiler/translator/ValidateOutputs.h
+++ b/src/compiler/translator/ValidateOutputs.h
@@ -11,6 +11,8 @@
#ifndef COMPILER_TRANSLATOR_VALIDATEOUTPUTS_H_
#define COMPILER_TRANSLATOR_VALIDATEOUTPUTS_H_
+#include <GLSLANG/ShaderLang.h>
+
#include "compiler/translator/ExtensionBehavior.h"
namespace sh
@@ -23,7 +25,7 @@
// Returns true if the shader has no conflicting or otherwise erroneous fragment outputs.
bool ValidateOutputs(TIntermBlock *root,
const TExtensionBehavior &extBehavior,
- int maxDrawBuffers,
+ const ShBuiltInResources &resources,
bool usesPixelLocalStorage,
TDiagnostics *diagnostics);
diff --git a/src/libANGLE/ProgramExecutable.cpp b/src/libANGLE/ProgramExecutable.cpp
index acd0c62..58df9e1 100644
--- a/src/libANGLE/ProgramExecutable.cpp
+++ b/src/libANGLE/ProgramExecutable.cpp
@@ -400,8 +400,6 @@
mSamplerBindings.clear();
mSamplerBoundTextureUnits.clear();
mImageBindings.clear();
-
- mOutputVariableTypes.clear();
}
void ProgramExecutable::load(bool isSeparable, gl::BinaryInputStream *stream)
@@ -485,13 +483,6 @@
stream->readBool(&locationData.ignored);
}
- size_t outputTypeCount = stream->readInt<size_t>();
- mOutputVariableTypes.resize(outputTypeCount);
- for (size_t outputIndex = 0; outputIndex < outputTypeCount; ++outputIndex)
- {
- mOutputVariableTypes[outputIndex] = stream->readInt<GLenum>();
- }
-
size_t secondaryOutputVarCount = stream->readInt<size_t>();
ASSERT(mSecondaryOutputLocations.empty());
mSecondaryOutputLocations.resize(secondaryOutputVarCount);
@@ -608,12 +599,6 @@
stream->writeBool(outputVar.ignored);
}
- stream->writeInt(mOutputVariableTypes.size());
- for (const auto &outputVariableType : mOutputVariableTypes)
- {
- stream->writeInt(outputVariableType);
- }
-
stream->writeInt(getSecondaryOutputLocations().size());
for (const auto &outputVar : getSecondaryOutputLocations())
{
@@ -1141,73 +1126,15 @@
const ProgramAliasedBindings &fragmentOutputLocations,
const ProgramAliasedBindings &fragmentOutputIndices)
{
- ASSERT(mOutputVariableTypes.empty());
ASSERT(mPODStruct.activeOutputVariablesMask.none());
ASSERT(mPODStruct.drawBufferTypeMask.none());
ASSERT(!mPODStruct.hasYUVOutput);
- // Gather output variable types
- for (const sh::ShaderVariable &outputVariable : outputVariables)
- {
- if (outputVariable.isBuiltIn() && outputVariable.name != "gl_FragColor" &&
- outputVariable.name != "gl_FragData")
- {
- continue;
- }
-
- unsigned int baseLocation =
- (outputVariable.location == -1 ? 0u
- : static_cast<unsigned int>(outputVariable.location));
-
- // GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of
- // structures, so we may use getBasicTypeElementCount().
- unsigned int elementCount = outputVariable.getBasicTypeElementCount();
- for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
- {
- const unsigned int location = baseLocation + elementIndex;
- if (location >= mOutputVariableTypes.size())
- {
- mOutputVariableTypes.resize(location + 1, GL_NONE);
- }
- ASSERT(location < mPODStruct.activeOutputVariablesMask.size());
- mPODStruct.activeOutputVariablesMask.set(location);
- mOutputVariableTypes[location] = VariableComponentType(outputVariable.type);
- ComponentType componentType = GLenumToComponentType(mOutputVariableTypes[location]);
- SetComponentTypeMask(componentType, location, &mPODStruct.drawBufferTypeMask);
- }
-
- if (outputVariable.yuv)
- {
- ASSERT(outputVariables.size() == 1);
- mPODStruct.hasYUVOutput = true;
- }
- }
-
- if (version >= ES_3_1)
- {
- // [OpenGL ES 3.1] Chapter 8.22 Page 203:
- // A link error will be generated if the sum of the number of active image uniforms used in
- // all shaders, the number of active shader storage blocks, and the number of active
- // fragment shader outputs exceeds the implementation-dependent value of
- // MAX_COMBINED_SHADER_OUTPUT_RESOURCES.
- if (combinedImageUniformsCount + combinedShaderStorageBlocksCount +
- mPODStruct.activeOutputVariablesMask.count() >
- static_cast<GLuint>(caps.maxCombinedShaderOutputResources))
- {
- mInfoLog
- << "The sum of the number of active image uniforms, active shader storage blocks "
- "and active fragment shader outputs exceeds "
- "MAX_COMBINED_SHADER_OUTPUT_RESOURCES ("
- << caps.maxCombinedShaderOutputResources << ")";
- return false;
- }
- }
-
mOutputVariables = outputVariables;
if (fragmentShaderVersion == 100)
{
- return true;
+ return gatherOutputTypes();
}
// EXT_blend_func_extended doesn't specify anything related to binding specific elements of an
@@ -1385,6 +1312,80 @@
}
}
+ if (!gatherOutputTypes())
+ {
+ return false;
+ }
+
+ if (version >= ES_3_1)
+ {
+ // [OpenGL ES 3.1] Chapter 8.22 Page 203:
+ // A link error will be generated if the sum of the number of active image uniforms used in
+ // all shaders, the number of active shader storage blocks, and the number of active
+ // fragment shader outputs exceeds the implementation-dependent value of
+ // MAX_COMBINED_SHADER_OUTPUT_RESOURCES.
+ if (combinedImageUniformsCount + combinedShaderStorageBlocksCount +
+ mPODStruct.activeOutputVariablesMask.count() >
+ static_cast<GLuint>(caps.maxCombinedShaderOutputResources))
+ {
+ mInfoLog
+ << "The sum of the number of active image uniforms, active shader storage blocks "
+ "and active fragment shader outputs exceeds "
+ "MAX_COMBINED_SHADER_OUTPUT_RESOURCES ("
+ << caps.maxCombinedShaderOutputResources << ")";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool ProgramExecutable::gatherOutputTypes()
+{
+ for (const sh::ShaderVariable &outputVariable : mOutputVariables)
+ {
+ if (outputVariable.isBuiltIn() && outputVariable.name != "gl_FragColor" &&
+ outputVariable.name != "gl_FragData")
+ {
+ continue;
+ }
+
+ unsigned int baseLocation =
+ (outputVariable.location == -1 ? 0u
+ : static_cast<unsigned int>(outputVariable.location));
+
+ const ComponentType componentType =
+ GLenumToComponentType(VariableComponentType(outputVariable.type));
+
+ // GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of
+ // structures, so we may use getBasicTypeElementCount().
+ unsigned int elementCount = outputVariable.getBasicTypeElementCount();
+ for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
+ {
+ const unsigned int location = baseLocation + elementIndex;
+ ASSERT(location < mPODStruct.activeOutputVariablesMask.size());
+ mPODStruct.activeOutputVariablesMask.set(location);
+ const ComponentType storedComponentType =
+ gl::GetComponentTypeMask(mPODStruct.drawBufferTypeMask, location);
+ if (storedComponentType == ComponentType::InvalidEnum)
+ {
+ SetComponentTypeMask(componentType, location, &mPODStruct.drawBufferTypeMask);
+ }
+ else if (storedComponentType != componentType)
+ {
+ mInfoLog << "Inconsistent component types for fragment outputs at location "
+ << location;
+ return false;
+ }
+ }
+
+ if (outputVariable.yuv)
+ {
+ ASSERT(mOutputVariables.size() == 1);
+ mPODStruct.hasYUVOutput = true;
+ }
+ }
+
return true;
}
diff --git a/src/libANGLE/ProgramExecutable.h b/src/libANGLE/ProgramExecutable.h
index 077e9d5..732e9a3 100644
--- a/src/libANGLE/ProgramExecutable.h
+++ b/src/libANGLE/ProgramExecutable.h
@@ -533,6 +533,8 @@
const ProgramAliasedBindings &fragmentOutputLocations,
const ProgramAliasedBindings &fragmentOutputIndices);
+ bool gatherOutputTypes();
+
void linkSamplerAndImageBindings(GLuint *combinedImageUniformsCount);
bool linkAtomicCounterBuffers(const Caps &caps, InfoLog &infoLog);
@@ -646,9 +648,6 @@
ShaderMap<std::vector<sh::ShaderVariable>> mLinkedUniforms;
ShaderMap<std::vector<sh::InterfaceBlock>> mLinkedUniformBlocks;
- // Fragment output variable base types: FLOAT, INT, or UINT. Ordered by location.
- std::vector<GLenum> mOutputVariableTypes;
-
// Cache for sampler validation
mutable Optional<bool> mCachedValidateSamplersResult;
};
diff --git a/src/tests/compiler_tests/EXT_blend_func_extended_test.cpp b/src/tests/compiler_tests/EXT_blend_func_extended_test.cpp
index 8746454..b86f855 100644
--- a/src/tests/compiler_tests/EXT_blend_func_extended_test.cpp
+++ b/src/tests/compiler_tests/EXT_blend_func_extended_test.cpp
@@ -114,6 +114,36 @@
fragColor = vec4(1.0);
})";
+// Shader that specifies an output with out-of-bounds location
+// for index 0 when another output uses index 1 is invalid.
+const char ESSL300_Index0OutOfBoundsFailureShader[] =
+ R"(precision mediump float;
+layout(location = 1, index = 0) out mediump vec4 fragColor;
+layout(location = 0, index = 1) out mediump vec4 secondaryFragColor;
+void main() {
+ fragColor = vec4(1);
+ secondaryFragColor = vec4(1);
+})";
+
+// Shader that specifies an output with out-of-bounds location for index 1 is invalid.
+const char ESSL300_Index1OutOfBoundsFailureShader[] =
+ R"(precision mediump float;
+layout(location = 1, index = 1) out mediump vec4 secondaryFragColor;
+void main() {
+ secondaryFragColor = vec4(1);
+})";
+
+// Shader that specifies two outputs with the same location
+// but different indices and different base types is invalid.
+const char ESSL300_IndexTypeMismatchFailureShader[] =
+ R"(precision mediump float;
+layout(location = 0, index = 0) out mediump vec4 fragColor;
+layout(location = 0, index = 1) out mediump ivec4 secondaryFragColor;
+void main() {
+ fragColor = vec4(1);
+ secondaryFragColor = ivec4(1);
+})";
+
// Global index layout qualifier fails.
const char ESSL300_GlobalIndexFailureShader[] =
R"(precision mediump float;
@@ -260,6 +290,9 @@
Values(sh::ESSLVersion300, sh::ESSLVersion310),
Values(ESSL300_LocationIndexFailureShader,
ESSL300_DoubleIndexFailureShader,
+ ESSL300_Index0OutOfBoundsFailureShader,
+ ESSL300_Index1OutOfBoundsFailureShader,
+ ESSL300_IndexTypeMismatchFailureShader,
ESSL300_GlobalIndexFailureShader,
ESSL300_IndexOnUniformVariableFailureShader,
ESSL300_IndexOnStructFailureShader,
diff --git a/src/tests/gl_tests/BlendFuncExtendedTest.cpp b/src/tests/gl_tests/BlendFuncExtendedTest.cpp
index 945a6e5..868e4d1 100644
--- a/src/tests/gl_tests/BlendFuncExtendedTest.cpp
+++ b/src/tests/gl_tests/BlendFuncExtendedTest.cpp
@@ -862,6 +862,179 @@
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)
{