diff --git a/CONTRIBUTORS b/CONTRIBUTORS index fbc478f71..c030fe822 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -64,6 +64,10 @@ Intel Corporation Andy Chen Josh Triplett Sudarsana Nagineni + Jiajia Qin + Jiawei Shao + Jie Chen + Qiankun Miao Klarälvdalens Datakonsult AB Milian Wolff diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h index df8314719..965f7abbc 100644 --- a/include/GLSLANG/ShaderLang.h +++ b/include/GLSLANG/ShaderLang.h @@ -49,7 +49,7 @@ typedef unsigned int GLenum; // Version number for shader translation API. // It is incremented every time the API changes. -#define ANGLE_SH_VERSION 162 +#define ANGLE_SH_VERSION 163 typedef enum { SH_GLES2_SPEC, @@ -208,6 +208,14 @@ const ShCompileOptions SH_REWRITE_INTEGER_UNARY_MINUS_OPERATOR = UINT64_C(1) << // It works by using an expression to emulate this function. const ShCompileOptions SH_EMULATE_ISNAN_FLOAT_FUNCTION = UINT64_C(1) << 27; +// This flag will use all uniforms of unused std140 and shared uniform blocks at the +// beginning of the vertex/fragment shader's main(). It is intended as a workaround for Mac +// drivers with shader version 4.10. In those drivers, they will treat unused +// std140 and shared uniform blocks' members as inactive. However, WebGL2.0 based on +// OpenGL ES3.0.4 requires all members of a named uniform block declared with a shared or std140 +// layout qualifier to be considered active. The uniform block itself is also considered active. +const ShCompileOptions SH_USE_UNUSED_STANDARD_SHARED_BLOCKS = UINT64_C(1) << 28; + // Defines alternate strategies for implementing array index clamping. typedef enum { // Use the clamp intrinsic for array index clamping. diff --git a/src/compiler.gypi b/src/compiler.gypi index aed899136..175ee7bee 100644 --- a/src/compiler.gypi +++ b/src/compiler.gypi @@ -110,6 +110,8 @@ 'compiler/translator/Types.h', 'compiler/translator/UnfoldShortCircuitAST.cpp', 'compiler/translator/UnfoldShortCircuitAST.h', + 'compiler/translator/UseInterfaceBlockFields.cpp', + 'compiler/translator/UseInterfaceBlockFields.h', 'compiler/translator/ValidateGlobalInitializer.cpp', 'compiler/translator/ValidateGlobalInitializer.h', 'compiler/translator/ValidateLimitations.cpp', diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp index 74c010475..1893198dc 100644 --- a/src/compiler/translator/Compiler.cpp +++ b/src/compiler/translator/Compiler.cpp @@ -26,6 +26,7 @@ #include "compiler/translator/RewriteDoWhile.h" #include "compiler/translator/ScalarizeVecAndMatConstructorArgs.h" #include "compiler/translator/UnfoldShortCircuitAST.h" +#include "compiler/translator/UseInterfaceBlockFields.h" #include "compiler/translator/ValidateLimitations.h" #include "compiler/translator/ValidateMaxParameters.h" #include "compiler/translator/ValidateOutputs.h" @@ -391,6 +392,10 @@ TIntermNode *TCompiler::compileTreeImpl(const char *const shaderStrings[], if (success && shouldCollectVariables(compileOptions)) { collectVariables(root); + if (compileOptions & SH_USE_UNUSED_STANDARD_SHARED_BLOCKS) + { + useAllMembersInUnusedStandardAndSharedBlocks(root); + } if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS) { success = enforcePackingRestrictions(); @@ -853,6 +858,22 @@ void TCompiler::initializeGLPosition(TIntermNode* root) InitializeVariables(root, list); } +void TCompiler::useAllMembersInUnusedStandardAndSharedBlocks(TIntermNode *root) +{ + sh::InterfaceBlockList list; + + for (auto block : interfaceBlocks) + { + if (!block.staticUse && + (block.layout == sh::BLOCKLAYOUT_STANDARD || block.layout == sh::BLOCKLAYOUT_SHARED)) + { + list.push_back(block); + } + } + + sh::UseInterfaceBlockFields(root, list); +} + void TCompiler::initializeOutputVariables(TIntermNode *root) { InitVariableList list; diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h index ace1abbae..93877b5a3 100644 --- a/src/compiler/translator/Compiler.h +++ b/src/compiler/translator/Compiler.h @@ -132,6 +132,10 @@ class TCompiler : public TShHandleBase // Returns true if, after applying the packing rules in the GLSL 1.017 spec // Appendix A, section 7, the shader does not use too many uniforms. bool enforcePackingRestrictions(); + // Insert statements to reference all members in unused uniform blocks with standard and shared + // layout. This is to work around a Mac driver that treats unused standard/shared + // uniform blocks as inactive. + void useAllMembersInUnusedStandardAndSharedBlocks(TIntermNode *root); // Insert statements to initialize output variables in the beginning of main(). // This is to avoid undefined behaviors. void initializeOutputVariables(TIntermNode *root); diff --git a/src/compiler/translator/UseInterfaceBlockFields.cpp b/src/compiler/translator/UseInterfaceBlockFields.cpp new file mode 100644 index 000000000..443d5b017 --- /dev/null +++ b/src/compiler/translator/UseInterfaceBlockFields.cpp @@ -0,0 +1,176 @@ +// +// Copyright 2016 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. +// + +// UseInterfaceBlockFields.cpp: insert statements to reference all members in InterfaceBlock list at +// the beginning of main. This is to work around a Mac driver that treats unused standard/shared +// uniform blocks as inactive. + +#include "compiler/translator/UseInterfaceBlockFields.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +class UseUniformBlockMembers : public TIntermTraverser +{ + public: + UseUniformBlockMembers(const InterfaceBlockList &blocks) + : TIntermTraverser(true, false, false), mBlocks(blocks), mCodeInserted(false) + { + } + + protected: + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + + private: + void insertUseCode(TIntermSequence *sequence); + void AddFieldUseStatements(const ShaderVariable &var, TIntermSequence *sequence); + + const InterfaceBlockList &mBlocks; + bool mCodeInserted; +}; + +bool UseUniformBlockMembers::visitAggregate(Visit visit, TIntermAggregate *node) +{ + bool visitChildren = !mCodeInserted; + switch (node->getOp()) + { + case EOpSequence: + break; + case EOpFunction: + { + ASSERT(visit == PreVisit); + if (node->getName() == "main(") + { + TIntermSequence *sequence = node->getSequence(); + ASSERT((sequence->size() == 1) || (sequence->size() == 2)); + TIntermAggregate *body = nullptr; + if (sequence->size() == 1) + { + body = new TIntermAggregate(EOpSequence); + sequence->push_back(body); + } + else + { + body = (*sequence)[1]->getAsAggregate(); + } + ASSERT(body); + insertUseCode(body->getSequence()); + mCodeInserted = true; + visitChildren = false; + } + break; + } + default: + visitChildren = false; + break; + } + return visitChildren; +} + +void UseUniformBlockMembers::AddFieldUseStatements(const ShaderVariable &var, + TIntermSequence *sequence) +{ + TString name = TString(var.name.c_str()); + TType type = GetShaderVariableType(var); + + if (var.isArray()) + { + size_t pos = name.find_last_of('['); + if (pos != TString::npos) + { + name = name.substr(0, pos); + } + TType elementType = type; + elementType.clearArrayness(); + + TIntermSymbol *arraySymbol = new TIntermSymbol(0, name, type); + for (unsigned int i = 0; i < var.arraySize; ++i) + { + TIntermBinary *element = + new TIntermBinary(EOpIndexDirect, arraySymbol, TIntermTyped::CreateIndexNode(i)); + + sequence->insert(sequence->begin(), element); + } + } + else if (var.isStruct()) + { + TIntermSymbol *structSymbol = new TIntermSymbol(0, name, type); + for (unsigned int i = 0; i < var.fields.size(); ++i) + { + TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct, structSymbol, + TIntermTyped::CreateIndexNode(i)); + + sequence->insert(sequence->begin(), element); + } + } + else + { + TIntermSymbol *symbol = new TIntermSymbol(0, name, type); + + sequence->insert(sequence->begin(), symbol); + } +} + +void UseUniformBlockMembers::insertUseCode(TIntermSequence *sequence) +{ + for (const auto &block : mBlocks) + { + if (block.instanceName.empty()) + { + for (const auto &var : block.fields) + { + AddFieldUseStatements(var, sequence); + } + } + else if (block.arraySize > 0) + { + TType type = GetInterfaceBlockType(block); + TString name = TString(block.instanceName.c_str()); + TIntermSymbol *arraySymbol = new TIntermSymbol(0, name, type); + for (unsigned int i = 0; i < block.arraySize; ++i) + { + TIntermBinary *instanceSymbol = new TIntermBinary(EOpIndexDirect, arraySymbol, + TIntermTyped::CreateIndexNode(i)); + for (unsigned int j = 0; j < block.fields.size(); ++j) + { + TIntermBinary *element = + new TIntermBinary(EOpIndexDirectInterfaceBlock, instanceSymbol, + TIntermTyped::CreateIndexNode(j)); + sequence->insert(sequence->begin(), element); + } + } + } + else + { + TType type = GetInterfaceBlockType(block); + TString name = TString(block.instanceName.c_str()); + TIntermSymbol *blockSymbol = new TIntermSymbol(0, name, type); + for (unsigned int i = 0; i < block.fields.size(); ++i) + { + TIntermBinary *element = new TIntermBinary( + EOpIndexDirectInterfaceBlock, blockSymbol, TIntermTyped::CreateIndexNode(i)); + + sequence->insert(sequence->begin(), element); + } + } + } +} + +} // namespace anonymous + +void UseInterfaceBlockFields(TIntermNode *root, const InterfaceBlockList &blocks) +{ + UseUniformBlockMembers useUniformBlock(blocks); + root->traverse(&useUniformBlock); +} + +} // namespace sh diff --git a/src/compiler/translator/UseInterfaceBlockFields.h b/src/compiler/translator/UseInterfaceBlockFields.h new file mode 100644 index 000000000..024bfd4b1 --- /dev/null +++ b/src/compiler/translator/UseInterfaceBlockFields.h @@ -0,0 +1,25 @@ +// +// Copyright 2016 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. +// + +// UseInterfaceBlockFields.h: insert statements to reference all members in InterfaceBlock list at +// the beginning of main. This is to work around a Mac driver that treats unused standard/shared +// uniform blocks as inactive. + +#ifndef COMPILER_TRANSLATOR_USEINTERFACEBLOCKFIELDS_H_ +#define COMPILER_TRANSLATOR_USEINTERFACEBLOCKFIELDS_H_ + +#include + +class TIntermNode; +namespace sh +{ + +using InterfaceBlockList = std::vector; + +void UseInterfaceBlockFields(TIntermNode *root, const InterfaceBlockList &blocks); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_USEINTERFACEBLOCKFIELDS_H_ diff --git a/src/compiler/translator/util.cpp b/src/compiler/translator/util.cpp index d6339a873..224402272 100644 --- a/src/compiler/translator/util.cpp +++ b/src/compiler/translator/util.cpp @@ -278,10 +278,38 @@ InterpolationType GetInterpolationType(TQualifier qualifier) } } +TType GetInterfaceBlockType(const sh::InterfaceBlock &block) +{ + TType type; + TFieldList *fields = new TFieldList; + TSourceLoc loc; + for (const auto &field : block.fields) + { + TType *fieldType = new TType(GetShaderVariableType(field)); + fields->push_back(new TField(fieldType, new TString(field.name.c_str()), loc)); + } + + TInterfaceBlock *interfaceBlock = new TInterfaceBlock( + new TString(block.name.c_str()), fields, new TString(block.instanceName.c_str()), + block.arraySize, TLayoutQualifier::create()); + + type.setBasicType(EbtInterfaceBlock); + type.setInterfaceBlock(interfaceBlock); + return type; +} + TType GetShaderVariableBasicType(const sh::ShaderVariable &var) { switch (var.type) { + case GL_BOOL: + return TType(EbtBool); + case GL_BOOL_VEC2: + return TType(EbtBool, 2); + case GL_BOOL_VEC3: + return TType(EbtBool, 3); + case GL_BOOL_VEC4: + return TType(EbtBool, 4); case GL_FLOAT: return TType(EbtFloat); case GL_FLOAT_VEC2: diff --git a/src/compiler/translator/util.h b/src/compiler/translator/util.h index 4c1713a8a..b52689165 100644 --- a/src/compiler/translator/util.h +++ b/src/compiler/translator/util.h @@ -38,6 +38,7 @@ bool IsVarying(TQualifier qualifier); InterpolationType GetInterpolationType(TQualifier qualifier); TString ArrayString(const TType &type); +TType GetInterfaceBlockType(const sh::InterfaceBlock &block); TType GetShaderVariableBasicType(const sh::ShaderVariable &var); TType GetShaderVariableType(const sh::ShaderVariable &var); diff --git a/src/libANGLE/renderer/gl/ShaderGL.cpp b/src/libANGLE/renderer/gl/ShaderGL.cpp index 82de442a3..1574507d1 100644 --- a/src/libANGLE/renderer/gl/ShaderGL.cpp +++ b/src/libANGLE/renderer/gl/ShaderGL.cpp @@ -70,6 +70,11 @@ ShCompileOptions ShaderGL::prepareSourceAndReturnOptions(std::stringstream *sour options |= SH_EMULATE_ISNAN_FLOAT_FUNCTION; } + if (mWorkarounds.useUnusedBlocksWithStandardOrSharedLayout) + { + options |= SH_USE_UNUSED_STANDARD_SHARED_BLOCKS; + } + return options; } diff --git a/src/libANGLE/renderer/gl/WorkaroundsGL.h b/src/libANGLE/renderer/gl/WorkaroundsGL.h index 17d037d0c..4056dc687 100644 --- a/src/libANGLE/renderer/gl/WorkaroundsGL.h +++ b/src/libANGLE/renderer/gl/WorkaroundsGL.h @@ -24,7 +24,8 @@ struct WorkaroundsGL unpackOverlappingRowsSeparatelyUnpackBuffer(false), emulateAbsIntFunction(false), addAndTrueToLoopCondition(false), - emulateIsnanFloat(false) + emulateIsnanFloat(false), + useUnusedBlocksWithStandardOrSharedLayout(false) { } @@ -105,6 +106,11 @@ struct WorkaroundsGL // this bug, we use an expression to emulate function isnan(). // Tracking bug: http://crbug.com/650547 bool emulateIsnanFloat; + + // On Mac with OpenGL version 4.1, unused std140 or shared uniform blocks will be + // treated as inactive which is not consistent with WebGL2.0 spec. Reference all members in a + // unused std140 or shared uniform block at the beginning of main to work around it. + bool useUnusedBlocksWithStandardOrSharedLayout; }; } diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp index b9e6e53b1..15cf6271a 100644 --- a/src/libANGLE/renderer/gl/renderergl_utils.cpp +++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp @@ -897,6 +897,7 @@ void GenerateWorkarounds(const FunctionsGL *functions, WorkaroundsGL *workaround #if defined(ANGLE_PLATFORM_APPLE) workarounds->doWhileGLSLCausesGPUHang = true; + workarounds->useUnusedBlocksWithStandardOrSharedLayout = true; #endif workarounds->finishDoesNotCauseQueriesToBeAvailable = diff --git a/src/tests/gl_tests/UniformBufferTest.cpp b/src/tests/gl_tests/UniformBufferTest.cpp index 25719c115..d9985c0e8 100644 --- a/src/tests/gl_tests/UniformBufferTest.cpp +++ b/src/tests/gl_tests/UniformBufferTest.cpp @@ -5,6 +5,7 @@ // #include "test_utils/ANGLETest.h" +#include "test_utils/gl_raii.h" using namespace angle; @@ -396,6 +397,158 @@ TEST_P(UniformBufferTest, ActiveUniformNames) EXPECT_EQ("blockName.f", std::string(&strBuffer[0])); } +// Tests active uniforms and blocks when the layout is std140, shared and packed. +TEST_P(UniformBufferTest, ActiveUniformNumberAndName) +{ + // TODO(Jiajia): Figure out why this fails on Intel on Mac. + // This case can pass on Intel Mac-10.11/10.12. But it fails on Intel Mac-10.10. + if (IsIntel() && IsOSX()) + { + std::cout << "Test skipped on Intel on Mac." << std::endl; + return; + } + + if (IsAMD() && IsWindows()) + { + std::cout << "Test skipped on AMD on Win." << std::endl; + return; + } + + const std::string &vertexShaderSource = + "#version 300 es\n" + "in vec2 position;\n" + "out float v;\n" + "struct S {\n" + " highp ivec3 a;\n" + " mediump ivec2 b[4];\n" + "};\n" + "struct T {\n" + " S c[2];\n" + "};\n" + "layout(std140) uniform blockName0 {\n" + " S s0;\n" + " lowp vec2 v0;\n" + " T t0[2];\n" + " highp uint u0;\n" + "};\n" + "layout(std140) uniform blockName1 {\n" + " float f1;\n" + " bool b1;\n" + "} instanceName1;\n" + "layout(shared) uniform blockName2 {\n" + " float f2;\n" + "};\n" + "layout(packed) uniform blockName3 {\n" + " float f3;\n" + "};\n" + "void main() {\n" + " v = instanceName1.f1;\n" + " gl_Position = vec4(position, 0, 1);\n" + "}"; + + const std::string &fragmentShaderSource = + "#version 300 es\n" + "precision highp float;\n" + "in float v;\n" + "out vec4 color;\n" + "void main() {\n" + " color = vec4(v, 0, 0, 1);\n" + "}"; + + ANGLE_GL_PROGRAM(program, vertexShaderSource, fragmentShaderSource); + + GLint activeUniforms; + glGetProgramiv(program.get(), GL_ACTIVE_UNIFORMS, &activeUniforms); + + ASSERT_EQ(15, activeUniforms); + + GLint activeUniformBlocks; + glGetProgramiv(program.get(), GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks); + + ASSERT_EQ(3, activeUniformBlocks); + + GLint maxLength, size; + GLenum type; + GLsizei length; + glGetProgramiv(program.get(), GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLength); + std::vector strBuffer(maxLength + 1, 0); + + glGetActiveUniform(program.get(), 0, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("s0.a", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 1, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(4, size); + EXPECT_EQ("s0.b[0]", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 2, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("v0", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 3, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("t0[0].c[0].a", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 4, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(4, size); + EXPECT_EQ("t0[0].c[0].b[0]", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 5, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("t0[0].c[1].a", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 6, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(4, size); + EXPECT_EQ("t0[0].c[1].b[0]", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 7, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("t0[1].c[0].a", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 8, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(4, size); + EXPECT_EQ("t0[1].c[0].b[0]", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 9, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("t0[1].c[1].a", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 10, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(4, size); + EXPECT_EQ("t0[1].c[1].b[0]", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 11, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("u0", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 12, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("blockName1.f1", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 13, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("blockName1.b1", std::string(&strBuffer[0])); + + glGetActiveUniform(program.get(), 14, maxLength, &length, &size, &type, &strBuffer[0]); + ASSERT_GL_NO_ERROR(); + EXPECT_EQ(1, size); + EXPECT_EQ("f2", std::string(&strBuffer[0])); +} + // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against. ANGLE_INSTANTIATE_TEST(UniformBufferTest, ES3_D3D11(),