Bug 1798929 - Reuse samplerByTexUnit capacity to avoid (de)allocs. r=gfx-reviewers...
[gecko.git] / dom / canvas / WebGLContextDraw.cpp
blobe2bed5cbe59df7c7262fe7f0a3f6332e64fb8771
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 auto& samplerByTexUnit =
83 mWebGL->mReuseable_ScopedResolveTexturesForDraw_samplerByTexUnit;
84 if (!samplerByTexUnit.empty()) {
85 samplerByTexUnit.clear();
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 auto& prevSamplerForTexUnit = samplerByTexUnit[texUnit];
100 if (!prevSamplerForTexUnit) {
101 prevSamplerForTexUnit = &uniform;
103 if (&uniform.texListForType != &prevSamplerForTexUnit->texListForType) {
104 // Pointing to different tex lists means different types!
105 const auto linkInfo = mWebGL->mActiveProgramLinkInfo;
106 const auto LocInfoBySampler = [&](const webgl::SamplerUniformInfo* p)
107 -> const webgl::LocationInfo* {
108 for (const auto& pair : linkInfo->locationMap) {
109 const auto& locInfo = pair.second;
110 if (locInfo.samplerInfo == p) {
111 return &locInfo;
114 MOZ_CRASH("Can't find sampler location.");
116 const auto& cur = *LocInfoBySampler(&uniform);
117 const auto& prev = *LocInfoBySampler(prevSamplerForTexUnit);
118 mWebGL->ErrorInvalidOperation(
119 "Tex unit %u referenced by samplers of different types:"
120 " %s (via %s) and %s (via %s).",
121 texUnit, EnumString(cur.info.info.elemType).c_str(),
122 cur.PrettyName().c_str(),
123 EnumString(prev.info.info.elemType).c_str(),
124 prev.PrettyName().c_str());
125 *out_error = true;
126 return;
130 const auto& tex = texList[texUnit];
131 if (!tex) continue;
133 const auto& sampler = mWebGL->mBoundSamplers[texUnit];
134 const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
135 if (!samplingInfo) { // There was an error.
136 *out_error = true;
137 return;
139 if (!samplingInfo->IsComplete()) {
140 if (samplingInfo->incompleteReason) {
141 const auto& targetName = GetEnumName(tex->Target().get());
142 mWebGL->GenerateWarning("%s at unit %u is incomplete: %s", targetName,
143 texUnit, samplingInfo->incompleteReason);
145 mRebindRequests.push_back({texUnit, tex});
146 continue;
149 // We have more validation to do if we're otherwise complete:
150 const auto& texBaseType = samplingInfo->usage->format->baseType;
151 if (texBaseType != uniformBaseType) {
152 const auto& targetName = GetEnumName(tex->Target().get());
153 const auto& srcType = ToString(texBaseType);
154 const auto& dstType = ToString(uniformBaseType);
155 mWebGL->ErrorInvalidOperation(
156 "%s at unit %u is of type %s, but"
157 " the shader samples as %s.",
158 targetName, texUnit, srcType, dstType);
159 *out_error = true;
160 return;
163 if (uniform.isShadowSampler != samplingInfo->isDepthTexCompare) {
164 const auto& targetName = GetEnumName(tex->Target().get());
165 mWebGL->ErrorInvalidOperation(
166 "%s at unit %u is%s a depth texture"
167 " with TEXTURE_COMPARE_MODE, but"
168 " the shader sampler is%s a shadow"
169 " sampler.",
170 targetName, texUnit, samplingInfo->isDepthTexCompare ? "" : " not",
171 uniform.isShadowSampler ? "" : " not");
172 *out_error = true;
173 return;
176 if (!ValidateNoSamplingFeedback(*tex, samplingInfo->levels, fb.get(),
177 texUnit)) {
178 *out_error = true;
179 return;
184 const auto& gl = mWebGL->gl;
185 for (const auto& itr : mRebindRequests) {
186 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
187 GLuint incompleteTex = 0; // Tex 0 is always incomplete.
188 const auto& overrideTex = webgl->mIncompleteTexOverride;
189 if (overrideTex) {
190 // In all but the simplest cases, this will be incomplete anyway, since
191 // e.g. int-samplers need int-textures. This is useful for e.g.
192 // dom-to-texture failures, though.
193 incompleteTex = overrideTex->name;
195 gl->fBindTexture(itr.tex->Target().get(), incompleteTex);
199 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
200 if (mRebindRequests.empty()) return;
202 gl::GLContext* gl = mWebGL->gl;
204 for (const auto& itr : mRebindRequests) {
205 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
206 gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
209 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
212 ////////////////////////////////////////
214 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
215 const auto stencilBits = [&]() -> uint8_t {
216 if (!mStencilTestEnabled) return 0;
218 if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0;
220 if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment()) return 8;
222 if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
223 return 8;
225 return 0;
226 }();
227 const uint32_t stencilMax = (1 << stencilBits) - 1;
229 const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
230 const auto fnClamp = [&](const int32_t x) {
231 return std::max(0, std::min(x, (int32_t)stencilMax));
234 bool ok = true;
235 ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
236 ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
237 ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
239 if (!ok) {
240 ErrorInvalidOperation(
241 "Stencil front/back state must effectively match."
242 " (before front/back comparison, WRITEMASK and VALUE_MASK"
243 " are masked with (2^s)-1, and REF is clamped to"
244 " [0, (2^s)-1], where `s` is the number of enabled stencil"
245 " bits in the draw framebuffer)");
247 return ok;
250 // -
252 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget,
253 const uint32_t useId,
254 const GLenum boundTarget,
255 const uint32_t boundId) const {
256 const auto fnName = [&](const GLenum target, const uint32_t id) {
257 auto name = nsCString(EnumString(target).c_str());
258 if (id != static_cast<uint32_t>(-1)) {
259 name += nsPrintfCString("[%u]", id);
261 return name;
263 const auto& useName = fnName(useTarget, useId);
264 const auto& boundName = fnName(boundTarget, boundId);
265 GenerateError(LOCAL_GL_INVALID_OPERATION,
266 "Illegal use of buffer at %s"
267 " while also bound to %s.",
268 useName.BeginReading(), boundName.BeginReading());
271 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
272 const GLenum nonTfTarget,
273 const uint32_t nonTfId) const {
274 bool dupe = false;
275 const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
276 for (const auto& cur : tfAttribs) {
277 dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
279 if (MOZ_LIKELY(!dupe)) return true;
281 dupe = false;
282 for (const auto tfId : IntegerRange(tfAttribs.size())) {
283 const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
284 if (&nonTfBuffer == tfBuffer) {
285 dupe = true;
286 GenErrorIllegalUse(nonTfTarget, nonTfId,
287 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
290 MOZ_ASSERT(dupe);
291 return false;
294 bool WebGLContext::ValidateBuffersForTf(
295 const WebGLTransformFeedback& tfo,
296 const webgl::LinkedProgramInfo& linkInfo) const {
297 size_t numUsed;
298 switch (linkInfo.transformFeedbackBufferMode) {
299 case LOCAL_GL_INTERLEAVED_ATTRIBS:
300 numUsed = 1;
301 break;
303 case LOCAL_GL_SEPARATE_ATTRIBS:
304 numUsed = linkInfo.active.activeTfVaryings.size();
305 break;
307 default:
308 MOZ_CRASH();
311 std::vector<webgl::BufferAndIndex> tfBuffers;
312 tfBuffers.reserve(numUsed);
313 for (const auto i : IntegerRange(numUsed)) {
314 tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
315 static_cast<uint32_t>(i)});
318 return ValidateBuffersForTf(tfBuffers);
321 bool WebGLContext::ValidateBuffersForTf(
322 const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
323 bool dupe = false;
324 const auto fnCheck = [&](const WebGLBuffer* const nonTf,
325 const GLenum nonTfTarget, const uint32_t nonTfId) {
326 for (const auto& tf : tfBuffers) {
327 dupe |= (nonTf && tf.buffer == nonTf);
330 if (MOZ_LIKELY(!dupe)) return false;
332 for (const auto& tf : tfBuffers) {
333 if (nonTf && tf.buffer == nonTf) {
334 dupe = true;
335 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
336 nonTfTarget, nonTfId);
339 return true;
342 fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
343 fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
344 fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
345 fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
346 fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
347 // fnCheck(mBoundTransformFeedbackBuffer.get(),
348 // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
349 fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
351 for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
352 const auto& cur = mIndexedUniformBufferBindings[i];
353 fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
356 fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
357 LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
358 for (const auto i : IntegerRange(MaxVertexAttribs())) {
359 const auto& binding = mBoundVertexArray->AttribBinding(i);
360 fnCheck(binding.buffer.get(), LOCAL_GL_ARRAY_BUFFER, i);
363 return !dupe;
366 ////////////////////////////////////////
368 template <typename T>
369 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
370 std::vector<T> intersection;
371 std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
372 std::back_inserter(intersection));
373 return !intersection.empty();
376 template <size_t N>
377 static size_t FindFirstOne(const std::bitset<N>& bs) {
378 MOZ_ASSERT(bs.any());
379 // We don't need this to be fast, so don't bother with CLZ intrinsics.
380 for (const auto i : IntegerRange(N)) {
381 if (bs[i]) return i;
383 return -1;
386 const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
387 const GLenum mode,
388 const uint32_t instanceCount) {
389 if (!webgl->BindCurFBForDraw()) return nullptr;
391 const auto& fb = webgl->mBoundDrawFramebuffer;
392 if (fb) {
393 const auto& info = *fb->GetCompletenessInfo();
394 const auto isF32WithBlending = info.isAttachmentF32 & webgl->mBlendEnabled;
395 if (isF32WithBlending.any()) {
396 if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
397 const auto first = FindFirstOne(isF32WithBlending);
398 webgl->ErrorInvalidOperation(
399 "Attachment %u is float32 with blending enabled, which requires "
400 "EXT_float_blend.",
401 uint32_t(first));
402 return nullptr;
404 webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
408 switch (mode) {
409 case LOCAL_GL_TRIANGLES:
410 case LOCAL_GL_TRIANGLE_STRIP:
411 case LOCAL_GL_TRIANGLE_FAN:
412 case LOCAL_GL_POINTS:
413 case LOCAL_GL_LINE_STRIP:
414 case LOCAL_GL_LINE_LOOP:
415 case LOCAL_GL_LINES:
416 break;
417 default:
418 webgl->ErrorInvalidEnumInfo("mode", mode);
419 return nullptr;
422 if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
424 if (!webgl->mActiveProgramLinkInfo) {
425 webgl->ErrorInvalidOperation("The current program is not linked.");
426 return nullptr;
428 const auto& linkInfo = webgl->mActiveProgramLinkInfo;
430 // -
431 // Check UBO sizes.
433 for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
434 const auto& cur = linkInfo->uniformBlocks[i];
435 const auto& dataSize = cur.info.dataSize;
436 const auto& binding = cur.binding;
437 if (!binding) {
438 webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
439 return nullptr;
442 const auto availByteCount = binding->ByteCount();
443 if (dataSize > availByteCount) {
444 webgl->ErrorInvalidOperation(
445 "Buffer for uniform block is smaller"
446 " than UNIFORM_BLOCK_DATA_SIZE.");
447 return nullptr;
450 if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
451 LOCAL_GL_UNIFORM_BUFFER, i))
452 return nullptr;
455 // -
457 const auto& tfo = webgl->mBoundTransformFeedback;
458 if (tfo && tfo->IsActiveAndNotPaused()) {
459 if (fb) {
460 const auto& info = *fb->GetCompletenessInfo();
461 if (info.isMultiview) {
462 webgl->ErrorInvalidOperation(
463 "Cannot render to multiview with transform feedback.");
464 return nullptr;
468 if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
471 // -
473 const auto& fragOutputs = linkInfo->fragOutputs;
474 const auto fnValidateFragOutputType =
475 [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
476 const auto itr = fragOutputs.find(loc);
477 MOZ_DIAGNOSTIC_ASSERT(itr != fragOutputs.end());
479 const auto& info = itr->second;
480 const auto& srcBaseType = info.baseType;
481 if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
482 const auto& srcStr = ToString(srcBaseType);
483 const auto& dstStr = ToString(dstBaseType);
484 webgl->ErrorInvalidOperation(
485 "Program frag output at location %u is type %s,"
486 " but destination draw buffer is type %s.",
487 uint32_t(loc), srcStr, dstStr);
488 return false;
490 return true;
493 if (!webgl->mRasterizerDiscardEnabled) {
494 uint8_t fbZLayerCount = 1;
495 auto hasAttachment = std::bitset<webgl::kMaxDrawBuffers>(1);
496 auto drawBufferEnabled = std::bitset<webgl::kMaxDrawBuffers>();
497 if (fb) {
498 drawBufferEnabled = fb->DrawBufferEnabled();
499 const auto& info = *fb->GetCompletenessInfo();
500 fbZLayerCount = info.zLayerCount;
501 hasAttachment = info.hasAttachment;
502 } else {
503 drawBufferEnabled[0] = (webgl->mDefaultFB_DrawBuffer0 == LOCAL_GL_BACK);
506 if (fbZLayerCount != linkInfo->zLayerCount) {
507 webgl->ErrorInvalidOperation(
508 "Multiview count mismatch: shader: %u, framebuffer: %u",
509 uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
510 return nullptr;
513 const auto writable =
514 hasAttachment & drawBufferEnabled & webgl->mColorWriteMaskNonzero;
515 if (writable.any()) {
516 // Do we have any undefined outputs with real attachments that
517 // aren't masked-out by color write mask or drawBuffers?
518 const auto wouldWriteUndefined = ~linkInfo->hasOutput & writable;
519 if (wouldWriteUndefined.any()) {
520 const auto first = FindFirstOne(wouldWriteUndefined);
521 webgl->ErrorInvalidOperation(
522 "Program has no frag output at location %u, the"
523 " destination draw buffer has an attached"
524 " image, and its color write mask is not all false,"
525 " and DRAW_BUFFER%u is not NONE.",
526 uint32_t(first), uint32_t(first));
527 return nullptr;
530 const auto outputWrites = linkInfo->hasOutput & writable;
532 if (fb) {
533 for (const auto& attach : fb->ColorDrawBuffers()) {
534 const auto i =
535 uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
536 if (!outputWrites[i]) continue;
537 const auto& imageInfo = attach->GetImageInfo();
538 if (!imageInfo) continue;
539 const auto& dstBaseType = imageInfo->mFormat->format->baseType;
540 if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
542 } else {
543 if (outputWrites[0]) {
544 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
545 return nullptr;
551 // -
553 const auto fetchLimits = linkInfo->GetDrawFetchLimits();
554 if (!fetchLimits) return nullptr;
556 if (instanceCount > fetchLimits->maxInstances) {
557 webgl->ErrorInvalidOperation(
558 "Instance fetch requires %u, but attribs only"
559 " supply %u.",
560 instanceCount, uint32_t(fetchLimits->maxInstances));
561 return nullptr;
564 if (tfo) {
565 for (const auto& used : fetchLimits->usedBuffers) {
566 MOZ_ASSERT(used.buffer);
567 if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
568 used.id))
569 return nullptr;
573 // -
575 webgl->RunContextLossTimer();
577 return fetchLimits;
580 ////////////////////////////////////////
582 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
583 uint8_t vertsPerPrim;
585 switch (mode) {
586 case LOCAL_GL_POINTS:
587 vertsPerPrim = 1;
588 break;
589 case LOCAL_GL_LINES:
590 vertsPerPrim = 2;
591 break;
592 case LOCAL_GL_TRIANGLES:
593 vertsPerPrim = 3;
594 break;
595 default:
596 MOZ_CRASH("`mode`");
599 return vertCount / vertsPerPrim * vertsPerPrim;
602 class ScopedDrawWithTransformFeedback final {
603 WebGLContext* const mWebGL;
604 WebGLTransformFeedback* const mTFO;
605 const bool mWithTF;
606 uint32_t mUsedVerts;
608 public:
609 ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
610 uint32_t vertCount, uint32_t instanceCount,
611 bool* const out_error)
612 : mWebGL(webgl),
613 mTFO(mWebGL->mBoundTransformFeedback),
614 mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
615 mUsedVerts(0) {
616 *out_error = false;
617 if (!mWithTF) return;
619 if (mode != mTFO->mActive_PrimMode) {
620 mWebGL->ErrorInvalidOperation(
621 "Drawing with transform feedback requires"
622 " `mode` to match BeginTransformFeedback's"
623 " `primitiveMode`.");
624 *out_error = true;
625 return;
628 const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
629 const auto usedVerts =
630 CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
632 const auto remainingCapacity =
633 mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
634 if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
635 mWebGL->ErrorInvalidOperation(
636 "Insufficient buffer capacity remaining for"
637 " transform feedback.");
638 *out_error = true;
639 return;
642 mUsedVerts = usedVerts.value();
645 void Advance() const {
646 if (!mWithTF) return;
648 mTFO->mActive_VertPosition += mUsedVerts;
650 for (const auto& cur : mTFO->mIndexedBindings) {
651 const auto& buffer = cur.mBufferBinding;
652 if (buffer) {
653 buffer->ResetLastUpdateFenceId();
659 static bool HasInstancedDrawing(const WebGLContext& webgl) {
660 return webgl.IsWebGL2() ||
661 webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
664 ////////////////////////////////////////
666 void WebGLContext::DrawArraysInstanced(const GLenum mode, const GLint first,
667 const GLsizei vertCount,
668 const GLsizei instanceCount) {
669 const FuncScope funcScope(*this, "drawArraysInstanced");
670 // AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
671 if (IsContextLost()) return;
672 const gl::GLContext::TlsScope inTls(gl);
674 // -
676 if (!ValidateNonNegative("first", first) ||
677 !ValidateNonNegative("vertCount", vertCount) ||
678 !ValidateNonNegative("instanceCount", instanceCount)) {
679 return;
682 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
683 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
684 if (mPrimRestartTypeBytes != 0) {
685 mPrimRestartTypeBytes = 0;
687 // OSX appears to have severe perf issues with leaving this enabled.
688 gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
692 // -
694 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
695 if (!fetchLimits) return;
697 // -
699 const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
700 if (!totalVertCount_safe.isValid()) {
701 ErrorOutOfMemory("`first+vertCount` out of range.");
702 return;
704 auto totalVertCount = totalVertCount_safe.value();
706 if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
707 ErrorInvalidOperation(
708 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
709 uint32_t(fetchLimits->maxVerts));
710 return;
713 // -
715 bool error = false;
717 // -
719 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
720 if (error) return;
722 const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
723 instanceCount, &error);
724 if (error) return;
726 // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
727 // attribs are fetched from. There are two ways to fix this:
728 // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
729 // `byteOffset`
730 // 2. OR offset all non-instanced vertex attrib pointers back, and call
731 // DrawArrays with first:0.
732 // * But now gl_VertexID will be wrong! So we inject a uniform to offset it
733 // back correctly.
734 // #1 ought to be the lowest overhead for any first>0,
735 // but DrawElements can't be used with transform-feedback,
736 // so we need #2 to also work.
737 // For now, only implement #2.
739 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
741 auto driverFirst = first;
743 if (first && mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst) {
744 // This is not particularly optimized, but we can if we need to.
745 bool hasInstancedUserAttrib = false;
746 bool hasVertexAttrib = false;
747 for (const auto& a : activeAttribs) {
748 if (a.location == -1) {
749 if (a.name == "gl_VertexID") {
750 hasVertexAttrib = true;
752 continue;
754 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
755 if (binding.layout.divisor) {
756 hasInstancedUserAttrib = true;
757 } else {
758 hasVertexAttrib = true;
761 if (hasInstancedUserAttrib && hasVertexAttrib) {
762 driverFirst = 0;
765 if (driverFirst != first) {
766 for (const auto& a : activeAttribs) {
767 if (a.location == -1) continue;
768 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
769 if (binding.layout.divisor) continue;
771 mBoundVertexArray->DoVertexAttrib(a.location, first);
774 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, first);
778 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
779 auto fakeVertCount = uint64_t(driverFirst) + vertCount;
780 if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default) {
781 fakeVertCount = 0;
783 if (!(vertCount && instanceCount)) {
784 fakeVertCount = 0;
787 auto undoAttrib0 = MakeScopeExit([&]() {
788 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
789 WebGLVertexAttrib0Status::Default);
790 UndoFakeVertexAttrib0();
792 if (fakeVertCount) {
793 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
794 error = true;
795 undoAttrib0.release();
797 } else {
798 // No fake-verts needed.
799 undoAttrib0.release();
802 ScopedDrawCallWrapper wrapper(*this);
803 if (vertCount && instanceCount) {
804 if (HasInstancedDrawing(*this)) {
805 gl->fDrawArraysInstanced(mode, driverFirst, vertCount, instanceCount);
806 } else {
807 MOZ_ASSERT(instanceCount == 1);
808 gl->fDrawArrays(mode, driverFirst, vertCount);
813 if (driverFirst != first) {
814 gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, 0);
816 for (const auto& a : activeAttribs) {
817 if (a.location == -1) continue;
818 const auto& binding = mBoundVertexArray->AttribBinding(a.location);
819 if (binding.layout.divisor) continue;
821 mBoundVertexArray->DoVertexAttrib(a.location, 0);
825 Draw_cleanup();
826 scopedTF.Advance();
829 ////////////////////////////////////////
831 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
832 const GLenum type,
833 const WebGLintptr byteOffset,
834 const GLsizei instanceCount) {
835 if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
836 !mBoundTransformFeedback->mIsPaused) {
837 ErrorInvalidOperation(
838 "DrawElements* functions are incompatible with"
839 " transform feedback.");
840 return nullptr;
843 if (!ValidateNonNegative("vertCount", rawIndexCount) ||
844 !ValidateNonNegative("byteOffset", byteOffset) ||
845 !ValidateNonNegative("instanceCount", instanceCount)) {
846 return nullptr;
848 const auto indexCount = uint32_t(rawIndexCount);
850 uint8_t bytesPerIndex = 0;
851 switch (type) {
852 case LOCAL_GL_UNSIGNED_BYTE:
853 bytesPerIndex = 1;
854 break;
856 case LOCAL_GL_UNSIGNED_SHORT:
857 bytesPerIndex = 2;
858 break;
860 case LOCAL_GL_UNSIGNED_INT:
861 if (IsWebGL2() ||
862 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
863 bytesPerIndex = 4;
865 break;
867 if (!bytesPerIndex) {
868 ErrorInvalidEnumInfo("type", type);
869 return nullptr;
871 if (byteOffset % bytesPerIndex != 0) {
872 ErrorInvalidOperation(
873 "`byteOffset` must be a multiple of the size of `type`");
874 return nullptr;
877 ////
879 if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
880 MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
881 if (mPrimRestartTypeBytes != bytesPerIndex) {
882 mPrimRestartTypeBytes = bytesPerIndex;
884 const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
885 gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
886 gl->fPrimitiveRestartIndex(ones);
890 ////
891 // Index fetching
893 const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
894 if (!indexBuffer) {
895 ErrorInvalidOperation("Index buffer not bound.");
896 return nullptr;
899 const size_t availBytes = indexBuffer->ByteLength();
900 const auto availIndices =
901 AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
902 if (instanceCount && indexCount > availIndices) {
903 ErrorInvalidOperation("Index buffer too small.");
904 return nullptr;
907 return indexBuffer.get();
910 static void HandleDrawElementsErrors(
911 WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
912 const auto err = errorScope.GetError();
913 if (err == LOCAL_GL_INVALID_OPERATION) {
914 webgl->ErrorInvalidOperation(
915 "Driver rejected indexed draw call, possibly"
916 " due to out-of-bounds indices.");
917 return;
920 MOZ_ASSERT(!err);
921 if (err) {
922 webgl->ErrorImplementationBug(
923 "Unexpected driver error during indexed draw"
924 " call. Please file a bug.");
925 return;
929 void WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount,
930 GLenum type, WebGLintptr byteOffset,
931 GLsizei instanceCount) {
932 const FuncScope funcScope(*this, "drawElementsInstanced");
933 // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
934 if (IsContextLost()) return;
936 const gl::GLContext::TlsScope inTls(gl);
938 const auto indexBuffer =
939 DrawElements_check(indexCount, type, byteOffset, instanceCount);
940 if (!indexBuffer) return;
942 // -
944 const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
945 if (!fetchLimits) return;
947 const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
949 uint64_t fakeVertCount = 0;
950 if (whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default) {
951 fakeVertCount = fetchLimits->maxVerts;
953 if (!indexCount || !instanceCount) {
954 fakeVertCount = 0;
956 if (fakeVertCount == UINT64_MAX) { // Ok well that's too many!
957 const auto exactMaxVertId =
958 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
959 MOZ_RELEASE_ASSERT(exactMaxVertId);
960 fakeVertCount = uint32_t{*exactMaxVertId};
961 fakeVertCount += 1;
964 // -
967 uint64_t indexCapacity = indexBuffer->ByteLength();
968 switch (type) {
969 case LOCAL_GL_UNSIGNED_BYTE:
970 break;
971 case LOCAL_GL_UNSIGNED_SHORT:
972 indexCapacity /= 2;
973 break;
974 case LOCAL_GL_UNSIGNED_INT:
975 indexCapacity /= 4;
976 break;
979 uint32_t maxVertId = 0;
980 const auto isFetchValid = [&]() {
981 if (!indexCount || !instanceCount) return true;
983 const auto globalMaxVertId =
984 indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
985 if (!globalMaxVertId) return true;
986 if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
988 const auto exactMaxVertId =
989 indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
990 maxVertId = exactMaxVertId.value();
991 return maxVertId < fetchLimits->maxVerts;
992 }();
993 if (!isFetchValid) {
994 ErrorInvalidOperation(
995 "Indexed vertex fetch requires %u vertices, but"
996 " attribs only supply %u.",
997 maxVertId + 1, uint32_t(fetchLimits->maxVerts));
998 return;
1002 // -
1004 bool error = false;
1006 // -
1008 auto undoAttrib0 = MakeScopeExit([&]() {
1009 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need !=
1010 WebGLVertexAttrib0Status::Default);
1011 UndoFakeVertexAttrib0();
1013 if (fakeVertCount) {
1014 if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) {
1015 error = true;
1016 undoAttrib0.release();
1018 } else {
1019 // No fake-verts needed.
1020 undoAttrib0.release();
1023 // -
1025 const ScopedResolveTexturesForDraw scopedResolve(this, &error);
1026 if (error) return;
1029 ScopedDrawCallWrapper wrapper(*this);
1031 UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
1032 if (MOZ_UNLIKELY(gl->IsANGLE() &&
1033 gl->mDebugFlags &
1034 gl::GLContext::DebugFlagAbortOnError)) {
1035 // ANGLE does range validation even when it doesn't need to.
1036 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
1037 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
1040 if (indexCount && instanceCount) {
1041 if (HasInstancedDrawing(*this)) {
1042 gl->fDrawElementsInstanced(mode, indexCount, type,
1043 reinterpret_cast<GLvoid*>(byteOffset),
1044 instanceCount);
1045 } else {
1046 MOZ_ASSERT(instanceCount == 1);
1047 gl->fDrawElements(mode, indexCount, type,
1048 reinterpret_cast<GLvoid*>(byteOffset));
1052 if (errorScope) {
1053 HandleDrawElementsErrors(this, *errorScope);
1058 Draw_cleanup();
1061 ////////////////////////////////////////
1063 void WebGLContext::Draw_cleanup() {
1064 if (gl->WorkAroundDriverBugs()) {
1065 if (gl->Renderer() == gl::GLRenderer::Tegra) {
1066 mDrawCallsSinceLastFlush++;
1068 if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
1069 gl->fFlush();
1070 mDrawCallsSinceLastFlush = 0;
1075 // Let's check for a really common error: Viewport is larger than the actual
1076 // destination framebuffer.
1077 uint32_t destWidth;
1078 uint32_t destHeight;
1079 if (mBoundDrawFramebuffer) {
1080 const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
1081 destWidth = info->width;
1082 destHeight = info->height;
1083 } else {
1084 destWidth = mDefaultFB->mSize.width;
1085 destHeight = mDefaultFB->mSize.height;
1088 if (mViewportWidth > int32_t(destWidth) ||
1089 mViewportHeight > int32_t(destHeight)) {
1090 if (!mAlreadyWarnedAboutViewportLargerThanDest) {
1091 GenerateWarning(
1092 "Drawing to a destination rect smaller than the viewport"
1093 " rect. (This warning will only be given once)");
1094 mAlreadyWarnedAboutViewportLargerThanDest = true;
1099 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
1100 MOZ_ASSERT(mCurrentProgram);
1101 MOZ_ASSERT(mActiveProgramLinkInfo);
1103 bool legacyAttrib0 = mNeedsLegacyVertexAttrib0Handling;
1104 if (gl->WorkAroundDriverBugs() && kIsMacOS) {
1105 // Also programs with no attribs:
1106 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
1107 const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
1108 bool hasNonInstancedUserAttrib = false;
1109 for (const auto& a : activeAttribs) {
1110 if (a.location == -1) continue;
1111 const auto& layout = mBoundVertexArray->AttribBinding(a.location).layout;
1112 if (layout.divisor == 0) {
1113 hasNonInstancedUserAttrib = true;
1116 legacyAttrib0 |= !hasNonInstancedUserAttrib;
1119 if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
1120 MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling,
1121 "Invariant need because this turns on index buffer "
1122 "validation, needed for fake-attrib0.");
1124 if (!mActiveProgramLinkInfo->attrib0Active) {
1125 // Attrib0 unused, so just ensure that the legacy code has enough buffer.
1126 return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
1129 const auto& isAttribArray0Enabled =
1130 mBoundVertexArray->AttribBinding(0).layout.isArray;
1131 return isAttribArray0Enabled
1132 ? WebGLVertexAttrib0Status::Default
1133 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
1136 bool WebGLContext::DoFakeVertexAttrib0(
1137 const uint64_t fakeVertexCount,
1138 const WebGLVertexAttrib0Status whatDoesAttrib0Need) {
1139 MOZ_ASSERT(fakeVertexCount);
1140 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default);
1142 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
1143 // Padded/strided to vec4, so 4x4bytes.
1144 const auto effectiveVertAttribBytes =
1145 CheckedInt<int32_t>(fakeVertexCount) * 4 * 4;
1146 if (!effectiveVertAttribBytes.isValid()) {
1147 ErrorOutOfMemory("`offset + count` too large for Mesa.");
1148 return false;
1152 if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
1153 GenerateWarning(
1154 "Drawing without vertex attrib 0 array enabled forces the browser "
1155 "to do expensive emulation work when running on desktop OpenGL "
1156 "platforms, for example on Mac. It is preferable to always draw "
1157 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1158 "to bind some always-used attribute to location 0.");
1159 mAlreadyWarnedAboutFakeVertexAttrib0 = true;
1162 gl->fEnableVertexAttribArray(0);
1164 if (!mFakeVertexAttrib0BufferObject) {
1165 gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
1166 mFakeVertexAttrib0BufferObjectSize = 0;
1168 gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
1170 ////
1172 switch (mGenericVertexAttribTypes[0]) {
1173 case webgl::AttribBaseType::Boolean:
1174 case webgl::AttribBaseType::Float:
1175 gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
1176 break;
1178 case webgl::AttribBaseType::Int:
1179 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
1180 break;
1182 case webgl::AttribBaseType::Uint:
1183 gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
1184 break;
1187 ////
1189 const auto maxFakeVerts = StaticPrefs::webgl_fake_verts_max();
1190 if (fakeVertexCount > maxFakeVerts) {
1191 ErrorOutOfMemory(
1192 "Draw requires faking a vertex attrib 0 array, but required vert count"
1193 " (%" PRIu64 ") is more than webgl.fake-verts.max (%u).",
1194 fakeVertexCount, maxFakeVerts);
1195 return false;
1198 const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
1199 const auto checked_dataSize =
1200 CheckedInt<intptr_t>(fakeVertexCount) * bytesPerVert;
1201 if (!checked_dataSize.isValid()) {
1202 ErrorOutOfMemory(
1203 "Integer overflow trying to construct a fake vertex attrib 0"
1204 " array for a draw-operation with %" PRIu64
1205 " vertices. Try"
1206 " reducing the number of vertices.",
1207 fakeVertexCount);
1208 return false;
1210 const auto dataSize = checked_dataSize.value();
1212 if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
1213 gl::GLContext::LocalErrorScope errorScope(*gl);
1215 gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
1216 LOCAL_GL_DYNAMIC_DRAW);
1218 const auto err = errorScope.GetError();
1219 if (err) {
1220 ErrorOutOfMemory(
1221 "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize);
1222 return false;
1225 mFakeVertexAttrib0BufferObjectSize = dataSize;
1226 mFakeVertexAttrib0DataDefined = false;
1229 if (whatDoesAttrib0Need ==
1230 WebGLVertexAttrib0Status::EmulatedUninitializedArray)
1231 return true;
1233 ////
1235 if (mFakeVertexAttrib0DataDefined &&
1236 memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
1237 0) {
1238 return true;
1241 ////
1243 const auto data = UniqueBuffer::Take(malloc(dataSize));
1244 if (!data) {
1245 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1246 return false;
1248 auto itr = (uint8_t*)data.get();
1249 const auto itrEnd = itr + dataSize;
1250 while (itr != itrEnd) {
1251 memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
1252 itr += bytesPerVert;
1256 gl::GLContext::LocalErrorScope errorScope(*gl);
1258 gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
1260 const auto err = errorScope.GetError();
1261 if (err) {
1262 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1263 return false;
1267 ////
1269 memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
1270 mFakeVertexAttrib0DataDefined = true;
1271 return true;
1274 void WebGLContext::UndoFakeVertexAttrib0() {
1275 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER));
1276 mBoundVertexArray->DoVertexAttrib(0);
1279 } // namespace mozilla