summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLContextDraw.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/canvas/WebGLContextDraw.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/WebGLContextDraw.cpp')
-rw-r--r--dom/canvas/WebGLContextDraw.cpp1309
1 files changed, 1309 insertions, 0 deletions
diff --git a/dom/canvas/WebGLContextDraw.cpp b/dom/canvas/WebGLContextDraw.cpp
new file mode 100644
index 0000000000..b61b5aeee2
--- /dev/null
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -0,0 +1,1309 @@
+/* -*- 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