Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / canvas / WebGLContextDraw.cpp
blobd5f7b7ca2b26b152b6532b4b81b4f51d3273ae99
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 const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
337 const GLenum mode,
338 const uint32_t instanceCount) {
339 if (!webgl->BindCurFBForDraw()) return nullptr;
341 const auto& fb = webgl->mBoundDrawFramebuffer;
342 if (fb && webgl->mBlendEnabled) {
343 const auto& info = *fb->GetCompletenessInfo();
344 if (info.hasFloat32) {
345 if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
346 webgl->ErrorInvalidOperation(
347 "Float32 blending requires EXT_float_blend.");
348 return nullptr;
350 webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
354 switch (mode) {
355 case LOCAL_GL_TRIANGLES:
356 case LOCAL_GL_TRIANGLE_STRIP:
357 case LOCAL_GL_TRIANGLE_FAN:
358 case LOCAL_GL_POINTS:
359 case LOCAL_GL_LINE_STRIP:
360 case LOCAL_GL_LINE_LOOP:
361 case LOCAL_GL_LINES:
362 break;
363 default:
364 webgl->ErrorInvalidEnumInfo("mode", mode);
365 return nullptr;
368 if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
370 if (!webgl->mActiveProgramLinkInfo) {
371 webgl->ErrorInvalidOperation("The current program is not linked.");
372 return nullptr;
374 const auto& linkInfo = webgl->mActiveProgramLinkInfo;
376 // -
377 // Check UBO sizes.
379 for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
380 const auto& cur = linkInfo->uniformBlocks[i];
381 const auto& dataSize = cur.info.dataSize;
382 const auto& binding = cur.binding;
383 if (!binding) {
384 webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
385 return nullptr;
388 const auto availByteCount = binding->ByteCount();
389 if (dataSize > availByteCount) {
390 webgl->ErrorInvalidOperation(
391 "Buffer for uniform block is smaller"
392 " than UNIFORM_BLOCK_DATA_SIZE.");
393 return nullptr;
396 if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
397 LOCAL_GL_UNIFORM_BUFFER, i))
398 return nullptr;
401 // -
403 const auto& tfo = webgl->mBoundTransformFeedback;
404 if (tfo && tfo->IsActiveAndNotPaused()) {
405 if (fb) {
406 const auto& info = *fb->GetCompletenessInfo();
407 if (info.isMultiview) {
408 webgl->ErrorInvalidOperation(
409 "Cannot render to multiview with transform feedback.");
410 return nullptr;
414 if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
417 // -
419 const auto& fragOutputs = linkInfo->fragOutputs;
420 const auto fnValidateFragOutputType =
421 [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
422 const auto itr = fragOutputs.find(loc);
423 if (MOZ_UNLIKELY(itr == fragOutputs.end())) {
424 webgl->ErrorInvalidOperation(
425 "Program has no frag output at location %u, but"
426 " destination draw buffer has an attached"
427 " image.",
428 uint32_t(loc));
429 return false;
432 const auto& info = itr->second;
433 const auto& srcBaseType = info.baseType;
434 if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
435 const auto& srcStr = ToString(srcBaseType);
436 const auto& dstStr = ToString(dstBaseType);
437 webgl->ErrorInvalidOperation(
438 "Program frag output at location %u is type %s,"
439 " but destination draw buffer is type %s.",
440 uint32_t(loc), srcStr, dstStr);
441 return false;
443 return true;
446 if (!webgl->mRasterizerDiscardEnabled) {
447 uint8_t fbZLayerCount = 1;
448 if (fb) {
449 const auto& info = *fb->GetCompletenessInfo();
450 fbZLayerCount = info.zLayerCount;
453 if (fbZLayerCount != linkInfo->zLayerCount) {
454 webgl->ErrorInvalidOperation(
455 "Multiview count mismatch: shader: %u, framebuffer: %u",
456 uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
457 return nullptr;
460 if (webgl->mColorWriteMask) {
461 if (fb) {
462 for (const auto& attach : fb->ColorDrawBuffers()) {
463 const auto i =
464 uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
465 const auto& imageInfo = attach->GetImageInfo();
466 if (!imageInfo) continue;
467 const auto& dstBaseType = imageInfo->mFormat->format->baseType;
468 if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
470 } else {
471 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
472 return nullptr;
477 // -
479 const auto fetchLimits = linkInfo->GetDrawFetchLimits();
480 if (!fetchLimits) return nullptr;
482 if (instanceCount > fetchLimits->maxInstances) {
483 webgl->ErrorInvalidOperation(
484 "Instance fetch requires %u, but attribs only"
485 " supply %u.",
486 instanceCount, uint32_t(fetchLimits->maxInstances));
487 return nullptr;
490 if (tfo) {
491 for (const auto& used : fetchLimits->usedBuffers) {
492 MOZ_ASSERT(used.buffer);
493 if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
494 used.id))
495 return nullptr;
499 // -
501 webgl->RunContextLossTimer();
503 return fetchLimits;
506 ////////////////////////////////////////
508 class ScopedFakeVertexAttrib0 final {
509 WebGLContext* const mWebGL;
510 bool mDidFake = false;
512 public:
513 ScopedFakeVertexAttrib0(WebGLContext* const webgl, const uint64_t vertexCount,
514 bool* const out_error)
515 : mWebGL(webgl) {
516 *out_error = false;
518 if (!mWebGL->DoFakeVertexAttrib0(vertexCount)) {
519 *out_error = true;
520 return;
522 mDidFake = true;
525 ~ScopedFakeVertexAttrib0() {
526 if (mDidFake) {
527 mWebGL->UndoFakeVertexAttrib0();
532 ////////////////////////////////////////
534 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
535 uint8_t vertsPerPrim;
537 switch (mode) {
538 case LOCAL_GL_POINTS:
539 vertsPerPrim = 1;
540 break;
541 case LOCAL_GL_LINES:
542 vertsPerPrim = 2;
543 break;
544 case LOCAL_GL_TRIANGLES:
545 vertsPerPrim = 3;
546 break;
547 default:
548 MOZ_CRASH("`mode`");
551 return vertCount / vertsPerPrim * vertsPerPrim;
554 class ScopedDrawWithTransformFeedback final {
555 WebGLContext* const mWebGL;
556 WebGLTransformFeedback* const mTFO;
557 const bool mWithTF;
558 uint32_t mUsedVerts;
560 public:
561 ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
562 uint32_t vertCount, uint32_t instanceCount,
563 bool* const out_error)
564 : mWebGL(webgl),
565 mTFO(mWebGL->mBoundTransformFeedback),
566 mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
567 mUsedVerts(0) {
568 *out_error = false;
569 if (!mWithTF) return;
571 if (mode != mTFO->mActive_PrimMode) {
572 mWebGL->ErrorInvalidOperation(
573 "Drawing with transform feedback requires"
574 " `mode` to match BeginTransformFeedback's"
575 " `primitiveMode`.");
576 *out_error = true;
577 return;
580 const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
581 const auto usedVerts =
582 CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
584 const auto remainingCapacity =
585 mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
586 if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
587 mWebGL->ErrorInvalidOperation(
588 "Insufficient buffer capacity remaining for"
589 " transform feedback.");
590 *out_error = true;
591 return;
594 mUsedVerts = usedVerts.value();
597 void Advance() const {
598 if (!mWithTF) return;
600 mTFO->mActive_VertPosition += mUsedVerts;
602 for (const auto& cur : mTFO->mIndexedBindings) {
603 const auto& buffer = cur.mBufferBinding;
604 if (buffer) {
605 buffer->ResetLastUpdateFenceId();
611 static bool HasInstancedDrawing(const WebGLContext& webgl) {
612 return webgl.IsWebGL2() ||
613 webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
616 ////////////////////////////////////////
618 void WebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
619 GLsizei vertCount,
620 GLsizei instanceCount) {
621 const FuncScope funcScope(*this, "drawArraysInstanced");
622 AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
623 if (IsContextLost()) return;
624 const gl::GLContext::TlsScope inTls(gl);
626 // -
628 if (!ValidateNonNegative("first", first) ||
629 !ValidateNonNegative("vertCount", vertCount) ||
630 !ValidateNonNegative("instanceCount", instanceCount)) {
631 return;
634 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
635 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
636 if (mPrimRestartTypeBytes != 0) {
637 mPrimRestartTypeBytes = 0;
639 // OSX appears to have severe perf issues with leaving this enabled.
640 gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
644 // -
646 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
647 if (!fetchLimits) return;
649 // -
651 const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
652 if (!totalVertCount_safe.isValid()) {
653 ErrorOutOfMemory("`first+vertCount` out of range.");
654 return;
656 auto totalVertCount = totalVertCount_safe.value();
658 if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
659 ErrorInvalidOperation(
660 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
661 uint32_t(fetchLimits->maxVerts));
662 return;
665 // -
667 bool error = false;
668 const ScopedFakeVertexAttrib0 attrib0(this, totalVertCount, &error);
669 if (error) return;
671 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
672 if (error) return;
674 const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
675 instanceCount, &error);
676 if (error) return;
679 ScopedDrawCallWrapper wrapper(*this);
680 if (vertCount && instanceCount) {
681 AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS);
682 if (HasInstancedDrawing(*this)) {
683 gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
684 } else {
685 MOZ_ASSERT(instanceCount == 1);
686 gl->fDrawArrays(mode, first, vertCount);
691 Draw_cleanup();
692 scopedTF.Advance();
695 ////////////////////////////////////////
697 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
698 const GLenum type,
699 const WebGLintptr byteOffset,
700 const GLsizei instanceCount) {
701 if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
702 !mBoundTransformFeedback->mIsPaused) {
703 ErrorInvalidOperation(
704 "DrawElements* functions are incompatible with"
705 " transform feedback.");
706 return nullptr;
709 if (!ValidateNonNegative("vertCount", rawIndexCount) ||
710 !ValidateNonNegative("byteOffset", byteOffset) ||
711 !ValidateNonNegative("instanceCount", instanceCount)) {
712 return nullptr;
714 const auto indexCount = uint32_t(rawIndexCount);
716 uint8_t bytesPerIndex = 0;
717 switch (type) {
718 case LOCAL_GL_UNSIGNED_BYTE:
719 bytesPerIndex = 1;
720 break;
722 case LOCAL_GL_UNSIGNED_SHORT:
723 bytesPerIndex = 2;
724 break;
726 case LOCAL_GL_UNSIGNED_INT:
727 if (IsWebGL2() ||
728 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
729 bytesPerIndex = 4;
731 break;
733 if (!bytesPerIndex) {
734 ErrorInvalidEnumInfo("type", type);
735 return nullptr;
737 if (byteOffset % bytesPerIndex != 0) {
738 ErrorInvalidOperation(
739 "`byteOffset` must be a multiple of the size of `type`");
740 return nullptr;
743 ////
745 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
746 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
747 if (mPrimRestartTypeBytes != bytesPerIndex) {
748 mPrimRestartTypeBytes = bytesPerIndex;
750 const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
751 gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
752 gl->fPrimitiveRestartIndex(ones);
756 ////
757 // Index fetching
759 const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
760 if (!indexBuffer) {
761 ErrorInvalidOperation("Index buffer not bound.");
762 return nullptr;
765 const size_t availBytes = indexBuffer->ByteLength();
766 const auto availIndices =
767 AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
768 if (instanceCount && indexCount > availIndices) {
769 ErrorInvalidOperation("Index buffer too small.");
770 return nullptr;
773 return indexBuffer.get();
776 static void HandleDrawElementsErrors(
777 WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
778 const auto err = errorScope.GetError();
779 if (err == LOCAL_GL_INVALID_OPERATION) {
780 webgl->ErrorInvalidOperation(
781 "Driver rejected indexed draw call, possibly"
782 " due to out-of-bounds indices.");
783 return;
786 MOZ_ASSERT(!err);
787 if (err) {
788 webgl->ErrorImplementationBug(
789 "Unexpected driver error during indexed draw"
790 " call. Please file a bug.");
791 return;
795 void WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount,
796 GLenum type, WebGLintptr byteOffset,
797 GLsizei instanceCount) {
798 const FuncScope funcScope(*this, "drawElementsInstanced");
799 AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
800 if (IsContextLost()) return;
802 const gl::GLContext::TlsScope inTls(gl);
804 const auto indexBuffer =
805 DrawElements_check(indexCount, type, byteOffset, instanceCount);
806 if (!indexBuffer) return;
808 // -
810 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
811 if (!fetchLimits) return;
813 bool collapseToDrawArrays = false;
814 auto fakeVertCount = fetchLimits->maxVerts;
815 if (fetchLimits->maxVerts == UINT64_MAX) {
816 // This isn't observable, and keeps FakeVertexAttrib0 sane.
817 collapseToDrawArrays = true;
818 fakeVertCount = 1;
821 // -
824 uint64_t indexCapacity = indexBuffer->ByteLength();
825 switch (type) {
826 case LOCAL_GL_UNSIGNED_BYTE:
827 break;
828 case LOCAL_GL_UNSIGNED_SHORT:
829 indexCapacity /= 2;
830 break;
831 case LOCAL_GL_UNSIGNED_INT:
832 indexCapacity /= 4;
833 break;
836 uint32_t maxVertId = 0;
837 const auto isFetchValid = [&]() {
838 if (!indexCount || !instanceCount) return true;
840 const auto globalMaxVertId =
841 indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
842 if (!globalMaxVertId) return true;
843 if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
845 const auto exactMaxVertId =
846 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
847 maxVertId = exactMaxVertId.value();
848 return maxVertId < fetchLimits->maxVerts;
849 }();
850 if (!isFetchValid) {
851 ErrorInvalidOperation(
852 "Indexed vertex fetch requires %u vertices, but"
853 " attribs only supply %u.",
854 maxVertId + 1, uint32_t(fetchLimits->maxVerts));
855 return;
859 // -
861 bool error = false;
862 const ScopedFakeVertexAttrib0 attrib0(this, fakeVertCount, &error);
863 if (error) return;
865 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
866 if (error) return;
869 ScopedDrawCallWrapper wrapper(*this);
871 UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
872 if (MOZ_UNLIKELY(gl->IsANGLE() &&
873 gl->mDebugFlags &
874 gl::GLContext::DebugFlagAbortOnError)) {
875 // ANGLE does range validation even when it doesn't need to.
876 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
877 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
880 if (indexCount && instanceCount) {
881 AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
882 if (HasInstancedDrawing(*this)) {
883 if (MOZ_UNLIKELY(collapseToDrawArrays)) {
884 gl->fDrawArraysInstanced(mode, 0, 1, instanceCount);
885 } else {
886 gl->fDrawElementsInstanced(mode, indexCount, type,
887 reinterpret_cast<GLvoid*>(byteOffset),
888 instanceCount);
890 } else {
891 MOZ_ASSERT(instanceCount == 1);
892 if (MOZ_UNLIKELY(collapseToDrawArrays)) {
893 gl->fDrawArrays(mode, 0, 1);
894 } else {
895 gl->fDrawElements(mode, indexCount, type,
896 reinterpret_cast<GLvoid*>(byteOffset));
901 if (errorScope) {
902 HandleDrawElementsErrors(this, *errorScope);
907 Draw_cleanup();
910 ////////////////////////////////////////
912 void WebGLContext::Draw_cleanup() {
913 if (gl->WorkAroundDriverBugs()) {
914 if (gl->Renderer() == gl::GLRenderer::Tegra) {
915 mDrawCallsSinceLastFlush++;
917 if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
918 gl->fFlush();
919 mDrawCallsSinceLastFlush = 0;
924 // Let's check for a really common error: Viewport is larger than the actual
925 // destination framebuffer.
926 uint32_t destWidth;
927 uint32_t destHeight;
928 if (mBoundDrawFramebuffer) {
929 const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
930 destWidth = info->width;
931 destHeight = info->height;
932 } else {
933 destWidth = mDefaultFB->mSize.width;
934 destHeight = mDefaultFB->mSize.height;
937 if (mViewportWidth > int32_t(destWidth) ||
938 mViewportHeight > int32_t(destHeight)) {
939 if (!mAlreadyWarnedAboutViewportLargerThanDest) {
940 GenerateWarning(
941 "Drawing to a destination rect smaller than the viewport"
942 " rect. (This warning will only be given once)");
943 mAlreadyWarnedAboutViewportLargerThanDest = true;
948 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
949 MOZ_ASSERT(mCurrentProgram);
950 MOZ_ASSERT(mActiveProgramLinkInfo);
952 bool legacyAttrib0 = gl->IsCompatibilityProfile();
953 #ifdef XP_MACOSX
954 if (gl->WorkAroundDriverBugs()) {
955 // Failures in conformance/attribs/gl-disabled-vertex-attrib.
956 // Even in Core profiles on NV. Sigh.
957 legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
959 // Also programs with no attribs:
960 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
961 legacyAttrib0 |= !mActiveProgramLinkInfo->active.activeAttribs.size();
963 #endif
965 if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
967 if (!mActiveProgramLinkInfo->attrib0Active) {
968 // Ensure that the legacy code has enough buffer.
969 return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
972 const auto& isAttribArray0Enabled =
973 mBoundVertexArray->AttribBinding(0).layout.isArray;
974 return isAttribArray0Enabled
975 ? WebGLVertexAttrib0Status::Default
976 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
979 bool WebGLContext::DoFakeVertexAttrib0(const uint64_t vertexCount) {
980 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
981 if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
982 return true;
984 if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
985 GenerateWarning(
986 "Drawing without vertex attrib 0 array enabled forces the browser "
987 "to do expensive emulation work when running on desktop OpenGL "
988 "platforms, for example on Mac. It is preferable to always draw "
989 "with vertex attrib 0 array enabled, by using bindAttribLocation "
990 "to bind some always-used attribute to location 0.");
991 mAlreadyWarnedAboutFakeVertexAttrib0 = true;
994 gl->fEnableVertexAttribArray(0);
996 if (!mFakeVertexAttrib0BufferObject) {
997 gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
998 mFakeVertexAttrib0BufferObjectSize = 0;
1000 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
1002 ////
1004 switch (mGenericVertexAttribTypes[0]) {
1005 case webgl::AttribBaseType::Boolean:
1006 case webgl::AttribBaseType::Float:
1007 gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
1008 break;
1010 case webgl::AttribBaseType::Int:
1011 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
1012 break;
1014 case webgl::AttribBaseType::Uint:
1015 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
1016 break;
1019 ////
1021 const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
1022 const auto checked_dataSize = CheckedUint32(vertexCount) * bytesPerVert;
1023 if (!checked_dataSize.isValid()) {
1024 ErrorOutOfMemory(
1025 "Integer overflow trying to construct a fake vertex attrib 0"
1026 " array for a draw-operation with %" PRIu64
1027 " vertices. Try"
1028 " reducing the number of vertices.",
1029 vertexCount);
1030 return false;
1032 const auto dataSize = checked_dataSize.value();
1034 if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
1035 gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
1036 LOCAL_GL_DYNAMIC_DRAW);
1037 mFakeVertexAttrib0BufferObjectSize = dataSize;
1038 mFakeVertexAttrib0DataDefined = false;
1041 if (whatDoesAttrib0Need ==
1042 WebGLVertexAttrib0Status::EmulatedUninitializedArray)
1043 return true;
1045 ////
1047 if (mFakeVertexAttrib0DataDefined &&
1048 memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
1049 0) {
1050 return true;
1053 ////
1055 const UniqueBuffer data(malloc(dataSize));
1056 if (!data) {
1057 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1058 return false;
1060 auto itr = (uint8_t*)data.get();
1061 const auto itrEnd = itr + dataSize;
1062 while (itr != itrEnd) {
1063 memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
1064 itr += bytesPerVert;
1068 gl::GLContext::LocalErrorScope errorScope(*gl);
1070 gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
1072 const auto err = errorScope.GetError();
1073 if (err) {
1074 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1075 return false;
1079 ////
1081 memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
1082 mFakeVertexAttrib0DataDefined = true;
1083 return true;
1086 void WebGLContext::UndoFakeVertexAttrib0() {
1087 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
1088 if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
1089 return;
1091 const auto& binding = mBoundVertexArray->AttribBinding(0);
1092 const auto& buffer = binding.buffer;
1094 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
1096 if (buffer) {
1097 const auto& desc = mBoundVertexArray->AttribDesc(0);
1099 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, buffer->mGLName);
1100 DoVertexAttribPointer(*gl, 0, desc);
1101 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
1105 } // namespace mozilla