Bug 1839170 - Refactor Snap pulling, Add Firefox Snap Core22 and GNOME 42 SDK symbols...
[gecko.git] / dom / canvas / WebGLContextDraw.cpp
blobc94db3d6e485de1bf521dc4ee852986f8773fdf0
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 "WebGLFramebuffer.h"
19 #include "WebGLProgram.h"
20 #include "WebGLRenderbuffer.h"
21 #include "WebGLShader.h"
22 #include "WebGLTexture.h"
23 #include "WebGLTransformFeedback.h"
24 #include "WebGLVertexArray.h"
26 #include <algorithm>
28 namespace mozilla {
30 // For a Tegra workaround.
31 static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
33 ////////////////////////////////////////
35 class ScopedResolveTexturesForDraw {
36 struct TexRebindRequest {
37 uint32_t texUnit;
38 WebGLTexture* tex;
41 WebGLContext* const mWebGL;
42 std::vector<TexRebindRequest> mRebindRequests;
44 public:
45 ScopedResolveTexturesForDraw(WebGLContext* webgl, bool* const out_error);
46 ~ScopedResolveTexturesForDraw();
49 static bool ValidateNoSamplingFeedback(const WebGLTexture& tex,
50 const uint32_t sampledLevels,
51 const WebGLFramebuffer* const fb,
52 const uint32_t texUnit) {
53 if (!fb) return true;
55 const auto& texAttachments = fb->GetCompletenessInfo()->texAttachments;
56 for (const auto& attach : texAttachments) {
57 if (attach->Texture() != &tex) continue;
59 const auto& srcBase = tex.Es3_level_base();
60 const auto srcLast = srcBase + sampledLevels - 1;
61 const auto& dstLevel = attach->MipLevel();
62 if (MOZ_UNLIKELY(srcBase <= dstLevel && dstLevel <= srcLast)) {
63 const auto& webgl = tex.mContext;
64 const auto& texTargetStr = EnumString(tex.Target().get());
65 const auto& attachStr = EnumString(attach->mAttachmentPoint);
66 webgl->ErrorInvalidOperation(
67 "Texture level %u would be read by %s unit %u,"
68 " but written by framebuffer attachment %s,"
69 " which would be illegal feedback.",
70 dstLevel, texTargetStr.c_str(), texUnit, attachStr.c_str());
71 return false;
74 return true;
77 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
78 WebGLContext* webgl, bool* const out_error)
79 : mWebGL(webgl) {
80 const auto& fb = mWebGL->mBoundDrawFramebuffer;
82 struct SamplerByTexUnit {
83 uint8_t texUnit;
84 const webgl::SamplerUniformInfo* sampler;
86 Vector<SamplerByTexUnit, 8> samplerByTexUnit;
88 MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
89 const auto& samplerUniforms = mWebGL->mActiveProgramLinkInfo->samplerUniforms;
90 for (const auto& pUniform : samplerUniforms) {
91 const auto& uniform = *pUniform;
92 const auto& texList = uniform.texListForType;
94 const auto& uniformBaseType = uniform.texBaseType;
95 for (const auto& texUnit : uniform.texUnits) {
96 MOZ_ASSERT(texUnit < texList.Length());
99 decltype(SamplerByTexUnit::sampler) prevSamplerForTexUnit = nullptr;
100 for (const auto& cur : samplerByTexUnit) {
101 if (cur.texUnit == texUnit) {
102 prevSamplerForTexUnit = cur.sampler;
105 if (!prevSamplerForTexUnit) {
106 prevSamplerForTexUnit = &uniform;
107 MOZ_RELEASE_ASSERT(samplerByTexUnit.append(
108 SamplerByTexUnit{texUnit, prevSamplerForTexUnit}));
111 if (MOZ_UNLIKELY(&uniform.texListForType !=
112 &prevSamplerForTexUnit->texListForType)) {
113 // Pointing to different tex lists means different types!
114 const auto linkInfo = mWebGL->mActiveProgramLinkInfo;
115 const auto LocInfoBySampler = [&](const webgl::SamplerUniformInfo* p)
116 -> const webgl::LocationInfo* {
117 for (const auto& pair : linkInfo->locationMap) {
118 const auto& locInfo = pair.second;
119 if (locInfo.samplerInfo == p) {
120 return &locInfo;
123 MOZ_CRASH("Can't find sampler location.");
125 const auto& cur = *LocInfoBySampler(&uniform);
126 const auto& prev = *LocInfoBySampler(prevSamplerForTexUnit);
127 mWebGL->ErrorInvalidOperation(
128 "Tex unit %u referenced by samplers of different types:"
129 " %s (via %s) and %s (via %s).",
130 texUnit, EnumString(cur.info.info.elemType).c_str(),
131 cur.PrettyName().c_str(),
132 EnumString(prev.info.info.elemType).c_str(),
133 prev.PrettyName().c_str());
134 *out_error = true;
135 return;
139 const auto& tex = texList[texUnit];
140 if (!tex) continue;
142 const auto& sampler = mWebGL->mBoundSamplers[texUnit];
143 const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
144 if (MOZ_UNLIKELY(!samplingInfo)) { // There was an error.
145 *out_error = true;
146 return;
148 if (MOZ_UNLIKELY(!samplingInfo->IsComplete())) {
149 if (samplingInfo->incompleteReason) {
150 const auto& targetName = GetEnumName(tex->Target().get());
151 mWebGL->GenerateWarning("%s at unit %u is incomplete: %s", targetName,
152 texUnit, samplingInfo->incompleteReason);
154 mRebindRequests.push_back({texUnit, tex});
155 continue;
158 // We have more validation to do if we're otherwise complete:
159 const auto& texBaseType = samplingInfo->usage->format->baseType;
160 if (MOZ_UNLIKELY(texBaseType != uniformBaseType)) {
161 const auto& targetName = GetEnumName(tex->Target().get());
162 const auto& srcType = ToString(texBaseType);
163 const auto& dstType = ToString(uniformBaseType);
164 mWebGL->ErrorInvalidOperation(
165 "%s at unit %u is of type %s, but"
166 " the shader samples as %s.",
167 targetName, texUnit, srcType, dstType);
168 *out_error = true;
169 return;
172 if (MOZ_UNLIKELY(uniform.isShadowSampler !=
173 samplingInfo->isDepthTexCompare)) {
174 const auto& targetName = GetEnumName(tex->Target().get());
175 mWebGL->ErrorInvalidOperation(
176 "%s at unit %u is%s a depth texture"
177 " with TEXTURE_COMPARE_MODE, but"
178 " the shader sampler is%s a shadow"
179 " sampler.",
180 targetName, texUnit, samplingInfo->isDepthTexCompare ? "" : " not",
181 uniform.isShadowSampler ? "" : " not");
182 *out_error = true;
183 return;
186 if (MOZ_UNLIKELY(!ValidateNoSamplingFeedback(*tex, samplingInfo->levels,
187 fb.get(), texUnit))) {
188 *out_error = true;
189 return;
194 const auto& gl = mWebGL->gl;
195 for (const auto& itr : mRebindRequests) {
196 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
197 GLuint incompleteTex = 0; // Tex 0 is always incomplete.
198 const auto& overrideTex = webgl->mIncompleteTexOverride;
199 if (overrideTex) {
200 // In all but the simplest cases, this will be incomplete anyway, since
201 // e.g. int-samplers need int-textures. This is useful for e.g.
202 // dom-to-texture failures, though.
203 incompleteTex = overrideTex->name;
205 gl->fBindTexture(itr.tex->Target().get(), incompleteTex);
209 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
210 if (mRebindRequests.empty()) return;
212 gl::GLContext* gl = mWebGL->gl;
214 for (const auto& itr : mRebindRequests) {
215 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
216 gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
219 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
222 ////////////////////////////////////////
224 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
225 const auto stencilBits = [&]() -> uint8_t {
226 if (!mStencilTestEnabled) return 0;
228 if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0;
230 if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment()) return 8;
232 if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
233 return 8;
235 return 0;
236 }();
237 const uint32_t stencilMax = (1 << stencilBits) - 1;
239 const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
240 const auto fnClamp = [&](const int32_t x) {
241 return std::max(0, std::min(x, (int32_t)stencilMax));
244 bool ok = true;
245 ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
246 ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
247 ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
249 if (!ok) {
250 ErrorInvalidOperation(
251 "Stencil front/back state must effectively match."
252 " (before front/back comparison, WRITEMASK and VALUE_MASK"
253 " are masked with (2^s)-1, and REF is clamped to"
254 " [0, (2^s)-1], where `s` is the number of enabled stencil"
255 " bits in the draw framebuffer)");
257 return ok;
260 // -
262 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget,
263 const uint32_t useId,
264 const GLenum boundTarget,
265 const uint32_t boundId) const {
266 const auto fnName = [&](const GLenum target, const uint32_t id) {
267 auto name = nsCString(EnumString(target).c_str());
268 if (id != static_cast<uint32_t>(-1)) {
269 name += nsPrintfCString("[%u]", id);
271 return name;
273 const auto& useName = fnName(useTarget, useId);
274 const auto& boundName = fnName(boundTarget, boundId);
275 GenerateError(LOCAL_GL_INVALID_OPERATION,
276 "Illegal use of buffer at %s"
277 " while also bound to %s.",
278 useName.BeginReading(), boundName.BeginReading());
281 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
282 const GLenum nonTfTarget,
283 const uint32_t nonTfId) const {
284 bool dupe = false;
285 const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
286 for (const auto& cur : tfAttribs) {
287 dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
289 if (MOZ_LIKELY(!dupe)) return true;
291 dupe = false;
292 for (const auto tfId : IntegerRange(tfAttribs.size())) {
293 const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
294 if (&nonTfBuffer == tfBuffer) {
295 dupe = true;
296 GenErrorIllegalUse(nonTfTarget, nonTfId,
297 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
300 MOZ_ASSERT(dupe);
301 return false;
304 bool WebGLContext::ValidateBuffersForTf(
305 const WebGLTransformFeedback& tfo,
306 const webgl::LinkedProgramInfo& linkInfo) const {
307 size_t numUsed;
308 switch (linkInfo.transformFeedbackBufferMode) {
309 case LOCAL_GL_INTERLEAVED_ATTRIBS:
310 numUsed = 1;
311 break;
313 case LOCAL_GL_SEPARATE_ATTRIBS:
314 numUsed = linkInfo.active.activeTfVaryings.size();
315 break;
317 default:
318 MOZ_CRASH();
321 std::vector<webgl::BufferAndIndex> tfBuffers;
322 tfBuffers.reserve(numUsed);
323 for (const auto i : IntegerRange(numUsed)) {
324 tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
325 static_cast<uint32_t>(i)});
328 return ValidateBuffersForTf(tfBuffers);
331 bool WebGLContext::ValidateBuffersForTf(
332 const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
333 bool dupe = false;
334 const auto fnCheck = [&](const WebGLBuffer* const nonTf,
335 const GLenum nonTfTarget, const uint32_t nonTfId) {
336 for (const auto& tf : tfBuffers) {
337 dupe |= (nonTf && tf.buffer == nonTf);
340 if (MOZ_LIKELY(!dupe)) return false;
342 for (const auto& tf : tfBuffers) {
343 if (nonTf && tf.buffer == nonTf) {
344 dupe = true;
345 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
346 nonTfTarget, nonTfId);
349 return true;
352 fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
353 fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
354 fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
355 fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
356 fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
357 // fnCheck(mBoundTransformFeedbackBuffer.get(),
358 // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
359 fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
361 for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
362 const auto& cur = mIndexedUniformBufferBindings[i];
363 fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
366 fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
367 LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
368 for (const auto i : IntegerRange(MaxVertexAttribs())) {
369 const auto& binding = mBoundVertexArray->AttribBinding(i);
370 fnCheck(binding.buffer.get(), LOCAL_GL_ARRAY_BUFFER, i);
373 return !dupe;
376 ////////////////////////////////////////
378 template <typename T>
379 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
380 std::vector<T> intersection;
381 std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
382 std::back_inserter(intersection));
383 return !intersection.empty();
386 template <size_t N>
387 static size_t FindFirstOne(const std::bitset<N>& bs) {
388 MOZ_ASSERT(bs.any());
389 // We don't need this to be fast, so don't bother with CLZ intrinsics.
390 for (const auto i : IntegerRange(N)) {
391 if (bs[i]) return i;
393 return -1;
396 const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
397 const GLenum mode,
398 const uint32_t instanceCount) {
399 if (!webgl->BindCurFBForDraw()) return nullptr;
401 const auto& fb = webgl->mBoundDrawFramebuffer;
402 if (fb) {
403 const auto& info = *fb->GetCompletenessInfo();
404 const auto isF32WithBlending = info.isAttachmentF32 & webgl->mBlendEnabled;
405 if (isF32WithBlending.any()) {
406 if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
407 const auto first = FindFirstOne(isF32WithBlending);
408 webgl->ErrorInvalidOperation(
409 "Attachment %u is float32 with blending enabled, which requires "
410 "EXT_float_blend.",
411 uint32_t(first));
412 return nullptr;
414 webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
418 switch (mode) {
419 case LOCAL_GL_TRIANGLES:
420 case LOCAL_GL_TRIANGLE_STRIP:
421 case LOCAL_GL_TRIANGLE_FAN:
422 case LOCAL_GL_POINTS:
423 case LOCAL_GL_LINE_STRIP:
424 case LOCAL_GL_LINE_LOOP:
425 case LOCAL_GL_LINES:
426 break;
427 default:
428 webgl->ErrorInvalidEnumInfo("mode", mode);
429 return nullptr;
432 if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
434 if (!webgl->mActiveProgramLinkInfo) {
435 webgl->ErrorInvalidOperation("The current program is not linked.");
436 return nullptr;
438 const auto& linkInfo = webgl->mActiveProgramLinkInfo;
440 // -
441 // Check UBO sizes.
443 for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
444 const auto& cur = linkInfo->uniformBlocks[i];
445 const auto& dataSize = cur.info.dataSize;
446 const auto& binding = cur.binding;
447 if (!binding) {
448 webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
449 return nullptr;
452 const auto availByteCount = binding->ByteCount();
453 if (dataSize > availByteCount) {
454 webgl->ErrorInvalidOperation(
455 "Buffer for uniform block is smaller"
456 " than UNIFORM_BLOCK_DATA_SIZE.");
457 return nullptr;
460 if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
461 LOCAL_GL_UNIFORM_BUFFER, i))
462 return nullptr;
465 // -
467 const auto& tfo = webgl->mBoundTransformFeedback;
468 if (tfo && tfo->IsActiveAndNotPaused()) {
469 if (fb) {
470 const auto& info = *fb->GetCompletenessInfo();
471 if (info.isMultiview) {
472 webgl->ErrorInvalidOperation(
473 "Cannot render to multiview with transform feedback.");
474 return nullptr;
478 if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
481 // -
483 const auto& fragOutputs = linkInfo->fragOutputs;
484 const auto fnValidateFragOutputType =
485 [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
486 const auto itr = fragOutputs.find(loc);
487 MOZ_DIAGNOSTIC_ASSERT(itr != fragOutputs.end());
489 const auto& info = itr->second;
490 const auto& srcBaseType = info.baseType;
491 if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
492 const auto& srcStr = ToString(srcBaseType);
493 const auto& dstStr = ToString(dstBaseType);
494 webgl->ErrorInvalidOperation(
495 "Program frag output at location %u is type %s,"
496 " but destination draw buffer is type %s.",
497 uint32_t(loc), srcStr, dstStr);
498 return false;
500 return true;
503 if (!webgl->mRasterizerDiscardEnabled) {
504 uint8_t fbZLayerCount = 1;
505 auto hasAttachment = std::bitset<webgl::kMaxDrawBuffers>(1);
506 auto drawBufferEnabled = std::bitset<webgl::kMaxDrawBuffers>();
507 if (fb) {
508 drawBufferEnabled = fb->DrawBufferEnabled();
509 const auto& info = *fb->GetCompletenessInfo();
510 fbZLayerCount = info.zLayerCount;
511 hasAttachment = info.hasAttachment;
512 } else {
513 drawBufferEnabled[0] = (webgl->mDefaultFB_DrawBuffer0 == LOCAL_GL_BACK);
516 if (fbZLayerCount != linkInfo->zLayerCount) {
517 webgl->ErrorInvalidOperation(
518 "Multiview count mismatch: shader: %u, framebuffer: %u",
519 uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
520 return nullptr;
523 const auto writable =
524 hasAttachment & drawBufferEnabled & webgl->mColorWriteMaskNonzero;
525 if (writable.any()) {
526 // Do we have any undefined outputs with real attachments that
527 // aren't masked-out by color write mask or drawBuffers?
528 const auto wouldWriteUndefined = ~linkInfo->hasOutput & writable;
529 if (wouldWriteUndefined.any()) {
530 const auto first = FindFirstOne(wouldWriteUndefined);
531 webgl->ErrorInvalidOperation(
532 "Program has no frag output at location %u, the"
533 " destination draw buffer has an attached"
534 " image, and its color write mask is not all false,"
535 " and DRAW_BUFFER%u is not NONE.",
536 uint32_t(first), uint32_t(first));
537 return nullptr;
540 const auto outputWrites = linkInfo->hasOutput & writable;
542 if (fb) {
543 for (const auto& attach : fb->ColorDrawBuffers()) {
544 const auto i =
545 uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
546 if (!outputWrites[i]) continue;
547 const auto& imageInfo = attach->GetImageInfo();
548 if (!imageInfo) continue;
549 const auto& dstBaseType = imageInfo->mFormat->format->baseType;
550 if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
552 } else {
553 if (outputWrites[0]) {
554 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
555 return nullptr;
561 // -
563 const auto fetchLimits = linkInfo->GetDrawFetchLimits();
564 if (!fetchLimits) return nullptr;
566 if (instanceCount > fetchLimits->maxInstances) {
567 webgl->ErrorInvalidOperation(
568 "Instance fetch requires %u, but attribs only"
569 " supply %u.",
570 instanceCount, uint32_t(fetchLimits->maxInstances));
571 return nullptr;
574 if (tfo) {
575 for (const auto& used : fetchLimits->usedBuffers) {
576 MOZ_ASSERT(used.buffer);
577 if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
578 used.id))
579 return nullptr;
583 // -
585 webgl->RunContextLossTimer();
587 return fetchLimits;
590 ////////////////////////////////////////
592 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
593 uint8_t vertsPerPrim;
595 switch (mode) {
596 case LOCAL_GL_POINTS:
597 vertsPerPrim = 1;
598 break;
599 case LOCAL_GL_LINES:
600 vertsPerPrim = 2;
601 break;
602 case LOCAL_GL_TRIANGLES:
603 vertsPerPrim = 3;
604 break;
605 default:
606 MOZ_CRASH("`mode`");
609 return vertCount / vertsPerPrim * vertsPerPrim;
612 class ScopedDrawWithTransformFeedback final {
613 WebGLContext* const mWebGL;
614 WebGLTransformFeedback* const mTFO;
615 const bool mWithTF;
616 uint32_t mUsedVerts;
618 public:
619 ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
620 uint32_t vertCount, uint32_t instanceCount,
621 bool* const out_error)
622 : mWebGL(webgl),
623 mTFO(mWebGL->mBoundTransformFeedback),
624 mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
625 mUsedVerts(0) {
626 *out_error = false;
627 if (!mWithTF) return;
629 if (mode != mTFO->mActive_PrimMode) {
630 mWebGL->ErrorInvalidOperation(
631 "Drawing with transform feedback requires"
632 " `mode` to match BeginTransformFeedback's"
633 " `primitiveMode`.");
634 *out_error = true;
635 return;
638 const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
639 const auto usedVerts =
640 CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
642 const auto remainingCapacity =
643 mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
644 if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
645 mWebGL->ErrorInvalidOperation(
646 "Insufficient buffer capacity remaining for"
647 " transform feedback.");
648 *out_error = true;
649 return;
652 mUsedVerts = usedVerts.value();
655 void Advance() const {
656 if (!mWithTF) return;
658 mTFO->mActive_VertPosition += mUsedVerts;
660 for (const auto& cur : mTFO->mIndexedBindings) {
661 const auto& buffer = cur.mBufferBinding;
662 if (buffer) {
663 buffer->ResetLastUpdateFenceId();
669 static bool HasInstancedDrawing(const WebGLContext& webgl) {
670 return webgl.IsWebGL2() ||
671 webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
674 ////////////////////////////////////////
676 void WebGLContext::DrawArraysInstanced(const GLenum mode, const GLint first,
677 const GLsizei vertCount,
678 const GLsizei instanceCount) {
679 const FuncScope funcScope(*this, "drawArraysInstanced");
680 // AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
681 if (IsContextLost()) return;
682 const gl::GLContext::TlsScope inTls(gl);
684 // -
686 if (!ValidateNonNegative("first", first) ||
687 !ValidateNonNegative("vertCount", vertCount) ||
688 !ValidateNonNegative("instanceCount", instanceCount)) {
689 return;
692 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
693 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
694 if (mPrimRestartTypeBytes != 0) {
695 mPrimRestartTypeBytes = 0;
697 // OSX appears to have severe perf issues with leaving this enabled.
698 gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
702 // -
704 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
705 if (!fetchLimits) return;
707 // -
709 const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
710 if (!totalVertCount_safe.isValid()) {
711 ErrorOutOfMemory("`first+vertCount` out of range.");
712 return;
714 auto totalVertCount = totalVertCount_safe.value();
716 if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
717 ErrorInvalidOperation(
718 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
719 uint32_t(fetchLimits->maxVerts));
720 return;
723 // -
725 bool error = false;
727 // -
729 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
730 if (error) return;
732 const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
733 instanceCount, &error);
734 if (error) return;
736 // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
737 // attribs are fetched from. There are two ways to fix this:
738 // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
739 // `byteOffset`
740 // 2. OR offset all non-instanced vertex attrib pointers back, and call
741 // DrawArrays with first:0.
742 // * But now gl_VertexID will be wrong! So we inject a uniform to offset it
743 // back correctly.
744 // #1 ought to be the lowest overhead for any first>0,
745 // but DrawElements can't be used with transform-feedback,
746 // so we need #2 to also work.
747 // For now, only implement #2.
749 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
751 auto driverFirst = first;
753 if (first && mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst) {
754 // This is not particularly optimized, but we can if we need to.
755 bool hasInstancedUserAttrib = false;
756 bool hasVertexAttrib = false;
757 for (const auto& a : activeAttribs) {
758 if (a.location == -1) {
759 if (a.name == "gl_VertexID") {
760 hasVertexAttrib = true;
762 continue;
764 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
765 if (binding.layout.divisor) {
766 hasInstancedUserAttrib = true;
767 } else {
768 hasVertexAttrib = true;
771 if (hasInstancedUserAttrib && hasVertexAttrib) {
772 driverFirst = 0;
775 if (driverFirst != first) {
776 for (const auto& a : activeAttribs) {
777 if (a.location == -1) continue;
778 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
779 if (binding.layout.divisor) continue;
781 mBoundVertexArray->DoVertexAttrib(a.location, first);
784 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, first);
788 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
789 auto fakeVertCount = uint64_t(driverFirst) + vertCount;
790 if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default) {
791 fakeVertCount = 0;
793 if (!(vertCount && instanceCount)) {
794 fakeVertCount = 0;
797 auto undoAttrib0 = MakeScopeExit([&]() {
798 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
799 WebGLVertexAttrib0Status::Default);
800 UndoFakeVertexAttrib0();
802 if (fakeVertCount) {
803 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
804 error = true;
805 undoAttrib0.release();
807 } else {
808 // No fake-verts needed.
809 undoAttrib0.release();
812 ScopedDrawCallWrapper wrapper(*this);
813 if (vertCount && instanceCount) {
814 if (HasInstancedDrawing(*this)) {
815 gl->fDrawArraysInstanced(mode, driverFirst, vertCount, instanceCount);
816 } else {
817 MOZ_ASSERT(instanceCount == 1);
818 gl->fDrawArrays(mode, driverFirst, vertCount);
823 if (driverFirst != first) {
824 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, 0);
826 for (const auto& a : activeAttribs) {
827 if (a.location == -1) continue;
828 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
829 if (binding.layout.divisor) continue;
831 mBoundVertexArray->DoVertexAttrib(a.location, 0);
835 Draw_cleanup();
836 scopedTF.Advance();
839 ////////////////////////////////////////
841 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
842 const GLenum type,
843 const WebGLintptr byteOffset,
844 const GLsizei instanceCount) {
845 if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
846 !mBoundTransformFeedback->mIsPaused) {
847 ErrorInvalidOperation(
848 "DrawElements* functions are incompatible with"
849 " transform feedback.");
850 return nullptr;
853 if (!ValidateNonNegative("vertCount", rawIndexCount) ||
854 !ValidateNonNegative("byteOffset", byteOffset) ||
855 !ValidateNonNegative("instanceCount", instanceCount)) {
856 return nullptr;
858 const auto indexCount = uint32_t(rawIndexCount);
860 uint8_t bytesPerIndex = 0;
861 switch (type) {
862 case LOCAL_GL_UNSIGNED_BYTE:
863 bytesPerIndex = 1;
864 break;
866 case LOCAL_GL_UNSIGNED_SHORT:
867 bytesPerIndex = 2;
868 break;
870 case LOCAL_GL_UNSIGNED_INT:
871 if (IsWebGL2() ||
872 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
873 bytesPerIndex = 4;
875 break;
877 if (!bytesPerIndex) {
878 ErrorInvalidEnumInfo("type", type);
879 return nullptr;
881 if (byteOffset % bytesPerIndex != 0) {
882 ErrorInvalidOperation(
883 "`byteOffset` must be a multiple of the size of `type`");
884 return nullptr;
887 ////
889 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
890 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
891 if (mPrimRestartTypeBytes != bytesPerIndex) {
892 mPrimRestartTypeBytes = bytesPerIndex;
894 const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
895 gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
896 gl->fPrimitiveRestartIndex(ones);
900 ////
901 // Index fetching
903 const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
904 if (!indexBuffer) {
905 ErrorInvalidOperation("Index buffer not bound.");
906 return nullptr;
909 const size_t availBytes = indexBuffer->ByteLength();
910 const auto availIndices =
911 AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
912 if (instanceCount && indexCount > availIndices) {
913 ErrorInvalidOperation("Index buffer too small.");
914 return nullptr;
917 return indexBuffer.get();
920 static void HandleDrawElementsErrors(
921 WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
922 const auto err = errorScope.GetError();
923 if (err == LOCAL_GL_INVALID_OPERATION) {
924 webgl->ErrorInvalidOperation(
925 "Driver rejected indexed draw call, possibly"
926 " due to out-of-bounds indices.");
927 return;
930 MOZ_ASSERT(!err);
931 if (err) {
932 webgl->ErrorImplementationBug(
933 "Unexpected driver error during indexed draw"
934 " call. Please file a bug.");
935 return;
939 void WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount,
940 GLenum type, WebGLintptr byteOffset,
941 GLsizei instanceCount) {
942 const FuncScope funcScope(*this, "drawElementsInstanced");
943 // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
944 if (IsContextLost()) return;
946 const gl::GLContext::TlsScope inTls(gl);
948 const auto indexBuffer =
949 DrawElements_check(indexCount, type, byteOffset, instanceCount);
950 if (!indexBuffer) return;
952 // -
954 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
955 if (!fetchLimits) return;
957 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
959 uint64_t fakeVertCount = 0;
960 if (whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default) {
961 fakeVertCount = fetchLimits->maxVerts;
963 if (!indexCount || !instanceCount) {
964 fakeVertCount = 0;
966 if (fakeVertCount == UINT64_MAX) { // Ok well that's too many!
967 const auto exactMaxVertId =
968 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
969 MOZ_RELEASE_ASSERT(exactMaxVertId);
970 fakeVertCount = uint32_t{*exactMaxVertId};
971 fakeVertCount += 1;
974 // -
977 uint64_t indexCapacity = indexBuffer->ByteLength();
978 switch (type) {
979 case LOCAL_GL_UNSIGNED_BYTE:
980 break;
981 case LOCAL_GL_UNSIGNED_SHORT:
982 indexCapacity /= 2;
983 break;
984 case LOCAL_GL_UNSIGNED_INT:
985 indexCapacity /= 4;
986 break;
989 uint32_t maxVertId = 0;
990 const auto isFetchValid = [&]() {
991 if (!indexCount || !instanceCount) return true;
993 const auto globalMaxVertId =
994 indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
995 if (!globalMaxVertId) return true;
996 if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
998 const auto exactMaxVertId =
999 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
1000 maxVertId = exactMaxVertId.value();
1001 return maxVertId < fetchLimits->maxVerts;
1002 }();
1003 if (!isFetchValid) {
1004 ErrorInvalidOperation(
1005 "Indexed vertex fetch requires %u vertices, but"
1006 " attribs only supply %u.",
1007 maxVertId + 1, uint32_t(fetchLimits->maxVerts));
1008 return;
1012 // -
1014 bool error = false;
1016 // -
1018 auto undoAttrib0 = MakeScopeExit([&]() {
1019 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
1020 WebGLVertexAttrib0Status::Default);
1021 UndoFakeVertexAttrib0();
1023 if (fakeVertCount) {
1024 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
1025 error = true;
1026 undoAttrib0.release();
1028 } else {
1029 // No fake-verts needed.
1030 undoAttrib0.release();
1033 // -
1035 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
1036 if (error) return;
1039 ScopedDrawCallWrapper wrapper(*this);
1041 UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
1042 if (MOZ_UNLIKELY(gl->IsANGLE() &&
1043 gl->mDebugFlags &
1044 gl::GLContext::DebugFlagAbortOnError)) {
1045 // ANGLE does range validation even when it doesn't need to.
1046 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
1047 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
1050 if (indexCount && instanceCount) {
1051 if (HasInstancedDrawing(*this)) {
1052 gl->fDrawElementsInstanced(mode, indexCount, type,
1053 reinterpret_cast<GLvoid*>(byteOffset),
1054 instanceCount);
1055 } else {
1056 MOZ_ASSERT(instanceCount == 1);
1057 gl->fDrawElements(mode, indexCount, type,
1058 reinterpret_cast<GLvoid*>(byteOffset));
1062 if (errorScope) {
1063 HandleDrawElementsErrors(this, *errorScope);
1068 Draw_cleanup();
1071 ////////////////////////////////////////
1073 void WebGLContext::Draw_cleanup() {
1074 if (gl->WorkAroundDriverBugs()) {
1075 if (gl->Renderer() == gl::GLRenderer::Tegra) {
1076 mDrawCallsSinceLastFlush++;
1078 if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
1079 gl->fFlush();
1080 mDrawCallsSinceLastFlush = 0;
1085 // Let's check for a really common error: Viewport is larger than the actual
1086 // destination framebuffer.
1087 uint32_t destWidth;
1088 uint32_t destHeight;
1089 if (mBoundDrawFramebuffer) {
1090 const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
1091 destWidth = info->width;
1092 destHeight = info->height;
1093 } else {
1094 destWidth = mDefaultFB->mSize.width;
1095 destHeight = mDefaultFB->mSize.height;
1098 if (mViewportWidth > int32_t(destWidth) ||
1099 mViewportHeight > int32_t(destHeight)) {
1100 if (!mAlreadyWarnedAboutViewportLargerThanDest) {
1101 GenerateWarning(
1102 "Drawing to a destination rect smaller than the viewport"
1103 " rect. (This warning will only be given once)");
1104 mAlreadyWarnedAboutViewportLargerThanDest = true;
1109 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
1110 MOZ_ASSERT(mCurrentProgram);
1111 MOZ_ASSERT(mActiveProgramLinkInfo);
1113 bool legacyAttrib0 = mNeedsLegacyVertexAttrib0Handling;
1114 if (gl->WorkAroundDriverBugs() && kIsMacOS) {
1115 // Also programs with no attribs:
1116 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
1117 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
1118 bool hasNonInstancedUserAttrib = false;
1119 for (const auto& a : activeAttribs) {
1120 if (a.location == -1) continue;
1121 const auto& layout = mBoundVertexArray->AttribBinding(a.location).layout;
1122 if (layout.divisor == 0) {
1123 hasNonInstancedUserAttrib = true;
1126 legacyAttrib0 |= !hasNonInstancedUserAttrib;
1129 if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
1130 MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling,
1131 "Invariant need because this turns on index buffer "
1132 "validation, needed for fake-attrib0.");
1134 if (!mActiveProgramLinkInfo->attrib0Active) {
1135 // Attrib0 unused, so just ensure that the legacy code has enough buffer.
1136 return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
1139 const auto& isAttribArray0Enabled =
1140 mBoundVertexArray->AttribBinding(0).layout.isArray;
1141 return isAttribArray0Enabled
1142 ? WebGLVertexAttrib0Status::Default
1143 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
1146 bool WebGLContext::DoFakeVertexAttrib0(
1147 const uint64_t fakeVertexCount,
1148 const WebGLVertexAttrib0Status whatDoesAttrib0Need) {
1149 MOZ_ASSERT(fakeVertexCount);
1150 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default);
1152 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
1153 // Padded/strided to vec4, so 4x4bytes.
1154 const auto effectiveVertAttribBytes =
1155 CheckedInt<int32_t>(fakeVertexCount) * 4 * 4;
1156 if (!effectiveVertAttribBytes.isValid()) {
1157 ErrorOutOfMemory("`offset + count` too large for Mesa.");
1158 return false;
1162 if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
1163 GenerateWarning(
1164 "Drawing without vertex attrib 0 array enabled forces the browser "
1165 "to do expensive emulation work when running on desktop OpenGL "
1166 "platforms, for example on Mac. It is preferable to always draw "
1167 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1168 "to bind some always-used attribute to location 0.");
1169 mAlreadyWarnedAboutFakeVertexAttrib0 = true;
1172 gl->fEnableVertexAttribArray(0);
1174 if (!mFakeVertexAttrib0BufferObject) {
1175 gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
1176 mFakeVertexAttrib0BufferObjectSize = 0;
1178 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
1180 ////
1182 switch (mGenericVertexAttribTypes[0]) {
1183 case webgl::AttribBaseType::Boolean:
1184 case webgl::AttribBaseType::Float:
1185 gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
1186 break;
1188 case webgl::AttribBaseType::Int:
1189 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
1190 break;
1192 case webgl::AttribBaseType::Uint:
1193 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
1194 break;
1197 ////
1199 const auto maxFakeVerts = StaticPrefs::webgl_fake_verts_max();
1200 if (fakeVertexCount > maxFakeVerts) {
1201 ErrorOutOfMemory(
1202 "Draw requires faking a vertex attrib 0 array, but required vert count"
1203 " (%" PRIu64 ") is more than webgl.fake-verts.max (%u).",
1204 fakeVertexCount, maxFakeVerts);
1205 return false;
1208 const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
1209 const auto checked_dataSize =
1210 CheckedInt<intptr_t>(fakeVertexCount) * bytesPerVert;
1211 if (!checked_dataSize.isValid()) {
1212 ErrorOutOfMemory(
1213 "Integer overflow trying to construct a fake vertex attrib 0"
1214 " array for a draw-operation with %" PRIu64
1215 " vertices. Try"
1216 " reducing the number of vertices.",
1217 fakeVertexCount);
1218 return false;
1220 const auto dataSize = checked_dataSize.value();
1222 if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
1223 gl::GLContext::LocalErrorScope errorScope(*gl);
1225 gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
1226 LOCAL_GL_DYNAMIC_DRAW);
1228 const auto err = errorScope.GetError();
1229 if (err) {
1230 ErrorOutOfMemory(
1231 "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize);
1232 return false;
1235 mFakeVertexAttrib0BufferObjectSize = dataSize;
1236 mFakeVertexAttrib0DataDefined = false;
1239 if (whatDoesAttrib0Need ==
1240 WebGLVertexAttrib0Status::EmulatedUninitializedArray)
1241 return true;
1243 ////
1245 if (mFakeVertexAttrib0DataDefined &&
1246 memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
1247 0) {
1248 return true;
1251 ////
1253 const auto data = UniqueBuffer::Take(malloc(dataSize));
1254 if (!data) {
1255 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1256 return false;
1258 auto itr = (uint8_t*)data.get();
1259 const auto itrEnd = itr + dataSize;
1260 while (itr != itrEnd) {
1261 memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
1262 itr += bytesPerVert;
1266 gl::GLContext::LocalErrorScope errorScope(*gl);
1268 gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
1270 const auto err = errorScope.GetError();
1271 if (err) {
1272 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1273 return false;
1277 ////
1279 memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
1280 mFakeVertexAttrib0DataDefined = true;
1281 return true;
1284 void WebGLContext::UndoFakeVertexAttrib0() {
1285 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
1286 mBoundVertexArray->DoVertexAttrib(0);
1289 } // namespace mozilla