Centralize renderer limitations for non-conformant renderers

Some renderer configurations (e.g. D3D11 Feature Level 9_3) have
some limitations and aren't quite conformant. This change
generates errors when applications hit these limitations, and
informs developers that they must work around them.

BUG=angleproject:1055

Change-Id: I6a4a9e5cc71288ca366a54c769ca0eb82e79a7f7
Reviewed-on: https://chromium-review.googlesource.com/282814
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp
index ddda278..b413578 100644
--- a/src/libANGLE/Caps.cpp
+++ b/src/libANGLE/Caps.cpp
@@ -193,6 +193,13 @@
     return extensionStrings;
 }
 
+Limitations::Limitations()
+    : noFrontFacingSupport(false),
+      noSampleAlphaToCoverageSupport(false),
+      attributeZeroRequiresZeroDivisorInEXT(false)
+{
+}
+
 static bool GetFormatSupport(const TextureCapsMap &textureCaps, const std::vector<GLenum> &requiredFormats,
                              bool requiresTexturing, bool requiresFiltering, bool requiresRendering)
 {
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index 85e6260..b700d00 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -225,6 +225,22 @@
     bool colorBufferFloat;
 };
 
+struct Limitations
+{
+    Limitations();
+
+    // Renderer doesn't support gl_FrontFacing in fragment shaders
+    bool noFrontFacingSupport;
+
+    // Renderer doesn't support GL_SAMPLE_ALPHA_TO_COVERAGE
+    bool noSampleAlphaToCoverageSupport;
+
+    // In glVertexAttribDivisorANGLE, attribute zero must have a zero divisor
+    bool attributeZeroRequiresZeroDivisorInEXT;
+
+    // TODO: add entry for renderers that don't support separate stencil masks/refs
+};
+
 struct TypePrecision
 {
     TypePrecision();
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index b21c7ee..2a24990 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -1389,6 +1389,11 @@
     return mExtensions;
 }
 
+const Limitations &Context::getLimitations() const
+{
+    return mLimitations;
+}
+
 void Context::detachTexture(GLuint texture)
 {
     // Simple pass-through to State's detachTexture method, as textures do not require
@@ -1605,6 +1610,8 @@
 
     mExtensions = mRenderer->getRendererExtensions();
 
+    mLimitations = mRenderer->getRendererLimitations();
+
     if (clientVersion < 3)
     {
         // Disable ES3+ extensions
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index e7c8515..367a922 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -190,6 +190,7 @@
     const Caps &getCaps() const;
     const TextureCapsMap &getTextureCaps() const;
     const Extensions &getExtensions() const;
+    const Limitations &getLimitations() const;
 
     const std::string &getRendererString() const;
 
@@ -222,6 +223,7 @@
     Caps mCaps;
     TextureCapsMap mTextureCaps;
     Extensions mExtensions;
+    Limitations mLimitations;
 
     // Shader compiler
     Compiler *mCompiler;
diff --git a/src/libANGLE/renderer/Renderer.cpp b/src/libANGLE/renderer/Renderer.cpp
index fbc2ad5..0370161 100644
--- a/src/libANGLE/renderer/Renderer.cpp
+++ b/src/libANGLE/renderer/Renderer.cpp
@@ -25,39 +25,43 @@
 {
 }
 
-const gl::Caps &Renderer::getRendererCaps() const
+void Renderer::ensureCapsInitialized() const
 {
     if (!mCapsInitialized)
     {
-        generateCaps(&mCaps, &mTextureCaps, &mExtensions);
+        generateCaps(&mCaps, &mTextureCaps, &mExtensions, &mLimitations);
         mCapsInitialized = true;
     }
+}
+
+const gl::Caps &Renderer::getRendererCaps() const
+{
+    ensureCapsInitialized();
 
     return mCaps;
 }
 
 const gl::TextureCapsMap &Renderer::getRendererTextureCaps() const
 {
-    if (!mCapsInitialized)
-    {
-        generateCaps(&mCaps, &mTextureCaps, &mExtensions);
-        mCapsInitialized = true;
-    }
+    ensureCapsInitialized();
 
     return mTextureCaps;
 }
 
 const gl::Extensions &Renderer::getRendererExtensions() const
 {
-    if (!mCapsInitialized)
-    {
-        generateCaps(&mCaps, &mTextureCaps, &mExtensions);
-        mCapsInitialized = true;
-    }
+    ensureCapsInitialized();
 
     return mExtensions;
 }
 
+const gl::Limitations &Renderer::getRendererLimitations() const
+{
+    ensureCapsInitialized();
+
+    return mLimitations;
+}
+
 const Workarounds &Renderer::getWorkarounds() const
 {
     if (!mWorkaroundsInitialized)
diff --git a/src/libANGLE/renderer/Renderer.h b/src/libANGLE/renderer/Renderer.h
index 0fae014..1359688 100644
--- a/src/libANGLE/renderer/Renderer.h
+++ b/src/libANGLE/renderer/Renderer.h
@@ -77,16 +77,21 @@
     const gl::Caps &getRendererCaps() const;
     const gl::TextureCapsMap &getRendererTextureCaps() const;
     const gl::Extensions &getRendererExtensions() const;
+    const gl::Limitations &getRendererLimitations() const;
     const Workarounds &getWorkarounds() const;
 
   private:
-    virtual void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap* outTextureCaps, gl::Extensions *outExtensions) const = 0;
+    void ensureCapsInitialized() const;
+    virtual void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap* outTextureCaps,
+                              gl::Extensions *outExtensions,
+                              gl::Limitations *outLimitations) const = 0;
     virtual Workarounds generateWorkarounds() const = 0;
 
     mutable bool mCapsInitialized;
     mutable gl::Caps mCaps;
     mutable gl::TextureCapsMap mTextureCaps;
     mutable gl::Extensions mExtensions;
+    mutable gl::Limitations mLimitations;
 
     mutable bool mWorkaroundsInitialized;
     mutable Workarounds mWorkarounds;
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index edbe3d5..0059516 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -1067,6 +1067,15 @@
     vertexShaderD3D->generateWorkarounds(&mVertexWorkarounds);
     mShaderVersion = vertexShaderD3D->getShaderVersion();
 
+    if (mRenderer->getRendererLimitations().noFrontFacingSupport)
+    {
+        if (fragmentShaderD3D->usesFrontFacing())
+        {
+            infoLog << "The current renderer doesn't support gl_FrontFacing";
+            return LinkResult(false, gl::Error(GL_NO_ERROR));
+        }
+    }
+
     // Map the varyings to the register file
     VaryingPacking packing = {};
     *registers = mDynamicHLSL->packVaryings(infoLog, packing, fragmentShaderD3D, vertexShaderD3D, transformFeedbackVaryings);
diff --git a/src/libANGLE/renderer/d3d/ShaderD3D.h b/src/libANGLE/renderer/d3d/ShaderD3D.h
index 5d9fc8b..0838ebe 100644
--- a/src/libANGLE/renderer/d3d/ShaderD3D.h
+++ b/src/libANGLE/renderer/d3d/ShaderD3D.h
@@ -43,6 +43,7 @@
     bool usesDepthRange() const { return mUsesDepthRange; }
     bool usesPointSize() const { return mUsesPointSize; }
     bool usesDeferredInit() const { return mUsesDeferredInit; }
+    bool usesFrontFacing() const { return mUsesFrontFacing; }
 
     GLenum getShaderType() const;
     ShShaderOutput getCompilerOutputType() const;
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 3a84cab..04f099d 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -3748,9 +3748,11 @@
     return d3d11::GetDXGIFormatInfo(d3d11::GetVertexFormatInfo(vertexFormatType, mRenderer11DeviceCaps.featureLevel).nativeFormat).componentType;
 }
 
-void Renderer11::generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps, gl::Extensions *outExtensions) const
+void Renderer11::generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps,
+                              gl::Extensions *outExtensions, gl::Limitations *outLimitations) const
 {
-    d3d11_gl::GenerateCaps(mDevice, mDeviceContext, mRenderer11DeviceCaps, outCaps, outTextureCaps, outExtensions);
+    d3d11_gl::GenerateCaps(mDevice, mDeviceContext, mRenderer11DeviceCaps, outCaps, outTextureCaps,
+                           outExtensions, outLimitations);
 }
 
 Workarounds Renderer11::generateWorkarounds() const
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
index 6ded0a0..d33c643 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
@@ -273,7 +273,10 @@
     gl::Error clearTextures(gl::SamplerType samplerType, size_t rangeStart, size_t rangeEnd) override;
 
   private:
-    void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps, gl::Extensions *outExtensions) const override;
+    void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps,
+                      gl::Extensions *outExtensions,
+                      gl::Limitations *outLimitations) const override;
+
     Workarounds generateWorkarounds() const override;
 
     gl::Error drawLineLoop(GLsizei count, GLenum type, const GLvoid *indices, int minIndex, gl::Buffer *elementArrayBuffer);
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
index a863977..78d150d 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
@@ -1004,7 +1004,8 @@
     }
 }
 
-void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, const Renderer11DeviceCaps &renderer11DeviceCaps, gl::Caps *caps, gl::TextureCapsMap *textureCapsMap, gl::Extensions *extensions)
+void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, const Renderer11DeviceCaps &renderer11DeviceCaps, gl::Caps *caps,
+                  gl::TextureCapsMap *textureCapsMap, gl::Extensions *extensions, gl::Limitations *limitations)
 {
     GLuint maxSamples = 0;
     D3D_FEATURE_LEVEL featureLevel = renderer11DeviceCaps.featureLevel;
@@ -1152,6 +1153,20 @@
     extensions->translatedShaderSource = true;
     extensions->fboRenderMipmap = false;
     extensions->debugMarker = true;
+
+    // D3D11 Feature Level 10_0+ uses SV_IsFrontFace in HLSL to emulate gl_FrontFacing.
+    // D3D11 Feature Level 9_3 doesn't support SV_IsFrontFace, and has no equivalent, so can't support gl_FrontFacing.
+    limitations->noFrontFacingSupport = (renderer11DeviceCaps.featureLevel <= D3D_FEATURE_LEVEL_9_3);
+
+    // D3D11 Feature Level 9_3 doesn't support alpha-to-coverage
+    limitations->noSampleAlphaToCoverageSupport = (renderer11DeviceCaps.featureLevel <= D3D_FEATURE_LEVEL_9_3);
+
+#ifdef ANGLE_ENABLE_WINDOWS_STORE
+    // Setting a non-zero divisor on attribute zero doesn't work on certain Windows Phone 8-era devices.
+    // We should prevent developers from doing this on ALL Windows Store devices. This will maintain consistency across all Windows devices.
+    // We allow non-zero divisors on attribute zero if the Client Version >= 3, since devices affected by this issue don't support ES3+.
+    limitations->attributeZeroRequiresZeroDivisorInEXT = true;
+#endif
 }
 
 }
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
index 376741f..108e84c 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
@@ -53,7 +53,8 @@
 {
 
 GLint GetMaximumClientVersion(D3D_FEATURE_LEVEL featureLevel);
-void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, const Renderer11DeviceCaps &renderer11DeviceCaps, gl::Caps *caps, gl::TextureCapsMap *textureCapsMap, gl::Extensions *extensions);
+void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, const Renderer11DeviceCaps &renderer11DeviceCaps, gl::Caps *caps,
+                  gl::TextureCapsMap *textureCapsMap, gl::Extensions *extensions, gl::Limitations *limitations);
 
 }
 
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
index e46080f..dad178e 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
@@ -2935,9 +2935,12 @@
     return d3d9::GetVertexFormatInfo(getCapsDeclTypes(), vertexFormatType).componentType;
 }
 
-void Renderer9::generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps, gl::Extensions *outExtensions) const
+void Renderer9::generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps,
+                             gl::Extensions *outExtensions,
+                             gl::Limitations * /*outLimitations */) const
 {
-    d3d9_gl::GenerateCaps(mD3d9, mDevice, mDeviceType, mAdapter, outCaps, outTextureCaps, outExtensions);
+    d3d9_gl::GenerateCaps(mD3d9, mDevice, mDeviceType, mAdapter, outCaps,
+                          outTextureCaps, outExtensions);
 }
 
 Workarounds Renderer9::generateWorkarounds() const
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
index 2883774..9d2e9d8 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
@@ -241,7 +241,10 @@
     gl::Error clearTextures(gl::SamplerType samplerType, size_t rangeStart, size_t rangeEnd) override;
 
   private:
-    void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps, gl::Extensions *outExtensions) const override;
+    void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap *outTextureCaps,
+                      gl::Extensions *outExtensions,
+                      gl::Limitations *outLimitations) const override;
+
     Workarounds generateWorkarounds() const override;
 
     void release();
diff --git a/src/libANGLE/renderer/gl/RendererGL.cpp b/src/libANGLE/renderer/gl/RendererGL.cpp
index 9b410ac..5b6d1f6 100644
--- a/src/libANGLE/renderer/gl/RendererGL.cpp
+++ b/src/libANGLE/renderer/gl/RendererGL.cpp
@@ -303,7 +303,9 @@
     return mMaxSupportedESVersion;
 }
 
-void RendererGL::generateCaps(gl::Caps *outCaps, gl::TextureCapsMap* outTextureCaps, gl::Extensions *outExtensions) const
+void RendererGL::generateCaps(gl::Caps *outCaps, gl::TextureCapsMap* outTextureCaps,
+                              gl::Extensions *outExtensions,
+                              gl::Limitations * /* outLimitations */) const
 {
     nativegl_gl::GenerateCaps(mFunctions, outCaps, outTextureCaps, outExtensions, &mMaxSupportedESVersion);
 }
diff --git a/src/libANGLE/renderer/gl/RendererGL.h b/src/libANGLE/renderer/gl/RendererGL.h
index 6d2b97e..c08a976 100644
--- a/src/libANGLE/renderer/gl/RendererGL.h
+++ b/src/libANGLE/renderer/gl/RendererGL.h
@@ -79,7 +79,10 @@
     const gl::Version &getMaxSupportedESVersion() const;
 
   private:
-    void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap* outTextureCaps, gl::Extensions *outExtensions) const override;
+    void generateCaps(gl::Caps *outCaps, gl::TextureCapsMap* outTextureCaps,
+                      gl::Extensions *outExtensions,
+                      gl::Limitations *outLimitations) const override;
+
     Workarounds generateWorkarounds() const override;
 
     mutable gl::Version mMaxSupportedESVersion;
diff --git a/src/libGLESv2/entry_points_gles_2_0.cpp b/src/libGLESv2/entry_points_gles_2_0.cpp
index ff480a4..2773de5 100644
--- a/src/libGLESv2/entry_points_gles_2_0.cpp
+++ b/src/libGLESv2/entry_points_gles_2_0.cpp
@@ -1264,6 +1264,20 @@
             return;
         }
 
+        if (context->getLimitations().noSampleAlphaToCoverageSupport)
+        {
+            if (cap == GL_SAMPLE_ALPHA_TO_COVERAGE)
+            {
+                const char *errorMessage = "Current renderer doesn't support alpha-to-coverage";
+                context->recordError(Error(GL_INVALID_OPERATION, errorMessage));
+
+                // We also output an error message to the debugger window if tracing is active, so that developers can see the error message.
+                ERR("%s", errorMessage);
+
+                return;
+            }
+        }
+
         context->getState().setEnableFeature(cap, true);
     }
 }
diff --git a/src/libGLESv2/entry_points_gles_2_0_ext.cpp b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
index 1379a33..1c2da75 100644
--- a/src/libGLESv2/entry_points_gles_2_0_ext.cpp
+++ b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
@@ -644,6 +644,21 @@
             return;
         }
 
+        if (context->getLimitations().attributeZeroRequiresZeroDivisorInEXT)
+        {
+            if (index == 0 && divisor != 0)
+            {
+                const char *errorMessage = "The current context doesn't support setting a non-zero divisor on the attribute with index zero. "
+                                           "Please reorder the attributes in your vertex shader so that attribute zero can have a zero divisor.";
+                context->recordError(Error(GL_INVALID_OPERATION, errorMessage));
+
+                // We also output an error message to the debugger window if tracing is active, so that developers can see the error message.
+                ERR("%s", errorMessage);
+
+                return;
+            }
+        }
+
         context->setVertexAttribDivisor(index, divisor);
     }
 }
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 0d579e5..470f20d 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -553,15 +553,6 @@
 {
     EGLPlatformParameters platform = GetParam().eglParameters;
 
-    // Disable this test on D3D11 feature level 9_3, since gl_FrontFacing isn't supported.
-    if (platform.renderer == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE)
-    {
-        if (platform.majorVersion == 9 && platform.minorVersion == 3)
-        {
-            return;
-        }
-    }
-
     const std::string vertexShaderSource = SHADER_SOURCE
     (
         attribute vec4 a_position;
@@ -594,6 +585,18 @@
     );
 
     GLuint program = CompileProgram(vertexShaderSource, fragmentShaderSource);
+
+    // Compilation should fail on D3D11 feature level 9_3, since gl_FrontFacing isn't supported.
+    if (platform.renderer == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE)
+    {
+        if (platform.majorVersion == 9 && platform.minorVersion == 3)
+        {
+            EXPECT_EQ(0u, program);
+            return;
+        }
+    }
+
+    // Otherwise, compilation should succeed
     EXPECT_NE(0u, program);
 }