no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / canvas / WebGLContextDraw.cpp
blobb61b5aeee2fa50c85d92896e7f85bfe6c56bdf04
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 iVertCount,
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", iVertCount) ||
688 !ValidateNonNegative("instanceCount", instanceCount)) {
689 return;
691 const auto vertCount = AssertedCast<uint32_t>(iVertCount);
693 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
694 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
695 if (mPrimRestartTypeBytes != 0) {
696 mPrimRestartTypeBytes = 0;
698 // OSX appears to have severe perf issues with leaving this enabled.
699 gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
703 // -
705 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
706 if (!fetchLimits) return;
708 // -
710 const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
711 if (!totalVertCount_safe.isValid()) {
712 ErrorOutOfMemory("`first+vertCount` out of range.");
713 return;
715 auto totalVertCount = totalVertCount_safe.value();
717 if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
718 ErrorInvalidOperation(
719 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
720 uint32_t(fetchLimits->maxVerts));
721 return;
724 if (vertCount > mMaxVertIdsPerDraw) {
725 ErrorOutOfMemory(
726 "Context's max vertCount is %u, but %u requested. "
727 "[webgl.max-vert-ids-per-draw]",
728 mMaxVertIdsPerDraw, vertCount);
729 return;
732 // -
734 bool error = false;
736 // -
738 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
739 if (error) return;
741 const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
742 instanceCount, &error);
743 if (error) return;
745 // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
746 // attribs are fetched from. There are two ways to fix this:
747 // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
748 // `byteOffset`
749 // 2. OR offset all non-instanced vertex attrib pointers back, and call
750 // DrawArrays with first:0.
751 // * But now gl_VertexID will be wrong! So we inject a uniform to offset it
752 // back correctly.
753 // #1 ought to be the lowest overhead for any first>0,
754 // but DrawElements can't be used with transform-feedback,
755 // so we need #2 to also work.
756 // For now, only implement #2.
758 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
760 auto driverFirst = first;
762 if (first && mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst) {
763 // This is not particularly optimized, but we can if we need to.
764 bool hasInstancedUserAttrib = false;
765 bool hasVertexAttrib = false;
766 for (const auto& a : activeAttribs) {
767 if (a.location == -1) {
768 if (a.name == "gl_VertexID") {
769 hasVertexAttrib = true;
771 continue;
773 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
774 if (binding.layout.divisor) {
775 hasInstancedUserAttrib = true;
776 } else {
777 hasVertexAttrib = true;
780 if (hasInstancedUserAttrib && hasVertexAttrib) {
781 driverFirst = 0;
784 if (driverFirst != first) {
785 for (const auto& a : activeAttribs) {
786 if (a.location == -1) continue;
787 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
788 if (binding.layout.divisor) continue;
790 mBoundVertexArray->DoVertexAttrib(a.location, first);
793 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, first);
797 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
798 auto fakeVertCount = uint64_t(driverFirst) + vertCount;
799 if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default) {
800 fakeVertCount = 0;
802 if (!(vertCount && instanceCount)) {
803 fakeVertCount = 0;
806 auto undoAttrib0 = MakeScopeExit([&]() {
807 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
808 WebGLVertexAttrib0Status::Default);
809 UndoFakeVertexAttrib0();
811 if (fakeVertCount) {
812 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
813 error = true;
814 undoAttrib0.release();
816 } else {
817 // No fake-verts needed.
818 undoAttrib0.release();
821 ScopedDrawCallWrapper wrapper(*this);
822 if (vertCount && instanceCount) {
823 if (HasInstancedDrawing(*this)) {
824 gl->fDrawArraysInstanced(mode, driverFirst, vertCount, instanceCount);
825 } else {
826 MOZ_ASSERT(instanceCount == 1);
827 gl->fDrawArrays(mode, driverFirst, vertCount);
832 if (driverFirst != first) {
833 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, 0);
835 for (const auto& a : activeAttribs) {
836 if (a.location == -1) continue;
837 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
838 if (binding.layout.divisor) continue;
840 mBoundVertexArray->DoVertexAttrib(a.location, 0);
844 Draw_cleanup();
845 scopedTF.Advance();
848 ////////////////////////////////////////
850 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
851 const GLenum type,
852 const WebGLintptr byteOffset,
853 const GLsizei instanceCount) {
854 if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
855 !mBoundTransformFeedback->mIsPaused) {
856 ErrorInvalidOperation(
857 "DrawElements* functions are incompatible with"
858 " transform feedback.");
859 return nullptr;
862 if (!ValidateNonNegative("vertCount", rawIndexCount) ||
863 !ValidateNonNegative("byteOffset", byteOffset) ||
864 !ValidateNonNegative("instanceCount", instanceCount)) {
865 return nullptr;
867 const auto indexCount = uint32_t(rawIndexCount);
869 uint8_t bytesPerIndex = 0;
870 switch (type) {
871 case LOCAL_GL_UNSIGNED_BYTE:
872 bytesPerIndex = 1;
873 break;
875 case LOCAL_GL_UNSIGNED_SHORT:
876 bytesPerIndex = 2;
877 break;
879 case LOCAL_GL_UNSIGNED_INT:
880 if (IsWebGL2() ||
881 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
882 bytesPerIndex = 4;
884 break;
886 if (!bytesPerIndex) {
887 ErrorInvalidEnumInfo("type", type);
888 return nullptr;
890 if (byteOffset % bytesPerIndex != 0) {
891 ErrorInvalidOperation(
892 "`byteOffset` must be a multiple of the size of `type`");
893 return nullptr;
896 ////
898 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
899 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
900 if (mPrimRestartTypeBytes != bytesPerIndex) {
901 mPrimRestartTypeBytes = bytesPerIndex;
903 const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
904 gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
905 gl->fPrimitiveRestartIndex(ones);
909 ////
910 // Index fetching
912 const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
913 if (!indexBuffer) {
914 ErrorInvalidOperation("Index buffer not bound.");
915 return nullptr;
918 const size_t availBytes = indexBuffer->ByteLength();
919 const auto availIndices =
920 AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
921 if (instanceCount && indexCount > availIndices) {
922 ErrorInvalidOperation("Index buffer too small.");
923 return nullptr;
926 return indexBuffer.get();
929 static void HandleDrawElementsErrors(
930 WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
931 const auto err = errorScope.GetError();
932 if (err == LOCAL_GL_INVALID_OPERATION) {
933 webgl->ErrorInvalidOperation(
934 "Driver rejected indexed draw call, possibly"
935 " due to out-of-bounds indices.");
936 return;
939 MOZ_ASSERT(!err);
940 if (err) {
941 webgl->ErrorImplementationBug(
942 "Unexpected driver error during indexed draw"
943 " call. Please file a bug.");
944 return;
948 void WebGLContext::DrawElementsInstanced(const GLenum mode,
949 const GLsizei iIndexCount,
950 const GLenum type,
951 const WebGLintptr byteOffset,
952 const GLsizei instanceCount) {
953 const FuncScope funcScope(*this, "drawElementsInstanced");
954 // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
955 if (IsContextLost()) return;
957 const gl::GLContext::TlsScope inTls(gl);
959 const auto indexBuffer =
960 DrawElements_check(iIndexCount, type, byteOffset, instanceCount);
961 if (!indexBuffer) return;
962 const auto indexCount = AssertedCast<uint32_t>(iIndexCount);
964 // -
966 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
967 if (!fetchLimits) return;
969 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
971 uint64_t fakeVertCount = 0;
972 if (whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default) {
973 fakeVertCount = fetchLimits->maxVerts;
975 if (!indexCount || !instanceCount) {
976 fakeVertCount = 0;
978 if (fakeVertCount == UINT64_MAX) { // Ok well that's too many!
979 const auto exactMaxVertId =
980 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
981 MOZ_RELEASE_ASSERT(exactMaxVertId);
982 fakeVertCount = uint32_t{*exactMaxVertId};
983 fakeVertCount += 1;
986 // -
989 uint64_t indexCapacity = indexBuffer->ByteLength();
990 switch (type) {
991 case LOCAL_GL_UNSIGNED_BYTE:
992 break;
993 case LOCAL_GL_UNSIGNED_SHORT:
994 indexCapacity /= 2;
995 break;
996 case LOCAL_GL_UNSIGNED_INT:
997 indexCapacity /= 4;
998 break;
1001 uint32_t maxVertId = 0;
1002 const auto isFetchValid = [&]() {
1003 if (!indexCount || !instanceCount) return true;
1005 const auto globalMaxVertId =
1006 indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
1007 if (!globalMaxVertId) return true;
1008 if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
1010 const auto exactMaxVertId =
1011 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
1012 maxVertId = exactMaxVertId.value();
1013 return maxVertId < fetchLimits->maxVerts;
1014 }();
1015 if (!isFetchValid) {
1016 ErrorInvalidOperation(
1017 "Indexed vertex fetch requires %u vertices, but"
1018 " attribs only supply %u.",
1019 maxVertId + 1, uint32_t(fetchLimits->maxVerts));
1020 return;
1024 if (indexCount > mMaxVertIdsPerDraw) {
1025 ErrorOutOfMemory(
1026 "Context's max indexCount is %u, but %u requested. "
1027 "[webgl.max-vert-ids-per-draw]",
1028 mMaxVertIdsPerDraw, indexCount);
1029 return;
1032 // -
1034 bool error = false;
1036 // -
1038 auto undoAttrib0 = MakeScopeExit([&]() {
1039 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
1040 WebGLVertexAttrib0Status::Default);
1041 UndoFakeVertexAttrib0();
1043 if (fakeVertCount) {
1044 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
1045 error = true;
1046 undoAttrib0.release();
1048 } else {
1049 // No fake-verts needed.
1050 undoAttrib0.release();
1053 // -
1055 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
1056 if (error) return;
1059 ScopedDrawCallWrapper wrapper(*this);
1061 UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
1062 if (MOZ_UNLIKELY(gl->IsANGLE() &&
1063 gl->mDebugFlags &
1064 gl::GLContext::DebugFlagAbortOnError)) {
1065 // ANGLE does range validation even when it doesn't need to.
1066 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
1067 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
1070 if (indexCount && instanceCount) {
1071 if (HasInstancedDrawing(*this)) {
1072 gl->fDrawElementsInstanced(mode, indexCount, type,
1073 reinterpret_cast<GLvoid*>(byteOffset),
1074 instanceCount);
1075 } else {
1076 MOZ_ASSERT(instanceCount == 1);
1077 gl->fDrawElements(mode, indexCount, type,
1078 reinterpret_cast<GLvoid*>(byteOffset));
1082 if (errorScope) {
1083 HandleDrawElementsErrors(this, *errorScope);
1088 Draw_cleanup();
1091 ////////////////////////////////////////
1093 void WebGLContext::Draw_cleanup() {
1094 if (gl->WorkAroundDriverBugs()) {
1095 if (gl->Renderer() == gl::GLRenderer::Tegra) {
1096 mDrawCallsSinceLastFlush++;
1098 if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
1099 gl->fFlush();
1100 mDrawCallsSinceLastFlush = 0;
1105 // Let's check for a really common error: Viewport is larger than the actual
1106 // destination framebuffer.
1107 uint32_t destWidth;
1108 uint32_t destHeight;
1109 if (mBoundDrawFramebuffer) {
1110 const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
1111 destWidth = info->width;
1112 destHeight = info->height;
1113 } else {
1114 destWidth = mDefaultFB->mSize.width;
1115 destHeight = mDefaultFB->mSize.height;
1118 if (mViewportWidth > int32_t(destWidth) ||
1119 mViewportHeight > int32_t(destHeight)) {
1120 if (!mAlreadyWarnedAboutViewportLargerThanDest) {
1121 GenerateWarning(
1122 "Drawing to a destination rect smaller than the viewport"
1123 " rect. (This warning will only be given once)");
1124 mAlreadyWarnedAboutViewportLargerThanDest = true;
1129 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
1130 MOZ_ASSERT(mCurrentProgram);
1131 MOZ_ASSERT(mActiveProgramLinkInfo);
1133 bool legacyAttrib0 = mNeedsLegacyVertexAttrib0Handling;
1134 if (gl->WorkAroundDriverBugs() && kIsMacOS) {
1135 // Also programs with no attribs:
1136 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
1137 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
1138 bool hasNonInstancedUserAttrib = false;
1139 for (const auto& a : activeAttribs) {
1140 if (a.location == -1) continue;
1141 const auto& layout = mBoundVertexArray->AttribBinding(a.location).layout;
1142 if (layout.divisor == 0) {
1143 hasNonInstancedUserAttrib = true;
1146 legacyAttrib0 |= !hasNonInstancedUserAttrib;
1149 if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
1150 MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling,
1151 "Invariant need because this turns on index buffer "
1152 "validation, needed for fake-attrib0.");
1154 if (!mActiveProgramLinkInfo->attrib0Active) {
1155 // Attrib0 unused, so just ensure that the legacy code has enough buffer.
1156 return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
1159 const auto& isAttribArray0Enabled =
1160 mBoundVertexArray->AttribBinding(0).layout.isArray;
1161 return isAttribArray0Enabled
1162 ? WebGLVertexAttrib0Status::Default
1163 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
1166 bool WebGLContext::DoFakeVertexAttrib0(
1167 const uint64_t fakeVertexCount,
1168 const WebGLVertexAttrib0Status whatDoesAttrib0Need) {
1169 MOZ_ASSERT(fakeVertexCount);
1170 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default);
1172 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
1173 // Padded/strided to vec4, so 4x4bytes.
1174 const auto effectiveVertAttribBytes =
1175 CheckedInt<int32_t>(fakeVertexCount) * 4 * 4;
1176 if (!effectiveVertAttribBytes.isValid()) {
1177 ErrorOutOfMemory("`offset + count` too large for Mesa.");
1178 return false;
1182 if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
1183 GenerateWarning(
1184 "Drawing without vertex attrib 0 array enabled forces the browser "
1185 "to do expensive emulation work when running on desktop OpenGL "
1186 "platforms, for example on Mac. It is preferable to always draw "
1187 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1188 "to bind some always-used attribute to location 0.");
1189 mAlreadyWarnedAboutFakeVertexAttrib0 = true;
1192 gl->fEnableVertexAttribArray(0);
1194 if (!mFakeVertexAttrib0BufferObject) {
1195 gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
1196 mFakeVertexAttrib0BufferObjectSize = 0;
1198 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
1200 ////
1202 switch (mGenericVertexAttribTypes[0]) {
1203 case webgl::AttribBaseType::Boolean:
1204 case webgl::AttribBaseType::Float:
1205 gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
1206 break;
1208 case webgl::AttribBaseType::Int:
1209 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
1210 break;
1212 case webgl::AttribBaseType::Uint:
1213 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
1214 break;
1217 ////
1219 const auto maxFakeVerts = StaticPrefs::webgl_fake_verts_max();
1220 if (fakeVertexCount > maxFakeVerts) {
1221 ErrorOutOfMemory(
1222 "Draw requires faking a vertex attrib 0 array, but required vert count"
1223 " (%" PRIu64 ") is more than webgl.fake-verts.max (%u).",
1224 fakeVertexCount, maxFakeVerts);
1225 return false;
1228 const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
1229 const auto checked_dataSize =
1230 CheckedInt<intptr_t>(fakeVertexCount) * bytesPerVert;
1231 if (!checked_dataSize.isValid()) {
1232 ErrorOutOfMemory(
1233 "Integer overflow trying to construct a fake vertex attrib 0"
1234 " array for a draw-operation with %" PRIu64
1235 " vertices. Try"
1236 " reducing the number of vertices.",
1237 fakeVertexCount);
1238 return false;
1240 const auto dataSize = checked_dataSize.value();
1242 if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
1243 gl::GLContext::LocalErrorScope errorScope(*gl);
1245 gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
1246 LOCAL_GL_DYNAMIC_DRAW);
1248 const auto err = errorScope.GetError();
1249 if (err) {
1250 ErrorOutOfMemory(
1251 "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize);
1252 return false;
1255 mFakeVertexAttrib0BufferObjectSize = dataSize;
1256 mFakeVertexAttrib0DataDefined = false;
1259 if (whatDoesAttrib0Need ==
1260 WebGLVertexAttrib0Status::EmulatedUninitializedArray)
1261 return true;
1263 ////
1265 if (mFakeVertexAttrib0DataDefined &&
1266 memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
1267 0) {
1268 return true;
1271 ////
1273 const auto data = UniqueBuffer::Take(malloc(dataSize));
1274 if (!data) {
1275 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1276 return false;
1278 auto itr = (uint8_t*)data.get();
1279 const auto itrEnd = itr + dataSize;
1280 while (itr != itrEnd) {
1281 memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
1282 itr += bytesPerVert;
1286 gl::GLContext::LocalErrorScope errorScope(*gl);
1288 gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
1290 const auto err = errorScope.GetError();
1291 if (err) {
1292 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1293 return false;
1297 ////
1299 memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
1300 mFakeVertexAttrib0DataDefined = true;
1301 return true;
1304 void WebGLContext::UndoFakeVertexAttrib0() {
1305 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
1306 mBoundVertexArray->DoVertexAttrib(0);
1309 } // namespace mozilla