Bug 1729395 - Handle message sender going away during message processing r=robwu
[gecko.git] / dom / canvas / WebGLContextDraw.cpp
blob18a902ef6e61ce90f348ac55c96824ecf0cb41d7
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/UniquePtrExtensions.h"
13 #include "nsPrintfCString.h"
14 #include "WebGLBuffer.h"
15 #include "WebGLContextUtils.h"
16 #include "WebGLFramebuffer.h"
17 #include "WebGLProgram.h"
18 #include "WebGLRenderbuffer.h"
19 #include "WebGLShader.h"
20 #include "WebGLTexture.h"
21 #include "WebGLTransformFeedback.h"
22 #include "WebGLVertexArray.h"
24 #include <algorithm>
26 namespace mozilla {
28 // For a Tegra workaround.
29 static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
31 ////////////////////////////////////////
33 class ScopedResolveTexturesForDraw {
34 struct TexRebindRequest {
35 uint32_t texUnit;
36 WebGLTexture* tex;
39 WebGLContext* const mWebGL;
40 std::vector<TexRebindRequest> mRebindRequests;
42 public:
43 ScopedResolveTexturesForDraw(WebGLContext* webgl, bool* const out_error);
44 ~ScopedResolveTexturesForDraw();
47 static bool ValidateNoSamplingFeedback(const WebGLTexture& tex,
48 const uint32_t sampledLevels,
49 const WebGLFramebuffer* const fb,
50 const uint32_t texUnit) {
51 if (!fb) return true;
53 const auto& texAttachments = fb->GetCompletenessInfo()->texAttachments;
54 for (const auto& attach : texAttachments) {
55 if (attach->Texture() != &tex) continue;
57 const auto& srcBase = tex.Es3_level_base();
58 const auto srcLast = srcBase + sampledLevels - 1;
59 const auto& dstLevel = attach->MipLevel();
60 if (MOZ_UNLIKELY(srcBase <= dstLevel && dstLevel <= srcLast)) {
61 const auto& webgl = tex.mContext;
62 const auto& texTargetStr = EnumString(tex.Target().get());
63 const auto& attachStr = EnumString(attach->mAttachmentPoint);
64 webgl->ErrorInvalidOperation(
65 "Texture level %u would be read by %s unit %u,"
66 " but written by framebuffer attachment %s,"
67 " which would be illegal feedback.",
68 dstLevel, texTargetStr.c_str(), texUnit, attachStr.c_str());
69 return false;
72 return true;
75 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
76 WebGLContext* webgl, bool* const out_error)
77 : mWebGL(webgl) {
78 const auto& fb = mWebGL->mBoundDrawFramebuffer;
80 MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
81 const auto& samplerUniforms = mWebGL->mActiveProgramLinkInfo->samplerUniforms;
82 for (const auto& pUniform : samplerUniforms) {
83 const auto& uniform = *pUniform;
84 const auto& texList = uniform.texListForType;
86 const auto& uniformBaseType = uniform.texBaseType;
87 for (const auto& texUnit : uniform.texUnits) {
88 MOZ_ASSERT(texUnit < texList.Length());
90 const auto& tex = texList[texUnit];
91 if (!tex) continue;
93 const auto& sampler = mWebGL->mBoundSamplers[texUnit];
94 const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
95 if (!samplingInfo) { // There was an error.
96 *out_error = true;
97 return;
99 if (!samplingInfo->IsComplete()) {
100 if (samplingInfo->incompleteReason) {
101 const auto& targetName = GetEnumName(tex->Target().get());
102 mWebGL->GenerateWarning("%s at unit %u is incomplete: %s", targetName,
103 texUnit, samplingInfo->incompleteReason);
105 mRebindRequests.push_back({texUnit, tex});
106 continue;
109 // We have more validation to do if we're otherwise complete:
110 const auto& texBaseType = samplingInfo->usage->format->baseType;
111 if (texBaseType != uniformBaseType) {
112 const auto& targetName = GetEnumName(tex->Target().get());
113 const auto& srcType = ToString(texBaseType);
114 const auto& dstType = ToString(uniformBaseType);
115 mWebGL->ErrorInvalidOperation(
116 "%s at unit %u is of type %s, but"
117 " the shader samples as %s.",
118 targetName, texUnit, srcType, dstType);
119 *out_error = true;
120 return;
123 if (uniform.isShadowSampler != samplingInfo->isDepthTexCompare) {
124 const auto& targetName = GetEnumName(tex->Target().get());
125 mWebGL->ErrorInvalidOperation(
126 "%s at unit %u is%s a depth texture"
127 " with TEXTURE_COMPARE_MODE, but"
128 " the shader sampler is%s a shadow"
129 " sampler.",
130 targetName, texUnit, samplingInfo->isDepthTexCompare ? "" : " not",
131 uniform.isShadowSampler ? "" : " not");
132 *out_error = true;
133 return;
136 if (!ValidateNoSamplingFeedback(*tex, samplingInfo->levels, fb.get(),
137 texUnit)) {
138 *out_error = true;
139 return;
144 const auto& gl = mWebGL->gl;
145 for (const auto& itr : mRebindRequests) {
146 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
147 GLuint incompleteTex = 0; // Tex 0 is always incomplete.
148 const auto& overrideTex = webgl->mIncompleteTexOverride;
149 if (overrideTex) {
150 // In all but the simplest cases, this will be incomplete anyway, since
151 // e.g. int-samplers need int-textures. This is useful for e.g.
152 // dom-to-texture failures, though.
153 incompleteTex = overrideTex->name;
155 gl->fBindTexture(itr.tex->Target().get(), incompleteTex);
159 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
160 if (mRebindRequests.empty()) return;
162 gl::GLContext* gl = mWebGL->gl;
164 for (const auto& itr : mRebindRequests) {
165 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
166 gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
169 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
172 ////////////////////////////////////////
174 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
175 const auto stencilBits = [&]() -> uint8_t {
176 if (!mStencilTestEnabled) return 0;
178 if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0;
180 if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment()) return 8;
182 if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
183 return 8;
185 return 0;
186 }();
187 const uint32_t stencilMax = (1 << stencilBits) - 1;
189 const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
190 const auto fnClamp = [&](const int32_t x) {
191 return std::max(0, std::min(x, (int32_t)stencilMax));
194 bool ok = true;
195 ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
196 ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
197 ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
199 if (!ok) {
200 ErrorInvalidOperation(
201 "Stencil front/back state must effectively match."
202 " (before front/back comparison, WRITEMASK and VALUE_MASK"
203 " are masked with (2^s)-1, and REF is clamped to"
204 " [0, (2^s)-1], where `s` is the number of enabled stencil"
205 " bits in the draw framebuffer)");
207 return ok;
210 // -
212 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget,
213 const uint32_t useId,
214 const GLenum boundTarget,
215 const uint32_t boundId) const {
216 const auto fnName = [&](const GLenum target, const uint32_t id) {
217 auto name = nsCString(EnumString(target).c_str());
218 if (id != static_cast<uint32_t>(-1)) {
219 name += nsPrintfCString("[%u]", id);
221 return name;
223 const auto& useName = fnName(useTarget, useId);
224 const auto& boundName = fnName(boundTarget, boundId);
225 GenerateError(LOCAL_GL_INVALID_OPERATION,
226 "Illegal use of buffer at %s"
227 " while also bound to %s.",
228 useName.BeginReading(), boundName.BeginReading());
231 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
232 const GLenum nonTfTarget,
233 const uint32_t nonTfId) const {
234 bool dupe = false;
235 const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
236 for (const auto& cur : tfAttribs) {
237 dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
239 if (MOZ_LIKELY(!dupe)) return true;
241 dupe = false;
242 for (const auto tfId : IntegerRange(tfAttribs.size())) {
243 const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
244 if (&nonTfBuffer == tfBuffer) {
245 dupe = true;
246 GenErrorIllegalUse(nonTfTarget, nonTfId,
247 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
250 MOZ_ASSERT(dupe);
251 return false;
254 bool WebGLContext::ValidateBuffersForTf(
255 const WebGLTransformFeedback& tfo,
256 const webgl::LinkedProgramInfo& linkInfo) const {
257 size_t numUsed;
258 switch (linkInfo.transformFeedbackBufferMode) {
259 case LOCAL_GL_INTERLEAVED_ATTRIBS:
260 numUsed = 1;
261 break;
263 case LOCAL_GL_SEPARATE_ATTRIBS:
264 numUsed = linkInfo.active.activeTfVaryings.size();
265 break;
267 default:
268 MOZ_CRASH();
271 std::vector<webgl::BufferAndIndex> tfBuffers;
272 tfBuffers.reserve(numUsed);
273 for (const auto i : IntegerRange(numUsed)) {
274 tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
275 static_cast<uint32_t>(i)});
278 return ValidateBuffersForTf(tfBuffers);
281 bool WebGLContext::ValidateBuffersForTf(
282 const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
283 bool dupe = false;
284 const auto fnCheck = [&](const WebGLBuffer* const nonTf,
285 const GLenum nonTfTarget, const uint32_t nonTfId) {
286 for (const auto& tf : tfBuffers) {
287 dupe |= (nonTf && tf.buffer == nonTf);
290 if (MOZ_LIKELY(!dupe)) return false;
292 for (const auto& tf : tfBuffers) {
293 if (nonTf && tf.buffer == nonTf) {
294 dupe = true;
295 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
296 nonTfTarget, nonTfId);
299 return true;
302 fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
303 fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
304 fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
305 fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
306 fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
307 // fnCheck(mBoundTransformFeedbackBuffer.get(),
308 // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
309 fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
311 for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
312 const auto& cur = mIndexedUniformBufferBindings[i];
313 fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
316 fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
317 LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
318 for (const auto i : IntegerRange(MaxVertexAttribs())) {
319 const auto& binding = mBoundVertexArray->AttribBinding(i);
320 fnCheck(binding.buffer.get(), LOCAL_GL_ARRAY_BUFFER, i);
323 return !dupe;
326 ////////////////////////////////////////
328 template <typename T>
329 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
330 std::vector<T> intersection;
331 std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
332 std::back_inserter(intersection));
333 return bool(intersection.size());
336 template <size_t N>
337 static size_t FindFirstOne(const std::bitset<N>& bs) {
338 MOZ_ASSERT(bs.any());
339 // We don't need this to be fast, so don't bother with CLZ intrinsics.
340 for (const auto i : IntegerRange(N)) {
341 if (bs[i]) return i;
343 return -1;
346 const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
347 const GLenum mode,
348 const uint32_t instanceCount) {
349 if (!webgl->BindCurFBForDraw()) return nullptr;
351 const auto& fb = webgl->mBoundDrawFramebuffer;
352 if (fb) {
353 const auto& info = *fb->GetCompletenessInfo();
354 const auto isF32WithBlending = info.isAttachmentF32 & webgl->mBlendEnabled;
355 if (isF32WithBlending.any()) {
356 if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
357 const auto first = FindFirstOne(isF32WithBlending);
358 webgl->ErrorInvalidOperation(
359 "Attachment %u is float32 with blending enabled, which requires "
360 "EXT_float_blend.",
361 uint32_t(first));
362 return nullptr;
364 webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
368 switch (mode) {
369 case LOCAL_GL_TRIANGLES:
370 case LOCAL_GL_TRIANGLE_STRIP:
371 case LOCAL_GL_TRIANGLE_FAN:
372 case LOCAL_GL_POINTS:
373 case LOCAL_GL_LINE_STRIP:
374 case LOCAL_GL_LINE_LOOP:
375 case LOCAL_GL_LINES:
376 break;
377 default:
378 webgl->ErrorInvalidEnumInfo("mode", mode);
379 return nullptr;
382 if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
384 if (!webgl->mActiveProgramLinkInfo) {
385 webgl->ErrorInvalidOperation("The current program is not linked.");
386 return nullptr;
388 const auto& linkInfo = webgl->mActiveProgramLinkInfo;
390 // -
391 // Check UBO sizes.
393 for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
394 const auto& cur = linkInfo->uniformBlocks[i];
395 const auto& dataSize = cur.info.dataSize;
396 const auto& binding = cur.binding;
397 if (!binding) {
398 webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
399 return nullptr;
402 const auto availByteCount = binding->ByteCount();
403 if (dataSize > availByteCount) {
404 webgl->ErrorInvalidOperation(
405 "Buffer for uniform block is smaller"
406 " than UNIFORM_BLOCK_DATA_SIZE.");
407 return nullptr;
410 if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
411 LOCAL_GL_UNIFORM_BUFFER, i))
412 return nullptr;
415 // -
417 const auto& tfo = webgl->mBoundTransformFeedback;
418 if (tfo && tfo->IsActiveAndNotPaused()) {
419 if (fb) {
420 const auto& info = *fb->GetCompletenessInfo();
421 if (info.isMultiview) {
422 webgl->ErrorInvalidOperation(
423 "Cannot render to multiview with transform feedback.");
424 return nullptr;
428 if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
431 // -
433 const auto& fragOutputs = linkInfo->fragOutputs;
434 const auto fnValidateFragOutputType =
435 [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
436 const auto itr = fragOutputs.find(loc);
437 MOZ_DIAGNOSTIC_ASSERT(itr != fragOutputs.end());
439 const auto& info = itr->second;
440 const auto& srcBaseType = info.baseType;
441 if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
442 const auto& srcStr = ToString(srcBaseType);
443 const auto& dstStr = ToString(dstBaseType);
444 webgl->ErrorInvalidOperation(
445 "Program frag output at location %u is type %s,"
446 " but destination draw buffer is type %s.",
447 uint32_t(loc), srcStr, dstStr);
448 return false;
450 return true;
453 if (!webgl->mRasterizerDiscardEnabled) {
454 uint8_t fbZLayerCount = 1;
455 auto hasAttachment = std::bitset<webgl::kMaxDrawBuffers>(1);
456 auto drawBufferEnabled = std::bitset<webgl::kMaxDrawBuffers>();
457 if (fb) {
458 drawBufferEnabled = fb->DrawBufferEnabled();
459 const auto& info = *fb->GetCompletenessInfo();
460 fbZLayerCount = info.zLayerCount;
461 hasAttachment = info.hasAttachment;
462 } else {
463 drawBufferEnabled[0] = (webgl->mDefaultFB_DrawBuffer0 == LOCAL_GL_BACK);
466 if (fbZLayerCount != linkInfo->zLayerCount) {
467 webgl->ErrorInvalidOperation(
468 "Multiview count mismatch: shader: %u, framebuffer: %u",
469 uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
470 return nullptr;
473 const auto writable =
474 hasAttachment & drawBufferEnabled & webgl->mColorWriteMaskNonzero;
475 if (writable.any()) {
476 // Do we have any undefined outputs with real attachments that
477 // aren't masked-out by color write mask or drawBuffers?
478 const auto wouldWriteUndefined = ~linkInfo->hasOutput & writable;
479 if (wouldWriteUndefined.any()) {
480 const auto first = FindFirstOne(wouldWriteUndefined);
481 webgl->ErrorInvalidOperation(
482 "Program has no frag output at location %u, the"
483 " destination draw buffer has an attached"
484 " image, and its color write mask is not all false,"
485 " and DRAW_BUFFER%u is not NONE.",
486 uint32_t(first), uint32_t(first));
487 return nullptr;
490 const auto outputWrites = linkInfo->hasOutput & writable;
492 if (fb) {
493 for (const auto& attach : fb->ColorDrawBuffers()) {
494 const auto i =
495 uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
496 if (!outputWrites[i]) continue;
497 const auto& imageInfo = attach->GetImageInfo();
498 if (!imageInfo) continue;
499 const auto& dstBaseType = imageInfo->mFormat->format->baseType;
500 if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
502 } else {
503 if (outputWrites[0]) {
504 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
505 return nullptr;
511 // -
513 const auto fetchLimits = linkInfo->GetDrawFetchLimits();
514 if (!fetchLimits) return nullptr;
516 if (instanceCount > fetchLimits->maxInstances) {
517 webgl->ErrorInvalidOperation(
518 "Instance fetch requires %u, but attribs only"
519 " supply %u.",
520 instanceCount, uint32_t(fetchLimits->maxInstances));
521 return nullptr;
524 if (tfo) {
525 for (const auto& used : fetchLimits->usedBuffers) {
526 MOZ_ASSERT(used.buffer);
527 if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
528 used.id))
529 return nullptr;
533 // -
535 webgl->RunContextLossTimer();
537 return fetchLimits;
540 ////////////////////////////////////////
542 class ScopedFakeVertexAttrib0 final {
543 WebGLContext* const mWebGL;
544 bool mDidFake = false;
546 public:
547 ScopedFakeVertexAttrib0(WebGLContext* const webgl, const uint64_t vertexCount,
548 bool* const out_error)
549 : mWebGL(webgl) {
550 *out_error = false;
552 if (!mWebGL->DoFakeVertexAttrib0(vertexCount)) {
553 *out_error = true;
554 return;
556 mDidFake = true;
559 ~ScopedFakeVertexAttrib0() {
560 if (mDidFake) {
561 mWebGL->UndoFakeVertexAttrib0();
566 ////////////////////////////////////////
568 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
569 uint8_t vertsPerPrim;
571 switch (mode) {
572 case LOCAL_GL_POINTS:
573 vertsPerPrim = 1;
574 break;
575 case LOCAL_GL_LINES:
576 vertsPerPrim = 2;
577 break;
578 case LOCAL_GL_TRIANGLES:
579 vertsPerPrim = 3;
580 break;
581 default:
582 MOZ_CRASH("`mode`");
585 return vertCount / vertsPerPrim * vertsPerPrim;
588 class ScopedDrawWithTransformFeedback final {
589 WebGLContext* const mWebGL;
590 WebGLTransformFeedback* const mTFO;
591 const bool mWithTF;
592 uint32_t mUsedVerts;
594 public:
595 ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
596 uint32_t vertCount, uint32_t instanceCount,
597 bool* const out_error)
598 : mWebGL(webgl),
599 mTFO(mWebGL->mBoundTransformFeedback),
600 mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
601 mUsedVerts(0) {
602 *out_error = false;
603 if (!mWithTF) return;
605 if (mode != mTFO->mActive_PrimMode) {
606 mWebGL->ErrorInvalidOperation(
607 "Drawing with transform feedback requires"
608 " `mode` to match BeginTransformFeedback's"
609 " `primitiveMode`.");
610 *out_error = true;
611 return;
614 const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
615 const auto usedVerts =
616 CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
618 const auto remainingCapacity =
619 mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
620 if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
621 mWebGL->ErrorInvalidOperation(
622 "Insufficient buffer capacity remaining for"
623 " transform feedback.");
624 *out_error = true;
625 return;
628 mUsedVerts = usedVerts.value();
631 void Advance() const {
632 if (!mWithTF) return;
634 mTFO->mActive_VertPosition += mUsedVerts;
636 for (const auto& cur : mTFO->mIndexedBindings) {
637 const auto& buffer = cur.mBufferBinding;
638 if (buffer) {
639 buffer->ResetLastUpdateFenceId();
645 static bool HasInstancedDrawing(const WebGLContext& webgl) {
646 return webgl.IsWebGL2() ||
647 webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
650 ////////////////////////////////////////
652 void WebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
653 GLsizei vertCount,
654 GLsizei instanceCount) {
655 const FuncScope funcScope(*this, "drawArraysInstanced");
656 AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
657 if (IsContextLost()) return;
658 const gl::GLContext::TlsScope inTls(gl);
660 // -
662 if (!ValidateNonNegative("first", first) ||
663 !ValidateNonNegative("vertCount", vertCount) ||
664 !ValidateNonNegative("instanceCount", instanceCount)) {
665 return;
668 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
669 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
670 if (mPrimRestartTypeBytes != 0) {
671 mPrimRestartTypeBytes = 0;
673 // OSX appears to have severe perf issues with leaving this enabled.
674 gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
678 // -
680 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
681 if (!fetchLimits) return;
683 // -
685 const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
686 if (!totalVertCount_safe.isValid()) {
687 ErrorOutOfMemory("`first+vertCount` out of range.");
688 return;
690 auto totalVertCount = totalVertCount_safe.value();
692 if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
693 ErrorInvalidOperation(
694 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
695 uint32_t(fetchLimits->maxVerts));
696 return;
699 // -
701 bool error = false;
702 const ScopedFakeVertexAttrib0 attrib0(this, totalVertCount, &error);
703 if (error) return;
705 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
706 if (error) return;
708 const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
709 instanceCount, &error);
710 if (error) return;
713 ScopedDrawCallWrapper wrapper(*this);
714 if (vertCount && instanceCount) {
715 AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS);
716 if (HasInstancedDrawing(*this)) {
717 gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
718 } else {
719 MOZ_ASSERT(instanceCount == 1);
720 gl->fDrawArrays(mode, first, vertCount);
725 Draw_cleanup();
726 scopedTF.Advance();
729 ////////////////////////////////////////
731 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
732 const GLenum type,
733 const WebGLintptr byteOffset,
734 const GLsizei instanceCount) {
735 if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
736 !mBoundTransformFeedback->mIsPaused) {
737 ErrorInvalidOperation(
738 "DrawElements* functions are incompatible with"
739 " transform feedback.");
740 return nullptr;
743 if (!ValidateNonNegative("vertCount", rawIndexCount) ||
744 !ValidateNonNegative("byteOffset", byteOffset) ||
745 !ValidateNonNegative("instanceCount", instanceCount)) {
746 return nullptr;
748 const auto indexCount = uint32_t(rawIndexCount);
750 uint8_t bytesPerIndex = 0;
751 switch (type) {
752 case LOCAL_GL_UNSIGNED_BYTE:
753 bytesPerIndex = 1;
754 break;
756 case LOCAL_GL_UNSIGNED_SHORT:
757 bytesPerIndex = 2;
758 break;
760 case LOCAL_GL_UNSIGNED_INT:
761 if (IsWebGL2() ||
762 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
763 bytesPerIndex = 4;
765 break;
767 if (!bytesPerIndex) {
768 ErrorInvalidEnumInfo("type", type);
769 return nullptr;
771 if (byteOffset % bytesPerIndex != 0) {
772 ErrorInvalidOperation(
773 "`byteOffset` must be a multiple of the size of `type`");
774 return nullptr;
777 ////
779 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
780 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
781 if (mPrimRestartTypeBytes != bytesPerIndex) {
782 mPrimRestartTypeBytes = bytesPerIndex;
784 const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
785 gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
786 gl->fPrimitiveRestartIndex(ones);
790 ////
791 // Index fetching
793 const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
794 if (!indexBuffer) {
795 ErrorInvalidOperation("Index buffer not bound.");
796 return nullptr;
799 const size_t availBytes = indexBuffer->ByteLength();
800 const auto availIndices =
801 AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
802 if (instanceCount && indexCount > availIndices) {
803 ErrorInvalidOperation("Index buffer too small.");
804 return nullptr;
807 return indexBuffer.get();
810 static void HandleDrawElementsErrors(
811 WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
812 const auto err = errorScope.GetError();
813 if (err == LOCAL_GL_INVALID_OPERATION) {
814 webgl->ErrorInvalidOperation(
815 "Driver rejected indexed draw call, possibly"
816 " due to out-of-bounds indices.");
817 return;
820 MOZ_ASSERT(!err);
821 if (err) {
822 webgl->ErrorImplementationBug(
823 "Unexpected driver error during indexed draw"
824 " call. Please file a bug.");
825 return;
829 void WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount,
830 GLenum type, WebGLintptr byteOffset,
831 GLsizei instanceCount) {
832 const FuncScope funcScope(*this, "drawElementsInstanced");
833 AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
834 if (IsContextLost()) return;
836 const gl::GLContext::TlsScope inTls(gl);
838 const auto indexBuffer =
839 DrawElements_check(indexCount, type, byteOffset, instanceCount);
840 if (!indexBuffer) return;
842 // -
844 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
845 if (!fetchLimits) return;
847 bool collapseToDrawArrays = false;
848 auto fakeVertCount = fetchLimits->maxVerts;
849 if (fetchLimits->maxVerts == UINT64_MAX) {
850 // This isn't observable, and keeps FakeVertexAttrib0 sane.
851 collapseToDrawArrays = true;
852 fakeVertCount = 1;
855 // -
858 uint64_t indexCapacity = indexBuffer->ByteLength();
859 switch (type) {
860 case LOCAL_GL_UNSIGNED_BYTE:
861 break;
862 case LOCAL_GL_UNSIGNED_SHORT:
863 indexCapacity /= 2;
864 break;
865 case LOCAL_GL_UNSIGNED_INT:
866 indexCapacity /= 4;
867 break;
870 uint32_t maxVertId = 0;
871 const auto isFetchValid = [&]() {
872 if (!indexCount || !instanceCount) return true;
874 const auto globalMaxVertId =
875 indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
876 if (!globalMaxVertId) return true;
877 if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
879 const auto exactMaxVertId =
880 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
881 maxVertId = exactMaxVertId.value();
882 return maxVertId < fetchLimits->maxVerts;
883 }();
884 if (!isFetchValid) {
885 ErrorInvalidOperation(
886 "Indexed vertex fetch requires %u vertices, but"
887 " attribs only supply %u.",
888 maxVertId + 1, uint32_t(fetchLimits->maxVerts));
889 return;
893 // -
895 bool error = false;
896 const ScopedFakeVertexAttrib0 attrib0(this, fakeVertCount, &error);
897 if (error) return;
899 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
900 if (error) return;
903 ScopedDrawCallWrapper wrapper(*this);
905 UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
906 if (MOZ_UNLIKELY(gl->IsANGLE() &&
907 gl->mDebugFlags &
908 gl::GLContext::DebugFlagAbortOnError)) {
909 // ANGLE does range validation even when it doesn't need to.
910 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
911 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
914 if (indexCount && instanceCount) {
915 AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
916 if (HasInstancedDrawing(*this)) {
917 if (MOZ_UNLIKELY(collapseToDrawArrays)) {
918 gl->fDrawArraysInstanced(mode, 0, 1, instanceCount);
919 } else {
920 gl->fDrawElementsInstanced(mode, indexCount, type,
921 reinterpret_cast<GLvoid*>(byteOffset),
922 instanceCount);
924 } else {
925 MOZ_ASSERT(instanceCount == 1);
926 if (MOZ_UNLIKELY(collapseToDrawArrays)) {
927 gl->fDrawArrays(mode, 0, 1);
928 } else {
929 gl->fDrawElements(mode, indexCount, type,
930 reinterpret_cast<GLvoid*>(byteOffset));
935 if (errorScope) {
936 HandleDrawElementsErrors(this, *errorScope);
941 Draw_cleanup();
944 ////////////////////////////////////////
946 void WebGLContext::Draw_cleanup() {
947 if (gl->WorkAroundDriverBugs()) {
948 if (gl->Renderer() == gl::GLRenderer::Tegra) {
949 mDrawCallsSinceLastFlush++;
951 if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
952 gl->fFlush();
953 mDrawCallsSinceLastFlush = 0;
958 // Let's check for a really common error: Viewport is larger than the actual
959 // destination framebuffer.
960 uint32_t destWidth;
961 uint32_t destHeight;
962 if (mBoundDrawFramebuffer) {
963 const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
964 destWidth = info->width;
965 destHeight = info->height;
966 } else {
967 destWidth = mDefaultFB->mSize.width;
968 destHeight = mDefaultFB->mSize.height;
971 if (mViewportWidth > int32_t(destWidth) ||
972 mViewportHeight > int32_t(destHeight)) {
973 if (!mAlreadyWarnedAboutViewportLargerThanDest) {
974 GenerateWarning(
975 "Drawing to a destination rect smaller than the viewport"
976 " rect. (This warning will only be given once)");
977 mAlreadyWarnedAboutViewportLargerThanDest = true;
982 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
983 MOZ_ASSERT(mCurrentProgram);
984 MOZ_ASSERT(mActiveProgramLinkInfo);
986 bool legacyAttrib0 = gl->IsCompatibilityProfile();
987 #ifdef XP_MACOSX
988 if (gl->WorkAroundDriverBugs()) {
989 // Failures in conformance/attribs/gl-disabled-vertex-attrib.
990 // Even in Core profiles on NV. Sigh.
991 legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
993 // Also programs with no attribs:
994 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
995 legacyAttrib0 |= !mActiveProgramLinkInfo->active.activeAttribs.size();
997 #endif
999 if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
1001 if (!mActiveProgramLinkInfo->attrib0Active) {
1002 // Ensure that the legacy code has enough buffer.
1003 return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
1006 const auto& isAttribArray0Enabled =
1007 mBoundVertexArray->AttribBinding(0).layout.isArray;
1008 return isAttribArray0Enabled
1009 ? WebGLVertexAttrib0Status::Default
1010 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
1013 bool WebGLContext::DoFakeVertexAttrib0(const uint64_t vertexCount) {
1014 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
1015 if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
1016 return true;
1018 if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
1019 GenerateWarning(
1020 "Drawing without vertex attrib 0 array enabled forces the browser "
1021 "to do expensive emulation work when running on desktop OpenGL "
1022 "platforms, for example on Mac. It is preferable to always draw "
1023 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1024 "to bind some always-used attribute to location 0.");
1025 mAlreadyWarnedAboutFakeVertexAttrib0 = true;
1028 gl->fEnableVertexAttribArray(0);
1030 if (!mFakeVertexAttrib0BufferObject) {
1031 gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
1032 mFakeVertexAttrib0BufferObjectSize = 0;
1034 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
1036 ////
1038 switch (mGenericVertexAttribTypes[0]) {
1039 case webgl::AttribBaseType::Boolean:
1040 case webgl::AttribBaseType::Float:
1041 gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
1042 break;
1044 case webgl::AttribBaseType::Int:
1045 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
1046 break;
1048 case webgl::AttribBaseType::Uint:
1049 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
1050 break;
1053 ////
1055 const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
1056 const auto checked_dataSize = CheckedUint32(vertexCount) * bytesPerVert;
1057 if (!checked_dataSize.isValid()) {
1058 ErrorOutOfMemory(
1059 "Integer overflow trying to construct a fake vertex attrib 0"
1060 " array for a draw-operation with %" PRIu64
1061 " vertices. Try"
1062 " reducing the number of vertices.",
1063 vertexCount);
1064 return false;
1066 const auto dataSize = checked_dataSize.value();
1068 if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
1069 gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
1070 LOCAL_GL_DYNAMIC_DRAW);
1071 mFakeVertexAttrib0BufferObjectSize = dataSize;
1072 mFakeVertexAttrib0DataDefined = false;
1075 if (whatDoesAttrib0Need ==
1076 WebGLVertexAttrib0Status::EmulatedUninitializedArray)
1077 return true;
1079 ////
1081 if (mFakeVertexAttrib0DataDefined &&
1082 memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
1083 0) {
1084 return true;
1087 ////
1089 const UniqueBuffer data(malloc(dataSize));
1090 if (!data) {
1091 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1092 return false;
1094 auto itr = (uint8_t*)data.get();
1095 const auto itrEnd = itr + dataSize;
1096 while (itr != itrEnd) {
1097 memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
1098 itr += bytesPerVert;
1102 gl::GLContext::LocalErrorScope errorScope(*gl);
1104 gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
1106 const auto err = errorScope.GetError();
1107 if (err) {
1108 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1109 return false;
1113 ////
1115 memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
1116 mFakeVertexAttrib0DataDefined = true;
1117 return true;
1120 void WebGLContext::UndoFakeVertexAttrib0() {
1121 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
1122 if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
1123 return;
1125 const auto& binding = mBoundVertexArray->AttribBinding(0);
1126 const auto& buffer = binding.buffer;
1128 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
1130 if (buffer) {
1131 const auto& desc = mBoundVertexArray->AttribDesc(0);
1133 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, buffer->mGLName);
1134 DoVertexAttribPointer(*gl, 0, desc);
1135 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
1139 } // namespace mozilla