Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  WebGLContextDraw.cpp   Sprache: 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 "nsPrintfCString.h"
#include "WebGLBuffer.h"
#include "WebGLContextUtils.h"
#include "WebGLFormats.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, boolconst 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, boolconst 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::clamp(x, 0, (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,
                                  boolconst 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);
    {
      std::unique_ptr<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);
  {
    const auto& attrib0 = mBoundVertexArray->AttribBinding(0);
    if (attrib0.layout.divisor) {
      gl->fVertexAttribDivisor(0, 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

Messung V0.5
C=95 H=94 G=94

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge