Blob Blame History Raw
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */

#include "WebGLProgram.h"

#include "GLContext.h"
#include "mozilla/CheckedInt.h"
#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 "WebGLTransformFeedback.h"
#include "WebGLUniformLocation.h"
#include "WebGLValidateStrings.h"
#include "WebGLVertexArray.h"

namespace mozilla {

/* If `name`: "foo[3]"
 * Then returns true, with
 *     `out_baseName`: "foo"
 *     `out_isArray`: true
 *     `out_index`: 3
 *
 * If `name`: "foo"
 * Then returns true, with
 *     `out_baseName`: "foo"
 *     `out_isArray`: false
 *     `out_index`: 0
 */
static bool ParseName(const nsCString& name, nsCString* const out_baseName,
                      bool* const out_isArray, size_t* const out_arrayIndex) {
  int32_t indexEnd = name.RFind("]");
  if (indexEnd == -1 || (uint32_t)indexEnd != name.Length() - 1) {
    *out_baseName = name;
    *out_isArray = false;
    *out_arrayIndex = 0;
    return true;
  }

  int32_t indexOpenBracket = name.RFind("[");
  if (indexOpenBracket == -1) return false;

  uint32_t indexStart = indexOpenBracket + 1;
  uint32_t indexLen = indexEnd - indexStart;
  if (indexLen == 0) return false;

  const nsAutoCString indexStr(Substring(name, indexStart, indexLen));

  nsresult errorcode;
  int32_t indexNum = indexStr.ToInteger(&errorcode);
  if (NS_FAILED(errorcode)) return false;

  if (indexNum < 0) return false;

  *out_baseName = StringHead(name, indexOpenBracket);
  *out_isArray = true;
  *out_arrayIndex = indexNum;
  return true;
}

static void AssembleName(const nsCString& baseName, bool isArray,
                         size_t arrayIndex, nsCString* const out_name) {
  *out_name = baseName;
  if (isArray) {
    out_name->Append('[');
    out_name->AppendInt(uint64_t(arrayIndex));
    out_name->Append(']');
  }
}

////

static GLenum AttribBaseType(GLenum attribType) {
  switch (attribType) {
    case LOCAL_GL_FLOAT:
    case LOCAL_GL_FLOAT_VEC2:
    case LOCAL_GL_FLOAT_VEC3:
    case LOCAL_GL_FLOAT_VEC4:

    case LOCAL_GL_FLOAT_MAT2:
    case LOCAL_GL_FLOAT_MAT2x3:
    case LOCAL_GL_FLOAT_MAT2x4:

    case LOCAL_GL_FLOAT_MAT3x2:
    case LOCAL_GL_FLOAT_MAT3:
    case LOCAL_GL_FLOAT_MAT3x4:

    case LOCAL_GL_FLOAT_MAT4x2:
    case LOCAL_GL_FLOAT_MAT4x3:
    case LOCAL_GL_FLOAT_MAT4:
      return LOCAL_GL_FLOAT;

    case LOCAL_GL_INT:
    case LOCAL_GL_INT_VEC2:
    case LOCAL_GL_INT_VEC3:
    case LOCAL_GL_INT_VEC4:
      return LOCAL_GL_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 LOCAL_GL_UNSIGNED_INT;

    default:
      MOZ_ASSERT(false, "unexpected attrib elemType");
      return 0;
  }
}

////

/*static*/ const webgl::UniformInfo::TexListT* webgl::UniformInfo::GetTexList(
    WebGLActiveInfo* activeInfo) {
  const auto& webgl = activeInfo->mWebGL;

  switch (activeInfo->mElemType) {
    case LOCAL_GL_SAMPLER_2D:
    case LOCAL_GL_SAMPLER_2D_SHADOW:
    case LOCAL_GL_INT_SAMPLER_2D:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
      return &webgl->mBound2DTextures;

    case LOCAL_GL_SAMPLER_CUBE:
    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
    case LOCAL_GL_INT_SAMPLER_CUBE:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
      return &webgl->mBoundCubeMapTextures;

    case LOCAL_GL_SAMPLER_3D:
    case LOCAL_GL_INT_SAMPLER_3D:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
      return &webgl->mBound3DTextures;

    case LOCAL_GL_SAMPLER_2D_ARRAY:
    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
      return &webgl->mBound2DArrayTextures;

    default:
      return nullptr;
  }
}

webgl::UniformInfo::UniformInfo(WebGLActiveInfo* activeInfo)
    : mActiveInfo(activeInfo), mSamplerTexList(GetTexList(activeInfo)) {
  if (mSamplerTexList) {
    mSamplerValues.assign(mActiveInfo->mElemCount, 0);
  }
}

//////////

//#define DUMP_SHADERVAR_MAPPINGS

static already_AddRefed<const webgl::LinkedProgramInfo> QueryProgramInfo(
    WebGLProgram* prog, gl::GLContext* gl) {
  WebGLContext* const webgl = prog->mContext;

  RefPtr<webgl::LinkedProgramInfo> info(new webgl::LinkedProgramInfo(prog));

  GLuint maxAttribLenWithNull = 0;
  gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
                    (GLint*)&maxAttribLenWithNull);
  if (maxAttribLenWithNull < 1) maxAttribLenWithNull = 1;

  GLuint maxUniformLenWithNull = 0;
  gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH,
                    (GLint*)&maxUniformLenWithNull);
  if (maxUniformLenWithNull < 1) maxUniformLenWithNull = 1;

  GLuint maxUniformBlockLenWithNull = 0;
  if (gl->IsSupported(gl::GLFeature::uniform_buffer_object)) {
    gl->fGetProgramiv(prog->mGLName,
                      LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH,
                      (GLint*)&maxUniformBlockLenWithNull);
    if (maxUniformBlockLenWithNull < 1) maxUniformBlockLenWithNull = 1;
  }

  GLuint maxTransformFeedbackVaryingLenWithNull = 0;
  if (gl->IsSupported(gl::GLFeature::transform_feedback2)) {
    gl->fGetProgramiv(prog->mGLName,
                      LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH,
                      (GLint*)&maxTransformFeedbackVaryingLenWithNull);
    if (maxTransformFeedbackVaryingLenWithNull < 1)
      maxTransformFeedbackVaryingLenWithNull = 1;
  }

  // Attribs (can't be arrays)

  GLuint numActiveAttribs = 0;
  gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES,
                    (GLint*)&numActiveAttribs);

  for (GLuint i = 0; i < numActiveAttribs; i++) {
    nsAutoCString mappedName;
    mappedName.SetLength(maxAttribLenWithNull - 1);

    GLsizei lengthWithoutNull = 0;
    GLint elemCount = 0;  // `size`
    GLenum elemType = 0;  // `type`
    gl->fGetActiveAttrib(prog->mGLName, i, mappedName.Length() + 1,
                         &lengthWithoutNull, &elemCount, &elemType,
                         mappedName.BeginWriting());
    GLenum error = gl->fGetError();
    if (error != LOCAL_GL_NO_ERROR) {
      gfxCriticalNote << "Failed to do glGetActiveAttrib: " << error;
    }

    mappedName.SetLength(lengthWithoutNull);

    ////

    nsCString userName;
    if (!prog->FindAttribUserNameByMappedName(mappedName, &userName)) {
      userName = mappedName;
    }

    ///////

    GLint loc =
        gl->fGetAttribLocation(prog->mGLName, mappedName.BeginReading());
    if (gl->WorkAroundDriverBugs() && mappedName.EqualsIgnoreCase("gl_", 3)) {
      // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or
      // Win+GL.
      loc = -1;
    }
#ifdef DUMP_SHADERVAR_MAPPINGS
    printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, numActiveAttribs, loc,
                  userName.BeginReading(), mappedName.BeginReading());
#endif
    MOZ_ASSERT_IF(mappedName.EqualsIgnoreCase("gl_", 3), loc == -1);

    ///////

    const bool isArray = false;
    const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(
        webgl, elemCount, elemType, isArray, userName, mappedName);
    const GLenum baseType = AttribBaseType(elemType);
    const webgl::AttribInfo attrib = {activeInfo, loc, baseType};
    info->attribs.push_back(attrib);

    if (loc == 0) {
      info->attrib0Active = true;
    }
  }

  // Uniforms (can be basically anything)

  const bool needsCheckForArrays = gl->WorkAroundDriverBugs();

  GLuint numActiveUniforms = 0;
  gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORMS,
                    (GLint*)&numActiveUniforms);

  for (GLuint i = 0; i < numActiveUniforms; i++) {
    nsAutoCString mappedName;
    mappedName.SetLength(maxUniformLenWithNull - 1);

    GLsizei lengthWithoutNull = 0;
    GLint elemCount = 0;  // `size`
    GLenum elemType = 0;  // `type`
    gl->fGetActiveUniform(prog->mGLName, i, mappedName.Length() + 1,
                          &lengthWithoutNull, &elemCount, &elemType,
                          mappedName.BeginWriting());

    mappedName.SetLength(lengthWithoutNull);

    ///////

    nsAutoCString baseMappedName;
    bool isArray;
    size_t arrayIndex;
    if (!ParseName(mappedName, &baseMappedName, &isArray, &arrayIndex))
      MOZ_CRASH("GFX: Failed to parse `mappedName` received from driver.");

    // Note that for good drivers, `isArray` should already be correct.
    // However, if FindUniform succeeds, it will be validator-guaranteed
    // correct.

    ///////

    nsAutoCString baseUserName;
    if (!prog->FindUniformByMappedName(baseMappedName, &baseUserName,
                                       &isArray)) {
      // Validator likely missing.
      baseUserName = baseMappedName;

      if (needsCheckForArrays && !isArray) {
        // By GLES 3, GetUniformLocation("foo[0]") should return -1 if `foo` is
        // not an array. Our current linux Try slaves return the location of
        // `foo` anyways, though.
        std::string mappedNameStr = baseMappedName.BeginReading();
        mappedNameStr += "[0]";

        GLint loc =
            gl->fGetUniformLocation(prog->mGLName, mappedNameStr.c_str());
        if (loc != -1) isArray = true;
      }
    }

      ///////

#ifdef DUMP_SHADERVAR_MAPPINGS
    printf_stderr("[uniform %u/%u] %s->%s\n", i, numActiveUniforms,
                  baseUserName.BeginReading(), mappedName.BeginReading());
#endif

    ///////

    const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(
        webgl, elemCount, elemType, isArray, baseUserName, baseMappedName);

    auto* uniform = new webgl::UniformInfo(activeInfo);
    info->uniforms.push_back(uniform);

    if (uniform->mSamplerTexList) {
      info->uniformSamplers.push_back(uniform);
    }
  }

  // Uniform Blocks (can be arrays, but can't contain sampler types)

  if (gl->IsSupported(gl::GLFeature::uniform_buffer_object)) {
    GLuint numActiveUniformBlocks = 0;
    gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORM_BLOCKS,
                      (GLint*)&numActiveUniformBlocks);

    for (GLuint i = 0; i < numActiveUniformBlocks; i++) {
      nsAutoCString mappedName;
      mappedName.SetLength(maxUniformBlockLenWithNull - 1);

      GLint lengthWithoutNull;
      gl->fGetActiveUniformBlockiv(prog->mGLName, i,
                                   LOCAL_GL_UNIFORM_BLOCK_NAME_LENGTH,
                                   &lengthWithoutNull);
      gl->fGetActiveUniformBlockName(
          prog->mGLName, i, maxUniformBlockLenWithNull, &lengthWithoutNull,
          mappedName.BeginWriting());
      mappedName.SetLength(lengthWithoutNull);

      ////

      nsCString userName;
      if (!prog->UnmapUniformBlockName(mappedName, &userName)) continue;

#ifdef DUMP_SHADERVAR_MAPPINGS
      printf_stderr("[uniform block %u/%u] %s->%s\n", i, numActiveUniformBlocks,
                    userName.BeginReading(), mappedName.BeginReading());
#endif

      ////

      GLuint dataSize = 0;
      gl->fGetActiveUniformBlockiv(prog->mGLName, i,
                                   LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
                                   (GLint*)&dataSize);

      auto* block =
          new webgl::UniformBlockInfo(webgl, userName, mappedName, dataSize);
      info->uniformBlocks.push_back(block);
    }
  }

  // Transform feedback varyings (can be arrays)

  if (gl->IsSupported(gl::GLFeature::transform_feedback2)) {
    GLuint numTransformFeedbackVaryings = 0;
    gl->fGetProgramiv(prog->mGLName, LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS,
                      (GLint*)&numTransformFeedbackVaryings);

    for (GLuint i = 0; i < numTransformFeedbackVaryings; i++) {
      nsAutoCString mappedName;
      mappedName.SetLength(maxTransformFeedbackVaryingLenWithNull - 1);

      GLint lengthWithoutNull;
      GLsizei elemCount;
      GLenum elemType;
      gl->fGetTransformFeedbackVarying(
          prog->mGLName, i, maxTransformFeedbackVaryingLenWithNull,
          &lengthWithoutNull, &elemCount, &elemType, mappedName.BeginWriting());
      mappedName.SetLength(lengthWithoutNull);

      ////

      nsAutoCString baseMappedName;
      bool isArray;
      size_t arrayIndex;
      if (!ParseName(mappedName, &baseMappedName, &isArray, &arrayIndex))
        MOZ_CRASH("GFX: Failed to parse `mappedName` received from driver.");

      nsAutoCString baseUserName;
      if (!prog->FindVaryingByMappedName(mappedName, &baseUserName, &isArray)) {
        baseUserName = baseMappedName;
      }

        ////

#ifdef DUMP_SHADERVAR_MAPPINGS
      printf_stderr("[transform feedback varying %u/%u] %s->%s\n", i,
                    numTransformFeedbackVaryings, baseUserName.BeginReading(),
                    mappedName.BeginReading());
#endif

      const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(
          webgl, elemCount, elemType, isArray, baseUserName, mappedName);
      info->transformFeedbackVaryings.push_back(activeInfo);
    }
  }

  // Frag outputs

  prog->EnumerateFragOutputs(info->fragDataMap);

  return info.forget();
}

////////////////////////////////////////////////////////////////////////////////

webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog)
    : prog(prog),
      transformFeedbackBufferMode(prog->mNextLink_TransformFeedbackBufferMode),
      attrib0Active(false) {}

webgl::LinkedProgramInfo::~LinkedProgramInfo() {
  for (auto& cur : uniforms) {
    delete cur;
  }
  for (auto& cur : uniformBlocks) {
    delete cur;
  }
}

const webgl::CachedDrawFetchLimits*
webgl::LinkedProgramInfo::GetDrawFetchLimits(const char* const funcName) const {
  const auto& webgl = prog->mContext;
  const auto& vao = webgl->mBoundVertexArray;

  const auto found = mDrawFetchCache.Find(vao);
  if (found) return found;

  std::vector<const CacheMapInvalidator*> cacheDeps;
  cacheDeps.push_back(vao.get());
  cacheDeps.push_back(&webgl->mGenericVertexAttribTypeInvalidator);

  {
    // We have to ensure that every enabled attrib array (not just the active
    // ones) has a non-null buffer.
    uint32_t i = 0;
    for (const auto& cur : vao->mAttribs) {
      if (cur.mEnabled && !cur.mBuf) {
        webgl->ErrorInvalidOperation(
            "%s: Vertex attrib array %u is enabled but"
            " has no buffer bound.",
            funcName, i);
        return nullptr;
      }
    }
  }

  bool hasActiveAttrib = false;
  bool hasActiveDivisor0 = false;
  webgl::CachedDrawFetchLimits fetchLimits = {UINT64_MAX, UINT64_MAX};

  for (const auto& progAttrib : this->attribs) {
    const auto& loc = progAttrib.mLoc;
    if (loc == -1) continue;
    hasActiveAttrib |= true;

    const auto& attribData = vao->mAttribs[loc];
    hasActiveDivisor0 |= (attribData.mDivisor == 0);

    GLenum attribDataBaseType;
    if (attribData.mEnabled) {
      MOZ_ASSERT(attribData.mBuf);
      if (attribData.mBuf->IsBoundForTF()) {
        webgl->ErrorInvalidOperation(
            "%s: Vertex attrib %u's buffer is bound for"
            " transform feedback.",
            funcName, loc);
        return nullptr;
      }
      cacheDeps.push_back(&attribData.mBuf->mFetchInvalidator);

      attribDataBaseType = attribData.BaseType();

      const size_t availBytes = attribData.mBuf->ByteLength();
      const auto availElems =
          AvailGroups(availBytes, attribData.ByteOffset(),
                      attribData.BytesPerVertex(), attribData.ExplicitStride());
      if (attribData.mDivisor) {
        const auto availInstances =
            CheckedInt<uint64_t>(availElems) * attribData.mDivisor;
        if (availInstances.isValid()) {
          fetchLimits.maxInstances =
              std::min(fetchLimits.maxInstances, availInstances.value());
        }  // If not valid, it overflowed too large, so we're super safe.
      } else {
        fetchLimits.maxVerts = std::min(fetchLimits.maxVerts, availElems);
      }
    } else {
      attribDataBaseType = webgl->mGenericVertexAttribTypes[loc];
    }

    if (attribDataBaseType != progAttrib.mBaseType) {
      nsCString progType, dataType;
      WebGLContext::EnumName(progAttrib.mBaseType, &progType);
      WebGLContext::EnumName(attribDataBaseType, &dataType);
      webgl->ErrorInvalidOperation(
          "%s: Vertex attrib %u requires data of type %s,"
          " but is being supplied with type %s.",
          funcName, loc, progType.BeginReading(), dataType.BeginReading());
      return nullptr;
    }
  }

  if (hasActiveAttrib && !hasActiveDivisor0) {
    webgl->ErrorInvalidOperation(
        "%s: One active vertex attrib (if any are active)"
        " must have a divisor of 0.",
        funcName);
    return nullptr;
  }

  // --

  return mDrawFetchCache.Insert(vao.get(), Move(fetchLimits), Move(cacheDeps));
}

////////////////////////////////////////////////////////////////////////////////
// WebGLProgram

WebGLProgram::WebGLProgram(WebGLContext* webgl)
    : WebGLRefCountedObject(webgl),
      mGLName(webgl->gl->fCreateProgram()),
      mNumActiveTFOs(0),
      mNextLink_TransformFeedbackBufferMode(LOCAL_GL_INTERLEAVED_ATTRIBS) {
  mContext->mPrograms.insertBack(this);
}

WebGLProgram::~WebGLProgram() { DeleteOnce(); }

void WebGLProgram::Delete() {
  gl::GLContext* gl = mContext->GL();
  gl->fDeleteProgram(mGLName);

  mVertShader = nullptr;
  mFragShader = nullptr;

  mMostRecentLinkInfo = nullptr;

  LinkedListElement<WebGLProgram>::removeFrom(mContext->mPrograms);
}

////////////////////////////////////////////////////////////////////////////////
// GL funcs

void WebGLProgram::AttachShader(WebGLShader* shader) {
  WebGLRefPtr<WebGLShader>* shaderSlot;
  switch (shader->mType) {
    case LOCAL_GL_VERTEX_SHADER:
      shaderSlot = &mVertShader;
      break;
    case LOCAL_GL_FRAGMENT_SHADER:
      shaderSlot = &mFragShader;
      break;
    default:
      mContext->ErrorInvalidOperation("attachShader: Bad type for shader.");
      return;
  }

  if (*shaderSlot) {
    if (shader == *shaderSlot) {
      mContext->ErrorInvalidOperation(
          "attachShader: `shader` is already attached.");
    } else {
      mContext->ErrorInvalidOperation(
          "attachShader: Only one of each type of"
          " shader may be attached to a program.");
    }
    return;
  }

  *shaderSlot = shader;

  mContext->gl->fAttachShader(mGLName, shader->mGLName);
}

void WebGLProgram::BindAttribLocation(GLuint loc, const nsAString& name) {
  if (!ValidateGLSLVariableName(name, mContext, "bindAttribLocation")) return;

  if (loc >= mContext->MaxVertexAttribs()) {
    mContext->ErrorInvalidValue(
        "bindAttribLocation: `location` must be less than"
        " MAX_VERTEX_ATTRIBS.");
    return;
  }

  if (StringBeginsWith(name, NS_LITERAL_STRING("gl_"))) {
    mContext->ErrorInvalidOperation(
        "bindAttribLocation: Can't set the location of a"
        " name that starts with 'gl_'.");
    return;
  }

  NS_LossyConvertUTF16toASCII asciiName(name);

  auto res = mNextLink_BoundAttribLocs.insert({asciiName, loc});

  const bool wasInserted = res.second;
  if (!wasInserted) {
    auto itr = res.first;
    itr->second = loc;
  }
}

void WebGLProgram::DetachShader(const WebGLShader* shader) {
  MOZ_ASSERT(shader);

  WebGLRefPtr<WebGLShader>* shaderSlot;
  switch (shader->mType) {
    case LOCAL_GL_VERTEX_SHADER:
      shaderSlot = &mVertShader;
      break;
    case LOCAL_GL_FRAGMENT_SHADER:
      shaderSlot = &mFragShader;
      break;
    default:
      mContext->ErrorInvalidOperation("attachShader: Bad type for shader.");
      return;
  }

  if (*shaderSlot != shader) {
    mContext->ErrorInvalidOperation("detachShader: `shader` is not attached.");
    return;
  }

  *shaderSlot = nullptr;

  mContext->gl->fDetachShader(mGLName, shader->mGLName);
}

already_AddRefed<WebGLActiveInfo> WebGLProgram::GetActiveAttrib(
    GLuint index) const {
  if (!mMostRecentLinkInfo) {
    RefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
    return ret.forget();
  }

  const auto& attribs = mMostRecentLinkInfo->attribs;

  if (index >= attribs.size()) {
    mContext->ErrorInvalidValue("`index` (%i) must be less than %s (%zu).",
                                index, "ACTIVE_ATTRIBS", attribs.size());
    return nullptr;
  }

  RefPtr<WebGLActiveInfo> ret = attribs[index].mActiveInfo;
  return ret.forget();
}

already_AddRefed<WebGLActiveInfo> WebGLProgram::GetActiveUniform(
    GLuint index) const {
  if (!mMostRecentLinkInfo) {
    // According to the spec, this can return null.
    RefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
    return ret.forget();
  }

  const auto& uniforms = mMostRecentLinkInfo->uniforms;

  if (index >= uniforms.size()) {
    mContext->ErrorInvalidValue("`index` (%i) must be less than %s (%zu).",
                                index, "ACTIVE_UNIFORMS", uniforms.size());
    return nullptr;
  }

  RefPtr<WebGLActiveInfo> ret = uniforms[index]->mActiveInfo;
  return ret.forget();
}

void WebGLProgram::GetAttachedShaders(
    nsTArray<RefPtr<WebGLShader>>* const out) const {
  out->TruncateLength(0);

  if (mVertShader) out->AppendElement(mVertShader);

  if (mFragShader) out->AppendElement(mFragShader);
}

GLint WebGLProgram::GetAttribLocation(const nsAString& userName_wide) const {
  if (!ValidateGLSLVariableName(userName_wide, mContext, "getAttribLocation"))
    return -1;

  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getAttribLocation: `program` must be linked.");
    return -1;
  }

  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, "getFragDataLocation"))
    return -1;

  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getFragDataLocation: `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;
  }
#endif
  return GetFragDataByUserName(this, userName);
}

void WebGLProgram::GetProgramInfoLog(nsAString* const out) const {
  CopyASCIItoUTF16(mLinkLog, *out);
}

static GLint GetProgramiv(gl::GLContext* gl, GLuint program, GLenum pname) {
  GLint ret = 0;
  gl->fGetProgramiv(program, pname, &ret);
  return ret;
}

JS::Value WebGLProgram::GetProgramParameter(GLenum pname) const {
  gl::GLContext* gl = mContext->gl;

  if (mContext->IsWebGL2()) {
    switch (pname) {
      case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
        if (!IsLinked()) return JS::NumberValue(0);
        return JS::NumberValue(LinkInfo()->uniformBlocks.size());

      case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
        if (!IsLinked()) return JS::NumberValue(0);
        return JS::NumberValue(LinkInfo()->transformFeedbackVaryings.size());

      case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
        if (!IsLinked()) return JS::NumberValue(LOCAL_GL_INTERLEAVED_ATTRIBS);
        return JS::NumberValue(LinkInfo()->transformFeedbackBufferMode);
    }
  }

  switch (pname) {
    case LOCAL_GL_ATTACHED_SHADERS:
      return JS::NumberValue(int(bool(mVertShader.get())) +
                             int(bool(mFragShader)));

    case LOCAL_GL_ACTIVE_UNIFORMS:
      if (!IsLinked()) return JS::NumberValue(0);
      return JS::NumberValue(LinkInfo()->uniforms.size());

    case LOCAL_GL_ACTIVE_ATTRIBUTES:
      if (!IsLinked()) return JS::NumberValue(0);
      return JS::NumberValue(LinkInfo()->attribs.size());

    case LOCAL_GL_DELETE_STATUS:
      return JS::BooleanValue(IsDeleteRequested());

    case LOCAL_GL_LINK_STATUS:
      return JS::BooleanValue(IsLinked());

    case LOCAL_GL_VALIDATE_STATUS:
#ifdef XP_MACOSX
      // See comment in ValidateProgram.
      if (gl->WorkAroundDriverBugs()) return JS::BooleanValue(true);
#endif
      // Todo: Implement this in our code.
      return JS::BooleanValue(bool(GetProgramiv(gl, mGLName, pname)));

    default:
      mContext->ErrorInvalidEnumInfo("getProgramParameter: `pname`", pname);
      return JS::NullValue();
  }
}

GLuint WebGLProgram::GetUniformBlockIndex(
    const nsAString& userName_wide) const {
  if (!ValidateGLSLVariableName(userName_wide, mContext,
                                "getUniformBlockIndex"))
    return LOCAL_GL_INVALID_INDEX;

  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getUniformBlockIndex: `program` must be linked.");
    return LOCAL_GL_INVALID_INDEX;
  }

  const NS_LossyConvertUTF16toASCII userName(userName_wide);

  const webgl::UniformBlockInfo* info = nullptr;
  for (const auto& cur : LinkInfo()->uniformBlocks) {
    if (cur->mUserName == userName) {
      info = cur;
      break;
    }
  }
  if (!info) return LOCAL_GL_INVALID_INDEX;

  const auto& mappedName = info->mMappedName;

  gl::GLContext* gl = mContext->GL();
  return gl->fGetUniformBlockIndex(mGLName, mappedName.BeginReading());
}

void WebGLProgram::GetActiveUniformBlockName(GLuint uniformBlockIndex,
                                             nsAString& retval) const {
  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getActiveUniformBlockName: `program` must be linked.");
    return;
  }

  const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
  GLuint uniformBlockCount = (GLuint)linkInfo->uniformBlocks.size();
  if (uniformBlockIndex >= uniformBlockCount) {
    mContext->ErrorInvalidValue("getActiveUniformBlockName: index %u invalid.",
                                uniformBlockIndex);
    return;
  }

  const auto& blockInfo = linkInfo->uniformBlocks[uniformBlockIndex];
  retval.Assign(NS_ConvertASCIItoUTF16(blockInfo->mUserName));
}

JS::Value WebGLProgram::GetActiveUniformBlockParam(GLuint uniformBlockIndex,
                                                   GLenum pname) const {
  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getActiveUniformBlockParameter: `program` must be linked.");
    return JS::NullValue();
  }

  const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
  GLuint uniformBlockCount = (GLuint)linkInfo->uniformBlocks.size();
  if (uniformBlockIndex >= uniformBlockCount) {
    mContext->ErrorInvalidValue(
        "getActiveUniformBlockParameter: index %u invalid.", uniformBlockIndex);
    return JS::NullValue();
  }

  gl::GLContext* gl = mContext->GL();
  GLint param = 0;

  switch (pname) {
    case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
    case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
      gl->fGetActiveUniformBlockiv(mGLName, uniformBlockIndex, pname, &param);
      return JS::BooleanValue(bool(param));

    case LOCAL_GL_UNIFORM_BLOCK_BINDING:
    case LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE:
    case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS:
      gl->fGetActiveUniformBlockiv(mGLName, uniformBlockIndex, pname, &param);
      return JS::NumberValue(param);

    default:
      MOZ_CRASH("bad `pname`.");
  }
}

JS::Value WebGLProgram::GetActiveUniformBlockActiveUniforms(
    JSContext* cx, GLuint uniformBlockIndex,
    ErrorResult* const out_error) const {
  const char funcName[] = "getActiveUniformBlockParameter";
  if (!IsLinked()) {
    mContext->ErrorInvalidOperation("%s: `program` must be linked.", funcName);
    return JS::NullValue();
  }

  const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
  GLuint uniformBlockCount = (GLuint)linkInfo->uniformBlocks.size();
  if (uniformBlockIndex >= uniformBlockCount) {
    mContext->ErrorInvalidValue("%s: Index %u invalid.", funcName,
                                uniformBlockIndex);
    return JS::NullValue();
  }

  gl::GLContext* gl = mContext->GL();
  GLint activeUniformCount = 0;
  gl->fGetActiveUniformBlockiv(mGLName, uniformBlockIndex,
                               LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS,
                               &activeUniformCount);
  JS::RootedObject obj(
      cx, dom::Uint32Array::Create(cx, mContext, activeUniformCount, nullptr));
  if (!obj) {
    *out_error = NS_ERROR_OUT_OF_MEMORY;
    return JS::NullValue();
  }

  dom::Uint32Array result;
  DebugOnly<bool> inited = result.Init(obj);
  MOZ_ASSERT(inited);
  result.ComputeLengthAndData();
  gl->fGetActiveUniformBlockiv(mGLName, uniformBlockIndex,
                               LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
                               (GLint*)result.Data());

  return JS::ObjectValue(*obj);
}

already_AddRefed<WebGLUniformLocation> WebGLProgram::GetUniformLocation(
    const nsAString& userName_wide) const {
  if (!ValidateGLSLVariableName(userName_wide, mContext, "getUniformLocation"))
    return nullptr;

  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getUniformLocation: `program` must be linked.");
    return nullptr;
  }

  const NS_LossyConvertUTF16toASCII userName(userName_wide);

  // GLES 2.0.25, Section 2.10, p35
  // If the the uniform location is an array, then the location of the first
  // element of that array can be retrieved by either using the name of the
  // uniform array, or the name of the uniform array appended with "[0]".
  nsCString mappedName;
  size_t arrayIndex;
  webgl::UniformInfo* info;
  if (!LinkInfo()->FindUniform(userName, &mappedName, &arrayIndex, &info))
    return nullptr;

  gl::GLContext* gl = mContext->GL();

  GLint loc = gl->fGetUniformLocation(mGLName, mappedName.BeginReading());
  if (loc == -1) return nullptr;

  RefPtr<WebGLUniformLocation> locObj =
      new WebGLUniformLocation(mContext, LinkInfo(), info, loc, arrayIndex);
  return locObj.forget();
}

void WebGLProgram::GetUniformIndices(
    const dom::Sequence<nsString>& uniformNames,
    dom::Nullable<nsTArray<GLuint>>& retval) const {
  const char funcName[] = "getUniformIndices";
  if (!IsLinked()) {
    mContext->ErrorInvalidOperation("%s: `program` must be linked.", funcName);
    return;
  }

  size_t count = uniformNames.Length();
  nsTArray<GLuint>& arr = retval.SetValue();

  gl::GLContext* gl = mContext->GL();

  for (size_t i = 0; i < count; i++) {
    const NS_LossyConvertUTF16toASCII userName(uniformNames[i]);

    nsCString mappedName;
    size_t arrayIndex;
    webgl::UniformInfo* info;
    if (!LinkInfo()->FindUniform(userName, &mappedName, &arrayIndex, &info)) {
      arr.AppendElement(LOCAL_GL_INVALID_INDEX);
      continue;
    }

    const GLchar* const mappedNameBegin = mappedName.get();

    GLuint index = LOCAL_GL_INVALID_INDEX;
    gl->fGetUniformIndices(mGLName, 1, &mappedNameBegin, &index);
    arr.AppendElement(index);
  }
}

void WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex,
                                       GLuint uniformBlockBinding) const {
  const char funcName[] = "getActiveUniformBlockName";
  if (!IsLinked()) {
    mContext->ErrorInvalidOperation("%s: `program` must be linked.", funcName);
    return;
  }

  const auto& uniformBlocks = LinkInfo()->uniformBlocks;
  if (uniformBlockIndex >= uniformBlocks.size()) {
    mContext->ErrorInvalidValue("%s: Index %u invalid.", funcName,
                                uniformBlockIndex);
    return;
  }
  const auto& uniformBlock = uniformBlocks[uniformBlockIndex];

  const auto& indexedBindings = mContext->mIndexedUniformBufferBindings;
  if (uniformBlockBinding >= indexedBindings.size()) {
    mContext->ErrorInvalidValue("%s: Binding %u invalid.", funcName,
                                uniformBlockBinding);
    return;
  }
  const auto& indexedBinding = indexedBindings[uniformBlockBinding];

  ////

  gl::GLContext* gl = mContext->GL();
  gl->fUniformBlockBinding(mGLName, uniformBlockIndex, uniformBlockBinding);

  ////

  uniformBlock->mBinding = &indexedBinding;
}

bool WebGLProgram::ValidateForLink() {
  if (!mVertShader || !mVertShader->IsCompiled()) {
    mLinkLog.AssignLiteral("Must have a compiled vertex shader attached.");
    return false;
  }

  if (!mFragShader || !mFragShader->IsCompiled()) {
    mLinkLog.AssignLiteral("Must have an compiled fragment shader attached.");
    return false;
  }

  if (!mFragShader->CanLinkTo(mVertShader, &mLinkLog)) return false;

  const auto& gl = mContext->gl;

  if (gl->WorkAroundDriverBugs() && mContext->mIsMesa) {
    // Bug 777028: Mesa can't handle more than 16 samplers per program,
    // counting each array entry.
    size_t numSamplerUniforms_upperBound =
        mVertShader->CalcNumSamplerUniforms() +
        mFragShader->CalcNumSamplerUniforms();
    if (numSamplerUniforms_upperBound > 16) {
      mLinkLog.AssignLiteral(
          "Programs with more than 16 samplers are disallowed on"
          " Mesa drivers to avoid crashing.");
      return false;
    }

    // Bug 1203135: Mesa crashes internally if we exceed the reported maximum
    // attribute count.
    if (mVertShader->NumAttributes() > mContext->MaxVertexAttribs()) {
      mLinkLog.AssignLiteral(
          "Number of attributes exceeds Mesa's reported max"
          " attribute count.");
      return false;
    }
  }

  return true;
}

void WebGLProgram::LinkProgram() {
  const char funcName[] = "linkProgram";

  if (mNumActiveTFOs) {
    mContext->ErrorInvalidOperation(
        "%s: Program is in-use by one or more active"
        " transform feedback objects.",
        funcName);
    return;
  }

  // as some of the validation changes program state

  mLinkLog.Truncate();
  mMostRecentLinkInfo = nullptr;

  if (!ValidateForLink()) {
    mContext->GenerateWarning("%s: %s", funcName, mLinkLog.BeginReading());
    return;
  }

  // Bind the attrib locations.
  // This can't be done trivially, because we have to deal with mapped attrib
  // names.
  for (const auto& pair : mNextLink_BoundAttribLocs) {
    const auto& name = pair.first;
    const auto& index = pair.second;

    mVertShader->BindAttribLocation(mGLName, name, index);
  }

  // Storage for transform feedback varyings before link.
  // (Work around for bug seen on nVidia drivers.)
  std::vector<std::string> scopedMappedTFVaryings;

  if (mContext->IsWebGL2()) {
    mVertShader->MapTransformFeedbackVaryings(
        mNextLink_TransformFeedbackVaryings, &scopedMappedTFVaryings);

    std::vector<const char*> driverVaryings;
    driverVaryings.reserve(scopedMappedTFVaryings.size());
    for (const auto& cur : scopedMappedTFVaryings) {
      driverVaryings.push_back(cur.c_str());
    }

    mContext->gl->fTransformFeedbackVaryings(
        mGLName, driverVaryings.size(), driverVaryings.data(),
        mNextLink_TransformFeedbackBufferMode);
  }

  LinkAndUpdate();

  if (mMostRecentLinkInfo) {
    nsCString postLinkLog;
    if (ValidateAfterTentativeLink(&postLinkLog)) return;

    mMostRecentLinkInfo = nullptr;
    mLinkLog = postLinkLog;
  }

  // Failed link.
  if (mContext->ShouldGenerateWarnings()) {
    // report shader/program infoLogs as warnings.
    // note that shader compilation errors can be deferred to linkProgram,
    // which is why we can't do anything in compileShader. In practice we could
    // report in compileShader the translation errors generated by ANGLE,
    // but it seems saner to keep a single way of obtaining shader infologs.
    if (!mLinkLog.IsEmpty()) {
      mContext->GenerateWarning(
          "linkProgram: Failed to link, leaving the following"
          " log:\n%s\n",
          mLinkLog.BeginReading());
    }
  }
}

static uint8_t NumUsedLocationsByElemType(GLenum elemType) {
  // GLES 3.0.4 p55

  switch (elemType) {
    case LOCAL_GL_FLOAT_MAT2:
    case LOCAL_GL_FLOAT_MAT2x3:
    case LOCAL_GL_FLOAT_MAT2x4:
      return 2;

    case LOCAL_GL_FLOAT_MAT3x2:
    case LOCAL_GL_FLOAT_MAT3:
    case LOCAL_GL_FLOAT_MAT3x4:
      return 3;

    case LOCAL_GL_FLOAT_MAT4x2:
    case LOCAL_GL_FLOAT_MAT4x3:
    case LOCAL_GL_FLOAT_MAT4:
      return 4;

    default:
      return 1;
  }
}

static uint8_t NumComponents(GLenum elemType) {
  switch (elemType) {
    case LOCAL_GL_FLOAT:
    case LOCAL_GL_INT:
    case LOCAL_GL_UNSIGNED_INT:
    case LOCAL_GL_BOOL:
      return 1;

    case LOCAL_GL_FLOAT_VEC2:
    case LOCAL_GL_INT_VEC2:
    case LOCAL_GL_UNSIGNED_INT_VEC2:
    case LOCAL_GL_BOOL_VEC2:
      return 2;

    case LOCAL_GL_FLOAT_VEC3:
    case LOCAL_GL_INT_VEC3:
    case LOCAL_GL_UNSIGNED_INT_VEC3:
    case LOCAL_GL_BOOL_VEC3:
      return 3;

    case LOCAL_GL_FLOAT_VEC4:
    case LOCAL_GL_INT_VEC4:
    case LOCAL_GL_UNSIGNED_INT_VEC4:
    case LOCAL_GL_BOOL_VEC4:
    case LOCAL_GL_FLOAT_MAT2:
      return 4;

    case LOCAL_GL_FLOAT_MAT2x3:
    case LOCAL_GL_FLOAT_MAT3x2:
      return 6;

    case LOCAL_GL_FLOAT_MAT2x4:
    case LOCAL_GL_FLOAT_MAT4x2:
      return 8;

    case LOCAL_GL_FLOAT_MAT3:
      return 9;

    case LOCAL_GL_FLOAT_MAT3x4:
    case LOCAL_GL_FLOAT_MAT4x3:
      return 12;

    case LOCAL_GL_FLOAT_MAT4:
      return 16;

    default:
      MOZ_CRASH("`elemType`");
  }
}

bool WebGLProgram::ValidateAfterTentativeLink(
    nsCString* const out_linkLog) const {
  const auto& linkInfo = mMostRecentLinkInfo;
  const auto& gl = mContext->gl;

  // Check if the attrib name conflicting to uniform name
  for (const auto& attrib : linkInfo->attribs) {
    const auto& attribName = attrib.mActiveInfo->mBaseUserName;

    for (const auto& uniform : linkInfo->uniforms) {
      const auto& uniformName = uniform->mActiveInfo->mBaseUserName;
      if (attribName == uniformName) {
        *out_linkLog = nsPrintfCString(
            "Attrib name conflicts with uniform name:"
            " %s",
            attribName.BeginReading());
        return false;
      }
    }
  }

  std::map<uint32_t, const webgl::AttribInfo*> attribsByLoc;
  for (const auto& attrib : linkInfo->attribs) {
    if (attrib.mLoc == -1) continue;

    const auto& elemType = attrib.mActiveInfo->mElemType;
    const auto numUsedLocs = NumUsedLocationsByElemType(elemType);
    for (uint32_t i = 0; i < numUsedLocs; i++) {
      const uint32_t usedLoc = attrib.mLoc + i;

      const auto res = attribsByLoc.insert({usedLoc, &attrib});
      const bool& didInsert = res.second;
      if (!didInsert) {
        const auto& aliasingName = attrib.mActiveInfo->mBaseUserName;
        const auto& itrExisting = res.first;
        const auto& existingInfo = itrExisting->second;
        const auto& existingName = existingInfo->mActiveInfo->mBaseUserName;
        *out_linkLog = nsPrintfCString(
            "Attrib \"%s\" aliases locations used by"
            " attrib \"%s\".",
            aliasingName.BeginReading(), existingName.BeginReading());
        return false;
      }
    }
  }

  // Forbid:
  // * Unrecognized varying name
  // * Duplicate varying name
  // * Too many components for specified buffer mode
  if (!mNextLink_TransformFeedbackVaryings.empty()) {
    GLuint maxComponentsPerIndex = 0;
    switch (mNextLink_TransformFeedbackBufferMode) {
      case LOCAL_GL_INTERLEAVED_ATTRIBS:
        gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,
                         &maxComponentsPerIndex);
        break;

      case LOCAL_GL_SEPARATE_ATTRIBS:
        gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,
                         &maxComponentsPerIndex);
        break;

      default:
        MOZ_CRASH("`bufferMode`");
    }

    std::vector<size_t> componentsPerVert;
    std::set<const WebGLActiveInfo*> alreadyUsed;
    for (const auto& wideUserName : mNextLink_TransformFeedbackVaryings) {
      if (componentsPerVert.empty() ||
          mNextLink_TransformFeedbackBufferMode == LOCAL_GL_SEPARATE_ATTRIBS) {
        componentsPerVert.push_back(0);
      }

      ////

      const WebGLActiveInfo* curInfo = nullptr;
      for (const auto& info : linkInfo->transformFeedbackVaryings) {
        const NS_ConvertASCIItoUTF16 info_wideUserName(info->mBaseUserName);
        if (info_wideUserName == wideUserName) {
          curInfo = info.get();
          break;
        }
      }

      if (!curInfo) {
        const NS_LossyConvertUTF16toASCII asciiUserName(wideUserName);
        *out_linkLog = nsPrintfCString(
            "Transform feedback varying \"%s\" not"
            " found.",
            asciiUserName.BeginReading());
        return false;
      }

      const auto insertResPair = alreadyUsed.insert(curInfo);
      const auto& didInsert = insertResPair.second;
      if (!didInsert) {
        const NS_LossyConvertUTF16toASCII asciiUserName(wideUserName);
        *out_linkLog = nsPrintfCString(
            "Transform feedback varying \"%s\""
            " specified twice.",
            asciiUserName.BeginReading());
        return false;
      }

      ////

      size_t varyingComponents = NumComponents(curInfo->mElemType);
      varyingComponents *= curInfo->mElemCount;

      auto& totalComponentsForIndex = *(componentsPerVert.rbegin());
      totalComponentsForIndex += varyingComponents;

      if (totalComponentsForIndex > maxComponentsPerIndex) {
        const NS_LossyConvertUTF16toASCII asciiUserName(wideUserName);
        *out_linkLog = nsPrintfCString(
            "Transform feedback varying \"%s\""
            " pushed `componentsForIndex` over the"
            " limit of %u.",
            asciiUserName.BeginReading(), maxComponentsPerIndex);
        return false;
      }
    }

    linkInfo->componentsPerTFVert.swap(componentsPerVert);
  }

  return true;
}

bool WebGLProgram::UseProgram() const {
  const char funcName[] = "useProgram";

  if (!mMostRecentLinkInfo) {
    mContext->ErrorInvalidOperation(
        "%s: Program has not been successfully linked.", funcName);
    return false;
  }

  if (mContext->mBoundTransformFeedback &&
      mContext->mBoundTransformFeedback->mIsActive &&
      !mContext->mBoundTransformFeedback->mIsPaused) {
    mContext->ErrorInvalidOperation(
        "%s: Transform feedback active and not paused.", funcName);
    return false;
  }

  mContext->gl->fUseProgram(mGLName);
  return true;
}

void WebGLProgram::ValidateProgram() const {
  gl::GLContext* gl = mContext->gl;

#ifdef XP_MACOSX
  // See bug 593867 for NVIDIA and bug 657201 for ATI. The latter is confirmed
  // with Mac OS 10.6.7.
  if (gl->WorkAroundDriverBugs()) {
    mContext->GenerateWarning(
        "validateProgram: Implemented as a no-op on"
        " Mac to work around crashes.");
    return;
  }
#endif

  gl->fValidateProgram(mGLName);
}

////////////////////////////////////////////////////////////////////////////////

void WebGLProgram::LinkAndUpdate() {
  mMostRecentLinkInfo = nullptr;

  gl::GLContext* gl = mContext->gl;
  gl->fLinkProgram(mGLName);

  // Grab the program log.
  GLuint logLenWithNull = 0;
  gl->fGetProgramiv(mGLName, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&logLenWithNull);
  if (logLenWithNull > 1) {
    mLinkLog.SetLength(logLenWithNull - 1);
    gl->fGetProgramInfoLog(mGLName, logLenWithNull, nullptr,
                           mLinkLog.BeginWriting());
  } else {
    mLinkLog.SetLength(0);
  }

  GLint ok = 0;
  gl->fGetProgramiv(mGLName, LOCAL_GL_LINK_STATUS, &ok);
  if (!ok) return;

  mMostRecentLinkInfo = QueryProgramInfo(this, gl);
  MOZ_RELEASE_ASSERT(mMostRecentLinkInfo,
                     "GFX: most recent link info not set.");
}

bool WebGLProgram::FindAttribUserNameByMappedName(
    const nsACString& mappedName, nsCString* const out_userName) const {
  if (mVertShader->FindAttribUserNameByMappedName(mappedName, out_userName))
    return true;

  return false;
}

bool WebGLProgram::FindVaryingByMappedName(const nsACString& mappedName,
                                           nsCString* const out_userName,
                                           bool* const out_isArray) const {
  if (mVertShader->FindVaryingByMappedName(mappedName, out_userName,
                                           out_isArray))
    return true;

  return false;
}

bool WebGLProgram::FindUniformByMappedName(const nsACString& mappedName,
                                           nsCString* const out_userName,
                                           bool* const out_isArray) const {
  if (mVertShader->FindUniformByMappedName(mappedName, out_userName,
                                           out_isArray))
    return true;

  if (mFragShader->FindUniformByMappedName(mappedName, out_userName,
                                           out_isArray))
    return true;

  return false;
}

void WebGLProgram::TransformFeedbackVaryings(
    const dom::Sequence<nsString>& varyings, GLenum bufferMode) {
  const char funcName[] = "transformFeedbackVaryings";

  const auto& gl = mContext->gl;

  switch (bufferMode) {
    case LOCAL_GL_INTERLEAVED_ATTRIBS:
      break;

    case LOCAL_GL_SEPARATE_ATTRIBS: {
      GLuint maxAttribs = 0;
      gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
                       &maxAttribs);
      if (varyings.Length() > maxAttribs) {
        mContext->ErrorInvalidValue("%s: Length of `varyings` exceeds %s.",
                                    funcName,
                                    "TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
        return;
      }
    } break;

    default:
      mContext->ErrorInvalidEnum("%s: Bad `bufferMode`: 0x%04x.", funcName,
                                 bufferMode);
      return;
  }

  ////

  mNextLink_TransformFeedbackVaryings.assign(
      varyings.Elements(), varyings.Elements() + varyings.Length());
  mNextLink_TransformFeedbackBufferMode = bufferMode;
}

already_AddRefed<WebGLActiveInfo> WebGLProgram::GetTransformFeedbackVarying(
    GLuint index) const {
  // No docs in the WebGL 2 spec for this function. Taking the language for
  // getActiveAttrib, which states that the function returns null on any error.
  if (!IsLinked()) {
    mContext->ErrorInvalidOperation(
        "getTransformFeedbackVarying: `program` must be "
        "linked.");
    return nullptr;
  }

  if (index >= LinkInfo()->transformFeedbackVaryings.size()) {
    mContext->ErrorInvalidValue(
        "getTransformFeedbackVarying: `index` is greater or "
        "equal to TRANSFORM_FEEDBACK_VARYINGS.");
    return nullptr;
  }

  RefPtr<WebGLActiveInfo> ret = LinkInfo()->transformFeedbackVaryings[index];
  return ret.forget();
}

bool WebGLProgram::UnmapUniformBlockName(const nsCString& mappedName,
                                         nsCString* const out_userName) const {
  nsCString baseMappedName;
  bool isArray;
  size_t arrayIndex;
  if (!ParseName(mappedName, &baseMappedName, &isArray, &arrayIndex))
    return false;

  nsCString baseUserName;
  if (!mVertShader->UnmapUniformBlockName(baseMappedName, &baseUserName) &&
      !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 ']'.
}

bool webgl::LinkedProgramInfo::FindAttrib(
    const nsCString& userName, const webgl::AttribInfo** const out) const {
  // VS inputs cannot be arrays or structures.
  // `userName` is thus always `baseUserName`.
  for (const auto& attrib : attribs) {
    if (attrib.mActiveInfo->mBaseUserName == userName) {
      *out = &attrib;
      return true;
    }
  }

  return false;
}

bool webgl::LinkedProgramInfo::FindUniform(
    const nsCString& userName, nsCString* const out_mappedName,
    size_t* const out_arrayIndex, webgl::UniformInfo** const out_info) const {
  nsCString baseUserName;
  bool isArray;
  size_t arrayIndex;
  if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex)) return false;

  webgl::UniformInfo* info = nullptr;
  for (const auto& uniform : uniforms) {
    if (uniform->mActiveInfo->mBaseUserName == baseUserName) {
      info = uniform;
      break;
    }
  }
  if (!info) return false;

  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::WebGLProgramBinding::Wrap(js, this, givenProto);
}

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgram, mVertShader, mFragShader)

NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLProgram, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLProgram, Release)

}  // namespace mozilla