forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1309 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1309 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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/. */
 | 
						|
 | 
						|
#include "WebGLContext.h"
 | 
						|
 | 
						|
#include "MozFramebuffer.h"
 | 
						|
#include "GLContext.h"
 | 
						|
#include "mozilla/CheckedInt.h"
 | 
						|
#include "mozilla/ProfilerLabels.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/StaticPrefs_webgl.h"
 | 
						|
#include "mozilla/UniquePtrExtensions.h"
 | 
						|
#include "nsPrintfCString.h"
 | 
						|
#include "WebGLBuffer.h"
 | 
						|
#include "WebGLContextUtils.h"
 | 
						|
#include "WebGLFramebuffer.h"
 | 
						|
#include "WebGLProgram.h"
 | 
						|
#include "WebGLRenderbuffer.h"
 | 
						|
#include "WebGLShader.h"
 | 
						|
#include "WebGLTexture.h"
 | 
						|
#include "WebGLTransformFeedback.h"
 | 
						|
#include "WebGLVertexArray.h"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
// For a Tegra workaround.
 | 
						|
static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
class ScopedResolveTexturesForDraw {
 | 
						|
  struct TexRebindRequest {
 | 
						|
    uint32_t texUnit;
 | 
						|
    WebGLTexture* tex;
 | 
						|
  };
 | 
						|
 | 
						|
  WebGLContext* const mWebGL;
 | 
						|
  std::vector<TexRebindRequest> mRebindRequests;
 | 
						|
 | 
						|
 public:
 | 
						|
  ScopedResolveTexturesForDraw(WebGLContext* webgl, bool* const out_error);
 | 
						|
  ~ScopedResolveTexturesForDraw();
 | 
						|
};
 | 
						|
 | 
						|
static bool ValidateNoSamplingFeedback(const WebGLTexture& tex,
 | 
						|
                                       const uint32_t sampledLevels,
 | 
						|
                                       const WebGLFramebuffer* const fb,
 | 
						|
                                       const uint32_t texUnit) {
 | 
						|
  if (!fb) return true;
 | 
						|
 | 
						|
  const auto& texAttachments = fb->GetCompletenessInfo()->texAttachments;
 | 
						|
  for (const auto& attach : texAttachments) {
 | 
						|
    if (attach->Texture() != &tex) continue;
 | 
						|
 | 
						|
    const auto& srcBase = tex.Es3_level_base();
 | 
						|
    const auto srcLast = srcBase + sampledLevels - 1;
 | 
						|
    const auto& dstLevel = attach->MipLevel();
 | 
						|
    if (MOZ_UNLIKELY(srcBase <= dstLevel && dstLevel <= srcLast)) {
 | 
						|
      const auto& webgl = tex.mContext;
 | 
						|
      const auto& texTargetStr = EnumString(tex.Target().get());
 | 
						|
      const auto& attachStr = EnumString(attach->mAttachmentPoint);
 | 
						|
      webgl->ErrorInvalidOperation(
 | 
						|
          "Texture level %u would be read by %s unit %u,"
 | 
						|
          " but written by framebuffer attachment %s,"
 | 
						|
          " which would be illegal feedback.",
 | 
						|
          dstLevel, texTargetStr.c_str(), texUnit, attachStr.c_str());
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
 | 
						|
    WebGLContext* webgl, bool* const out_error)
 | 
						|
    : mWebGL(webgl) {
 | 
						|
  const auto& fb = mWebGL->mBoundDrawFramebuffer;
 | 
						|
 | 
						|
  struct SamplerByTexUnit {
 | 
						|
    uint8_t texUnit;
 | 
						|
    const webgl::SamplerUniformInfo* sampler;
 | 
						|
  };
 | 
						|
  Vector<SamplerByTexUnit, 8> samplerByTexUnit;
 | 
						|
 | 
						|
  MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
 | 
						|
  const auto& samplerUniforms = mWebGL->mActiveProgramLinkInfo->samplerUniforms;
 | 
						|
  for (const auto& pUniform : samplerUniforms) {
 | 
						|
    const auto& uniform = *pUniform;
 | 
						|
    const auto& texList = uniform.texListForType;
 | 
						|
 | 
						|
    const auto& uniformBaseType = uniform.texBaseType;
 | 
						|
    for (const auto& texUnit : uniform.texUnits) {
 | 
						|
      MOZ_ASSERT(texUnit < texList.Length());
 | 
						|
 | 
						|
      {
 | 
						|
        decltype(SamplerByTexUnit::sampler) prevSamplerForTexUnit = nullptr;
 | 
						|
        for (const auto& cur : samplerByTexUnit) {
 | 
						|
          if (cur.texUnit == texUnit) {
 | 
						|
            prevSamplerForTexUnit = cur.sampler;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (!prevSamplerForTexUnit) {
 | 
						|
          prevSamplerForTexUnit = &uniform;
 | 
						|
          MOZ_RELEASE_ASSERT(samplerByTexUnit.append(
 | 
						|
              SamplerByTexUnit{texUnit, prevSamplerForTexUnit}));
 | 
						|
        }
 | 
						|
 | 
						|
        if (MOZ_UNLIKELY(&uniform.texListForType !=
 | 
						|
                         &prevSamplerForTexUnit->texListForType)) {
 | 
						|
          // Pointing to different tex lists means different types!
 | 
						|
          const auto linkInfo = mWebGL->mActiveProgramLinkInfo;
 | 
						|
          const auto LocInfoBySampler = [&](const webgl::SamplerUniformInfo* p)
 | 
						|
              -> const webgl::LocationInfo* {
 | 
						|
            for (const auto& pair : linkInfo->locationMap) {
 | 
						|
              const auto& locInfo = pair.second;
 | 
						|
              if (locInfo.samplerInfo == p) {
 | 
						|
                return &locInfo;
 | 
						|
              }
 | 
						|
            }
 | 
						|
            MOZ_CRASH("Can't find sampler location.");
 | 
						|
          };
 | 
						|
          const auto& cur = *LocInfoBySampler(&uniform);
 | 
						|
          const auto& prev = *LocInfoBySampler(prevSamplerForTexUnit);
 | 
						|
          mWebGL->ErrorInvalidOperation(
 | 
						|
              "Tex unit %u referenced by samplers of different types:"
 | 
						|
              " %s (via %s) and %s (via %s).",
 | 
						|
              texUnit, EnumString(cur.info.info.elemType).c_str(),
 | 
						|
              cur.PrettyName().c_str(),
 | 
						|
              EnumString(prev.info.info.elemType).c_str(),
 | 
						|
              prev.PrettyName().c_str());
 | 
						|
          *out_error = true;
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      const auto& tex = texList[texUnit];
 | 
						|
      if (!tex) continue;
 | 
						|
 | 
						|
      const auto& sampler = mWebGL->mBoundSamplers[texUnit];
 | 
						|
      const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
 | 
						|
      if (MOZ_UNLIKELY(!samplingInfo)) {  // There was an error.
 | 
						|
        *out_error = true;
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      if (MOZ_UNLIKELY(!samplingInfo->IsComplete())) {
 | 
						|
        if (samplingInfo->incompleteReason) {
 | 
						|
          const auto& targetName = GetEnumName(tex->Target().get());
 | 
						|
          mWebGL->GenerateWarning("%s at unit %u is incomplete: %s", targetName,
 | 
						|
                                  texUnit, samplingInfo->incompleteReason);
 | 
						|
        }
 | 
						|
        mRebindRequests.push_back({texUnit, tex});
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // We have more validation to do if we're otherwise complete:
 | 
						|
      const auto& texBaseType = samplingInfo->usage->format->baseType;
 | 
						|
      if (MOZ_UNLIKELY(texBaseType != uniformBaseType)) {
 | 
						|
        const auto& targetName = GetEnumName(tex->Target().get());
 | 
						|
        const auto& srcType = ToString(texBaseType);
 | 
						|
        const auto& dstType = ToString(uniformBaseType);
 | 
						|
        mWebGL->ErrorInvalidOperation(
 | 
						|
            "%s at unit %u is of type %s, but"
 | 
						|
            " the shader samples as %s.",
 | 
						|
            targetName, texUnit, srcType, dstType);
 | 
						|
        *out_error = true;
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (MOZ_UNLIKELY(uniform.isShadowSampler !=
 | 
						|
                       samplingInfo->isDepthTexCompare)) {
 | 
						|
        const auto& targetName = GetEnumName(tex->Target().get());
 | 
						|
        mWebGL->ErrorInvalidOperation(
 | 
						|
            "%s at unit %u is%s a depth texture"
 | 
						|
            " with TEXTURE_COMPARE_MODE, but"
 | 
						|
            " the shader sampler is%s a shadow"
 | 
						|
            " sampler.",
 | 
						|
            targetName, texUnit, samplingInfo->isDepthTexCompare ? "" : " not",
 | 
						|
            uniform.isShadowSampler ? "" : " not");
 | 
						|
        *out_error = true;
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (MOZ_UNLIKELY(!ValidateNoSamplingFeedback(*tex, samplingInfo->levels,
 | 
						|
                                                   fb.get(), texUnit))) {
 | 
						|
        *out_error = true;
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const auto& gl = mWebGL->gl;
 | 
						|
  for (const auto& itr : mRebindRequests) {
 | 
						|
    gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
 | 
						|
    GLuint incompleteTex = 0;  // Tex 0 is always incomplete.
 | 
						|
    const auto& overrideTex = webgl->mIncompleteTexOverride;
 | 
						|
    if (overrideTex) {
 | 
						|
      // In all but the simplest cases, this will be incomplete anyway, since
 | 
						|
      // e.g. int-samplers need int-textures. This is useful for e.g.
 | 
						|
      // dom-to-texture failures, though.
 | 
						|
      incompleteTex = overrideTex->name;
 | 
						|
    }
 | 
						|
    gl->fBindTexture(itr.tex->Target().get(), incompleteTex);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
 | 
						|
  if (mRebindRequests.empty()) return;
 | 
						|
 | 
						|
  gl::GLContext* gl = mWebGL->gl;
 | 
						|
 | 
						|
  for (const auto& itr : mRebindRequests) {
 | 
						|
    gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
 | 
						|
    gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
 | 
						|
  }
 | 
						|
 | 
						|
  gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
bool WebGLContext::ValidateStencilParamsForDrawCall() const {
 | 
						|
  const auto stencilBits = [&]() -> uint8_t {
 | 
						|
    if (!mStencilTestEnabled) return 0;
 | 
						|
 | 
						|
    if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0;
 | 
						|
 | 
						|
    if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment()) return 8;
 | 
						|
 | 
						|
    if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
 | 
						|
      return 8;
 | 
						|
 | 
						|
    return 0;
 | 
						|
  }();
 | 
						|
  const uint32_t stencilMax = (1 << stencilBits) - 1;
 | 
						|
 | 
						|
  const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
 | 
						|
  const auto fnClamp = [&](const int32_t x) {
 | 
						|
    return std::max(0, std::min(x, (int32_t)stencilMax));
 | 
						|
  };
 | 
						|
 | 
						|
  bool ok = true;
 | 
						|
  ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
 | 
						|
  ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
 | 
						|
  ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
 | 
						|
 | 
						|
  if (!ok) {
 | 
						|
    ErrorInvalidOperation(
 | 
						|
        "Stencil front/back state must effectively match."
 | 
						|
        " (before front/back comparison, WRITEMASK and VALUE_MASK"
 | 
						|
        " are masked with (2^s)-1, and REF is clamped to"
 | 
						|
        " [0, (2^s)-1], where `s` is the number of enabled stencil"
 | 
						|
        " bits in the draw framebuffer)");
 | 
						|
  }
 | 
						|
  return ok;
 | 
						|
}
 | 
						|
 | 
						|
// -
 | 
						|
 | 
						|
void WebGLContext::GenErrorIllegalUse(const GLenum useTarget,
 | 
						|
                                      const uint32_t useId,
 | 
						|
                                      const GLenum boundTarget,
 | 
						|
                                      const uint32_t boundId) const {
 | 
						|
  const auto fnName = [&](const GLenum target, const uint32_t id) {
 | 
						|
    auto name = nsCString(EnumString(target).c_str());
 | 
						|
    if (id != static_cast<uint32_t>(-1)) {
 | 
						|
      name += nsPrintfCString("[%u]", id);
 | 
						|
    }
 | 
						|
    return name;
 | 
						|
  };
 | 
						|
  const auto& useName = fnName(useTarget, useId);
 | 
						|
  const auto& boundName = fnName(boundTarget, boundId);
 | 
						|
  GenerateError(LOCAL_GL_INVALID_OPERATION,
 | 
						|
                "Illegal use of buffer at %s"
 | 
						|
                " while also bound to %s.",
 | 
						|
                useName.BeginReading(), boundName.BeginReading());
 | 
						|
}
 | 
						|
 | 
						|
bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
 | 
						|
                                          const GLenum nonTfTarget,
 | 
						|
                                          const uint32_t nonTfId) const {
 | 
						|
  bool dupe = false;
 | 
						|
  const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
 | 
						|
  for (const auto& cur : tfAttribs) {
 | 
						|
    dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
 | 
						|
  }
 | 
						|
  if (MOZ_LIKELY(!dupe)) return true;
 | 
						|
 | 
						|
  dupe = false;
 | 
						|
  for (const auto tfId : IntegerRange(tfAttribs.size())) {
 | 
						|
    const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
 | 
						|
    if (&nonTfBuffer == tfBuffer) {
 | 
						|
      dupe = true;
 | 
						|
      GenErrorIllegalUse(nonTfTarget, nonTfId,
 | 
						|
                         LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(dupe);
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool WebGLContext::ValidateBuffersForTf(
 | 
						|
    const WebGLTransformFeedback& tfo,
 | 
						|
    const webgl::LinkedProgramInfo& linkInfo) const {
 | 
						|
  size_t numUsed;
 | 
						|
  switch (linkInfo.transformFeedbackBufferMode) {
 | 
						|
    case LOCAL_GL_INTERLEAVED_ATTRIBS:
 | 
						|
      numUsed = 1;
 | 
						|
      break;
 | 
						|
 | 
						|
    case LOCAL_GL_SEPARATE_ATTRIBS:
 | 
						|
      numUsed = linkInfo.active.activeTfVaryings.size();
 | 
						|
      break;
 | 
						|
 | 
						|
    default:
 | 
						|
      MOZ_CRASH();
 | 
						|
  }
 | 
						|
 | 
						|
  std::vector<webgl::BufferAndIndex> tfBuffers;
 | 
						|
  tfBuffers.reserve(numUsed);
 | 
						|
  for (const auto i : IntegerRange(numUsed)) {
 | 
						|
    tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
 | 
						|
                         static_cast<uint32_t>(i)});
 | 
						|
  }
 | 
						|
 | 
						|
  return ValidateBuffersForTf(tfBuffers);
 | 
						|
}
 | 
						|
 | 
						|
bool WebGLContext::ValidateBuffersForTf(
 | 
						|
    const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
 | 
						|
  bool dupe = false;
 | 
						|
  const auto fnCheck = [&](const WebGLBuffer* const nonTf,
 | 
						|
                           const GLenum nonTfTarget, const uint32_t nonTfId) {
 | 
						|
    for (const auto& tf : tfBuffers) {
 | 
						|
      dupe |= (nonTf && tf.buffer == nonTf);
 | 
						|
    }
 | 
						|
 | 
						|
    if (MOZ_LIKELY(!dupe)) return false;
 | 
						|
 | 
						|
    for (const auto& tf : tfBuffers) {
 | 
						|
      if (nonTf && tf.buffer == nonTf) {
 | 
						|
        dupe = true;
 | 
						|
        GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
 | 
						|
                           nonTfTarget, nonTfId);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  };
 | 
						|
 | 
						|
  fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
 | 
						|
  fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
 | 
						|
  fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
 | 
						|
  fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
 | 
						|
  fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
 | 
						|
  // fnCheck(mBoundTransformFeedbackBuffer.get(),
 | 
						|
  // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
 | 
						|
  fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
 | 
						|
 | 
						|
  for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
 | 
						|
    const auto& cur = mIndexedUniformBufferBindings[i];
 | 
						|
    fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
 | 
						|
  }
 | 
						|
 | 
						|
  fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
 | 
						|
          LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
 | 
						|
  for (const auto i : IntegerRange(MaxVertexAttribs())) {
 | 
						|
    const auto& binding = mBoundVertexArray->AttribBinding(i);
 | 
						|
    fnCheck(binding.buffer.get(), LOCAL_GL_ARRAY_BUFFER, i);
 | 
						|
  }
 | 
						|
 | 
						|
  return !dupe;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
template <typename T>
 | 
						|
static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
 | 
						|
  std::vector<T> intersection;
 | 
						|
  std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
 | 
						|
                        std::back_inserter(intersection));
 | 
						|
  return !intersection.empty();
 | 
						|
}
 | 
						|
 | 
						|
template <size_t N>
 | 
						|
static size_t FindFirstOne(const std::bitset<N>& bs) {
 | 
						|
  MOZ_ASSERT(bs.any());
 | 
						|
  // We don't need this to be fast, so don't bother with CLZ intrinsics.
 | 
						|
  for (const auto i : IntegerRange(N)) {
 | 
						|
    if (bs[i]) return i;
 | 
						|
  }
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
 | 
						|
                                                 const GLenum mode,
 | 
						|
                                                 const uint32_t instanceCount) {
 | 
						|
  if (!webgl->BindCurFBForDraw()) return nullptr;
 | 
						|
 | 
						|
  const auto& fb = webgl->mBoundDrawFramebuffer;
 | 
						|
  if (fb) {
 | 
						|
    const auto& info = *fb->GetCompletenessInfo();
 | 
						|
    const auto isF32WithBlending = info.isAttachmentF32 & webgl->mBlendEnabled;
 | 
						|
    if (isF32WithBlending.any()) {
 | 
						|
      if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
 | 
						|
        const auto first = FindFirstOne(isF32WithBlending);
 | 
						|
        webgl->ErrorInvalidOperation(
 | 
						|
            "Attachment %u is float32 with blending enabled, which requires "
 | 
						|
            "EXT_float_blend.",
 | 
						|
            uint32_t(first));
 | 
						|
        return nullptr;
 | 
						|
      }
 | 
						|
      webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  switch (mode) {
 | 
						|
    case LOCAL_GL_TRIANGLES:
 | 
						|
    case LOCAL_GL_TRIANGLE_STRIP:
 | 
						|
    case LOCAL_GL_TRIANGLE_FAN:
 | 
						|
    case LOCAL_GL_POINTS:
 | 
						|
    case LOCAL_GL_LINE_STRIP:
 | 
						|
    case LOCAL_GL_LINE_LOOP:
 | 
						|
    case LOCAL_GL_LINES:
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      webgl->ErrorInvalidEnumInfo("mode", mode);
 | 
						|
      return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
 | 
						|
 | 
						|
  if (!webgl->mActiveProgramLinkInfo) {
 | 
						|
    webgl->ErrorInvalidOperation("The current program is not linked.");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  const auto& linkInfo = webgl->mActiveProgramLinkInfo;
 | 
						|
 | 
						|
  // -
 | 
						|
  // Check UBO sizes.
 | 
						|
 | 
						|
  for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
 | 
						|
    const auto& cur = linkInfo->uniformBlocks[i];
 | 
						|
    const auto& dataSize = cur.info.dataSize;
 | 
						|
    const auto& binding = cur.binding;
 | 
						|
    if (!binding) {
 | 
						|
      webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto availByteCount = binding->ByteCount();
 | 
						|
    if (dataSize > availByteCount) {
 | 
						|
      webgl->ErrorInvalidOperation(
 | 
						|
          "Buffer for uniform block is smaller"
 | 
						|
          " than UNIFORM_BLOCK_DATA_SIZE.");
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
 | 
						|
                                       LOCAL_GL_UNIFORM_BUFFER, i))
 | 
						|
      return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const auto& tfo = webgl->mBoundTransformFeedback;
 | 
						|
  if (tfo && tfo->IsActiveAndNotPaused()) {
 | 
						|
    if (fb) {
 | 
						|
      const auto& info = *fb->GetCompletenessInfo();
 | 
						|
      if (info.isMultiview) {
 | 
						|
        webgl->ErrorInvalidOperation(
 | 
						|
            "Cannot render to multiview with transform feedback.");
 | 
						|
        return nullptr;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const auto& fragOutputs = linkInfo->fragOutputs;
 | 
						|
  const auto fnValidateFragOutputType =
 | 
						|
      [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
 | 
						|
        const auto itr = fragOutputs.find(loc);
 | 
						|
        MOZ_DIAGNOSTIC_ASSERT(itr != fragOutputs.end());
 | 
						|
 | 
						|
        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;
 | 
						|
      };
 | 
						|
 | 
						|
  if (!webgl->mRasterizerDiscardEnabled) {
 | 
						|
    uint8_t fbZLayerCount = 1;
 | 
						|
    auto hasAttachment = std::bitset<webgl::kMaxDrawBuffers>(1);
 | 
						|
    auto drawBufferEnabled = std::bitset<webgl::kMaxDrawBuffers>();
 | 
						|
    if (fb) {
 | 
						|
      drawBufferEnabled = fb->DrawBufferEnabled();
 | 
						|
      const auto& info = *fb->GetCompletenessInfo();
 | 
						|
      fbZLayerCount = info.zLayerCount;
 | 
						|
      hasAttachment = info.hasAttachment;
 | 
						|
    } else {
 | 
						|
      drawBufferEnabled[0] = (webgl->mDefaultFB_DrawBuffer0 == LOCAL_GL_BACK);
 | 
						|
    }
 | 
						|
 | 
						|
    if (fbZLayerCount != linkInfo->zLayerCount) {
 | 
						|
      webgl->ErrorInvalidOperation(
 | 
						|
          "Multiview count mismatch: shader: %u, framebuffer: %u",
 | 
						|
          uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto writable =
 | 
						|
        hasAttachment & drawBufferEnabled & webgl->mColorWriteMaskNonzero;
 | 
						|
    if (writable.any()) {
 | 
						|
      // Do we have any undefined outputs with real attachments that
 | 
						|
      // aren't masked-out by color write mask or drawBuffers?
 | 
						|
      const auto wouldWriteUndefined = ~linkInfo->hasOutput & writable;
 | 
						|
      if (wouldWriteUndefined.any()) {
 | 
						|
        const auto first = FindFirstOne(wouldWriteUndefined);
 | 
						|
        webgl->ErrorInvalidOperation(
 | 
						|
            "Program has no frag output at location %u, the"
 | 
						|
            " destination draw buffer has an attached"
 | 
						|
            " image, and its color write mask is not all false,"
 | 
						|
            " and DRAW_BUFFER%u is not NONE.",
 | 
						|
            uint32_t(first), uint32_t(first));
 | 
						|
        return nullptr;
 | 
						|
      }
 | 
						|
 | 
						|
      const auto outputWrites = linkInfo->hasOutput & writable;
 | 
						|
 | 
						|
      if (fb) {
 | 
						|
        for (const auto& attach : fb->ColorDrawBuffers()) {
 | 
						|
          const auto i =
 | 
						|
              uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
 | 
						|
          if (!outputWrites[i]) continue;
 | 
						|
          const auto& imageInfo = attach->GetImageInfo();
 | 
						|
          if (!imageInfo) continue;
 | 
						|
          const auto& dstBaseType = imageInfo->mFormat->format->baseType;
 | 
						|
          if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        if (outputWrites[0]) {
 | 
						|
          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));
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (tfo) {
 | 
						|
    for (const auto& used : fetchLimits->usedBuffers) {
 | 
						|
      MOZ_ASSERT(used.buffer);
 | 
						|
      if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
 | 
						|
                                         used.id))
 | 
						|
        return nullptr;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  webgl->RunContextLossTimer();
 | 
						|
 | 
						|
  return fetchLimits;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
 | 
						|
  uint8_t vertsPerPrim;
 | 
						|
 | 
						|
  switch (mode) {
 | 
						|
    case LOCAL_GL_POINTS:
 | 
						|
      vertsPerPrim = 1;
 | 
						|
      break;
 | 
						|
    case LOCAL_GL_LINES:
 | 
						|
      vertsPerPrim = 2;
 | 
						|
      break;
 | 
						|
    case LOCAL_GL_TRIANGLES:
 | 
						|
      vertsPerPrim = 3;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      MOZ_CRASH("`mode`");
 | 
						|
  }
 | 
						|
 | 
						|
  return vertCount / vertsPerPrim * vertsPerPrim;
 | 
						|
}
 | 
						|
 | 
						|
class ScopedDrawWithTransformFeedback final {
 | 
						|
  WebGLContext* const mWebGL;
 | 
						|
  WebGLTransformFeedback* const mTFO;
 | 
						|
  const bool mWithTF;
 | 
						|
  uint32_t mUsedVerts;
 | 
						|
 | 
						|
 public:
 | 
						|
  ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
 | 
						|
                                  uint32_t vertCount, uint32_t instanceCount,
 | 
						|
                                  bool* const out_error)
 | 
						|
      : mWebGL(webgl),
 | 
						|
        mTFO(mWebGL->mBoundTransformFeedback),
 | 
						|
        mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
 | 
						|
        mUsedVerts(0) {
 | 
						|
    *out_error = false;
 | 
						|
    if (!mWithTF) return;
 | 
						|
 | 
						|
    if (mode != mTFO->mActive_PrimMode) {
 | 
						|
      mWebGL->ErrorInvalidOperation(
 | 
						|
          "Drawing with transform feedback requires"
 | 
						|
          " `mode` to match BeginTransformFeedback's"
 | 
						|
          " `primitiveMode`.");
 | 
						|
      *out_error = true;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
 | 
						|
    const auto usedVerts =
 | 
						|
        CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
 | 
						|
 | 
						|
    const auto remainingCapacity =
 | 
						|
        mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
 | 
						|
    if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
 | 
						|
      mWebGL->ErrorInvalidOperation(
 | 
						|
          "Insufficient buffer capacity remaining for"
 | 
						|
          " transform feedback.");
 | 
						|
      *out_error = true;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    mUsedVerts = usedVerts.value();
 | 
						|
  }
 | 
						|
 | 
						|
  void Advance() const {
 | 
						|
    if (!mWithTF) return;
 | 
						|
 | 
						|
    mTFO->mActive_VertPosition += mUsedVerts;
 | 
						|
 | 
						|
    for (const auto& cur : mTFO->mIndexedBindings) {
 | 
						|
      const auto& buffer = cur.mBufferBinding;
 | 
						|
      if (buffer) {
 | 
						|
        buffer->ResetLastUpdateFenceId();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
static bool HasInstancedDrawing(const WebGLContext& webgl) {
 | 
						|
  return webgl.IsWebGL2() ||
 | 
						|
         webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
void WebGLContext::DrawArraysInstanced(const GLenum mode, const GLint first,
 | 
						|
                                       const GLsizei iVertCount,
 | 
						|
                                       const GLsizei instanceCount) {
 | 
						|
  const FuncScope funcScope(*this, "drawArraysInstanced");
 | 
						|
  // AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
 | 
						|
  if (IsContextLost()) return;
 | 
						|
  const gl::GLContext::TlsScope inTls(gl);
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  if (!ValidateNonNegative("first", first) ||
 | 
						|
      !ValidateNonNegative("vertCount", iVertCount) ||
 | 
						|
      !ValidateNonNegative("instanceCount", instanceCount)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  const auto vertCount = AssertedCast<uint32_t>(iVertCount);
 | 
						|
 | 
						|
  if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
 | 
						|
    MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
 | 
						|
    if (mPrimRestartTypeBytes != 0) {
 | 
						|
      mPrimRestartTypeBytes = 0;
 | 
						|
 | 
						|
      // OSX appears to have severe perf issues with leaving this enabled.
 | 
						|
      gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
 | 
						|
  if (!fetchLimits) return;
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
 | 
						|
  if (!totalVertCount_safe.isValid()) {
 | 
						|
    ErrorOutOfMemory("`first+vertCount` out of range.");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  auto totalVertCount = totalVertCount_safe.value();
 | 
						|
 | 
						|
  if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
 | 
						|
    ErrorInvalidOperation(
 | 
						|
        "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
 | 
						|
        uint32_t(fetchLimits->maxVerts));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (vertCount > mMaxVertIdsPerDraw) {
 | 
						|
    ErrorOutOfMemory(
 | 
						|
        "Context's max vertCount is %u, but %u requested. "
 | 
						|
        "[webgl.max-vert-ids-per-draw]",
 | 
						|
        mMaxVertIdsPerDraw, vertCount);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  bool error = false;
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const ScopedResolveTexturesForDraw scopedResolve(this, &error);
 | 
						|
  if (error) return;
 | 
						|
 | 
						|
  const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
 | 
						|
                                                 instanceCount, &error);
 | 
						|
  if (error) return;
 | 
						|
 | 
						|
  // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
 | 
						|
  // attribs are fetched from. There are two ways to fix this:
 | 
						|
  // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
 | 
						|
  // `byteOffset`
 | 
						|
  // 2. OR offset all non-instanced vertex attrib pointers back, and call
 | 
						|
  // DrawArrays with first:0.
 | 
						|
  //   * But now gl_VertexID will be wrong! So we inject a uniform to offset it
 | 
						|
  //   back correctly.
 | 
						|
  // #1 ought to be the lowest overhead for any first>0,
 | 
						|
  // but DrawElements can't be used with transform-feedback,
 | 
						|
  // so we need #2 to also work.
 | 
						|
  // For now, only implement #2.
 | 
						|
 | 
						|
  const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
 | 
						|
 | 
						|
  auto driverFirst = first;
 | 
						|
 | 
						|
  if (first && mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst) {
 | 
						|
    // This is not particularly optimized, but we can if we need to.
 | 
						|
    bool hasInstancedUserAttrib = false;
 | 
						|
    bool hasVertexAttrib = false;
 | 
						|
    for (const auto& a : activeAttribs) {
 | 
						|
      if (a.location == -1) {
 | 
						|
        if (a.name == "gl_VertexID") {
 | 
						|
          hasVertexAttrib = true;
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      const auto& binding = mBoundVertexArray->AttribBinding(a.location);
 | 
						|
      if (binding.layout.divisor) {
 | 
						|
        hasInstancedUserAttrib = true;
 | 
						|
      } else {
 | 
						|
        hasVertexAttrib = true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (hasInstancedUserAttrib && hasVertexAttrib) {
 | 
						|
      driverFirst = 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (driverFirst != first) {
 | 
						|
    for (const auto& a : activeAttribs) {
 | 
						|
      if (a.location == -1) continue;
 | 
						|
      const auto& binding = mBoundVertexArray->AttribBinding(a.location);
 | 
						|
      if (binding.layout.divisor) continue;
 | 
						|
 | 
						|
      mBoundVertexArray->DoVertexAttrib(a.location, first);
 | 
						|
    }
 | 
						|
 | 
						|
    gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, first);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 | 
						|
    auto fakeVertCount = uint64_t(driverFirst) + vertCount;
 | 
						|
    if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default) {
 | 
						|
      fakeVertCount = 0;
 | 
						|
    }
 | 
						|
    if (!(vertCount && instanceCount)) {
 | 
						|
      fakeVertCount = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    auto undoAttrib0 = MakeScopeExit([&]() {
 | 
						|
      MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
 | 
						|
                         WebGLVertexAttrib0Status::Default);
 | 
						|
      UndoFakeVertexAttrib0();
 | 
						|
    });
 | 
						|
    if (fakeVertCount) {
 | 
						|
      if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
 | 
						|
        error = true;
 | 
						|
        undoAttrib0.release();
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // No fake-verts needed.
 | 
						|
      undoAttrib0.release();
 | 
						|
    }
 | 
						|
 | 
						|
    ScopedDrawCallWrapper wrapper(*this);
 | 
						|
    if (vertCount && instanceCount) {
 | 
						|
      if (HasInstancedDrawing(*this)) {
 | 
						|
        gl->fDrawArraysInstanced(mode, driverFirst, vertCount, instanceCount);
 | 
						|
      } else {
 | 
						|
        MOZ_ASSERT(instanceCount == 1);
 | 
						|
        gl->fDrawArrays(mode, driverFirst, vertCount);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (driverFirst != first) {
 | 
						|
    gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, 0);
 | 
						|
 | 
						|
    for (const auto& a : activeAttribs) {
 | 
						|
      if (a.location == -1) continue;
 | 
						|
      const auto& binding = mBoundVertexArray->AttribBinding(a.location);
 | 
						|
      if (binding.layout.divisor) continue;
 | 
						|
 | 
						|
      mBoundVertexArray->DoVertexAttrib(a.location, 0);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Draw_cleanup();
 | 
						|
  scopedTF.Advance();
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
 | 
						|
                                              const GLenum type,
 | 
						|
                                              const WebGLintptr byteOffset,
 | 
						|
                                              const GLsizei instanceCount) {
 | 
						|
  if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
 | 
						|
      !mBoundTransformFeedback->mIsPaused) {
 | 
						|
    ErrorInvalidOperation(
 | 
						|
        "DrawElements* functions are incompatible with"
 | 
						|
        " transform feedback.");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!ValidateNonNegative("vertCount", rawIndexCount) ||
 | 
						|
      !ValidateNonNegative("byteOffset", byteOffset) ||
 | 
						|
      !ValidateNonNegative("instanceCount", instanceCount)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  const auto indexCount = uint32_t(rawIndexCount);
 | 
						|
 | 
						|
  uint8_t bytesPerIndex = 0;
 | 
						|
  switch (type) {
 | 
						|
    case LOCAL_GL_UNSIGNED_BYTE:
 | 
						|
      bytesPerIndex = 1;
 | 
						|
      break;
 | 
						|
 | 
						|
    case LOCAL_GL_UNSIGNED_SHORT:
 | 
						|
      bytesPerIndex = 2;
 | 
						|
      break;
 | 
						|
 | 
						|
    case LOCAL_GL_UNSIGNED_INT:
 | 
						|
      if (IsWebGL2() ||
 | 
						|
          IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
 | 
						|
        bytesPerIndex = 4;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
  }
 | 
						|
  if (!bytesPerIndex) {
 | 
						|
    ErrorInvalidEnumInfo("type", type);
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  if (byteOffset % bytesPerIndex != 0) {
 | 
						|
    ErrorInvalidOperation(
 | 
						|
        "`byteOffset` must be a multiple of the size of `type`");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  ////
 | 
						|
 | 
						|
  if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
 | 
						|
    MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
 | 
						|
    if (mPrimRestartTypeBytes != bytesPerIndex) {
 | 
						|
      mPrimRestartTypeBytes = bytesPerIndex;
 | 
						|
 | 
						|
      const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
 | 
						|
      gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
 | 
						|
      gl->fPrimitiveRestartIndex(ones);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  ////
 | 
						|
  // Index fetching
 | 
						|
 | 
						|
  const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
 | 
						|
  if (!indexBuffer) {
 | 
						|
    ErrorInvalidOperation("Index buffer not bound.");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  const size_t availBytes = indexBuffer->ByteLength();
 | 
						|
  const auto availIndices =
 | 
						|
      AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
 | 
						|
  if (instanceCount && indexCount > availIndices) {
 | 
						|
    ErrorInvalidOperation("Index buffer too small.");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  return indexBuffer.get();
 | 
						|
}
 | 
						|
 | 
						|
static void HandleDrawElementsErrors(
 | 
						|
    WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
 | 
						|
  const auto err = errorScope.GetError();
 | 
						|
  if (err == LOCAL_GL_INVALID_OPERATION) {
 | 
						|
    webgl->ErrorInvalidOperation(
 | 
						|
        "Driver rejected indexed draw call, possibly"
 | 
						|
        " due to out-of-bounds indices.");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(!err);
 | 
						|
  if (err) {
 | 
						|
    webgl->ErrorImplementationBug(
 | 
						|
        "Unexpected driver error during indexed draw"
 | 
						|
        " call. Please file a bug.");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WebGLContext::DrawElementsInstanced(const GLenum mode,
 | 
						|
                                         const GLsizei iIndexCount,
 | 
						|
                                         const GLenum type,
 | 
						|
                                         const WebGLintptr byteOffset,
 | 
						|
                                         const GLsizei instanceCount) {
 | 
						|
  const FuncScope funcScope(*this, "drawElementsInstanced");
 | 
						|
  // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
 | 
						|
  if (IsContextLost()) return;
 | 
						|
 | 
						|
  const gl::GLContext::TlsScope inTls(gl);
 | 
						|
 | 
						|
  const auto indexBuffer =
 | 
						|
      DrawElements_check(iIndexCount, type, byteOffset, instanceCount);
 | 
						|
  if (!indexBuffer) return;
 | 
						|
  const auto indexCount = AssertedCast<uint32_t>(iIndexCount);
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
 | 
						|
  if (!fetchLimits) return;
 | 
						|
 | 
						|
  const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 | 
						|
 | 
						|
  uint64_t fakeVertCount = 0;
 | 
						|
  if (whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default) {
 | 
						|
    fakeVertCount = fetchLimits->maxVerts;
 | 
						|
  }
 | 
						|
  if (!indexCount || !instanceCount) {
 | 
						|
    fakeVertCount = 0;
 | 
						|
  }
 | 
						|
  if (fakeVertCount == UINT64_MAX) {  // Ok well that's too many!
 | 
						|
    const auto exactMaxVertId =
 | 
						|
        indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
 | 
						|
    MOZ_RELEASE_ASSERT(exactMaxVertId);
 | 
						|
    fakeVertCount = uint32_t{*exactMaxVertId};
 | 
						|
    fakeVertCount += 1;
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  {
 | 
						|
    uint64_t indexCapacity = indexBuffer->ByteLength();
 | 
						|
    switch (type) {
 | 
						|
      case LOCAL_GL_UNSIGNED_BYTE:
 | 
						|
        break;
 | 
						|
      case LOCAL_GL_UNSIGNED_SHORT:
 | 
						|
        indexCapacity /= 2;
 | 
						|
        break;
 | 
						|
      case LOCAL_GL_UNSIGNED_INT:
 | 
						|
        indexCapacity /= 4;
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t maxVertId = 0;
 | 
						|
    const auto isFetchValid = [&]() {
 | 
						|
      if (!indexCount || !instanceCount) return true;
 | 
						|
 | 
						|
      const auto globalMaxVertId =
 | 
						|
          indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
 | 
						|
      if (!globalMaxVertId) return true;
 | 
						|
      if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
 | 
						|
 | 
						|
      const auto exactMaxVertId =
 | 
						|
          indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
 | 
						|
      maxVertId = exactMaxVertId.value();
 | 
						|
      return maxVertId < fetchLimits->maxVerts;
 | 
						|
    }();
 | 
						|
    if (!isFetchValid) {
 | 
						|
      ErrorInvalidOperation(
 | 
						|
          "Indexed vertex fetch requires %u vertices, but"
 | 
						|
          " attribs only supply %u.",
 | 
						|
          maxVertId + 1, uint32_t(fetchLimits->maxVerts));
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (indexCount > mMaxVertIdsPerDraw) {
 | 
						|
    ErrorOutOfMemory(
 | 
						|
        "Context's max indexCount is %u, but %u requested. "
 | 
						|
        "[webgl.max-vert-ids-per-draw]",
 | 
						|
        mMaxVertIdsPerDraw, indexCount);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  bool error = false;
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  auto undoAttrib0 = MakeScopeExit([&]() {
 | 
						|
    MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
 | 
						|
                       WebGLVertexAttrib0Status::Default);
 | 
						|
    UndoFakeVertexAttrib0();
 | 
						|
  });
 | 
						|
  if (fakeVertCount) {
 | 
						|
    if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
 | 
						|
      error = true;
 | 
						|
      undoAttrib0.release();
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // No fake-verts needed.
 | 
						|
    undoAttrib0.release();
 | 
						|
  }
 | 
						|
 | 
						|
  // -
 | 
						|
 | 
						|
  const ScopedResolveTexturesForDraw scopedResolve(this, &error);
 | 
						|
  if (error) return;
 | 
						|
 | 
						|
  {
 | 
						|
    ScopedDrawCallWrapper wrapper(*this);
 | 
						|
    {
 | 
						|
      UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
 | 
						|
      if (MOZ_UNLIKELY(gl->IsANGLE() &&
 | 
						|
                       gl->mDebugFlags &
 | 
						|
                           gl::GLContext::DebugFlagAbortOnError)) {
 | 
						|
        // ANGLE does range validation even when it doesn't need to.
 | 
						|
        // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
 | 
						|
        errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
 | 
						|
      }
 | 
						|
 | 
						|
      if (indexCount && instanceCount) {
 | 
						|
        if (HasInstancedDrawing(*this)) {
 | 
						|
          gl->fDrawElementsInstanced(mode, indexCount, type,
 | 
						|
                                     reinterpret_cast<GLvoid*>(byteOffset),
 | 
						|
                                     instanceCount);
 | 
						|
        } else {
 | 
						|
          MOZ_ASSERT(instanceCount == 1);
 | 
						|
          gl->fDrawElements(mode, indexCount, type,
 | 
						|
                            reinterpret_cast<GLvoid*>(byteOffset));
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (errorScope) {
 | 
						|
        HandleDrawElementsErrors(this, *errorScope);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Draw_cleanup();
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////
 | 
						|
 | 
						|
void WebGLContext::Draw_cleanup() {
 | 
						|
  if (gl->WorkAroundDriverBugs()) {
 | 
						|
    if (gl->Renderer() == gl::GLRenderer::Tegra) {
 | 
						|
      mDrawCallsSinceLastFlush++;
 | 
						|
 | 
						|
      if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
 | 
						|
        gl->fFlush();
 | 
						|
        mDrawCallsSinceLastFlush = 0;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Let's check for a really common error: Viewport is larger than the actual
 | 
						|
  // destination framebuffer.
 | 
						|
  uint32_t destWidth;
 | 
						|
  uint32_t destHeight;
 | 
						|
  if (mBoundDrawFramebuffer) {
 | 
						|
    const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
 | 
						|
    destWidth = info->width;
 | 
						|
    destHeight = info->height;
 | 
						|
  } else {
 | 
						|
    destWidth = mDefaultFB->mSize.width;
 | 
						|
    destHeight = mDefaultFB->mSize.height;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mViewportWidth > int32_t(destWidth) ||
 | 
						|
      mViewportHeight > int32_t(destHeight)) {
 | 
						|
    if (!mAlreadyWarnedAboutViewportLargerThanDest) {
 | 
						|
      GenerateWarning(
 | 
						|
          "Drawing to a destination rect smaller than the viewport"
 | 
						|
          " rect. (This warning will only be given once)");
 | 
						|
      mAlreadyWarnedAboutViewportLargerThanDest = true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
 | 
						|
  MOZ_ASSERT(mCurrentProgram);
 | 
						|
  MOZ_ASSERT(mActiveProgramLinkInfo);
 | 
						|
 | 
						|
  bool legacyAttrib0 = mNeedsLegacyVertexAttrib0Handling;
 | 
						|
  if (gl->WorkAroundDriverBugs() && kIsMacOS) {
 | 
						|
    // Also programs with no attribs:
 | 
						|
    // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
 | 
						|
    const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
 | 
						|
    bool hasNonInstancedUserAttrib = false;
 | 
						|
    for (const auto& a : activeAttribs) {
 | 
						|
      if (a.location == -1) continue;
 | 
						|
      const auto& layout = mBoundVertexArray->AttribBinding(a.location).layout;
 | 
						|
      if (layout.divisor == 0) {
 | 
						|
        hasNonInstancedUserAttrib = true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    legacyAttrib0 |= !hasNonInstancedUserAttrib;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
 | 
						|
  MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling,
 | 
						|
                     "Invariant need because this turns on index buffer "
 | 
						|
                     "validation, needed for fake-attrib0.");
 | 
						|
 | 
						|
  if (!mActiveProgramLinkInfo->attrib0Active) {
 | 
						|
    // Attrib0 unused, so just ensure that the legacy code has enough buffer.
 | 
						|
    return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto& isAttribArray0Enabled =
 | 
						|
      mBoundVertexArray->AttribBinding(0).layout.isArray;
 | 
						|
  return isAttribArray0Enabled
 | 
						|
             ? WebGLVertexAttrib0Status::Default
 | 
						|
             : WebGLVertexAttrib0Status::EmulatedInitializedArray;
 | 
						|
}
 | 
						|
 | 
						|
bool WebGLContext::DoFakeVertexAttrib0(
 | 
						|
    const uint64_t fakeVertexCount,
 | 
						|
    const WebGLVertexAttrib0Status whatDoesAttrib0Need) {
 | 
						|
  MOZ_ASSERT(fakeVertexCount);
 | 
						|
  MOZ_RELEASE_ASSERT(whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default);
 | 
						|
 | 
						|
  if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
 | 
						|
    // Padded/strided to vec4, so 4x4bytes.
 | 
						|
    const auto effectiveVertAttribBytes =
 | 
						|
        CheckedInt<int32_t>(fakeVertexCount) * 4 * 4;
 | 
						|
    if (!effectiveVertAttribBytes.isValid()) {
 | 
						|
      ErrorOutOfMemory("`offset + count` too large for Mesa.");
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
 | 
						|
    GenerateWarning(
 | 
						|
        "Drawing without vertex attrib 0 array enabled forces the browser "
 | 
						|
        "to do expensive emulation work when running on desktop OpenGL "
 | 
						|
        "platforms, for example on Mac. It is preferable to always draw "
 | 
						|
        "with vertex attrib 0 array enabled, by using bindAttribLocation "
 | 
						|
        "to bind some always-used attribute to location 0.");
 | 
						|
    mAlreadyWarnedAboutFakeVertexAttrib0 = true;
 | 
						|
  }
 | 
						|
 | 
						|
  gl->fEnableVertexAttribArray(0);
 | 
						|
 | 
						|
  if (!mFakeVertexAttrib0BufferObject) {
 | 
						|
    gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
 | 
						|
    mFakeVertexAttrib0BufferObjectSize = 0;
 | 
						|
  }
 | 
						|
  gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
 | 
						|
 | 
						|
  ////
 | 
						|
 | 
						|
  switch (mGenericVertexAttribTypes[0]) {
 | 
						|
    case webgl::AttribBaseType::Boolean:
 | 
						|
    case webgl::AttribBaseType::Float:
 | 
						|
      gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
 | 
						|
      break;
 | 
						|
 | 
						|
    case webgl::AttribBaseType::Int:
 | 
						|
      gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
 | 
						|
      break;
 | 
						|
 | 
						|
    case webgl::AttribBaseType::Uint:
 | 
						|
      gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  ////
 | 
						|
 | 
						|
  const auto maxFakeVerts = StaticPrefs::webgl_fake_verts_max();
 | 
						|
  if (fakeVertexCount > maxFakeVerts) {
 | 
						|
    ErrorOutOfMemory(
 | 
						|
        "Draw requires faking a vertex attrib 0 array, but required vert count"
 | 
						|
        " (%" PRIu64 ") is more than webgl.fake-verts.max (%u).",
 | 
						|
        fakeVertexCount, maxFakeVerts);
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
 | 
						|
  const auto checked_dataSize =
 | 
						|
      CheckedInt<intptr_t>(fakeVertexCount) * bytesPerVert;
 | 
						|
  if (!checked_dataSize.isValid()) {
 | 
						|
    ErrorOutOfMemory(
 | 
						|
        "Integer overflow trying to construct a fake vertex attrib 0"
 | 
						|
        " array for a draw-operation with %" PRIu64
 | 
						|
        " vertices. Try"
 | 
						|
        " reducing the number of vertices.",
 | 
						|
        fakeVertexCount);
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  const auto dataSize = checked_dataSize.value();
 | 
						|
 | 
						|
  if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
 | 
						|
    gl::GLContext::LocalErrorScope errorScope(*gl);
 | 
						|
 | 
						|
    gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
 | 
						|
                    LOCAL_GL_DYNAMIC_DRAW);
 | 
						|
 | 
						|
    const auto err = errorScope.GetError();
 | 
						|
    if (err) {
 | 
						|
      ErrorOutOfMemory(
 | 
						|
          "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize);
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    mFakeVertexAttrib0BufferObjectSize = dataSize;
 | 
						|
    mFakeVertexAttrib0DataDefined = false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (whatDoesAttrib0Need ==
 | 
						|
      WebGLVertexAttrib0Status::EmulatedUninitializedArray)
 | 
						|
    return true;
 | 
						|
 | 
						|
  ////
 | 
						|
 | 
						|
  if (mFakeVertexAttrib0DataDefined &&
 | 
						|
      memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
 | 
						|
          0) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  ////
 | 
						|
 | 
						|
  const auto data = UniqueBuffer::Take(malloc(dataSize));
 | 
						|
  if (!data) {
 | 
						|
    ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  auto itr = (uint8_t*)data.get();
 | 
						|
  const auto itrEnd = itr + dataSize;
 | 
						|
  while (itr != itrEnd) {
 | 
						|
    memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
 | 
						|
    itr += bytesPerVert;
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    gl::GLContext::LocalErrorScope errorScope(*gl);
 | 
						|
 | 
						|
    gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
 | 
						|
 | 
						|
    const auto err = errorScope.GetError();
 | 
						|
    if (err) {
 | 
						|
      ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  ////
 | 
						|
 | 
						|
  memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
 | 
						|
  mFakeVertexAttrib0DataDefined = true;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void WebGLContext::UndoFakeVertexAttrib0() {
 | 
						|
  static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
 | 
						|
  mBoundVertexArray->DoVertexAttrib(0);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 |