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