Validate missing fragment outputs for dual-source blending

* A secondary fragment output must be declared when
  dual-source blending is enabled in WebGL contexts.

* Omitting locations for multiple fragment
  outputs is not allowed in WebGL contexts.

Bug: angleproject:1085
Change-Id: I57febdc02c9ccc571971a81b6671869f19b0aa96
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4834672
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Alexey Knyazev <lexa.knyazev@gmail.com>
This commit is contained in:
Alexey Knyazev
2023-08-21 00:00:00 +00:00
committed by Angle LUCI CQ
parent 7c3c7b7b9f
commit 9624db05ed
8 changed files with 245 additions and 9 deletions

View File

@@ -887,7 +887,7 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
if (mShaderVersion >= 300 && mShaderType == GL_FRAGMENT_SHADER &&
!ValidateOutputs(root, getExtensionBehavior(), mResources, hasPixelLocalStorageUniforms(),
&mDiagnostics))
IsWebGLBasedSpec(mShaderSpec), &mDiagnostics))
{
return false;
}

View File

@@ -31,7 +31,8 @@ class ValidateOutputsTraverser : public TIntermTraverser
public:
ValidateOutputsTraverser(const TExtensionBehavior &extBehavior,
const ShBuiltInResources &resources,
bool usesPixelLocalStorage);
bool usesPixelLocalStorage,
bool isWebGL);
void validate(TDiagnostics *diagnostics) const;
@@ -43,6 +44,7 @@ class ValidateOutputsTraverser : public TIntermTraverser
bool mEnablesBlendFuncExtended;
bool mUsesIndex1;
bool mUsesPixelLocalStorage;
bool mIsWebGL;
bool mUsesFragDepth;
typedef std::vector<TIntermSymbol *> OutputVector;
@@ -54,7 +56,8 @@ class ValidateOutputsTraverser : public TIntermTraverser
ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &extBehavior,
const ShBuiltInResources &resources,
bool usesPixelLocalStorage)
bool usesPixelLocalStorage,
bool isWebGL)
: TIntermTraverser(true, false, false),
mMaxDrawBuffers(resources.MaxDrawBuffers),
mMaxDualSourceDrawBuffers(resources.MaxDualSourceDrawBuffers),
@@ -62,6 +65,7 @@ ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &ext
IsExtensionEnabled(extBehavior, TExtension::EXT_blend_func_extended)),
mUsesIndex1(false),
mUsesPixelLocalStorage(usesPixelLocalStorage),
mIsWebGL(isWebGL),
mUsesFragDepth(false)
{}
@@ -187,6 +191,12 @@ void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const
"must explicitly specify all locations when using multiple fragment outputs and "
"pixel local storage, even if EXT_blend_func_extended is enabled";
}
else if (mIsWebGL)
{
unspecifiedLocationErrorMessage =
"must explicitly specify all locations when using multiple fragment outputs "
"in WebGL contexts, even if EXT_blend_func_extended is enabled";
}
if (unspecifiedLocationErrorMessage != nullptr)
{
for (const auto &symbol : mUnspecifiedLocationOutputs)
@@ -215,9 +225,11 @@ bool ValidateOutputs(TIntermBlock *root,
const TExtensionBehavior &extBehavior,
const ShBuiltInResources &resources,
bool usesPixelLocalStorage,
bool isWebGL,
TDiagnostics *diagnostics)
{
ValidateOutputsTraverser validateOutputs(extBehavior, resources, usesPixelLocalStorage);
ValidateOutputsTraverser validateOutputs(extBehavior, resources, usesPixelLocalStorage,
isWebGL);
root->traverse(&validateOutputs);
int numErrorsBefore = diagnostics->numErrors();
validateOutputs.validate(diagnostics);

View File

@@ -27,6 +27,7 @@ bool ValidateOutputs(TIntermBlock *root,
const TExtensionBehavior &extBehavior,
const ShBuiltInResources &resources,
bool usesPixelLocalStorage,
bool isWebGL,
TDiagnostics *diagnostics);
} // namespace sh

View File

@@ -429,6 +429,7 @@ void ProgramExecutable::reset()
mPODStruct.attributesMask.reset();
mPODStruct.maxActiveAttribLocation = 0;
mPODStruct.activeOutputVariablesMask.reset();
mPODStruct.activeSecondaryOutputVariablesMask.reset();
mPODStruct.defaultUniformRange = RangeUI(0, 0);
mPODStruct.samplerUniformRange = RangeUI(0, 0);
@@ -1207,6 +1208,7 @@ bool ProgramExecutable::linkValidateOutputVariables(
const ProgramAliasedBindings &fragmentOutputIndices)
{
ASSERT(mPODStruct.activeOutputVariablesMask.none());
ASSERT(mPODStruct.activeSecondaryOutputVariablesMask.none());
ASSERT(mPODStruct.drawBufferTypeMask.none());
ASSERT(!mPODStruct.hasYUVOutput);
@@ -1425,7 +1427,9 @@ bool ProgramExecutable::gatherOutputTypes()
for (const sh::ShaderVariable &outputVariable : mOutputVariables)
{
if (outputVariable.isBuiltIn() && outputVariable.name != "gl_FragColor" &&
outputVariable.name != "gl_FragData")
outputVariable.name != "gl_FragData" &&
outputVariable.name != "gl_SecondaryFragColorEXT" &&
outputVariable.name != "gl_SecondaryFragDataEXT")
{
continue;
}
@@ -1434,6 +1438,10 @@ bool ProgramExecutable::gatherOutputTypes()
(outputVariable.location == -1 ? 0u
: static_cast<unsigned int>(outputVariable.location));
const bool secondary =
outputVariable.index == 1 || (outputVariable.name == "gl_SecondaryFragColorEXT" ||
outputVariable.name == "gl_SecondaryFragDataEXT");
const ComponentType componentType =
GLenumToComponentType(VariableComponentType(outputVariable.type));
@@ -1444,7 +1452,15 @@ bool ProgramExecutable::gatherOutputTypes()
{
const unsigned int location = baseLocation + elementIndex;
ASSERT(location < mPODStruct.activeOutputVariablesMask.size());
mPODStruct.activeOutputVariablesMask.set(location);
ASSERT(location < mPODStruct.activeSecondaryOutputVariablesMask.size());
if (secondary)
{
mPODStruct.activeSecondaryOutputVariablesMask.set(location);
}
else
{
mPODStruct.activeOutputVariablesMask.set(location);
}
const ComponentType storedComponentType =
gl::GetComponentTypeMask(mPODStruct.drawBufferTypeMask, location);
if (storedComponentType == ComponentType::InvalidEnum)

View File

@@ -490,6 +490,10 @@ class ProgramExecutable final : public angle::Subject
{
return mPODStruct.activeOutputVariablesMask;
}
DrawBufferMask getActiveSecondaryOutputVariablesMask() const
{
return mPODStruct.activeSecondaryOutputVariablesMask;
}
bool linkUniforms(const Caps &caps,
const ShaderMap<std::vector<sh::ShaderVariable>> &shaderUniforms,
@@ -579,6 +583,7 @@ class ProgramExecutable final : public angle::Subject
// removed.
AttributesMask attributesMask;
DrawBufferMask activeOutputVariablesMask;
DrawBufferMask activeSecondaryOutputVariablesMask;
ComponentTypeMask drawBufferTypeMask;
RangeUI defaultUniformRange;

View File

@@ -486,11 +486,16 @@ bool ValidateFragmentShaderColorBufferMaskMatch(const Context *context)
const Program *program = context->getActiveLinkedProgram();
const Framebuffer *framebuffer = glState.getDrawFramebuffer();
auto drawBufferMask =
framebuffer->getDrawBufferMask() & glState.getBlendStateExt().compareColorMask(0);
const auto &blendStateExt = glState.getBlendStateExt();
auto drawBufferMask = framebuffer->getDrawBufferMask() & blendStateExt.compareColorMask(0);
auto dualSourceBlendingMask = drawBufferMask & blendStateExt.getEnabledMask() &
blendStateExt.getUsesExtendedBlendFactorMask();
auto fragmentOutputMask = program->getExecutable().getActiveOutputVariablesMask();
auto fragmentSecondaryOutputMask =
program->getExecutable().getActiveSecondaryOutputVariablesMask();
return drawBufferMask == (drawBufferMask & fragmentOutputMask);
return drawBufferMask == (drawBufferMask & fragmentOutputMask) &&
dualSourceBlendingMask == (dualSourceBlendingMask & fragmentSecondaryOutputMask);
}
bool ValidateFragmentShaderColorBufferTypeMatch(const Context *context)

View File

@@ -1177,6 +1177,10 @@ b/273271471 WIN INTEL VULKAN : ShaderAlgorithmTest.rgb_to_hsl_vertex_shader/* =
8131 LINUX INTEL : SampleMultisampleInterpolationTest.SampleMaskInPerSample/* = SKIP
8131 LINUX INTEL : SampleMultisampleInterpolationTest.SampleMaskInPerSampleNoPerspective/* = SKIP
// Driver bugs related to handling secondary fragment output arrays
8336 ANDROID VULKAN : WebGLCompatibilityTest.EXTBlendFuncExtendedMissingOutputsArrays/* = SKIP
8336 ANDROID VULKAN : WebGL2CompatibilityTest.EXTBlendFuncExtendedMissingOutputsArrays/* = SKIP
// bits 24..31 from glClearValueuiv value don't work on Intel Metal.
7794 MAC INTEL METAL : PixelLocalStorageTest.ClearValues_r32/* = SKIP

View File

@@ -6140,6 +6140,199 @@ void main() {
EXPECT_FALSE(prg.valid());
}
// Test that EXT_blend_func_extended does not allow omitting locations in WebGL 2.0 contexts.
TEST_P(WebGL2CompatibilityTest, EXTBlendFuncExtendedNoLocations)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
out highp vec4 color0;
out highp vec4 color1;
void main() {
color0 = vec4(1.0, 0.0, 0.0, 1.0);
color1 = vec4(0.0, 1.0, 0.0, 1.0);
})";
GLProgram prg;
prg.makeRaster(essl3_shaders::vs::Simple(), kFS);
EXPECT_FALSE(prg.valid());
}
// Test that a secondary fragment output is declared
// when using EXT_blend_func_extended in WebGL 1.0 contexts.
TEST_P(WebGLCompatibilityTest, EXTBlendFuncExtendedMissingOutputs)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT);
ASSERT_GL_NO_ERROR();
{
constexpr char kFragColor[] = R"(
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragColor);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kSecondaryFragColor[] = R"(#extension GL_EXT_blend_func_extended : require
void main() {
gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kSecondaryFragColor);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kFragColorAndSecondaryFragColor[] =
R"(#extension GL_EXT_blend_func_extended : require
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragColorAndSecondaryFragColor);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
}
}
// Test that a secondary fragment output is declared
// when using EXT_blend_func_extended in WebGL 1.0 contexts.
TEST_P(WebGLCompatibilityTest, EXTBlendFuncExtendedMissingOutputsArrays)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT);
ASSERT_GL_NO_ERROR();
{
constexpr char kFragData[] = R"(
void main() {
gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragData);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kSecondaryFragData[] = R"(#extension GL_EXT_blend_func_extended : require
void main() {
gl_SecondaryFragDataEXT[0] = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kSecondaryFragData);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kFragDataAndSecondaryFragData[] =
R"(#extension GL_EXT_blend_func_extended : require
void main() {
gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0);
gl_SecondaryFragDataEXT[0] = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragDataAndSecondaryFragData);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
}
}
// Test that a secondary fragment output is declared
// when using EXT_blend_func_extended in WebGL 2.0 contexts.
TEST_P(WebGL2CompatibilityTest, EXTBlendFuncExtendedMissingOutputs)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT);
ASSERT_GL_NO_ERROR();
{
constexpr char kColor0[] = R"(#version 300 es
out mediump vec4 color0;
void main() {
color0 = vec4(1.0, 0.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kColor0);
drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kColor1[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
layout(location = 0, index = 1) out mediump vec4 color1;
void main() {
color1 = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kColor1);
drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kColor0AndColor1[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
layout(location = 0, index = 0) out mediump vec4 color0;
layout(location = 0, index = 1) out mediump vec4 color1;
void main() {
color0 = vec4(1.0, 0.0, 0.0, 1.0);
color1 = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kColor0AndColor1);
drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
}
}
// Test that a secondary fragment output is declared
// when using EXT_blend_func_extended in WebGL 2.0 contexts.
TEST_P(WebGL2CompatibilityTest, EXTBlendFuncExtendedMissingOutputsArrays)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_SRC1_COLOR_EXT);
ASSERT_GL_NO_ERROR();
{
constexpr char kArrayColor0[] = R"(#version 300 es
out mediump vec4 color0[1];
void main() {
color0[0] = vec4(1.0, 0.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kArrayColor0);
drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kArrayColor1[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
layout(location = 0, index = 1) out mediump vec4 color1[1];
void main() {
color1[0] = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kArrayColor1);
drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
{
constexpr char kArrayColor0AndColor0[] = R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
layout(location = 0, index = 0) out mediump vec4 color0[1];
layout(location = 0, index = 1) out mediump vec4 color1[1];
void main() {
color0[0] = vec4(1.0, 0.0, 0.0, 1.0);
color1[0] = vec4(0.0, 1.0, 0.0, 1.0);
})";
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kArrayColor0AndColor0);
drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
}
}
// Test for a mishandling of instanced vertex attributes with zero-sized buffers bound on Apple
// OpenGL drivers.
TEST_P(WebGL2CompatibilityTest, DrawWithZeroSizedBuffer)