diff --git a/AUTHORS b/AUTHORS index a2ce91575..65c21aa24 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ TransGaming Inc. 3DLabs Inc. Ltd. Adobe Systems Inc. +Apple Inc. Autodesk, Inc. Cloud Party, Inc. Intel Corporation diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h index 1b8e32d54..967462cfb 100644 --- a/include/GLSLANG/ShaderLang.h +++ b/include/GLSLANG/ShaderLang.h @@ -151,7 +151,13 @@ typedef enum { SH_DEPENDENCY_GRAPH = 0x0400, // Enforce the GLSL 1.017 Appendix A section 7 packing restrictions. - SH_ENFORCE_PACKING_RESTRICTIONS = 0x0800 + SH_ENFORCE_PACKING_RESTRICTIONS = 0x0800, + + // This flag ensures all indirect (expression-based) array indexing + // is clamped to the bounds of the array. This ensures, for example, + // that you cannot read off the end of a uniform, whether an array + // vec234, or mat234 type. + SH_CLAMP_INDIRECT_ARRAY_BOUNDS = 0x1000 } ShCompileOptions; // diff --git a/src/build_angle.gypi b/src/build_angle.gypi index 0fd07968f..264048716 100644 --- a/src/build_angle.gypi +++ b/src/build_angle.gypi @@ -60,6 +60,8 @@ 'COMPILER_IMPLEMENTATION', ], 'sources': [ + 'compiler/ArrayBoundsClamper.cpp', + 'compiler/ArrayBoundsClamper.h', 'compiler/BaseTypes.h', 'compiler/BuiltInFunctionEmulator.cpp', 'compiler/BuiltInFunctionEmulator.h', diff --git a/src/compiler/ArrayBoundsClamper.cpp b/src/compiler/ArrayBoundsClamper.cpp new file mode 100644 index 000000000..6a317217c --- /dev/null +++ b/src/compiler/ArrayBoundsClamper.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "compiler/ArrayBoundsClamper.h" + +const char* kIntClampBegin = "// BEGIN: Generated code for array bounds clamping\n\n"; +const char* kIntClampEnd = "// END: Generated code for array bounds clamping\n\n"; +const char* kIntClampDefinition = "int webgl_int_clamp(int value, int minValue, int maxValue) { return ((value < minValue) ? minValue : ((value > maxValue) ? maxValue : value)); }\n\n"; + +namespace { + +class ArrayBoundsClamperMarker : public TIntermTraverser { +public: + ArrayBoundsClamperMarker() + : mNeedsClamp(false) + { + } + + virtual bool visitBinary(Visit visit, TIntermBinary* node) + { + if (node->getOp() == EOpIndexIndirect) + { + TIntermTyped* left = node->getLeft(); + if (left->isArray() || left->isVector() || left->isMatrix()) + { + node->setAddIndexClamp(); + mNeedsClamp = true; + } + } + return true; + } + + bool GetNeedsClamp() { return mNeedsClamp; } + +private: + bool mNeedsClamp; +}; + +} // anonymous namespace + +ArrayBoundsClamper::ArrayBoundsClamper() + : mArrayBoundsClampDefinitionNeeded(false) +{ +} + +void ArrayBoundsClamper::OutputClampingFunctionDefinition(TInfoSinkBase& out) const +{ + if (!mArrayBoundsClampDefinitionNeeded) + { + return; + } + out << kIntClampBegin << kIntClampDefinition << kIntClampEnd; +} + +void ArrayBoundsClamper::MarkIndirectArrayBoundsForClamping(TIntermNode* root) +{ + ASSERT(root); + + ArrayBoundsClamperMarker clamper; + root->traverse(&clamper); + if (clamper.GetNeedsClamp()) + { + SetArrayBoundsClampDefinitionNeeded(); + } +} + diff --git a/src/compiler/ArrayBoundsClamper.h b/src/compiler/ArrayBoundsClamper.h new file mode 100644 index 000000000..d4a8d6994 --- /dev/null +++ b/src/compiler/ArrayBoundsClamper.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef COMPILER_ARRAY_BOUNDS_CLAMPER_H_ +#define COMPILER_ARRAY_BOUNDS_CLAMPER_H_ + +#include "GLSLANG/ShaderLang.h" + +#include "compiler/InfoSink.h" +#include "compiler/intermediate.h" + +class ArrayBoundsClamper { +public: + ArrayBoundsClamper(); + + // Output array clamp function source into the shader source. + void OutputClampingFunctionDefinition(TInfoSinkBase& out) const; + + // Marks nodes in the tree that index arrays indirectly as + // requiring clamping. + void MarkIndirectArrayBoundsForClamping(TIntermNode* root); + + void Cleanup() + { + mArrayBoundsClampDefinitionNeeded = false; + } + +private: + bool GetArrayBoundsClampDefinitionNeeded() const { return mArrayBoundsClampDefinitionNeeded; } + void SetArrayBoundsClampDefinitionNeeded() { mArrayBoundsClampDefinitionNeeded = true; } + + bool mArrayBoundsClampDefinitionNeeded; +}; + +#endif // COMPILER_ARRAY_BOUNDS_CLAMPER_H_ diff --git a/src/compiler/Compiler.cpp b/src/compiler/Compiler.cpp index ab12ac93e..dce8c6872 100644 --- a/src/compiler/Compiler.cpp +++ b/src/compiler/Compiler.cpp @@ -4,6 +4,7 @@ // found in the LICENSE file. // +#include "compiler/ArrayBoundsClamper.h" #include "compiler/BuiltInFunctionEmulator.h" #include "compiler/DetectRecursion.h" #include "compiler/ForLoopUnroll.h" @@ -192,6 +193,10 @@ bool TCompiler::compile(const char* const shaderStrings[], if (success && (compileOptions & SH_EMULATE_BUILT_IN_FUNCTIONS)) builtInFunctionEmulator.MarkBuiltInFunctionsForEmulation(root); + // Clamping uniform array bounds needs to happen after validateLimitations pass. + if (success && (compileOptions & SH_CLAMP_INDIRECT_ARRAY_BOUNDS)) + arrayBoundsClamper.MarkIndirectArrayBoundsForClamping(root); + // Call mapLongVariableNames() before collectAttribsUniforms() so in // collectAttribsUniforms() we already have the mapped symbol names and // we could composite mapped and original variable names. @@ -237,6 +242,7 @@ bool TCompiler::InitBuiltInSymbolTable(const ShBuiltInResources& resources) void TCompiler::clearResults() { + arrayBoundsClamper.Cleanup(); infoSink.info.erase(); infoSink.obj.erase(); infoSink.debug.erase(); @@ -353,3 +359,9 @@ const BuiltInFunctionEmulator& TCompiler::getBuiltInFunctionEmulator() const { return builtInFunctionEmulator; } + +const ArrayBoundsClamper& TCompiler::getArrayBoundsClamper() const +{ + return arrayBoundsClamper; +} + diff --git a/src/compiler/OutputGLSLBase.cpp b/src/compiler/OutputGLSLBase.cpp index ed86c686d..3c3c9deb1 100644 --- a/src/compiler/OutputGLSLBase.cpp +++ b/src/compiler/OutputGLSLBase.cpp @@ -212,9 +212,38 @@ bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary* node) break; case EOpIndexDirect: - case EOpIndexIndirect: writeTriplet(visit, NULL, "[", "]"); break; + case EOpIndexIndirect: + if (node->getAddIndexClamp()) + { + if (visit == InVisit) + { + out << "[webgl_int_clamp("; + } + else if (visit == PostVisit) + { + int maxSize; + TIntermTyped *left = node->getLeft(); + TType leftType = left->getType(); + + if (left->isArray()) + { + // The shader will fail validation if the array length is not > 0. + maxSize = leftType.getArraySize() - 1; + } + else + { + maxSize = leftType.getNominalSize() - 1; + } + out << ", 0, " << maxSize << ")]"; + } + } + else + { + writeTriplet(visit, NULL, "[", "]"); + } + break; case EOpIndexDirectStruct: if (visit == InVisit) { diff --git a/src/compiler/ShHandle.h b/src/compiler/ShHandle.h index d6aa90b15..9a6f8ddf2 100644 --- a/src/compiler/ShHandle.h +++ b/src/compiler/ShHandle.h @@ -16,6 +16,7 @@ #include "GLSLANG/ShaderLang.h" +#include "compiler/ArrayBoundsClamper.h" #include "compiler/BuiltInFunctionEmulator.h" #include "compiler/ExtensionBehavior.h" #include "compiler/HashNames.h" @@ -108,6 +109,7 @@ protected: // Get built-in extensions with default behavior. const TExtensionBehavior& getExtensionBehavior() const; + const ArrayBoundsClamper& getArrayBoundsClamper() const; const BuiltInFunctionEmulator& getBuiltInFunctionEmulator() const; private: @@ -122,6 +124,7 @@ private: // Built-in extensions with default behavior. TExtensionBehavior extensionBehavior; + ArrayBoundsClamper arrayBoundsClamper; BuiltInFunctionEmulator builtInFunctionEmulator; // Results of compilation. diff --git a/src/compiler/TranslatorESSL.cpp b/src/compiler/TranslatorESSL.cpp index f718f1ef8..a4f047a3a 100644 --- a/src/compiler/TranslatorESSL.cpp +++ b/src/compiler/TranslatorESSL.cpp @@ -22,6 +22,9 @@ void TranslatorESSL::translate(TIntermNode* root) { getBuiltInFunctionEmulator().OutputEmulatedFunctionDefinition( sink, getShaderType() == SH_FRAGMENT_SHADER); + // Write array bounds clamping emulation if needed. + getArrayBoundsClamper().OutputClampingFunctionDefinition(sink); + // Write translated shader. TOutputESSL outputESSL(sink, getHashFunction(), getNameMap(), getSymbolTable()); root->traverse(&outputESSL); diff --git a/src/compiler/TranslatorGLSL.cpp b/src/compiler/TranslatorGLSL.cpp index 1e3ff381e..5f3dbccb2 100644 --- a/src/compiler/TranslatorGLSL.cpp +++ b/src/compiler/TranslatorGLSL.cpp @@ -35,6 +35,9 @@ void TranslatorGLSL::translate(TIntermNode* root) { getBuiltInFunctionEmulator().OutputEmulatedFunctionDefinition( sink, false); + // Write array bounds clamping emulation if needed. + getArrayBoundsClamper().OutputClampingFunctionDefinition(sink); + // Write translated shader. TOutputGLSL outputGLSL(sink, getHashFunction(), getNameMap(), getSymbolTable()); root->traverse(&outputGLSL); diff --git a/src/compiler/intermOut.cpp b/src/compiler/intermOut.cpp index e83c7b72f..f48a049c6 100644 --- a/src/compiler/intermOut.cpp +++ b/src/compiler/intermOut.cpp @@ -42,7 +42,7 @@ TString TType::getCompleteString() const if (qualifier != EvqTemporary && qualifier != EvqGlobal) stream << getQualifierString() << " " << getPrecisionString() << " "; if (array) - stream << "array of "; + stream << "array[" << getArraySize() << "] of "; if (matrix) stream << size << "X" << size << " matrix of "; else if (size > 1) diff --git a/src/compiler/intermediate.h b/src/compiler/intermediate.h index 1dbeef01c..09454f6f5 100644 --- a/src/compiler/intermediate.h +++ b/src/compiler/intermediate.h @@ -395,7 +395,7 @@ protected: // class TIntermBinary : public TIntermOperator { public: - TIntermBinary(TOperator o) : TIntermOperator(o) {} + TIntermBinary(TOperator o) : TIntermOperator(o), addIndexClamp(false) {} virtual TIntermBinary* getAsBinaryNode() { return this; } virtual void traverse(TIntermTraverser*); @@ -406,9 +406,15 @@ public: TIntermTyped* getRight() const { return right; } bool promote(TInfoSink&); + void setAddIndexClamp() { addIndexClamp = true; } + bool getAddIndexClamp() { return addIndexClamp; } + protected: TIntermTyped* left; TIntermTyped* right; + + // If set to true, wrap any EOpIndexIndirect with a clamp to bounds. + bool addIndexClamp; }; // diff --git a/src/compiler/translator_common.vcxproj b/src/compiler/translator_common.vcxproj index 0f383db84..19d7a11b8 100644 --- a/src/compiler/translator_common.vcxproj +++ b/src/compiler/translator_common.vcxproj @@ -138,6 +138,7 @@ + @@ -225,6 +226,7 @@ + @@ -279,4 +281,4 @@ - \ No newline at end of file +