Bug 1290774 - Validate frag shader output types match draw buffer types. r=lsalzman
☠☠ backed out by d6cd114bf585 ☠ ☠
authorJeff Gilbert <jgilbert@mozilla.com>
Fri, 01 Feb 2019 03:33:02 +0000
changeset 456384 58fe5a3d3c403199b9ff87d2f5051fd6e0564a45
parent 456383 c01ab8f120245d7f3a8f690bc6b2bd4d99c49352
child 456385 e7930893f8f0c64d1bbad3d87f9c397ad9eb7fd0
push id19
push usermdeboer@mozilla.com
push dateFri, 01 Feb 2019 10:05:45 +0000
reviewerslsalzman
bugs1290774
milestone67.0a1
Bug 1290774 - Validate frag shader output types match draw buffer types. r=lsalzman MozReview-Commit-ID: HhGKDVWXR1A Differential Revision: https://phabricator.services.mozilla.com/D18302
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLProgram.h
dom/canvas/WebGLShader.cpp
dom/canvas/WebGLShader.h
dom/canvas/WebGLShaderValidator.cpp
dom/canvas/WebGLShaderValidator.h
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1514,16 +1514,17 @@ class WebGLContext : public nsICanvasRen
   uint32_t mGLMaxDrawBuffers = 0;
 
   uint32_t mGLMaxViewportDims[2];
 
  public:
   GLenum LastColorAttachmentEnum() const {
     return LOCAL_GL_COLOR_ATTACHMENT0 + mGLMaxColorAttachments - 1;
   }
+  const auto& GLMaxDrawBuffers() const { return mGLMaxDrawBuffers; }
 
   const decltype(mOptions)& Options() const { return mOptions; }
 
  protected:
   // Texture sizes are often not actually the GL values. Let's be explicit that
   // these are implementation limits.
   uint32_t mGLMaxTextureSize = 0;
   uint32_t mGLMaxCubeMapTextureSize = 0;
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -306,16 +306,60 @@ const webgl::CachedDrawFetchLimits* Vali
       // Technically we don't know that this will be updated yet, but we can
       // speculatively mark it.
       buffer->ResetLastUpdateFenceId();
     }
   }
 
   // -
 
+  const auto& fragOutputs = linkInfo->fragOutputs;
+  const auto fnValidateFragOutputType =
+      [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
+        const auto itr = fragOutputs.find(loc);
+        if (MOZ_UNLIKELY(itr == fragOutputs.end())) {
+          webgl->ErrorInvalidOperation(
+              "Program has no frag output at location %u, but"
+              " destination draw buffer has an attached"
+              " image.",
+              uint32_t(loc));
+          return false;
+        }
+
+        const auto& info = itr->second;
+        const auto& srcBaseType = info.baseType;
+        if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
+          const auto& srcStr = ToString(srcBaseType);
+          const auto& dstStr = ToString(dstBaseType);
+          webgl->ErrorInvalidOperation(
+              "Program frag output at location %u is type %s,"
+              " but destination draw buffer is type %s.",
+              uint32_t(loc), srcStr, dstStr);
+          return false;
+        }
+        return true;
+      };
+
+  const auto& fb = webgl->mBoundDrawFramebuffer;
+  if (fb) {
+    for (const auto& attach : fb->ColorDrawBuffers()) {
+      const auto i =
+          uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
+      const auto& imageInfo = attach->GetImageInfo();
+      if (!imageInfo) continue;
+      const auto& dstBaseType = imageInfo->mFormat->format->baseType;
+      if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
+    }
+  } else {
+    if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
+      return nullptr;
+  }
+
+  // -
+
   const auto fetchLimits = linkInfo->GetDrawFetchLimits();
   if (!fetchLimits) return nullptr;
 
   if (instanceCount > fetchLimits->maxInstances) {
     webgl->ErrorInvalidOperation(
         "Instance fetch requires %u, but attribs only"
         " supply %u.",
         instanceCount, uint32_t(fetchLimits->maxInstances));
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/RefPtr.h"
 #include "nsPrintfCString.h"
 #include "WebGLActiveInfo.h"
 #include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLShader.h"
+#include "WebGLShaderValidator.h"
 #include "WebGLTransformFeedback.h"
 #include "WebGLUniformLocation.h"
 #include "WebGLValidateStrings.h"
 #include "WebGLVertexArray.h"
 
 namespace mozilla {
 
 /* If `name`: "foo[3]"
@@ -156,16 +157,48 @@ webgl::UniformInfo::UniformInfo(WebGLAct
       mIsShadowSampler(IsShadowSampler(mActiveInfo->mElemType)) {
   if (mSamplerTexList) {
     mSamplerValues.assign(mActiveInfo->mElemCount, 0);
   }
 }
 
 //////////
 
+static webgl::TextureBaseType FragOutputBaseType(const GLenum type) {
+  switch (type) {
+    case LOCAL_GL_FLOAT:
+    case LOCAL_GL_FLOAT_VEC2:
+    case LOCAL_GL_FLOAT_VEC3:
+    case LOCAL_GL_FLOAT_VEC4:
+      return webgl::TextureBaseType::Float;
+
+    case LOCAL_GL_INT:
+    case LOCAL_GL_INT_VEC2:
+    case LOCAL_GL_INT_VEC3:
+    case LOCAL_GL_INT_VEC4:
+      return webgl::TextureBaseType::Int;
+
+    case LOCAL_GL_UNSIGNED_INT:
+    case LOCAL_GL_UNSIGNED_INT_VEC2:
+    case LOCAL_GL_UNSIGNED_INT_VEC3:
+    case LOCAL_GL_UNSIGNED_INT_VEC4:
+      return webgl::TextureBaseType::UInt;
+
+    default:
+      break;
+  }
+
+  const auto& str = EnumString(type);
+  gfxCriticalError() << "Unhandled enum for FragOutputBaseType: "
+                     << str.c_str();
+  return webgl::TextureBaseType::Float;
+}
+
+// -
+
 //#define DUMP_SHADERVAR_MAPPINGS
 
 static RefPtr<const webgl::LinkedProgramInfo> QueryProgramInfo(
     WebGLProgram* prog, gl::GLContext* gl) {
   WebGLContext* const webgl = prog->mContext;
 
   RefPtr<webgl::LinkedProgramInfo> info(new webgl::LinkedProgramInfo(prog));
 
@@ -433,17 +466,76 @@ static RefPtr<const webgl::LinkedProgram
       const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(
           webgl, elemCount, elemType, isArray, baseUserName, mappedName);
       info->transformFeedbackVaryings.push_back(activeInfo);
     }
   }
 
   // Frag outputs
 
-  prog->EnumerateFragOutputs(info->fragDataMap);
+  {
+    const auto& fragShader = prog->FragShader();
+    const auto& handle = fragShader->Validator()->Handle();
+    const auto version = sh::GetShaderVersion(handle);
+
+    const auto fnAddInfo = [&](const webgl::FragOutputInfo& x) {
+      info->fragOutputs.insert({x.loc, x});
+    };
+
+    if (version == 300) {
+      const auto& fragOutputs = sh::GetOutputVariables(handle);
+      if (fragOutputs) {
+        for (const auto& cur : *fragOutputs) {
+          auto loc = cur.location;
+          if (loc == -1) loc = 0;
+
+          const auto info = webgl::FragOutputInfo{
+              uint8_t(loc), nsCString(cur.name.c_str()),
+              nsCString(cur.mappedName.c_str()), FragOutputBaseType(cur.type)};
+          if (!cur.isArray()) {
+            fnAddInfo(info);
+            continue;
+          }
+          MOZ_ASSERT(cur.arraySizes.size() == 1);
+          for (uint32_t i = 0; i < cur.arraySizes[0]; ++i) {
+            const auto indexStr = nsPrintfCString("[%u]", i);
+
+            auto userName = info.userName;
+            userName.Append(indexStr);
+            auto mappedName = info.mappedName;
+            mappedName.Append(indexStr);
+
+            const auto indexedInfo = webgl::FragOutputInfo{
+                uint8_t(info.loc + i), userName, mappedName, info.baseType};
+            fnAddInfo(indexedInfo);
+          }
+        }
+      }
+    } else {
+      // ANGLE's translator doesn't tell us about non-user frag outputs. :(
+
+      const auto& translatedSource = fragShader->TranslatedSource();
+      uint32_t drawBuffers = 1;
+      if (translatedSource.Find("(gl_FragData[1]") != -1 ||
+          translatedSource.Find("(webgl_FragData[1]") != -1) {
+        // The matching with the leading '(' prevents cleverly-named user vars breaking this.
+        // Since ANGLE initializes all outputs, if this is an MRT shader,
+        // FragData[1] will be present. FragData[0] is valid for non-MRT
+        // shaders.
+        drawBuffers = webgl->GLMaxDrawBuffers();
+      }
+
+      for (uint32_t i = 0; i < drawBuffers; ++i) {
+        const auto& name = nsPrintfCString("gl_FragData[%u]", i);
+        const auto info = webgl::FragOutputInfo{uint8_t(i), name, name,
+                                                webgl::TextureBaseType::Float};
+        fnAddInfo(info);
+      }
+    }
+  }
 
   return info;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog)
     : prog(prog),
@@ -744,53 +836,31 @@ GLint WebGLProgram::GetAttribLocation(co
   const NS_LossyConvertUTF16toASCII userName(userName_wide);
 
   const webgl::AttribInfo* info;
   if (!LinkInfo()->FindAttrib(userName, &info)) return -1;
 
   return GLint(info->mLoc);
 }
 
-static GLint GetFragDataByUserName(const WebGLProgram* prog,
-                                   const nsCString& userName) {
-  nsCString mappedName;
-  if (!prog->LinkInfo()->MapFragDataName(userName, &mappedName)) return -1;
-
-  return prog->mContext->gl->fGetFragDataLocation(prog->mGLName,
-                                                  mappedName.BeginReading());
-}
-
 GLint WebGLProgram::GetFragDataLocation(const nsAString& userName_wide) const {
   if (!ValidateGLSLVariableName(userName_wide, mContext)) return -1;
 
   if (!IsLinked()) {
     mContext->ErrorInvalidOperation("`program` must be linked.");
     return -1;
   }
 
   const NS_LossyConvertUTF16toASCII userName(userName_wide);
-#ifdef XP_MACOSX
-  const auto& gl = mContext->gl;
-  if (gl->WorkAroundDriverBugs()) {
-    // OSX doesn't return locs for indexed names, just the base names.
-    // Indicated by failure in:
-    // conformance2/programs/gl-get-frag-data-location.html
-    bool isArray;
-    size_t arrayIndex;
-    nsCString baseUserName;
-    if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex)) return -1;
-
-    if (arrayIndex >= mContext->mGLMaxDrawBuffers) return -1;
-
-    const auto baseLoc = GetFragDataByUserName(this, baseUserName);
-    const auto loc = baseLoc + GLint(arrayIndex);
-    return loc;
+  const auto& fragOutputs = LinkInfo()->fragOutputs;
+  for (const auto& pair : fragOutputs) {
+    const auto& info = pair.second;
+    if (info.userName == userName) return info.loc;
   }
-#endif
-  return GetFragDataByUserName(this, userName);
+  return -1;
 }
 
 void WebGLProgram::GetProgramInfoLog(nsAString* const out) const {
   CopyASCIItoUTF16(mLinkLog, *out);
 }
 
 static GLint GetProgramiv(gl::GLContext* gl, GLuint program, GLenum pname) {
   GLint ret = 0;
@@ -1539,23 +1609,16 @@ bool WebGLProgram::UnmapUniformBlockName
       !mFragShader->UnmapUniformBlockName(baseMappedName, &baseUserName)) {
     return false;
   }
 
   AssembleName(baseUserName, isArray, arrayIndex, out_userName);
   return true;
 }
 
-void WebGLProgram::EnumerateFragOutputs(
-    std::map<nsCString, const nsCString>& out_FragOutputs) const {
-  MOZ_ASSERT(mFragShader);
-
-  mFragShader->EnumerateFragOutputs(out_FragOutputs);
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 
 bool IsBaseName(const nsCString& name) {
   if (!name.Length()) return true;
 
   return name[name.Length() - 1] != ']';  // Doesn't end in ']'.
 }
 
@@ -1593,39 +1656,16 @@ bool webgl::LinkedProgramInfo::FindUnifo
   const auto& baseMappedName = info->mActiveInfo->mBaseMappedName;
   AssembleName(baseMappedName, isArray, arrayIndex, out_mappedName);
 
   *out_arrayIndex = arrayIndex;
   *out_info = info;
   return true;
 }
 
-bool webgl::LinkedProgramInfo::MapFragDataName(
-    const nsCString& userName, nsCString* const out_mappedName) const {
-  // FS outputs can be arrays, but not structures.
-
-  if (fragDataMap.empty()) {
-    // No mappings map from validation, so just forward it.
-    *out_mappedName = userName;
-    return true;
-  }
-
-  nsCString baseUserName;
-  bool isArray;
-  size_t arrayIndex;
-  if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex)) return false;
-
-  const auto itr = fragDataMap.find(baseUserName);
-  if (itr == fragDataMap.end()) return false;
-
-  const auto& baseMappedName = itr->second;
-  AssembleName(baseMappedName, isArray, arrayIndex, out_mappedName);
-  return true;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 
 JSObject* WebGLProgram::WrapObject(JSContext* js,
                                    JS::Handle<JSObject*> givenProto) {
   return dom::WebGLProgram_Binding::Wrap(js, this, givenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgram, mVertShader, mFragShader)
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -72,16 +72,23 @@ struct UniformBlockInfo final {
   UniformBlockInfo(WebGLContext* webgl, const nsACString& userName,
                    const nsACString& mappedName, uint32_t dataSize)
       : mUserName(userName),
         mMappedName(mappedName),
         mDataSize(dataSize),
         mBinding(&webgl->mIndexedUniformBufferBindings[0]) {}
 };
 
+struct FragOutputInfo final {
+  const uint8_t loc;
+  const nsCString userName;
+  const nsCString mappedName;
+  const TextureBaseType baseType;
+};
+
 struct CachedDrawFetchLimits final {
   uint64_t maxVerts;
   uint64_t maxInstances;
 };
 
 struct LinkedProgramInfo final : public RefCounted<LinkedProgramInfo>,
                                  public SupportsWeakPtr<LinkedProgramInfo>,
                                  public CacheInvalidator {
@@ -94,48 +101,42 @@ struct LinkedProgramInfo final : public 
 
   WebGLProgram* const prog;
   const GLenum transformFeedbackBufferMode;
 
   std::vector<AttribInfo> attribs;
   std::vector<UniformInfo*> uniforms;            // Owns its contents.
   std::vector<UniformBlockInfo*> uniformBlocks;  // Owns its contents.
   std::vector<RefPtr<WebGLActiveInfo>> transformFeedbackVaryings;
+  std::unordered_map<uint8_t, const FragOutputInfo> fragOutputs;
 
   // Needed for draw call validation.
   std::vector<UniformInfo*> uniformSamplers;
 
   mutable std::vector<size_t> componentsPerTFVert;
 
   bool attrib0Active;
 
   //////
 
-  // The maps for the frag data names to the translated names.
-  std::map<nsCString, const nsCString> fragDataMap;
-
-  //////
-
   mutable CacheWeakMap<const WebGLVertexArray*, CachedDrawFetchLimits>
       mDrawFetchCache;
 
   const CachedDrawFetchLimits* GetDrawFetchLimits() const;
 
   //////
 
   explicit LinkedProgramInfo(WebGLProgram* prog);
   ~LinkedProgramInfo();
 
   bool FindAttrib(const nsCString& userName,
                   const AttribInfo** const out_info) const;
   bool FindUniform(const nsCString& userName, nsCString* const out_mappedName,
                    size_t* const out_arrayIndex,
                    UniformInfo** const out_info) const;
-  bool MapFragDataName(const nsCString& userName,
-                       nsCString* const out_mappedName) const;
 };
 
 }  // namespace webgl
 
 class WebGLProgram final : public nsWrapperCache,
                            public WebGLRefCountedObject<WebGLProgram>,
                            public LinkedListElement<WebGLProgram> {
   friend class WebGLTransformFeedback;
@@ -201,16 +202,18 @@ class WebGLProgram final : public nsWrap
       std::map<nsCString, const nsCString>& out_FragOutputs) const;
 
   bool IsLinked() const { return mMostRecentLinkInfo; }
 
   const webgl::LinkedProgramInfo* LinkInfo() const {
     return mMostRecentLinkInfo.get();
   }
 
+  const auto& FragShader() const { return mFragShader; }
+
   WebGLContext* GetParentObject() const { return mContext; }
 
   virtual JSObject* WrapObject(JSContext* js,
                                JS::Handle<JSObject*> givenProto) override;
 
  private:
   ~WebGLProgram();
 
--- a/dom/canvas/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -341,26 +341,16 @@ bool WebGLShader::UnmapUniformBlockName(
   if (!mValidator) {
     *out_baseUserName = baseMappedName;
     return true;
   }
 
   return mValidator->UnmapUniformBlockName(baseMappedName, out_baseUserName);
 }
 
-void WebGLShader::EnumerateFragOutputs(
-    std::map<nsCString, const nsCString>& out_FragOutputs) const {
-  out_FragOutputs.clear();
-
-  if (!mValidator) {
-    return;
-  }
-  mValidator->EnumerateFragOutputs(out_FragOutputs);
-}
-
 void WebGLShader::MapTransformFeedbackVaryings(
     const std::vector<nsString>& varyings,
     std::vector<std::string>* out_mappedVaryings) const {
   MOZ_ASSERT(mType == LOCAL_GL_VERTEX_SHADER);
   MOZ_ASSERT(out_mappedVaryings);
 
   out_mappedVaryings->clear();
   out_mappedVaryings->reserve(varyings.size());
--- a/dom/canvas/WebGLShader.h
+++ b/dom/canvas/WebGLShader.h
@@ -55,22 +55,21 @@ class WebGLShader final : public nsWrapp
                                nsCString* const out_userName,
                                bool* const out_isArray) const;
   bool FindUniformByMappedName(const nsACString& mappedName,
                                nsCString* const out_userName,
                                bool* const out_isArray) const;
   bool UnmapUniformBlockName(const nsACString& baseMappedName,
                              nsCString* const out_baseUserName) const;
 
-  void EnumerateFragOutputs(
-      std::map<nsCString, const nsCString>& out_FragOutputs) const;
-
   bool IsCompiled() const {
     return mTranslationSuccessful && mCompilationSuccessful;
   }
+  const auto* Validator() const { return mValidator.get(); }
+  const auto& TranslatedSource() const { return mTranslatedSource; }
 
  private:
   void BindAttribLocation(GLuint prog, const nsCString& userName,
                           GLuint index) const;
   void MapTransformFeedbackVaryings(
       const std::vector<nsString>& varyings,
       std::vector<std::string>* out_mappedVaryings) const;
 
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -567,22 +567,10 @@ bool ShaderValidator::UnmapUniformBlockN
       *out_baseUserName = interface.name.data();
       return true;
     }
   }
 
   return false;
 }
 
-void ShaderValidator::EnumerateFragOutputs(
-    std::map<nsCString, const nsCString>& out_FragOutputs) const {
-  const auto* fragOutputs = sh::GetOutputVariables(mHandle);
-
-  if (fragOutputs) {
-    for (const auto& fragOutput : *fragOutputs) {
-      out_FragOutputs.insert({nsCString(fragOutput.name.c_str()),
-                              nsCString(fragOutput.mappedName.c_str())});
-    }
-  }
-}
-
 }  // namespace webgl
 }  // namespace mozilla
--- a/dom/canvas/WebGLShaderValidator.h
+++ b/dom/canvas/WebGLShaderValidator.h
@@ -1,20 +1,21 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WEBGL_SHADER_VALIDATOR_H_
 #define WEBGL_SHADER_VALIDATOR_H_
 
-#include "GLSLANG/ShaderLang.h"
+#include <string>
+
 #include "GLDefs.h"
+#include "GLSLANG/ShaderLang.h"
 #include "nsString.h"
-#include <string>
 
 namespace mozilla {
 namespace webgl {
 
 class ShaderValidator final {
   const ShHandle mHandle;
   const ShCompileOptions mCompileOptions;
   const int mMaxVaryingVectors;
@@ -38,16 +39,17 @@ class ShaderValidator final {
   ~ShaderValidator();
 
   bool ValidateAndTranslate(const char* source);
   void GetInfoLog(nsACString* out) const;
   void GetOutput(nsACString* out) const;
   bool CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const;
   size_t CalcNumSamplerUniforms() const;
   size_t NumAttributes() const;
+  const auto& Handle() const { return mHandle; }
 
   bool FindAttribUserNameByMappedName(
       const std::string& mappedName,
       const std::string** const out_userName) const;
 
   bool FindAttribMappedNameByUserName(
       const std::string& userName,
       const std::string** const out_mappedName) const;
@@ -60,19 +62,16 @@ class ShaderValidator final {
                                std::string* const out_userName,
                                bool* const out_isArray) const;
   bool FindUniformByMappedName(const std::string& mappedName,
                                std::string* const out_userName,
                                bool* const out_isArray) const;
   bool UnmapUniformBlockName(const nsACString& baseMappedName,
                              nsCString* const out_baseUserName) const;
 
-  void EnumerateFragOutputs(
-      std::map<nsCString, const nsCString>& out_FragOutputs) const;
-
   bool ValidateTransformFeedback(
       const std::vector<nsString>& userNames, uint32_t maxComponents,
       nsCString* const out_errorText,
       std::vector<std::string>* const out_mappedNames);
 };
 
 }  // namespace webgl
 }  // namespace mozilla