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 "WebGLFormats.h"
19 #include "WebGLFramebuffer.h"
20 #include "WebGLProgram.h"
21 #include "WebGLRenderbuffer.h"
22 #include "WebGLShader.h"
23 #include "WebGLTexture.h"
24 #include "WebGLTransformFeedback.h"
25 #include "WebGLVertexArray.h"
31 // For a Tegra workaround.
32 static const int MAX_DRAW_CALLS_SINCE_FLUSH
= 100;
34 ////////////////////////////////////////
36 class ScopedResolveTexturesForDraw
{
37 struct TexRebindRequest
{
42 WebGLContext
* const mWebGL
;
43 std::vector
<TexRebindRequest
> mRebindRequests
;
46 ScopedResolveTexturesForDraw(WebGLContext
* webgl
, bool* const out_error
);
47 ~ScopedResolveTexturesForDraw();
50 static bool ValidateNoSamplingFeedback(const WebGLTexture
& tex
,
51 const uint32_t sampledLevels
,
52 const WebGLFramebuffer
* const fb
,
53 const uint32_t texUnit
) {
56 const auto& texAttachments
= fb
->GetCompletenessInfo()->texAttachments
;
57 for (const auto& attach
: texAttachments
) {
58 if (attach
->Texture() != &tex
) continue;
60 const auto& srcBase
= tex
.Es3_level_base();
61 const auto srcLast
= srcBase
+ sampledLevels
- 1;
62 const auto& dstLevel
= attach
->MipLevel();
63 if (MOZ_UNLIKELY(srcBase
<= dstLevel
&& dstLevel
<= srcLast
)) {
64 const auto& webgl
= tex
.mContext
;
65 const auto& texTargetStr
= EnumString(tex
.Target().get());
66 const auto& attachStr
= EnumString(attach
->mAttachmentPoint
);
67 webgl
->ErrorInvalidOperation(
68 "Texture level %u would be read by %s unit %u,"
69 " but written by framebuffer attachment %s,"
70 " which would be illegal feedback.",
71 dstLevel
, texTargetStr
.c_str(), texUnit
, attachStr
.c_str());
78 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
79 WebGLContext
* webgl
, bool* const out_error
)
81 const auto& fb
= mWebGL
->mBoundDrawFramebuffer
;
83 struct SamplerByTexUnit
{
85 const webgl::SamplerUniformInfo
* sampler
;
87 Vector
<SamplerByTexUnit
, 8> samplerByTexUnit
;
89 MOZ_ASSERT(mWebGL
->mActiveProgramLinkInfo
);
90 const auto& samplerUniforms
= mWebGL
->mActiveProgramLinkInfo
->samplerUniforms
;
91 for (const auto& pUniform
: samplerUniforms
) {
92 const auto& uniform
= *pUniform
;
93 const auto& texList
= uniform
.texListForType
;
95 const auto& uniformBaseType
= uniform
.texBaseType
;
96 for (const auto& texUnit
: uniform
.texUnits
) {
97 MOZ_ASSERT(texUnit
< texList
.Length());
100 decltype(SamplerByTexUnit::sampler
) prevSamplerForTexUnit
= nullptr;
101 for (const auto& cur
: samplerByTexUnit
) {
102 if (cur
.texUnit
== texUnit
) {
103 prevSamplerForTexUnit
= cur
.sampler
;
106 if (!prevSamplerForTexUnit
) {
107 prevSamplerForTexUnit
= &uniform
;
108 MOZ_RELEASE_ASSERT(samplerByTexUnit
.append(
109 SamplerByTexUnit
{texUnit
, prevSamplerForTexUnit
}));
112 if (MOZ_UNLIKELY(&uniform
.texListForType
!=
113 &prevSamplerForTexUnit
->texListForType
)) {
114 // Pointing to different tex lists means different types!
115 const auto linkInfo
= mWebGL
->mActiveProgramLinkInfo
;
116 const auto LocInfoBySampler
= [&](const webgl::SamplerUniformInfo
* p
)
117 -> const webgl::LocationInfo
* {
118 for (const auto& pair
: linkInfo
->locationMap
) {
119 const auto& locInfo
= pair
.second
;
120 if (locInfo
.samplerInfo
== p
) {
124 MOZ_CRASH("Can't find sampler location.");
126 const auto& cur
= *LocInfoBySampler(&uniform
);
127 const auto& prev
= *LocInfoBySampler(prevSamplerForTexUnit
);
128 mWebGL
->ErrorInvalidOperation(
129 "Tex unit %u referenced by samplers of different types:"
130 " %s (via %s) and %s (via %s).",
131 texUnit
, EnumString(cur
.info
.info
.elemType
).c_str(),
132 cur
.PrettyName().c_str(),
133 EnumString(prev
.info
.info
.elemType
).c_str(),
134 prev
.PrettyName().c_str());
140 const auto& tex
= texList
[texUnit
];
143 const auto& sampler
= mWebGL
->mBoundSamplers
[texUnit
];
144 const auto& samplingInfo
= tex
->GetSampleableInfo(sampler
.get());
145 if (MOZ_UNLIKELY(!samplingInfo
)) { // There was an error.
149 if (MOZ_UNLIKELY(!samplingInfo
->IsComplete())) {
150 if (samplingInfo
->incompleteReason
) {
151 const auto& targetName
= GetEnumName(tex
->Target().get());
152 mWebGL
->GenerateWarning("%s at unit %u is incomplete: %s", targetName
,
153 texUnit
, samplingInfo
->incompleteReason
);
155 mRebindRequests
.push_back({texUnit
, tex
});
159 // We have more validation to do if we're otherwise complete:
160 const auto& texBaseType
= samplingInfo
->usage
->format
->baseType
;
161 if (MOZ_UNLIKELY(texBaseType
!= uniformBaseType
)) {
162 const auto& targetName
= GetEnumName(tex
->Target().get());
163 const auto& srcType
= ToString(texBaseType
);
164 const auto& dstType
= ToString(uniformBaseType
);
165 mWebGL
->ErrorInvalidOperation(
166 "%s at unit %u is of type %s, but"
167 " the shader samples as %s.",
168 targetName
, texUnit
, srcType
, dstType
);
173 if (MOZ_UNLIKELY(uniform
.isShadowSampler
!=
174 samplingInfo
->isDepthTexCompare
)) {
175 const auto& targetName
= GetEnumName(tex
->Target().get());
176 mWebGL
->ErrorInvalidOperation(
177 "%s at unit %u is%s a depth texture"
178 " with TEXTURE_COMPARE_MODE, but"
179 " the shader sampler is%s a shadow"
181 targetName
, texUnit
, samplingInfo
->isDepthTexCompare
? "" : " not",
182 uniform
.isShadowSampler
? "" : " not");
187 if (MOZ_UNLIKELY(!ValidateNoSamplingFeedback(*tex
, samplingInfo
->levels
,
188 fb
.get(), texUnit
))) {
195 const auto& gl
= mWebGL
->gl
;
196 for (const auto& itr
: mRebindRequests
) {
197 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ itr
.texUnit
);
198 GLuint incompleteTex
= 0; // Tex 0 is always incomplete.
199 const auto& overrideTex
= webgl
->mIncompleteTexOverride
;
201 // In all but the simplest cases, this will be incomplete anyway, since
202 // e.g. int-samplers need int-textures. This is useful for e.g.
203 // dom-to-texture failures, though.
204 incompleteTex
= overrideTex
->name
;
206 gl
->fBindTexture(itr
.tex
->Target().get(), incompleteTex
);
210 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
211 if (mRebindRequests
.empty()) return;
213 gl::GLContext
* gl
= mWebGL
->gl
;
215 for (const auto& itr
: mRebindRequests
) {
216 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ itr
.texUnit
);
217 gl
->fBindTexture(itr
.tex
->Target().get(), itr
.tex
->mGLName
);
220 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ mWebGL
->mActiveTexture
);
223 ////////////////////////////////////////
225 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
226 const auto stencilBits
= [&]() -> uint8_t {
227 if (!mStencilTestEnabled
) return 0;
229 if (!mBoundDrawFramebuffer
) return mOptions
.stencil
? 8 : 0;
231 if (mBoundDrawFramebuffer
->StencilAttachment().HasAttachment()) return 8;
233 if (mBoundDrawFramebuffer
->DepthStencilAttachment().HasAttachment())
238 const uint32_t stencilMax
= (1 << stencilBits
) - 1;
240 const auto fnMask
= [&](const uint32_t x
) { return x
& stencilMax
; };
241 const auto fnClamp
= [&](const int32_t x
) {
242 return std::clamp(x
, 0, (int32_t)stencilMax
);
246 ok
&= (fnMask(mStencilWriteMaskFront
) == fnMask(mStencilWriteMaskBack
));
247 ok
&= (fnMask(mStencilValueMaskFront
) == fnMask(mStencilValueMaskBack
));
248 ok
&= (fnClamp(mStencilRefFront
) == fnClamp(mStencilRefBack
));
251 ErrorInvalidOperation(
252 "Stencil front/back state must effectively match."
253 " (before front/back comparison, WRITEMASK and VALUE_MASK"
254 " are masked with (2^s)-1, and REF is clamped to"
255 " [0, (2^s)-1], where `s` is the number of enabled stencil"
256 " bits in the draw framebuffer)");
263 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget
,
264 const uint32_t useId
,
265 const GLenum boundTarget
,
266 const uint32_t boundId
) const {
267 const auto fnName
= [&](const GLenum target
, const uint32_t id
) {
268 auto name
= nsCString(EnumString(target
).c_str());
269 if (id
!= static_cast<uint32_t>(-1)) {
270 name
+= nsPrintfCString("[%u]", id
);
274 const auto& useName
= fnName(useTarget
, useId
);
275 const auto& boundName
= fnName(boundTarget
, boundId
);
276 GenerateError(LOCAL_GL_INVALID_OPERATION
,
277 "Illegal use of buffer at %s"
278 " while also bound to %s.",
279 useName
.BeginReading(), boundName
.BeginReading());
282 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer
& nonTfBuffer
,
283 const GLenum nonTfTarget
,
284 const uint32_t nonTfId
) const {
286 const auto& tfAttribs
= mBoundTransformFeedback
->mIndexedBindings
;
287 for (const auto& cur
: tfAttribs
) {
288 dupe
|= (&nonTfBuffer
== cur
.mBufferBinding
.get());
290 if (MOZ_LIKELY(!dupe
)) return true;
293 for (const auto tfId
: IntegerRange(tfAttribs
.size())) {
294 const auto& tfBuffer
= tfAttribs
[tfId
].mBufferBinding
;
295 if (&nonTfBuffer
== tfBuffer
) {
297 GenErrorIllegalUse(nonTfTarget
, nonTfId
,
298 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
, tfId
);
305 bool WebGLContext::ValidateBuffersForTf(
306 const WebGLTransformFeedback
& tfo
,
307 const webgl::LinkedProgramInfo
& linkInfo
) const {
309 switch (linkInfo
.transformFeedbackBufferMode
) {
310 case LOCAL_GL_INTERLEAVED_ATTRIBS
:
314 case LOCAL_GL_SEPARATE_ATTRIBS
:
315 numUsed
= linkInfo
.active
.activeTfVaryings
.size();
322 std::vector
<webgl::BufferAndIndex
> tfBuffers
;
323 tfBuffers
.reserve(numUsed
);
324 for (const auto i
: IntegerRange(numUsed
)) {
325 tfBuffers
.push_back({tfo
.mIndexedBindings
[i
].mBufferBinding
.get(),
326 static_cast<uint32_t>(i
)});
329 return ValidateBuffersForTf(tfBuffers
);
332 bool WebGLContext::ValidateBuffersForTf(
333 const std::vector
<webgl::BufferAndIndex
>& tfBuffers
) const {
335 const auto fnCheck
= [&](const WebGLBuffer
* const nonTf
,
336 const GLenum nonTfTarget
, const uint32_t nonTfId
) {
337 for (const auto& tf
: tfBuffers
) {
338 dupe
|= (nonTf
&& tf
.buffer
== nonTf
);
341 if (MOZ_LIKELY(!dupe
)) return false;
343 for (const auto& tf
: tfBuffers
) {
344 if (nonTf
&& tf
.buffer
== nonTf
) {
346 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
, tf
.id
,
347 nonTfTarget
, nonTfId
);
353 fnCheck(mBoundArrayBuffer
.get(), LOCAL_GL_ARRAY_BUFFER
, -1);
354 fnCheck(mBoundCopyReadBuffer
.get(), LOCAL_GL_COPY_READ_BUFFER
, -1);
355 fnCheck(mBoundCopyWriteBuffer
.get(), LOCAL_GL_COPY_WRITE_BUFFER
, -1);
356 fnCheck(mBoundPixelPackBuffer
.get(), LOCAL_GL_PIXEL_PACK_BUFFER
, -1);
357 fnCheck(mBoundPixelUnpackBuffer
.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER
, -1);
358 // fnCheck(mBoundTransformFeedbackBuffer.get(),
359 // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
360 fnCheck(mBoundUniformBuffer
.get(), LOCAL_GL_UNIFORM_BUFFER
, -1);
362 for (const auto i
: IntegerRange(mIndexedUniformBufferBindings
.size())) {
363 const auto& cur
= mIndexedUniformBufferBindings
[i
];
364 fnCheck(cur
.mBufferBinding
.get(), LOCAL_GL_UNIFORM_BUFFER
, i
);
367 fnCheck(mBoundVertexArray
->mElementArrayBuffer
.get(),
368 LOCAL_GL_ELEMENT_ARRAY_BUFFER
, -1);
369 for (const auto i
: IntegerRange(MaxVertexAttribs())) {
370 const auto& binding
= mBoundVertexArray
->AttribBinding(i
);
371 fnCheck(binding
.buffer
.get(), LOCAL_GL_ARRAY_BUFFER
, i
);
377 ////////////////////////////////////////
379 template <typename T
>
380 static bool DoSetsIntersect(const std::set
<T
>& a
, const std::set
<T
>& b
) {
381 std::vector
<T
> intersection
;
382 std::set_intersection(a
.begin(), a
.end(), b
.begin(), b
.end(),
383 std::back_inserter(intersection
));
384 return !intersection
.empty();
388 static size_t FindFirstOne(const std::bitset
<N
>& bs
) {
389 MOZ_ASSERT(bs
.any());
390 // We don't need this to be fast, so don't bother with CLZ intrinsics.
391 for (const auto i
: IntegerRange(N
)) {
397 const webgl::CachedDrawFetchLimits
* ValidateDraw(WebGLContext
* const webgl
,
399 const uint32_t instanceCount
) {
400 if (!webgl
->BindCurFBForDraw()) return nullptr;
402 const auto& fb
= webgl
->mBoundDrawFramebuffer
;
404 const auto& info
= *fb
->GetCompletenessInfo();
405 const auto isF32WithBlending
= info
.isAttachmentF32
& webgl
->mBlendEnabled
;
406 if (isF32WithBlending
.any()) {
407 if (!webgl
->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend
)) {
408 const auto first
= FindFirstOne(isF32WithBlending
);
409 webgl
->ErrorInvalidOperation(
410 "Attachment %u is float32 with blending enabled, which requires "
415 webgl
->WarnIfImplicit(WebGLExtensionID::EXT_float_blend
);
420 case LOCAL_GL_TRIANGLES
:
421 case LOCAL_GL_TRIANGLE_STRIP
:
422 case LOCAL_GL_TRIANGLE_FAN
:
423 case LOCAL_GL_POINTS
:
424 case LOCAL_GL_LINE_STRIP
:
425 case LOCAL_GL_LINE_LOOP
:
429 webgl
->ErrorInvalidEnumInfo("mode", mode
);
433 if (!webgl
->ValidateStencilParamsForDrawCall()) return nullptr;
435 if (!webgl
->mActiveProgramLinkInfo
) {
436 webgl
->ErrorInvalidOperation("The current program is not linked.");
439 const auto& linkInfo
= webgl
->mActiveProgramLinkInfo
;
444 for (const auto i
: IntegerRange(linkInfo
->uniformBlocks
.size())) {
445 const auto& cur
= linkInfo
->uniformBlocks
[i
];
446 const auto& dataSize
= cur
.info
.dataSize
;
447 const auto& binding
= cur
.binding
;
449 webgl
->ErrorInvalidOperation("Buffer for uniform block is null.");
453 const auto availByteCount
= binding
->ByteCount();
454 if (dataSize
> availByteCount
) {
455 webgl
->ErrorInvalidOperation(
456 "Buffer for uniform block is smaller"
457 " than UNIFORM_BLOCK_DATA_SIZE.");
461 if (!webgl
->ValidateBufferForNonTf(binding
->mBufferBinding
,
462 LOCAL_GL_UNIFORM_BUFFER
, i
))
468 const auto& tfo
= webgl
->mBoundTransformFeedback
;
469 if (tfo
&& tfo
->IsActiveAndNotPaused()) {
471 const auto& info
= *fb
->GetCompletenessInfo();
472 if (info
.isMultiview
) {
473 webgl
->ErrorInvalidOperation(
474 "Cannot render to multiview with transform feedback.");
479 if (!webgl
->ValidateBuffersForTf(*tfo
, *linkInfo
)) return nullptr;
484 const auto& fragOutputs
= linkInfo
->fragOutputs
;
485 const auto fnValidateFragOutputType
=
486 [&](const uint8_t loc
, const webgl::TextureBaseType dstBaseType
) {
487 const auto itr
= fragOutputs
.find(loc
);
488 MOZ_DIAGNOSTIC_ASSERT(itr
!= fragOutputs
.end());
490 const auto& info
= itr
->second
;
491 const auto& srcBaseType
= info
.baseType
;
492 if (MOZ_UNLIKELY(dstBaseType
!= srcBaseType
)) {
493 const auto& srcStr
= ToString(srcBaseType
);
494 const auto& dstStr
= ToString(dstBaseType
);
495 webgl
->ErrorInvalidOperation(
496 "Program frag output at location %u is type %s,"
497 " but destination draw buffer is type %s.",
498 uint32_t(loc
), srcStr
, dstStr
);
504 if (!webgl
->mRasterizerDiscardEnabled
) {
505 uint8_t fbZLayerCount
= 1;
506 auto hasAttachment
= std::bitset
<webgl::kMaxDrawBuffers
>(1);
507 auto drawBufferEnabled
= std::bitset
<webgl::kMaxDrawBuffers
>();
509 drawBufferEnabled
= fb
->DrawBufferEnabled();
510 const auto& info
= *fb
->GetCompletenessInfo();
511 fbZLayerCount
= info
.zLayerCount
;
512 hasAttachment
= info
.hasAttachment
;
514 drawBufferEnabled
[0] = (webgl
->mDefaultFB_DrawBuffer0
== LOCAL_GL_BACK
);
517 if (fbZLayerCount
!= linkInfo
->zLayerCount
) {
518 webgl
->ErrorInvalidOperation(
519 "Multiview count mismatch: shader: %u, framebuffer: %u",
520 uint32_t{linkInfo
->zLayerCount
}, uint32_t{fbZLayerCount
});
524 const auto writable
=
525 hasAttachment
& drawBufferEnabled
& webgl
->mColorWriteMaskNonzero
;
526 if (writable
.any()) {
527 // Do we have any undefined outputs with real attachments that
528 // aren't masked-out by color write mask or drawBuffers?
529 const auto wouldWriteUndefined
= ~linkInfo
->hasOutput
& writable
;
530 if (wouldWriteUndefined
.any()) {
531 const auto first
= FindFirstOne(wouldWriteUndefined
);
532 webgl
->ErrorInvalidOperation(
533 "Program has no frag output at location %u, the"
534 " destination draw buffer has an attached"
535 " image, and its color write mask is not all false,"
536 " and DRAW_BUFFER%u is not NONE.",
537 uint32_t(first
), uint32_t(first
));
541 const auto outputWrites
= linkInfo
->hasOutput
& writable
;
544 for (const auto& attach
: fb
->ColorDrawBuffers()) {
546 uint8_t(attach
->mAttachmentPoint
- LOCAL_GL_COLOR_ATTACHMENT0
);
547 if (!outputWrites
[i
]) continue;
548 const auto& imageInfo
= attach
->GetImageInfo();
549 if (!imageInfo
) continue;
550 const auto& dstBaseType
= imageInfo
->mFormat
->format
->baseType
;
551 if (!fnValidateFragOutputType(i
, dstBaseType
)) return nullptr;
554 if (outputWrites
[0]) {
555 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float
))
564 const auto fetchLimits
= linkInfo
->GetDrawFetchLimits();
565 if (!fetchLimits
) return nullptr;
567 if (instanceCount
> fetchLimits
->maxInstances
) {
568 webgl
->ErrorInvalidOperation(
569 "Instance fetch requires %u, but attribs only"
571 instanceCount
, uint32_t(fetchLimits
->maxInstances
));
576 for (const auto& used
: fetchLimits
->usedBuffers
) {
577 MOZ_ASSERT(used
.buffer
);
578 if (!webgl
->ValidateBufferForNonTf(*used
.buffer
, LOCAL_GL_ARRAY_BUFFER
,
586 webgl
->RunContextLossTimer();
591 ////////////////////////////////////////
593 static uint32_t UsedVertsForTFDraw(GLenum mode
, uint32_t vertCount
) {
594 uint8_t vertsPerPrim
;
597 case LOCAL_GL_POINTS
:
603 case LOCAL_GL_TRIANGLES
:
610 return vertCount
/ vertsPerPrim
* vertsPerPrim
;
613 class ScopedDrawWithTransformFeedback final
{
614 WebGLContext
* const mWebGL
;
615 WebGLTransformFeedback
* const mTFO
;
620 ScopedDrawWithTransformFeedback(WebGLContext
* webgl
, GLenum mode
,
621 uint32_t vertCount
, uint32_t instanceCount
,
622 bool* const out_error
)
624 mTFO(mWebGL
->mBoundTransformFeedback
),
625 mWithTF(mTFO
&& mTFO
->mIsActive
&& !mTFO
->mIsPaused
),
628 if (!mWithTF
) return;
630 if (mode
!= mTFO
->mActive_PrimMode
) {
631 mWebGL
->ErrorInvalidOperation(
632 "Drawing with transform feedback requires"
633 " `mode` to match BeginTransformFeedback's"
634 " `primitiveMode`.");
639 const auto usedVertsPerInstance
= UsedVertsForTFDraw(mode
, vertCount
);
640 const auto usedVerts
=
641 CheckedInt
<uint32_t>(usedVertsPerInstance
) * instanceCount
;
643 const auto remainingCapacity
=
644 mTFO
->mActive_VertCapacity
- mTFO
->mActive_VertPosition
;
645 if (!usedVerts
.isValid() || usedVerts
.value() > remainingCapacity
) {
646 mWebGL
->ErrorInvalidOperation(
647 "Insufficient buffer capacity remaining for"
648 " transform feedback.");
653 mUsedVerts
= usedVerts
.value();
656 void Advance() const {
657 if (!mWithTF
) return;
659 mTFO
->mActive_VertPosition
+= mUsedVerts
;
661 for (const auto& cur
: mTFO
->mIndexedBindings
) {
662 const auto& buffer
= cur
.mBufferBinding
;
664 buffer
->ResetLastUpdateFenceId();
670 static bool HasInstancedDrawing(const WebGLContext
& webgl
) {
671 return webgl
.IsWebGL2() ||
672 webgl
.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays
);
675 ////////////////////////////////////////
677 void WebGLContext::DrawArraysInstanced(const GLenum mode
, const GLint first
,
678 const GLsizei iVertCount
,
679 const GLsizei instanceCount
) {
680 const FuncScope
funcScope(*this, "drawArraysInstanced");
681 // AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
682 if (IsContextLost()) return;
683 const gl::GLContext::TlsScope
inTls(gl
);
687 if (!ValidateNonNegative("first", first
) ||
688 !ValidateNonNegative("vertCount", iVertCount
) ||
689 !ValidateNonNegative("instanceCount", instanceCount
)) {
692 const auto vertCount
= AssertedCast
<uint32_t>(iVertCount
);
694 if (IsWebGL2() && !gl
->IsSupported(gl::GLFeature::prim_restart_fixed
)) {
695 MOZ_ASSERT(gl
->IsSupported(gl::GLFeature::prim_restart
));
696 if (mPrimRestartTypeBytes
!= 0) {
697 mPrimRestartTypeBytes
= 0;
699 // OSX appears to have severe perf issues with leaving this enabled.
700 gl
->fDisable(LOCAL_GL_PRIMITIVE_RESTART
);
706 const auto fetchLimits
= ValidateDraw(this, mode
, instanceCount
);
707 if (!fetchLimits
) return;
711 const auto totalVertCount_safe
= CheckedInt
<uint32_t>(first
) + vertCount
;
712 if (!totalVertCount_safe
.isValid()) {
713 ErrorOutOfMemory("`first+vertCount` out of range.");
716 auto totalVertCount
= totalVertCount_safe
.value();
718 if (vertCount
&& instanceCount
&& totalVertCount
> fetchLimits
->maxVerts
) {
719 ErrorInvalidOperation(
720 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount
,
721 uint32_t(fetchLimits
->maxVerts
));
725 if (vertCount
> mMaxVertIdsPerDraw
) {
727 "Context's max vertCount is %u, but %u requested. "
728 "[webgl.max-vert-ids-per-draw]",
729 mMaxVertIdsPerDraw
, vertCount
);
739 const ScopedResolveTexturesForDraw
scopedResolve(this, &error
);
742 const ScopedDrawWithTransformFeedback
scopedTF(this, mode
, vertCount
,
743 instanceCount
, &error
);
746 // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
747 // attribs are fetched from. There are two ways to fix this:
748 // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
750 // 2. OR offset all non-instanced vertex attrib pointers back, and call
751 // DrawArrays with first:0.
752 // * But now gl_VertexID will be wrong! So we inject a uniform to offset it
754 // #1 ought to be the lowest overhead for any first>0,
755 // but DrawElements can't be used with transform-feedback,
756 // so we need #2 to also work.
757 // For now, only implement #2.
759 const auto& activeAttribs
= mActiveProgramLinkInfo
->active
.activeAttribs
;
761 auto driverFirst
= first
;
763 if (first
&& mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst
) {
764 // This is not particularly optimized, but we can if we need to.
765 bool hasInstancedUserAttrib
= false;
766 bool hasVertexAttrib
= false;
767 for (const auto& a
: activeAttribs
) {
768 if (a
.location
== -1) {
769 if (a
.name
== "gl_VertexID") {
770 hasVertexAttrib
= true;
774 const auto& binding
= mBoundVertexArray
->AttribBinding(a
.location
);
775 if (binding
.layout
.divisor
) {
776 hasInstancedUserAttrib
= true;
778 hasVertexAttrib
= true;
781 if (hasInstancedUserAttrib
&& hasVertexAttrib
) {
785 if (driverFirst
!= first
) {
786 for (const auto& a
: activeAttribs
) {
787 if (a
.location
== -1) continue;
788 const auto& binding
= mBoundVertexArray
->AttribBinding(a
.location
);
789 if (binding
.layout
.divisor
) continue;
791 mBoundVertexArray
->DoVertexAttrib(a
.location
, first
);
794 gl
->fUniform1i(mActiveProgramLinkInfo
->webgl_gl_VertexID_Offset
, first
);
798 const auto whatDoesAttrib0Need
= WhatDoesVertexAttrib0Need();
799 auto fakeVertCount
= uint64_t(driverFirst
) + vertCount
;
800 if (whatDoesAttrib0Need
== WebGLVertexAttrib0Status::Default
) {
803 if (!(vertCount
&& instanceCount
)) {
807 auto undoAttrib0
= MakeScopeExit([&]() {
808 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need
!=
809 WebGLVertexAttrib0Status::Default
);
810 UndoFakeVertexAttrib0();
813 if (!DoFakeVertexAttrib0(fakeVertCount
, whatDoesAttrib0Need
)) {
815 undoAttrib0
.release();
818 // No fake-verts needed.
819 undoAttrib0
.release();
822 ScopedDrawCallWrapper
wrapper(*this);
823 if (vertCount
&& instanceCount
) {
824 if (HasInstancedDrawing(*this)) {
825 gl
->fDrawArraysInstanced(mode
, driverFirst
, vertCount
, instanceCount
);
827 MOZ_ASSERT(instanceCount
== 1);
828 gl
->fDrawArrays(mode
, driverFirst
, vertCount
);
833 if (driverFirst
!= first
) {
834 gl
->fUniform1i(mActiveProgramLinkInfo
->webgl_gl_VertexID_Offset
, 0);
836 for (const auto& a
: activeAttribs
) {
837 if (a
.location
== -1) continue;
838 const auto& binding
= mBoundVertexArray
->AttribBinding(a
.location
);
839 if (binding
.layout
.divisor
) continue;
841 mBoundVertexArray
->DoVertexAttrib(a
.location
, 0);
849 ////////////////////////////////////////
851 WebGLBuffer
* WebGLContext::DrawElements_check(const GLsizei rawIndexCount
,
853 const WebGLintptr byteOffset
,
854 const GLsizei instanceCount
) {
855 if (mBoundTransformFeedback
&& mBoundTransformFeedback
->mIsActive
&&
856 !mBoundTransformFeedback
->mIsPaused
) {
857 ErrorInvalidOperation(
858 "DrawElements* functions are incompatible with"
859 " transform feedback.");
863 if (!ValidateNonNegative("vertCount", rawIndexCount
) ||
864 !ValidateNonNegative("byteOffset", byteOffset
) ||
865 !ValidateNonNegative("instanceCount", instanceCount
)) {
868 const auto indexCount
= uint32_t(rawIndexCount
);
870 uint8_t bytesPerIndex
= 0;
872 case LOCAL_GL_UNSIGNED_BYTE
:
876 case LOCAL_GL_UNSIGNED_SHORT
:
880 case LOCAL_GL_UNSIGNED_INT
:
882 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint
)) {
887 if (!bytesPerIndex
) {
888 ErrorInvalidEnumInfo("type", type
);
891 if (byteOffset
% bytesPerIndex
!= 0) {
892 ErrorInvalidOperation(
893 "`byteOffset` must be a multiple of the size of `type`");
899 if (IsWebGL2() && !gl
->IsSupported(gl::GLFeature::prim_restart_fixed
)) {
900 MOZ_ASSERT(gl
->IsSupported(gl::GLFeature::prim_restart
));
901 if (mPrimRestartTypeBytes
!= bytesPerIndex
) {
902 mPrimRestartTypeBytes
= bytesPerIndex
;
904 const uint32_t ones
= UINT32_MAX
>> (32 - 8 * mPrimRestartTypeBytes
);
905 gl
->fEnable(LOCAL_GL_PRIMITIVE_RESTART
);
906 gl
->fPrimitiveRestartIndex(ones
);
913 const auto& indexBuffer
= mBoundVertexArray
->mElementArrayBuffer
;
915 ErrorInvalidOperation("Index buffer not bound.");
919 const size_t availBytes
= indexBuffer
->ByteLength();
920 const auto availIndices
=
921 AvailGroups(availBytes
, byteOffset
, bytesPerIndex
, bytesPerIndex
);
922 if (instanceCount
&& indexCount
> availIndices
) {
923 ErrorInvalidOperation("Index buffer too small.");
927 return indexBuffer
.get();
930 static void HandleDrawElementsErrors(
931 WebGLContext
* webgl
, gl::GLContext::LocalErrorScope
& errorScope
) {
932 const auto err
= errorScope
.GetError();
933 if (err
== LOCAL_GL_INVALID_OPERATION
) {
934 webgl
->ErrorInvalidOperation(
935 "Driver rejected indexed draw call, possibly"
936 " due to out-of-bounds indices.");
942 webgl
->ErrorImplementationBug(
943 "Unexpected driver error during indexed draw"
944 " call. Please file a bug.");
949 void WebGLContext::DrawElementsInstanced(const GLenum mode
,
950 const GLsizei iIndexCount
,
952 const WebGLintptr byteOffset
,
953 const GLsizei instanceCount
) {
954 const FuncScope
funcScope(*this, "drawElementsInstanced");
955 // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
956 if (IsContextLost()) return;
958 const gl::GLContext::TlsScope
inTls(gl
);
960 const auto indexBuffer
=
961 DrawElements_check(iIndexCount
, type
, byteOffset
, instanceCount
);
962 if (!indexBuffer
) return;
963 const auto indexCount
= AssertedCast
<uint32_t>(iIndexCount
);
967 const auto fetchLimits
= ValidateDraw(this, mode
, instanceCount
);
968 if (!fetchLimits
) return;
970 const auto whatDoesAttrib0Need
= WhatDoesVertexAttrib0Need();
972 uint64_t fakeVertCount
= 0;
973 if (whatDoesAttrib0Need
!= WebGLVertexAttrib0Status::Default
) {
974 fakeVertCount
= fetchLimits
->maxVerts
;
976 if (!indexCount
|| !instanceCount
) {
979 if (fakeVertCount
== UINT64_MAX
) { // Ok well that's too many!
980 const auto exactMaxVertId
=
981 indexBuffer
->GetIndexedFetchMaxVert(type
, byteOffset
, indexCount
);
982 MOZ_RELEASE_ASSERT(exactMaxVertId
);
983 fakeVertCount
= uint32_t{*exactMaxVertId
};
990 uint64_t indexCapacity
= indexBuffer
->ByteLength();
992 case LOCAL_GL_UNSIGNED_BYTE
:
994 case LOCAL_GL_UNSIGNED_SHORT
:
997 case LOCAL_GL_UNSIGNED_INT
:
1002 uint32_t maxVertId
= 0;
1003 const auto isFetchValid
= [&]() {
1004 if (!indexCount
|| !instanceCount
) return true;
1006 const auto globalMaxVertId
=
1007 indexBuffer
->GetIndexedFetchMaxVert(type
, 0, indexCapacity
);
1008 if (!globalMaxVertId
) return true;
1009 if (globalMaxVertId
.value() < fetchLimits
->maxVerts
) return true;
1011 const auto exactMaxVertId
=
1012 indexBuffer
->GetIndexedFetchMaxVert(type
, byteOffset
, indexCount
);
1013 maxVertId
= exactMaxVertId
.value();
1014 return maxVertId
< fetchLimits
->maxVerts
;
1016 if (!isFetchValid
) {
1017 ErrorInvalidOperation(
1018 "Indexed vertex fetch requires %u vertices, but"
1019 " attribs only supply %u.",
1020 maxVertId
+ 1, uint32_t(fetchLimits
->maxVerts
));
1025 if (indexCount
> mMaxVertIdsPerDraw
) {
1027 "Context's max indexCount is %u, but %u requested. "
1028 "[webgl.max-vert-ids-per-draw]",
1029 mMaxVertIdsPerDraw
, indexCount
);
1039 auto undoAttrib0
= MakeScopeExit([&]() {
1040 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need
!=
1041 WebGLVertexAttrib0Status::Default
);
1042 UndoFakeVertexAttrib0();
1044 if (fakeVertCount
) {
1045 if (!DoFakeVertexAttrib0(fakeVertCount
, whatDoesAttrib0Need
)) {
1047 undoAttrib0
.release();
1050 // No fake-verts needed.
1051 undoAttrib0
.release();
1056 const ScopedResolveTexturesForDraw
scopedResolve(this, &error
);
1060 ScopedDrawCallWrapper
wrapper(*this);
1062 UniquePtr
<gl::GLContext::LocalErrorScope
> errorScope
;
1063 if (MOZ_UNLIKELY(gl
->IsANGLE() &&
1065 gl::GLContext::DebugFlagAbortOnError
)) {
1066 // ANGLE does range validation even when it doesn't need to.
1067 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
1068 errorScope
.reset(new gl::GLContext::LocalErrorScope(*gl
));
1071 if (indexCount
&& instanceCount
) {
1072 if (HasInstancedDrawing(*this)) {
1073 gl
->fDrawElementsInstanced(mode
, indexCount
, type
,
1074 reinterpret_cast<GLvoid
*>(byteOffset
),
1077 MOZ_ASSERT(instanceCount
== 1);
1078 gl
->fDrawElements(mode
, indexCount
, type
,
1079 reinterpret_cast<GLvoid
*>(byteOffset
));
1084 HandleDrawElementsErrors(this, *errorScope
);
1092 ////////////////////////////////////////
1094 void WebGLContext::Draw_cleanup() {
1095 if (gl
->WorkAroundDriverBugs()) {
1096 if (gl
->Renderer() == gl::GLRenderer::Tegra
) {
1097 mDrawCallsSinceLastFlush
++;
1099 if (mDrawCallsSinceLastFlush
>= MAX_DRAW_CALLS_SINCE_FLUSH
) {
1101 mDrawCallsSinceLastFlush
= 0;
1106 // Let's check for a really common error: Viewport is larger than the actual
1107 // destination framebuffer.
1109 uint32_t destHeight
;
1110 if (mBoundDrawFramebuffer
) {
1111 const auto& info
= mBoundDrawFramebuffer
->GetCompletenessInfo();
1112 destWidth
= info
->width
;
1113 destHeight
= info
->height
;
1115 destWidth
= mDefaultFB
->mSize
.width
;
1116 destHeight
= mDefaultFB
->mSize
.height
;
1119 if (mViewportWidth
> int32_t(destWidth
) ||
1120 mViewportHeight
> int32_t(destHeight
)) {
1121 if (!mAlreadyWarnedAboutViewportLargerThanDest
) {
1123 "Drawing to a destination rect smaller than the viewport"
1124 " rect. (This warning will only be given once)");
1125 mAlreadyWarnedAboutViewportLargerThanDest
= true;
1130 WebGLVertexAttrib0Status
WebGLContext::WhatDoesVertexAttrib0Need() const {
1131 MOZ_ASSERT(mCurrentProgram
);
1132 MOZ_ASSERT(mActiveProgramLinkInfo
);
1134 bool legacyAttrib0
= mNeedsLegacyVertexAttrib0Handling
;
1135 if (gl
->WorkAroundDriverBugs() && kIsMacOS
) {
1136 // Also programs with no attribs:
1137 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
1138 const auto& activeAttribs
= mActiveProgramLinkInfo
->active
.activeAttribs
;
1139 bool hasNonInstancedUserAttrib
= false;
1140 for (const auto& a
: activeAttribs
) {
1141 if (a
.location
== -1) continue;
1142 const auto& layout
= mBoundVertexArray
->AttribBinding(a
.location
).layout
;
1143 if (layout
.divisor
== 0) {
1144 hasNonInstancedUserAttrib
= true;
1147 legacyAttrib0
|= !hasNonInstancedUserAttrib
;
1150 if (!legacyAttrib0
) return WebGLVertexAttrib0Status::Default
;
1151 MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling
,
1152 "Invariant need because this turns on index buffer "
1153 "validation, needed for fake-attrib0.");
1155 if (!mActiveProgramLinkInfo
->attrib0Active
) {
1156 // Attrib0 unused, so just ensure that the legacy code has enough buffer.
1157 return WebGLVertexAttrib0Status::EmulatedUninitializedArray
;
1160 const auto& isAttribArray0Enabled
=
1161 mBoundVertexArray
->AttribBinding(0).layout
.isArray
;
1162 return isAttribArray0Enabled
1163 ? WebGLVertexAttrib0Status::Default
1164 : WebGLVertexAttrib0Status::EmulatedInitializedArray
;
1167 bool WebGLContext::DoFakeVertexAttrib0(
1168 const uint64_t fakeVertexCount
,
1169 const WebGLVertexAttrib0Status whatDoesAttrib0Need
) {
1170 MOZ_ASSERT(fakeVertexCount
);
1171 MOZ_RELEASE_ASSERT(whatDoesAttrib0Need
!= WebGLVertexAttrib0Status::Default
);
1173 if (gl
->WorkAroundDriverBugs() && gl
->IsMesa()) {
1174 // Padded/strided to vec4, so 4x4bytes.
1175 const auto effectiveVertAttribBytes
=
1176 CheckedInt
<int32_t>(fakeVertexCount
) * 4 * 4;
1177 if (!effectiveVertAttribBytes
.isValid()) {
1178 ErrorOutOfMemory("`offset + count` too large for Mesa.");
1183 if (!mAlreadyWarnedAboutFakeVertexAttrib0
) {
1185 "Drawing without vertex attrib 0 array enabled forces the browser "
1186 "to do expensive emulation work when running on desktop OpenGL "
1187 "platforms, for example on Mac. It is preferable to always draw "
1188 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1189 "to bind some always-used attribute to location 0.");
1190 mAlreadyWarnedAboutFakeVertexAttrib0
= true;
1193 gl
->fEnableVertexAttribArray(0);
1195 const auto& attrib0
= mBoundVertexArray
->AttribBinding(0);
1196 if (attrib0
.layout
.divisor
) {
1197 gl
->fVertexAttribDivisor(0, 0);
1201 if (!mFakeVertexAttrib0BufferObject
) {
1202 gl
->fGenBuffers(1, &mFakeVertexAttrib0BufferObject
);
1203 mFakeVertexAttrib0BufferObjectSize
= 0;
1205 gl
->fBindBuffer(LOCAL_GL_ARRAY_BUFFER
, mFakeVertexAttrib0BufferObject
);
1209 switch (mGenericVertexAttribTypes
[0]) {
1210 case webgl::AttribBaseType::Boolean
:
1211 case webgl::AttribBaseType::Float
:
1212 gl
->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT
, false, 0, 0);
1215 case webgl::AttribBaseType::Int
:
1216 gl
->fVertexAttribIPointer(0, 4, LOCAL_GL_INT
, 0, 0);
1219 case webgl::AttribBaseType::Uint
:
1220 gl
->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT
, 0, 0);
1226 const auto maxFakeVerts
= StaticPrefs::webgl_fake_verts_max();
1227 if (fakeVertexCount
> maxFakeVerts
) {
1229 "Draw requires faking a vertex attrib 0 array, but required vert count"
1230 " (%" PRIu64
") is more than webgl.fake-verts.max (%u).",
1231 fakeVertexCount
, maxFakeVerts
);
1235 const auto bytesPerVert
= sizeof(mFakeVertexAttrib0Data
);
1236 const auto checked_dataSize
=
1237 CheckedInt
<intptr_t>(fakeVertexCount
) * bytesPerVert
;
1238 if (!checked_dataSize
.isValid()) {
1240 "Integer overflow trying to construct a fake vertex attrib 0"
1241 " array for a draw-operation with %" PRIu64
1243 " reducing the number of vertices.",
1247 const auto dataSize
= checked_dataSize
.value();
1249 if (mFakeVertexAttrib0BufferObjectSize
< dataSize
) {
1250 gl::GLContext::LocalErrorScope
errorScope(*gl
);
1252 gl
->fBufferData(LOCAL_GL_ARRAY_BUFFER
, dataSize
, nullptr,
1253 LOCAL_GL_DYNAMIC_DRAW
);
1255 const auto err
= errorScope
.GetError();
1258 "Failed to allocate fake vertex attrib 0 data: %zi bytes", dataSize
);
1262 mFakeVertexAttrib0BufferObjectSize
= dataSize
;
1263 mFakeVertexAttrib0DataDefined
= false;
1266 if (whatDoesAttrib0Need
==
1267 WebGLVertexAttrib0Status::EmulatedUninitializedArray
)
1272 if (mFakeVertexAttrib0DataDefined
&&
1273 memcmp(mFakeVertexAttrib0Data
, mGenericVertexAttrib0Data
, bytesPerVert
) ==
1280 const auto data
= UniqueBuffer::Take(malloc(dataSize
));
1282 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1285 auto itr
= (uint8_t*)data
.get();
1286 const auto itrEnd
= itr
+ dataSize
;
1287 while (itr
!= itrEnd
) {
1288 memcpy(itr
, mGenericVertexAttrib0Data
, bytesPerVert
);
1289 itr
+= bytesPerVert
;
1293 gl::GLContext::LocalErrorScope
errorScope(*gl
);
1295 gl
->fBufferSubData(LOCAL_GL_ARRAY_BUFFER
, 0, dataSize
, data
.get());
1297 const auto err
= errorScope
.GetError();
1299 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1306 memcpy(mFakeVertexAttrib0Data
, mGenericVertexAttrib0Data
, bytesPerVert
);
1307 mFakeVertexAttrib0DataDefined
= true;
1311 void WebGLContext::UndoFakeVertexAttrib0() {
1312 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER
));
1313 mBoundVertexArray
->DoVertexAttrib(0);
1316 } // namespace mozilla