Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / canvas / WebGLContextDraw.cpp
blob31e0fe42e2059ff54f172ed6b4b0fb4fc554af4c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "WebGLContext.h"
8 #include "MozFramebuffer.h"
9 #include "GLContext.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/ProfilerLabels.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/StaticPrefs_webgl.h"
14 #include "mozilla/UniquePtrExtensions.h"
15 #include "nsPrintfCString.h"
16 #include "WebGLBuffer.h"
17 #include "WebGLContextUtils.h"
18 #include "WebGLFormats.h"
19 #include "WebGLFramebuffer.h"
20 #include "WebGLProgram.h"
21 #include "WebGLRenderbuffer.h"
22 #include "WebGLShader.h"
23 #include "WebGLTexture.h"
24 #include "WebGLTransformFeedback.h"
25 #include "WebGLVertexArray.h"
27 #include <algorithm>
29 namespace mozilla {
31 // For a Tegra workaround.
32 static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
34 ////////////////////////////////////////
36 class ScopedResolveTexturesForDraw {
37 struct TexRebindRequest {
38 uint32_t texUnit;
39 WebGLTexture* tex;
42 WebGLContext* const mWebGL;
43 std::vector<TexRebindRequest> mRebindRequests;
45 public:
46 ScopedResolveTexturesForDraw(WebGLContext* webgl, bool* const out_error);
47 ~ScopedResolveTexturesForDraw();
50 static bool ValidateNoSamplingFeedback(const WebGLTexture& tex,
51 const uint32_t sampledLevels,
52 const WebGLFramebuffer* const fb,
53 const uint32_t texUnit) {
54 if (!fb) return true;
56 const auto& texAttachments = fb->GetCompletenessInfo()->texAttachments;
57 for (const auto& attach : texAttachments) {
58 if (attach->Texture() != &tex) continue;
60 const auto& srcBase = tex.Es3_level_base();
61 const auto srcLast = srcBase + sampledLevels - 1;
62 const auto& dstLevel = attach->MipLevel();
63 if (MOZ_UNLIKELY(srcBase <= dstLevel && dstLevel <= srcLast)) {
64 const auto& webgl = tex.mContext;
65 const auto& texTargetStr = EnumString(tex.Target().get());
66 const auto& attachStr = EnumString(attach->mAttachmentPoint);
67 webgl->ErrorInvalidOperation(
68 "Texture level %u would be read by %s unit %u,"
69 " but written by framebuffer attachment %s,"
70 " which would be illegal feedback.",
71 dstLevel, texTargetStr.c_str(), texUnit, attachStr.c_str());
72 return false;
75 return true;
78 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
79 WebGLContext* webgl, bool* const out_error)
80 : mWebGL(webgl) {
81 const auto& fb = mWebGL->mBoundDrawFramebuffer;
83 struct SamplerByTexUnit {
84 uint8_t texUnit;
85 const webgl::SamplerUniformInfo* sampler;
87 Vector<SamplerByTexUnit, 8> samplerByTexUnit;
89 MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
90 const auto& samplerUniforms = mWebGL->mActiveProgramLinkInfo->samplerUniforms;
91 for (const auto& pUniform : samplerUniforms) {
92 const auto& uniform = *pUniform;
93 const auto& texList = uniform.texListForType;
95 const auto& uniformBaseType = uniform.texBaseType;
96 for (const auto& texUnit : uniform.texUnits) {
97 MOZ_ASSERT(texUnit < texList.Length());
100 decltype(SamplerByTexUnit::sampler) prevSamplerForTexUnit = nullptr;
101 for (const auto& cur : samplerByTexUnit) {
102 if (cur.texUnit == texUnit) {
103 prevSamplerForTexUnit = cur.sampler;
106 if (!prevSamplerForTexUnit) {
107 prevSamplerForTexUnit = &uniform;
108 MOZ_RELEASE_ASSERT(samplerByTexUnit.append(
109 SamplerByTexUnit{texUnit, prevSamplerForTexUnit}));
112 if (MOZ_UNLIKELY(&uniform.texListForType !=
113 &prevSamplerForTexUnit->texListForType)) {
114 // Pointing to different tex lists means different types!
115 const auto linkInfo = mWebGL->mActiveProgramLinkInfo;
116 const auto LocInfoBySampler = [&](const webgl::SamplerUniformInfo* p)
117 -> const webgl::LocationInfo* {
118 for (const auto& pair : linkInfo->locationMap) {
119 const auto& locInfo = pair.second;
120 if (locInfo.samplerInfo == p) {
121 return &locInfo;
124 MOZ_CRASH("Can't find sampler location.");
126 const auto& cur = *LocInfoBySampler(&uniform);
127 const auto& prev = *LocInfoBySampler(prevSamplerForTexUnit);
128 mWebGL->ErrorInvalidOperation(
129 "Tex unit %u referenced by samplers of different types:"
130 " %s (via %s) and %s (via %s).",
131 texUnit, EnumString(cur.info.info.elemType).c_str(),
132 cur.PrettyName().c_str(),
133 EnumString(prev.info.info.elemType).c_str(),
134 prev.PrettyName().c_str());
135 *out_error = true;
136 return;
140 const auto& tex = texList[texUnit];
141 if (!tex) continue;
143 const auto& sampler = mWebGL->mBoundSamplers[texUnit];
144 const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
145 if (MOZ_UNLIKELY(!samplingInfo)) { // There was an error.
146 *out_error = true;
147 return;
149 if (MOZ_UNLIKELY(!samplingInfo->IsComplete())) {
150 if (samplingInfo->incompleteReason) {
151 const auto& targetName = GetEnumName(tex->Target().get());
152 mWebGL->GenerateWarning("%s at unit %u is incomplete: %s", targetName,
153 texUnit, samplingInfo->incompleteReason);
155 mRebindRequests.push_back({texUnit, tex});
156 continue;
159 // We have more validation to do if we're otherwise complete:
160 const auto& texBaseType = samplingInfo->usage->format->baseType;
161 if (MOZ_UNLIKELY(texBaseType != uniformBaseType)) {
162 const auto& targetName = GetEnumName(tex->Target().get());
163 const auto& srcType = ToString(texBaseType);
164 const auto& dstType = ToString(uniformBaseType);
165 mWebGL->ErrorInvalidOperation(
166 "%s at unit %u is of type %s, but"
167 " the shader samples as %s.",
168 targetName, texUnit, srcType, dstType);
169 *out_error = true;
170 return;
173 if (MOZ_UNLIKELY(uniform.isShadowSampler !=
174 samplingInfo->isDepthTexCompare)) {
175 const auto& targetName = GetEnumName(tex->Target().get());
176 mWebGL->ErrorInvalidOperation(
177 "%s at unit %u is%s a depth texture"
178 " with TEXTURE_COMPARE_MODE, but"
179 " the shader sampler is%s a shadow"
180 " sampler.",
181 targetName, texUnit, samplingInfo->isDepthTexCompare ? "" : " not",
182 uniform.isShadowSampler ? "" : " not");
183 *out_error = true;
184 return;
187 if (MOZ_UNLIKELY(!ValidateNoSamplingFeedback(*tex, samplingInfo->levels,
188 fb.get(), texUnit))) {
189 *out_error = true;
190 return;
195 const auto& gl = mWebGL->gl;
196 for (const auto& itr : mRebindRequests) {
197 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
198 GLuint incompleteTex = 0; // Tex 0 is always incomplete.
199 const auto& overrideTex = webgl->mIncompleteTexOverride;
200 if (overrideTex) {
201 // In all but the simplest cases, this will be incomplete anyway, since
202 // e.g. int-samplers need int-textures. This is useful for e.g.
203 // dom-to-texture failures, though.
204 incompleteTex = overrideTex->name;
206 gl->fBindTexture(itr.tex->Target().get(), incompleteTex);
210 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
211 if (mRebindRequests.empty()) return;
213 gl::GLContext* gl = mWebGL->gl;
215 for (const auto& itr : mRebindRequests) {
216 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
217 gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
220 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
223 ////////////////////////////////////////
225 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
226 const auto stencilBits = [&]() -> uint8_t {
227 if (!mStencilTestEnabled) return 0;
229 if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0;
231 if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment()) return 8;
233 if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
234 return 8;
236 return 0;
237 }();
238 const uint32_t stencilMax = (1 << stencilBits) - 1;
240 const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
241 const auto fnClamp = [&](const int32_t x) {
242 return std::clamp(x, 0, (int32_t)stencilMax);
245 bool ok = true;
246 ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
247 ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
248 ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
250 if (!ok) {
251 ErrorInvalidOperation(
252 "Stencil front/back state must effectively match."
253 " (before front/back comparison, WRITEMASK and VALUE_MASK"
254 " are masked with (2^s)-1, and REF is clamped to"
255 " [0, (2^s)-1], where `s` is the number of enabled stencil"
256 " bits in the draw framebuffer)");
258 return ok;
261 // -
263 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget,
264 const uint32_t useId,
265 const GLenum boundTarget,
266 const uint32_t boundId) const {
267 const auto fnName = [&](const GLenum target, const uint32_t id) {
268 auto name = nsCString(EnumString(target).c_str());
269 if (id != static_cast<uint32_t>(-1)) {
270 name += nsPrintfCString("[%u]", id);
272 return name;
274 const auto& useName = fnName(useTarget, useId);
275 const auto& boundName = fnName(boundTarget, boundId);
276 GenerateError(LOCAL_GL_INVALID_OPERATION,
277 "Illegal use of buffer at %s"
278 " while also bound to %s.",
279 useName.BeginReading(), boundName.BeginReading());
282 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
283 const GLenum nonTfTarget,
284 const uint32_t nonTfId) const {
285 bool dupe = false;
286 const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
287 for (const auto& cur : tfAttribs) {
288 dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
290 if (MOZ_LIKELY(!dupe)) return true;
292 dupe = false;
293 for (const auto tfId : IntegerRange(tfAttribs.size())) {
294 const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
295 if (&nonTfBuffer == tfBuffer) {
296 dupe = true;
297 GenErrorIllegalUse(nonTfTarget, nonTfId,
298 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
301 MOZ_ASSERT(dupe);
302 return false;
305 bool WebGLContext::ValidateBuffersForTf(
306 const WebGLTransformFeedback& tfo,
307 const webgl::LinkedProgramInfo& linkInfo) const {
308 size_t numUsed;
309 switch (linkInfo.transformFeedbackBufferMode) {
310 case LOCAL_GL_INTERLEAVED_ATTRIBS:
311 numUsed = 1;
312 break;
314 case LOCAL_GL_SEPARATE_ATTRIBS:
315 numUsed = linkInfo.active.activeTfVaryings.size();
316 break;
318 default:
319 MOZ_CRASH();
322 std::vector<webgl::BufferAndIndex> tfBuffers;
323 tfBuffers.reserve(numUsed);
324 for (const auto i : IntegerRange(numUsed)) {
325 tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
326 static_cast<uint32_t>(i)});
329 return ValidateBuffersForTf(tfBuffers);
332 bool WebGLContext::ValidateBuffersForTf(
333 const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
334 bool dupe = false;
335 const auto fnCheck = [&](const WebGLBuffer* const nonTf,
336 const GLenum nonTfTarget, const uint32_t nonTfId) {
337 for (const auto& tf : tfBuffers) {
338 dupe |= (nonTf && tf.buffer == nonTf);
341 if (MOZ_LIKELY(!dupe)) return false;
343 for (const auto& tf : tfBuffers) {
344 if (nonTf && tf.buffer == nonTf) {
345 dupe = true;
346 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
347 nonTfTarget, nonTfId);
350 return true;
353 fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
354 fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
355 fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
356 fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
357 fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
358 // fnCheck(mBoundTransformFeedbackBuffer.get(),
359 // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
360 fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
362 for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
363 const auto& cur = mIndexedUniformBufferBindings[i];
364 fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
367 fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
368 LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
369 for (const auto i : IntegerRange(MaxVertexAttribs())) {
370 const auto& binding = mBoundVertexArray->AttribBinding(i);
371 fnCheck(binding.buffer.get(), LOCAL_GL_ARRAY_BUFFER, i);
374 return !dupe;
377 ////////////////////////////////////////
379 template <typename T>
380 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
381 std::vector<T> intersection;
382 std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
383 std::back_inserter(intersection));
384 return !intersection.empty();
387 template <size_t N>
388 static size_t FindFirstOne(const std::bitset<N>& bs) {
389 MOZ_ASSERT(bs.any());
390 // We don't need this to be fast, so don't bother with CLZ intrinsics.
391 for (const auto i : IntegerRange(N)) {
392 if (bs[i]) return i;
394 return -1;
397 const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
398 const GLenum mode,
399 const uint32_t instanceCount) {
400 if (!webgl->BindCurFBForDraw()) return nullptr;
402 const auto& fb = webgl->mBoundDrawFramebuffer;
403 if (fb) {
404 const auto& info = *fb->GetCompletenessInfo();
405 const auto isF32WithBlending = info.isAttachmentF32 & webgl->mBlendEnabled;
406 if (isF32WithBlending.any()) {
407 if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
408 const auto first = FindFirstOne(isF32WithBlending);
409 webgl->ErrorInvalidOperation(
410 "Attachment %u is float32 with blending enabled, which requires "
411 "EXT_float_blend.",
412 uint32_t(first));
413 return nullptr;
415 webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
419 switch (mode) {
420 case LOCAL_GL_TRIANGLES:
421 case LOCAL_GL_TRIANGLE_STRIP:
422 case LOCAL_GL_TRIANGLE_FAN:
423 case LOCAL_GL_POINTS:
424 case LOCAL_GL_LINE_STRIP:
425 case LOCAL_GL_LINE_LOOP:
426 case LOCAL_GL_LINES:
427 break;
428 default:
429 webgl->ErrorInvalidEnumInfo("mode", mode);
430 return nullptr;
433 if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
435 if (!webgl->mActiveProgramLinkInfo) {
436 webgl->ErrorInvalidOperation("The current program is not linked.");
437 return nullptr;
439 const auto& linkInfo = webgl->mActiveProgramLinkInfo;
441 // -
442 // Check UBO sizes.
444 for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
445 const auto& cur = linkInfo->uniformBlocks[i];
446 const auto& dataSize = cur.info.dataSize;
447 const auto& binding = cur.binding;
448 if (!binding) {
449 webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
450 return nullptr;
453 const auto availByteCount = binding->ByteCount();
454 if (dataSize > availByteCount) {
455 webgl->ErrorInvalidOperation(
456 "Buffer for uniform block is smaller"
457 " than UNIFORM_BLOCK_DATA_SIZE.");
458 return nullptr;
461 if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
462 LOCAL_GL_UNIFORM_BUFFER, i))
463 return nullptr;
466 // -
468 const auto& tfo = webgl->mBoundTransformFeedback;
469 if (tfo && tfo->IsActiveAndNotPaused()) {
470 if (fb) {
471 const auto& info = *fb->GetCompletenessInfo();
472 if (info.isMultiview) {
473 webgl->ErrorInvalidOperation(
474 "Cannot render to multiview with transform feedback.");
475 return nullptr;
479 if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
482 // -
484 const auto& fragOutputs = linkInfo->fragOutputs;
485 const auto fnValidateFragOutputType =
486 [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
487 const auto itr = fragOutputs.find(loc);
488 MOZ_DIAGNOSTIC_ASSERT(itr != fragOutputs.end());
490 const auto& info = itr->second;
491 const auto& srcBaseType = info.baseType;
492 if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
493 const auto& srcStr = ToString(srcBaseType);
494 const auto& dstStr = ToString(dstBaseType);
495 webgl->ErrorInvalidOperation(
496 "Program frag output at location %u is type %s,"
497 " but destination draw buffer is type %s.",
498 uint32_t(loc), srcStr, dstStr);
499 return false;
501 return true;
504 if (!webgl->mRasterizerDiscardEnabled) {
505 uint8_t fbZLayerCount = 1;
506 auto hasAttachment = std::bitset<webgl::kMaxDrawBuffers>(1);
507 auto drawBufferEnabled = std::bitset<webgl::kMaxDrawBuffers>();
508 if (fb) {
509 drawBufferEnabled = fb->DrawBufferEnabled();
510 const auto& info = *fb->GetCompletenessInfo();
511 fbZLayerCount = info.zLayerCount;
512 hasAttachment = info.hasAttachment;
513 } else {
514 drawBufferEnabled[0] = (webgl->mDefaultFB_DrawBuffer0 == LOCAL_GL_BACK);
517 if (fbZLayerCount != linkInfo->zLayerCount) {
518 webgl->ErrorInvalidOperation(
519 "Multiview count mismatch: shader: %u, framebuffer: %u",
520 uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
521 return nullptr;
524 const auto writable =
525 hasAttachment & drawBufferEnabled & webgl->mColorWriteMaskNonzero;
526 if (writable.any()) {
527 // Do we have any undefined outputs with real attachments that
528 // aren't masked-out by color write mask or drawBuffers?
529 const auto wouldWriteUndefined = ~linkInfo->hasOutput & writable;
530 if (wouldWriteUndefined.any()) {
531 const auto first = FindFirstOne(wouldWriteUndefined);
532 webgl->ErrorInvalidOperation(
533 "Program has no frag output at location %u, the"
534 " destination draw buffer has an attached"
535 " image, and its color write mask is not all false,"
536 " and DRAW_BUFFER%u is not NONE.",
537 uint32_t(first), uint32_t(first));
538 return nullptr;
541 const auto outputWrites = linkInfo->hasOutput & writable;
543 if (fb) {
544 for (const auto& attach : fb->ColorDrawBuffers()) {
545 const auto i =
546 uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
547 if (!outputWrites[i]) continue;
548 const auto& imageInfo = attach->GetImageInfo();
549 if (!imageInfo) continue;
550 const auto& dstBaseType = imageInfo->mFormat->format->baseType;
551 if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
553 } else {
554 if (outputWrites[0]) {
555 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
556 return nullptr;
562 // -
564 const auto fetchLimits = linkInfo->GetDrawFetchLimits();
565 if (!fetchLimits) return nullptr;
567 if (instanceCount > fetchLimits->maxInstances) {
568 webgl->ErrorInvalidOperation(
569 "Instance fetch requires %u, but attribs only"
570 " supply %u.",
571 instanceCount, uint32_t(fetchLimits->maxInstances));
572 return nullptr;
575 if (tfo) {
576 for (const auto& used : fetchLimits->usedBuffers) {
577 MOZ_ASSERT(used.buffer);
578 if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
579 used.id))
580 return nullptr;
584 // -
586 webgl->RunContextLossTimer();
588 return fetchLimits;
591 ////////////////////////////////////////
593 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
594 uint8_t vertsPerPrim;
596 switch (mode) {
597 case LOCAL_GL_POINTS:
598 vertsPerPrim = 1;
599 break;
600 case LOCAL_GL_LINES:
601 vertsPerPrim = 2;
602 break;
603 case LOCAL_GL_TRIANGLES:
604 vertsPerPrim = 3;
605 break;
606 default:
607 MOZ_CRASH("`mode`");
610 return vertCount / vertsPerPrim * vertsPerPrim;
613 class ScopedDrawWithTransformFeedback final {
614 WebGLContext* const mWebGL;
615 WebGLTransformFeedback* const mTFO;
616 const bool mWithTF;
617 uint32_t mUsedVerts;
619 public:
620 ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
621 uint32_t vertCount, uint32_t instanceCount,
622 bool* const out_error)
623 : mWebGL(webgl),
624 mTFO(mWebGL->mBoundTransformFeedback),
625 mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
626 mUsedVerts(0) {
627 *out_error = false;
628 if (!mWithTF) return;
630 if (mode != mTFO->mActive_PrimMode) {
631 mWebGL->ErrorInvalidOperation(
632 "Drawing with transform feedback requires"
633 " `mode` to match BeginTransformFeedback's"
634 " `primitiveMode`.");
635 *out_error = true;
636 return;
639 const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
640 const auto usedVerts =
641 CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
643 const auto remainingCapacity =
644 mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
645 if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
646 mWebGL->ErrorInvalidOperation(
647 "Insufficient buffer capacity remaining for"
648 " transform feedback.");
649 *out_error = true;
650 return;
653 mUsedVerts = usedVerts.value();
656 void Advance() const {
657 if (!mWithTF) return;
659 mTFO->mActive_VertPosition += mUsedVerts;
661 for (const auto& cur : mTFO->mIndexedBindings) {
662 const auto& buffer = cur.mBufferBinding;
663 if (buffer) {
664 buffer->ResetLastUpdateFenceId();
670 static bool HasInstancedDrawing(const WebGLContext& webgl) {
671 return webgl.IsWebGL2() ||
672 webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
675 ////////////////////////////////////////
677 void WebGLContext::DrawArraysInstanced(const GLenum mode, const GLint first,
678 const GLsizei iVertCount,
679 const GLsizei instanceCount) {
680 const FuncScope funcScope(*this, "drawArraysInstanced");
681 // AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
682 if (IsContextLost()) return;
683 const gl::GLContext::TlsScope inTls(gl);
685 // -
687 if (!ValidateNonNegative("first", first) ||
688 !ValidateNonNegative("vertCount", iVertCount) ||
689 !ValidateNonNegative("instanceCount", instanceCount)) {
690 return;
692 const auto vertCount = AssertedCast<uint32_t>(iVertCount);
694 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
695 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
696 if (mPrimRestartTypeBytes != 0) {
697 mPrimRestartTypeBytes = 0;
699 // OSX appears to have severe perf issues with leaving this enabled.
700 gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
704 // -
706 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
707 if (!fetchLimits) return;
709 // -
711 const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
712 if (!totalVertCount_safe.isValid()) {
713 ErrorOutOfMemory("`first+vertCount` out of range.");
714 return;
716 auto totalVertCount = totalVertCount_safe.value();
718 if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
719 ErrorInvalidOperation(
720 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
721 uint32_t(fetchLimits->maxVerts));
722 return;
725 if (vertCount > mMaxVertIdsPerDraw) {
726 ErrorOutOfMemory(
727 "Context's max vertCount is %u, but %u requested. "
728 "[webgl.max-vert-ids-per-draw]",
729 mMaxVertIdsPerDraw, vertCount);
730 return;
733 // -
735 bool error = false;
737 // -
739 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
740 if (error) return;
742 const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
743 instanceCount, &error);
744 if (error) return;
746 // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
747 // attribs are fetched from. There are two ways to fix this:
748 // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
749 // `byteOffset`
750 // 2. OR offset all non-instanced vertex attrib pointers back, and call
751 // DrawArrays with first:0.
752 // * But now gl_VertexID will be wrong! So we inject a uniform to offset it
753 // back correctly.
754 // #1 ought to be the lowest overhead for any first>0,
755 // but DrawElements can't be used with transform-feedback,
756 // so we need #2 to also work.
757 // For now, only implement #2.
759 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
761 auto driverFirst = first;
763 if (first && mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst) {
764 // This is not particularly optimized, but we can if we need to.
765 bool hasInstancedUserAttrib = false;
766 bool hasVertexAttrib = false;
767 for (const auto& a : activeAttribs) {
768 if (a.location == -1) {
769 if (a.name == "gl_VertexID") {
770 hasVertexAttrib = true;
772 continue;
774 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
775 if (binding.layout.divisor) {
776 hasInstancedUserAttrib = true;
777 } else {
778 hasVertexAttrib = true;
781 if (hasInstancedUserAttrib && hasVertexAttrib) {
782 driverFirst = 0;
785 if (driverFirst != first) {
786 for (const auto& a : activeAttribs) {
787 if (a.location == -1) continue;
788 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
789 if (binding.layout.divisor) continue;
791 mBoundVertexArray->DoVertexAttrib(a.location, first);
794 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, first);
798 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
799 auto fakeVertCount = uint64_t(driverFirst) + vertCount;
800 if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default) {
801 fakeVertCount = 0;
803 if (!(vertCount && instanceCount)) {
804 fakeVertCount = 0;
807 auto undoAttrib0 = MakeScopeExit([&]() {
808 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
809 WebGLVertexAttrib0Status::Default);
810 UndoFakeVertexAttrib0();
812 if (fakeVertCount) {
813 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
814 error = true;
815 undoAttrib0.release();
817 } else {
818 // No fake-verts needed.
819 undoAttrib0.release();
822 ScopedDrawCallWrapper wrapper(*this);
823 if (vertCount && instanceCount) {
824 if (HasInstancedDrawing(*this)) {
825 gl->fDrawArraysInstanced(mode, driverFirst, vertCount, instanceCount);
826 } else {
827 MOZ_ASSERT(instanceCount == 1);
828 gl->fDrawArrays(mode, driverFirst, vertCount);
833 if (driverFirst != first) {
834 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, 0);
836 for (const auto& a : activeAttribs) {
837 if (a.location == -1) continue;
838 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
839 if (binding.layout.divisor) continue;
841 mBoundVertexArray->DoVertexAttrib(a.location, 0);
845 Draw_cleanup();
846 scopedTF.Advance();
849 ////////////////////////////////////////
851 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
852 const GLenum type,
853 const WebGLintptr byteOffset,
854 const GLsizei instanceCount) {
855 if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
856 !mBoundTransformFeedback->mIsPaused) {
857 ErrorInvalidOperation(
858 "DrawElements* functions are incompatible with"
859 " transform feedback.");
860 return nullptr;
863 if (!ValidateNonNegative("vertCount", rawIndexCount) ||
864 !ValidateNonNegative("byteOffset", byteOffset) ||
865 !ValidateNonNegative("instanceCount", instanceCount)) {
866 return nullptr;
868 const auto indexCount = uint32_t(rawIndexCount);
870 uint8_t bytesPerIndex = 0;
871 switch (type) {
872 case LOCAL_GL_UNSIGNED_BYTE:
873 bytesPerIndex = 1;
874 break;
876 case LOCAL_GL_UNSIGNED_SHORT:
877 bytesPerIndex = 2;
878 break;
880 case LOCAL_GL_UNSIGNED_INT:
881 if (IsWebGL2() ||
882 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
883 bytesPerIndex = 4;
885 break;
887 if (!bytesPerIndex) {
888 ErrorInvalidEnumInfo("type", type);
889 return nullptr;
891 if (byteOffset % bytesPerIndex != 0) {
892 ErrorInvalidOperation(
893 "`byteOffset` must be a multiple of the size of `type`");
894 return nullptr;
897 ////
899 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
900 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
901 if (mPrimRestartTypeBytes != bytesPerIndex) {
902 mPrimRestartTypeBytes = bytesPerIndex;
904 const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
905 gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
906 gl->fPrimitiveRestartIndex(ones);
910 ////
911 // Index fetching
913 const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
914 if (!indexBuffer) {
915 ErrorInvalidOperation("Index buffer not bound.");
916 return nullptr;
919 const size_t availBytes = indexBuffer->ByteLength();
920 const auto availIndices =
921 AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
922 if (instanceCount && indexCount > availIndices) {
923 ErrorInvalidOperation("Index buffer too small.");
924 return nullptr;
927 return indexBuffer.get();
930 static void HandleDrawElementsErrors(
931 WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
932 const auto err = errorScope.GetError();
933 if (err == LOCAL_GL_INVALID_OPERATION) {
934 webgl->ErrorInvalidOperation(
935 "Driver rejected indexed draw call, possibly"
936 " due to out-of-bounds indices.");
937 return;
940 MOZ_ASSERT(!err);
941 if (err) {
942 webgl->ErrorImplementationBug(
943 "Unexpected driver error during indexed draw"
944 " call. Please file a bug.");
945 return;
949 void WebGLContext::DrawElementsInstanced(const GLenum mode,
950 const GLsizei iIndexCount,
951 const GLenum type,
952 const WebGLintptr byteOffset,
953 const GLsizei instanceCount) {
954 const FuncScope funcScope(*this, "drawElementsInstanced");
955 // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
956 if (IsContextLost()) return;
958 const gl::GLContext::TlsScope inTls(gl);
960 const auto indexBuffer =
961 DrawElements_check(iIndexCount, type, byteOffset, instanceCount);
962 if (!indexBuffer) return;
963 const auto indexCount = AssertedCast<uint32_t>(iIndexCount);
965 // -
967 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
968 if (!fetchLimits) return;
970 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
972 uint64_t fakeVertCount = 0;
973 if (whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default) {
974 fakeVertCount = fetchLimits->maxVerts;
976 if (!indexCount || !instanceCount) {
977 fakeVertCount = 0;
979 if (fakeVertCount == UINT64_MAX) { // Ok well that's too many!
980 const auto exactMaxVertId =
981 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
982 MOZ_RELEASE_ASSERT(exactMaxVertId);
983 fakeVertCount = uint32_t{*exactMaxVertId};
984 fakeVertCount += 1;
987 // -
990 uint64_t indexCapacity = indexBuffer->ByteLength();
991 switch (type) {
992 case LOCAL_GL_UNSIGNED_BYTE:
993 break;
994 case LOCAL_GL_UNSIGNED_SHORT:
995 indexCapacity /= 2;
996 break;
997 case LOCAL_GL_UNSIGNED_INT:
998 indexCapacity /= 4;
999 break;
1002 uint32_t maxVertId = 0;
1003 const auto isFetchValid = [&]() {
1004 if (!indexCount || !instanceCount) return true;
1006 const auto globalMaxVertId =
1007 indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
1008 if (!globalMaxVertId) return true;
1009 if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
1011 const auto exactMaxVertId =
1012 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
1013 maxVertId = exactMaxVertId.value();
1014 return maxVertId < fetchLimits->maxVerts;
1015 }();
1016 if (!isFetchValid) {
1017 ErrorInvalidOperation(
1018 "Indexed vertex fetch requires %u vertices, but"
1019 " attribs only supply %u.",
1020 maxVertId + 1, uint32_t(fetchLimits->maxVerts));
1021 return;
1025 if (indexCount > mMaxVertIdsPerDraw) {
1026 ErrorOutOfMemory(
1027 "Context's max indexCount is %u, but %u requested. "
1028 "[webgl.max-vert-ids-per-draw]",
1029 mMaxVertIdsPerDraw, indexCount);
1030 return;
1033 // -
1035 bool error = false;
1037 // -
1039 auto undoAttrib0 = MakeScopeExit([&]() {
1040 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
1041 WebGLVertexAttrib0Status::Default);
1042 UndoFakeVertexAttrib0();
1044 if (fakeVertCount) {
1045 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
1046 error = true;
1047 undoAttrib0.release();
1049 } else {
1050 // No fake-verts needed.
1051 undoAttrib0.release();
1054 // -
1056 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
1057 if (error) return;
1060 ScopedDrawCallWrapper wrapper(*this);
1062 UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
1063 if (MOZ_UNLIKELY(gl->IsANGLE() &&
1064 gl->mDebugFlags &
1065 gl::GLContext::DebugFlagAbortOnError)) {
1066 // ANGLE does range validation even when it doesn't need to.
1067 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
1068 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
1071 if (indexCount && instanceCount) {
1072 if (HasInstancedDrawing(*this)) {
1073 gl->fDrawElementsInstanced(mode, indexCount, type,
1074 reinterpret_cast<GLvoid*>(byteOffset),
1075 instanceCount);
1076 } else {
1077 MOZ_ASSERT(instanceCount == 1);
1078 gl->fDrawElements(mode, indexCount, type,
1079 reinterpret_cast<GLvoid*>(byteOffset));
1083 if (errorScope) {
1084 HandleDrawElementsErrors(this, *errorScope);
1089 Draw_cleanup();
1092 ////////////////////////////////////////
1094 void WebGLContext::Draw_cleanup() {
1095 if (gl->WorkAroundDriverBugs()) {
1096 if (gl->Renderer() == gl::GLRenderer::Tegra) {
1097 mDrawCallsSinceLastFlush++;
1099 if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
1100 gl->fFlush();
1101 mDrawCallsSinceLastFlush = 0;
1106 // Let's check for a really common error: Viewport is larger than the actual
1107 // destination framebuffer.
1108 uint32_t destWidth;
1109 uint32_t destHeight;
1110 if (mBoundDrawFramebuffer) {
1111 const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
1112 destWidth = info->width;
1113 destHeight = info->height;
1114 } else {
1115 destWidth = mDefaultFB->mSize.width;
1116 destHeight = mDefaultFB->mSize.height;
1119 if (mViewportWidth > int32_t(destWidth) ||
1120 mViewportHeight > int32_t(destHeight)) {
1121 if (!mAlreadyWarnedAboutViewportLargerThanDest) {
1122 GenerateWarning(
1123 "Drawing to a destination rect smaller than the viewport"
1124 " rect. (This warning will only be given once)");
1125 mAlreadyWarnedAboutViewportLargerThanDest = true;
1130 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
1131 MOZ_ASSERT(mCurrentProgram);
1132 MOZ_ASSERT(mActiveProgramLinkInfo);
1134 bool legacyAttrib0 = mNeedsLegacyVertexAttrib0Handling;
1135 if (gl->WorkAroundDriverBugs() && kIsMacOS) {
1136 // Also programs with no attribs:
1137 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
1138 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
1139 bool hasNonInstancedUserAttrib = false;
1140 for (const auto& a : activeAttribs) {
1141 if (a.location == -1) continue;
1142 const auto& layout = mBoundVertexArray->AttribBinding(a.location).layout;
1143 if (layout.divisor == 0) {
1144 hasNonInstancedUserAttrib = true;
1147 legacyAttrib0 |= !hasNonInstancedUserAttrib;
1150 if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
1151 MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling,
1152 "Invariant need because this turns on index buffer "
1153 "validation, needed for fake-attrib0.");
1155 if (!mActiveProgramLinkInfo->attrib0Active) {
1156 // Attrib0 unused, so just ensure that the legacy code has enough buffer.
1157 return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
1160 const auto& isAttribArray0Enabled =
1161 mBoundVertexArray->AttribBinding(0).layout.isArray;
1162 return isAttribArray0Enabled
1163 ? WebGLVertexAttrib0Status::Default
1164 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
1167 bool WebGLContext::DoFakeVertexAttrib0(
1168 const uint64_t fakeVertexCount,
1169 const WebGLVertexAttrib0Status whatDoesAttrib0Need) {
1170 MOZ_ASSERT(fakeVertexCount);
1171 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default);
1173 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
1174 // Padded/strided to vec4, so 4x4bytes.
1175 const auto effectiveVertAttribBytes =
1176 CheckedInt<int32_t>(fakeVertexCount) * 4 * 4;
1177 if (!effectiveVertAttribBytes.isValid()) {
1178 ErrorOutOfMemory("`offset + count` too large for Mesa.");
1179 return false;
1183 if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
1184 GenerateWarning(
1185 "Drawing without vertex attrib 0 array enabled forces the browser "
1186 "to do expensive emulation work when running on desktop OpenGL "
1187 "platforms, for example on Mac. It is preferable to always draw "
1188 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1189 "to bind some always-used attribute to location 0.");
1190 mAlreadyWarnedAboutFakeVertexAttrib0 = true;
1193 gl->fEnableVertexAttribArray(0);
1195 const auto& attrib0 = mBoundVertexArray->AttribBinding(0);
1196 if (attrib0.layout.divisor) {
1197 gl->fVertexAttribDivisor(0, 0);
1201 if (!mFakeVertexAttrib0BufferObject) {
1202 gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
1203 mFakeVertexAttrib0BufferObjectSize = 0;
1205 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
1207 ////
1209 switch (mGenericVertexAttribTypes[0]) {
1210 case webgl::AttribBaseType::Boolean:
1211 case webgl::AttribBaseType::Float:
1212 gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
1213 break;
1215 case webgl::AttribBaseType::Int:
1216 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
1217 break;
1219 case webgl::AttribBaseType::Uint:
1220 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
1221 break;
1224 ////
1226 const auto maxFakeVerts = StaticPrefs::webgl_fake_verts_max();
1227 if (fakeVertexCount > maxFakeVerts) {
1228 ErrorOutOfMemory(
1229 "Draw requires faking a vertex attrib 0 array, but required vert count"
1230 " (%" PRIu64 ") is more than webgl.fake-verts.max (%u).",
1231 fakeVertexCount, maxFakeVerts);
1232 return false;
1235 const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
1236 const auto checked_dataSize =
1237 CheckedInt<intptr_t>(fakeVertexCount) * bytesPerVert;
1238 if (!checked_dataSize.isValid()) {
1239 ErrorOutOfMemory(
1240 "Integer overflow trying to construct a fake vertex attrib 0"
1241 " array for a draw-operation with %" PRIu64
1242 " vertices. Try"
1243 " reducing the number of vertices.",
1244 fakeVertexCount);
1245 return false;
1247 const auto dataSize = checked_dataSize.value();
1249 if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
1250 gl::GLContext::LocalErrorScope errorScope(*gl);
1252 gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
1253 LOCAL_GL_DYNAMIC_DRAW);
1255 const auto err = errorScope.GetError();
1256 if (err) {
1257 ErrorOutOfMemory(
1258 "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize);
1259 return false;
1262 mFakeVertexAttrib0BufferObjectSize = dataSize;
1263 mFakeVertexAttrib0DataDefined = false;
1266 if (whatDoesAttrib0Need ==
1267 WebGLVertexAttrib0Status::EmulatedUninitializedArray)
1268 return true;
1270 ////
1272 if (mFakeVertexAttrib0DataDefined &&
1273 memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
1274 0) {
1275 return true;
1278 ////
1280 const auto data = UniqueBuffer::Take(malloc(dataSize));
1281 if (!data) {
1282 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1283 return false;
1285 auto itr = (uint8_t*)data.get();
1286 const auto itrEnd = itr + dataSize;
1287 while (itr != itrEnd) {
1288 memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
1289 itr += bytesPerVert;
1293 gl::GLContext::LocalErrorScope errorScope(*gl);
1295 gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
1297 const auto err = errorScope.GetError();
1298 if (err) {
1299 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1300 return false;
1304 ////
1306 memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
1307 mFakeVertexAttrib0DataDefined = true;
1308 return true;
1311 void WebGLContext::UndoFakeVertexAttrib0() {
1312 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
1313 mBoundVertexArray->DoVertexAttrib(0);
1316 } // namespace mozilla