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"
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"
30 // For a Tegra workaround.
31 static const int MAX_DRAW_CALLS_SINCE_FLUSH
= 100;
33 ////////////////////////////////////////
35 class ScopedResolveTexturesForDraw
{
36 struct TexRebindRequest
{
41 WebGLContext
* const mWebGL
;
42 std::vector
<TexRebindRequest
> mRebindRequests
;
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
) {
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());
77 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
78 WebGLContext
* webgl
, bool* const out_error
)
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
) {
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());
130 const auto& tex
= texList
[texUnit
];
133 const auto& sampler
= mWebGL
->mBoundSamplers
[texUnit
];
134 const auto& samplingInfo
= tex
->GetSampleableInfo(sampler
.get());
135 if (!samplingInfo
) { // There was an error.
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
});
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
);
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"
170 targetName
, texUnit
, samplingInfo
->isDepthTexCompare
? "" : " not",
171 uniform
.isShadowSampler
? "" : " not");
176 if (!ValidateNoSamplingFeedback(*tex
, samplingInfo
->levels
, fb
.get(),
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
;
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())
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
));
235 ok
&= (fnMask(mStencilWriteMaskFront
) == fnMask(mStencilWriteMaskBack
));
236 ok
&= (fnMask(mStencilValueMaskFront
) == fnMask(mStencilValueMaskBack
));
237 ok
&= (fnClamp(mStencilRefFront
) == fnClamp(mStencilRefBack
));
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)");
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
);
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 {
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;
282 for (const auto tfId
: IntegerRange(tfAttribs
.size())) {
283 const auto& tfBuffer
= tfAttribs
[tfId
].mBufferBinding
;
284 if (&nonTfBuffer
== tfBuffer
) {
286 GenErrorIllegalUse(nonTfTarget
, nonTfId
,
287 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
, tfId
);
294 bool WebGLContext::ValidateBuffersForTf(
295 const WebGLTransformFeedback
& tfo
,
296 const webgl::LinkedProgramInfo
& linkInfo
) const {
298 switch (linkInfo
.transformFeedbackBufferMode
) {
299 case LOCAL_GL_INTERLEAVED_ATTRIBS
:
303 case LOCAL_GL_SEPARATE_ATTRIBS
:
304 numUsed
= linkInfo
.active
.activeTfVaryings
.size();
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 {
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
) {
335 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
, tf
.id
,
336 nonTfTarget
, nonTfId
);
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
);
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();
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
)) {
386 const webgl::CachedDrawFetchLimits
* ValidateDraw(WebGLContext
* const webgl
,
388 const uint32_t instanceCount
) {
389 if (!webgl
->BindCurFBForDraw()) return nullptr;
391 const auto& fb
= webgl
->mBoundDrawFramebuffer
;
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 "
404 webgl
->WarnIfImplicit(WebGLExtensionID::EXT_float_blend
);
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
:
418 webgl
->ErrorInvalidEnumInfo("mode", mode
);
422 if (!webgl
->ValidateStencilParamsForDrawCall()) return nullptr;
424 if (!webgl
->mActiveProgramLinkInfo
) {
425 webgl
->ErrorInvalidOperation("The current program is not linked.");
428 const auto& linkInfo
= webgl
->mActiveProgramLinkInfo
;
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
;
438 webgl
->ErrorInvalidOperation("Buffer for uniform block is null.");
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.");
450 if (!webgl
->ValidateBufferForNonTf(binding
->mBufferBinding
,
451 LOCAL_GL_UNIFORM_BUFFER
, i
))
457 const auto& tfo
= webgl
->mBoundTransformFeedback
;
458 if (tfo
&& tfo
->IsActiveAndNotPaused()) {
460 const auto& info
= *fb
->GetCompletenessInfo();
461 if (info
.isMultiview
) {
462 webgl
->ErrorInvalidOperation(
463 "Cannot render to multiview with transform feedback.");
468 if (!webgl
->ValidateBuffersForTf(*tfo
, *linkInfo
)) return nullptr;
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
);
493 if (!webgl
->mRasterizerDiscardEnabled
) {
494 uint8_t fbZLayerCount
= 1;
495 auto hasAttachment
= std::bitset
<webgl::kMaxDrawBuffers
>(1);
496 auto drawBufferEnabled
= std::bitset
<webgl::kMaxDrawBuffers
>();
498 drawBufferEnabled
= fb
->DrawBufferEnabled();
499 const auto& info
= *fb
->GetCompletenessInfo();
500 fbZLayerCount
= info
.zLayerCount
;
501 hasAttachment
= info
.hasAttachment
;
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
});
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
));
530 const auto outputWrites
= linkInfo
->hasOutput
& writable
;
533 for (const auto& attach
: fb
->ColorDrawBuffers()) {
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;
543 if (outputWrites
[0]) {
544 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float
))
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"
560 instanceCount
, uint32_t(fetchLimits
->maxInstances
));
565 for (const auto& used
: fetchLimits
->usedBuffers
) {
566 MOZ_ASSERT(used
.buffer
);
567 if (!webgl
->ValidateBufferForNonTf(*used
.buffer
, LOCAL_GL_ARRAY_BUFFER
,
575 webgl
->RunContextLossTimer();
580 ////////////////////////////////////////
582 static uint32_t UsedVertsForTFDraw(GLenum mode
, uint32_t vertCount
) {
583 uint8_t vertsPerPrim
;
586 case LOCAL_GL_POINTS
:
592 case LOCAL_GL_TRIANGLES
:
599 return vertCount
/ vertsPerPrim
* vertsPerPrim
;
602 class ScopedDrawWithTransformFeedback final
{
603 WebGLContext
* const mWebGL
;
604 WebGLTransformFeedback
* const mTFO
;
609 ScopedDrawWithTransformFeedback(WebGLContext
* webgl
, GLenum mode
,
610 uint32_t vertCount
, uint32_t instanceCount
,
611 bool* const out_error
)
613 mTFO(mWebGL
->mBoundTransformFeedback
),
614 mWithTF(mTFO
&& mTFO
->mIsActive
&& !mTFO
->mIsPaused
),
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`.");
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.");
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
;
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
);
676 if (!ValidateNonNegative("first", first
) ||
677 !ValidateNonNegative("vertCount", vertCount
) ||
678 !ValidateNonNegative("instanceCount", instanceCount
)) {
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
);
694 const auto fetchLimits
= ValidateDraw(this, mode
, instanceCount
);
695 if (!fetchLimits
) return;
699 const auto totalVertCount_safe
= CheckedInt
<uint32_t>(first
) + vertCount
;
700 if (!totalVertCount_safe
.isValid()) {
701 ErrorOutOfMemory("`first+vertCount` out of range.");
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
));
719 const ScopedResolveTexturesForDraw
scopedResolve(this, &error
);
722 const ScopedDrawWithTransformFeedback
scopedTF(this, mode
, vertCount
,
723 instanceCount
, &error
);
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
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
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;
754 const auto& binding
= mBoundVertexArray
->AttribBinding(a
.location
);
755 if (binding
.layout
.divisor
) {
756 hasInstancedUserAttrib
= true;
758 hasVertexAttrib
= true;
761 if (hasInstancedUserAttrib
&& hasVertexAttrib
) {
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
) {
783 if (!(vertCount
&& instanceCount
)) {
787 auto undoAttrib0
= MakeScopeExit([&]() {
788 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need
!=
789 WebGLVertexAttrib0Status::Default
);
790 UndoFakeVertexAttrib0();
793 if (!DoFakeVertexAttrib0(fakeVertCount
, whatDoesAttrib0Need
)) {
795 undoAttrib0
.release();
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
);
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);
829 ////////////////////////////////////////
831 WebGLBuffer
* WebGLContext::DrawElements_check(const GLsizei rawIndexCount
,
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.");
843 if (!ValidateNonNegative("vertCount", rawIndexCount
) ||
844 !ValidateNonNegative("byteOffset", byteOffset
) ||
845 !ValidateNonNegative("instanceCount", instanceCount
)) {
848 const auto indexCount
= uint32_t(rawIndexCount
);
850 uint8_t bytesPerIndex
= 0;
852 case LOCAL_GL_UNSIGNED_BYTE
:
856 case LOCAL_GL_UNSIGNED_SHORT
:
860 case LOCAL_GL_UNSIGNED_INT
:
862 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint
)) {
867 if (!bytesPerIndex
) {
868 ErrorInvalidEnumInfo("type", type
);
871 if (byteOffset
% bytesPerIndex
!= 0) {
872 ErrorInvalidOperation(
873 "`byteOffset` must be a multiple of the size of `type`");
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
);
893 const auto& indexBuffer
= mBoundVertexArray
->mElementArrayBuffer
;
895 ErrorInvalidOperation("Index buffer not bound.");
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.");
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.");
922 webgl
->ErrorImplementationBug(
923 "Unexpected driver error during indexed draw"
924 " call. Please file a bug.");
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;
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
) {
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
};
967 uint64_t indexCapacity
= indexBuffer
->ByteLength();
969 case LOCAL_GL_UNSIGNED_BYTE
:
971 case LOCAL_GL_UNSIGNED_SHORT
:
974 case LOCAL_GL_UNSIGNED_INT
:
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
;
994 ErrorInvalidOperation(
995 "Indexed vertex fetch requires %u vertices, but"
996 " attribs only supply %u.",
997 maxVertId
+ 1, uint32_t(fetchLimits
->maxVerts
));
1008 auto undoAttrib0
= MakeScopeExit([&]() {
1009 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need
!=
1010 WebGLVertexAttrib0Status::Default
);
1011 UndoFakeVertexAttrib0();
1013 if (fakeVertCount
) {
1014 if (!DoFakeVertexAttrib0(fakeVertCount
, whatDoesAttrib0Need
)) {
1016 undoAttrib0
.release();
1019 // No fake-verts needed.
1020 undoAttrib0
.release();
1025 const ScopedResolveTexturesForDraw
scopedResolve(this, &error
);
1029 ScopedDrawCallWrapper
wrapper(*this);
1031 UniquePtr
<gl::GLContext::LocalErrorScope
> errorScope
;
1032 if (MOZ_UNLIKELY(gl
->IsANGLE() &&
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
),
1046 MOZ_ASSERT(instanceCount
== 1);
1047 gl
->fDrawElements(mode
, indexCount
, type
,
1048 reinterpret_cast<GLvoid
*>(byteOffset
));
1053 HandleDrawElementsErrors(this, *errorScope
);
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
) {
1070 mDrawCallsSinceLastFlush
= 0;
1075 // Let's check for a really common error: Viewport is larger than the actual
1076 // destination framebuffer.
1078 uint32_t destHeight
;
1079 if (mBoundDrawFramebuffer
) {
1080 const auto& info
= mBoundDrawFramebuffer
->GetCompletenessInfo();
1081 destWidth
= info
->width
;
1082 destHeight
= info
->height
;
1084 destWidth
= mDefaultFB
->mSize
.width
;
1085 destHeight
= mDefaultFB
->mSize
.height
;
1088 if (mViewportWidth
> int32_t(destWidth
) ||
1089 mViewportHeight
> int32_t(destHeight
)) {
1090 if (!mAlreadyWarnedAboutViewportLargerThanDest
) {
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.");
1152 if (!mAlreadyWarnedAboutFakeVertexAttrib0
) {
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
);
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);
1178 case webgl::AttribBaseType::Int
:
1179 gl
->fVertexAttribIPointer(0, 4, LOCAL_GL_INT
, 0, 0);
1182 case webgl::AttribBaseType::Uint
:
1183 gl
->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT
, 0, 0);
1189 const auto maxFakeVerts
= StaticPrefs::webgl_fake_verts_max();
1190 if (fakeVertexCount
> maxFakeVerts
) {
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
);
1198 const auto bytesPerVert
= sizeof(mFakeVertexAttrib0Data
);
1199 const auto checked_dataSize
=
1200 CheckedInt
<intptr_t>(fakeVertexCount
) * bytesPerVert
;
1201 if (!checked_dataSize
.isValid()) {
1203 "Integer overflow trying to construct a fake vertex attrib 0"
1204 " array for a draw-operation with %" PRIu64
1206 " reducing the number of vertices.",
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();
1221 "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize
);
1225 mFakeVertexAttrib0BufferObjectSize
= dataSize
;
1226 mFakeVertexAttrib0DataDefined
= false;
1229 if (whatDoesAttrib0Need
==
1230 WebGLVertexAttrib0Status::EmulatedUninitializedArray
)
1235 if (mFakeVertexAttrib0DataDefined
&&
1236 memcmp(mFakeVertexAttrib0Data
, mGenericVertexAttrib0Data
, bytesPerVert
) ==
1243 const auto data
= UniqueBuffer::Take(malloc(dataSize
));
1245 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
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();
1262 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1269 memcpy(mFakeVertexAttrib0Data
, mGenericVertexAttrib0Data
, bytesPerVert
);
1270 mFakeVertexAttrib0DataDefined
= true;
1274 void WebGLContext::UndoFakeVertexAttrib0() {
1275 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER
));
1276 mBoundVertexArray
->DoVertexAttrib(0);
1279 } // namespace mozilla