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/UniquePtrExtensions.h"
13 #include "nsPrintfCString.h"
14 #include "WebGLBuffer.h"
15 #include "WebGLContextUtils.h"
16 #include "WebGLFramebuffer.h"
17 #include "WebGLProgram.h"
18 #include "WebGLRenderbuffer.h"
19 #include "WebGLShader.h"
20 #include "WebGLTexture.h"
21 #include "WebGLTransformFeedback.h"
22 #include "WebGLVertexArray.h"
28 // For a Tegra workaround.
29 static const int MAX_DRAW_CALLS_SINCE_FLUSH
= 100;
31 ////////////////////////////////////////
33 class ScopedResolveTexturesForDraw
{
34 struct TexRebindRequest
{
39 WebGLContext
* const mWebGL
;
40 std::vector
<TexRebindRequest
> mRebindRequests
;
43 ScopedResolveTexturesForDraw(WebGLContext
* webgl
, bool* const out_error
);
44 ~ScopedResolveTexturesForDraw();
47 static bool ValidateNoSamplingFeedback(const WebGLTexture
& tex
,
48 const uint32_t sampledLevels
,
49 const WebGLFramebuffer
* const fb
,
50 const uint32_t texUnit
) {
53 const auto& texAttachments
= fb
->GetCompletenessInfo()->texAttachments
;
54 for (const auto& attach
: texAttachments
) {
55 if (attach
->Texture() != &tex
) continue;
57 const auto& srcBase
= tex
.Es3_level_base();
58 const auto srcLast
= srcBase
+ sampledLevels
- 1;
59 const auto& dstLevel
= attach
->MipLevel();
60 if (MOZ_UNLIKELY(srcBase
<= dstLevel
&& dstLevel
<= srcLast
)) {
61 const auto& webgl
= tex
.mContext
;
62 const auto& texTargetStr
= EnumString(tex
.Target().get());
63 const auto& attachStr
= EnumString(attach
->mAttachmentPoint
);
64 webgl
->ErrorInvalidOperation(
65 "Texture level %u would be read by %s unit %u,"
66 " but written by framebuffer attachment %s,"
67 " which would be illegal feedback.",
68 dstLevel
, texTargetStr
.c_str(), texUnit
, attachStr
.c_str());
75 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
76 WebGLContext
* webgl
, bool* const out_error
)
78 const auto& fb
= mWebGL
->mBoundDrawFramebuffer
;
80 MOZ_ASSERT(mWebGL
->mActiveProgramLinkInfo
);
81 const auto& samplerUniforms
= mWebGL
->mActiveProgramLinkInfo
->samplerUniforms
;
82 for (const auto& pUniform
: samplerUniforms
) {
83 const auto& uniform
= *pUniform
;
84 const auto& texList
= uniform
.texListForType
;
86 const auto& uniformBaseType
= uniform
.texBaseType
;
87 for (const auto& texUnit
: uniform
.texUnits
) {
88 MOZ_ASSERT(texUnit
< texList
.Length());
90 const auto& tex
= texList
[texUnit
];
93 const auto& sampler
= mWebGL
->mBoundSamplers
[texUnit
];
94 const auto& samplingInfo
= tex
->GetSampleableInfo(sampler
.get());
95 if (!samplingInfo
) { // There was an error.
99 if (!samplingInfo
->IsComplete()) {
100 if (samplingInfo
->incompleteReason
) {
101 const auto& targetName
= GetEnumName(tex
->Target().get());
102 mWebGL
->GenerateWarning("%s at unit %u is incomplete: %s", targetName
,
103 texUnit
, samplingInfo
->incompleteReason
);
105 mRebindRequests
.push_back({texUnit
, tex
});
109 // We have more validation to do if we're otherwise complete:
110 const auto& texBaseType
= samplingInfo
->usage
->format
->baseType
;
111 if (texBaseType
!= uniformBaseType
) {
112 const auto& targetName
= GetEnumName(tex
->Target().get());
113 const auto& srcType
= ToString(texBaseType
);
114 const auto& dstType
= ToString(uniformBaseType
);
115 mWebGL
->ErrorInvalidOperation(
116 "%s at unit %u is of type %s, but"
117 " the shader samples as %s.",
118 targetName
, texUnit
, srcType
, dstType
);
123 if (uniform
.isShadowSampler
!= samplingInfo
->isDepthTexCompare
) {
124 const auto& targetName
= GetEnumName(tex
->Target().get());
125 mWebGL
->ErrorInvalidOperation(
126 "%s at unit %u is%s a depth texture"
127 " with TEXTURE_COMPARE_MODE, but"
128 " the shader sampler is%s a shadow"
130 targetName
, texUnit
, samplingInfo
->isDepthTexCompare
? "" : " not",
131 uniform
.isShadowSampler
? "" : " not");
136 if (!ValidateNoSamplingFeedback(*tex
, samplingInfo
->levels
, fb
.get(),
144 const auto& gl
= mWebGL
->gl
;
145 for (const auto& itr
: mRebindRequests
) {
146 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ itr
.texUnit
);
147 GLuint incompleteTex
= 0; // Tex 0 is always incomplete.
148 const auto& overrideTex
= webgl
->mIncompleteTexOverride
;
150 // In all but the simplest cases, this will be incomplete anyway, since
151 // e.g. int-samplers need int-textures. This is useful for e.g.
152 // dom-to-texture failures, though.
153 incompleteTex
= overrideTex
->name
;
155 gl
->fBindTexture(itr
.tex
->Target().get(), incompleteTex
);
159 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
160 if (mRebindRequests
.empty()) return;
162 gl::GLContext
* gl
= mWebGL
->gl
;
164 for (const auto& itr
: mRebindRequests
) {
165 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ itr
.texUnit
);
166 gl
->fBindTexture(itr
.tex
->Target().get(), itr
.tex
->mGLName
);
169 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ mWebGL
->mActiveTexture
);
172 ////////////////////////////////////////
174 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
175 const auto stencilBits
= [&]() -> uint8_t {
176 if (!mStencilTestEnabled
) return 0;
178 if (!mBoundDrawFramebuffer
) return mOptions
.stencil
? 8 : 0;
180 if (mBoundDrawFramebuffer
->StencilAttachment().HasAttachment()) return 8;
182 if (mBoundDrawFramebuffer
->DepthStencilAttachment().HasAttachment())
187 const uint32_t stencilMax
= (1 << stencilBits
) - 1;
189 const auto fnMask
= [&](const uint32_t x
) { return x
& stencilMax
; };
190 const auto fnClamp
= [&](const int32_t x
) {
191 return std::max(0, std::min(x
, (int32_t)stencilMax
));
195 ok
&= (fnMask(mStencilWriteMaskFront
) == fnMask(mStencilWriteMaskBack
));
196 ok
&= (fnMask(mStencilValueMaskFront
) == fnMask(mStencilValueMaskBack
));
197 ok
&= (fnClamp(mStencilRefFront
) == fnClamp(mStencilRefBack
));
200 ErrorInvalidOperation(
201 "Stencil front/back state must effectively match."
202 " (before front/back comparison, WRITEMASK and VALUE_MASK"
203 " are masked with (2^s)-1, and REF is clamped to"
204 " [0, (2^s)-1], where `s` is the number of enabled stencil"
205 " bits in the draw framebuffer)");
212 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget
,
213 const uint32_t useId
,
214 const GLenum boundTarget
,
215 const uint32_t boundId
) const {
216 const auto fnName
= [&](const GLenum target
, const uint32_t id
) {
217 auto name
= nsCString(EnumString(target
).c_str());
218 if (id
!= static_cast<uint32_t>(-1)) {
219 name
+= nsPrintfCString("[%u]", id
);
223 const auto& useName
= fnName(useTarget
, useId
);
224 const auto& boundName
= fnName(boundTarget
, boundId
);
225 GenerateError(LOCAL_GL_INVALID_OPERATION
,
226 "Illegal use of buffer at %s"
227 " while also bound to %s.",
228 useName
.BeginReading(), boundName
.BeginReading());
231 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer
& nonTfBuffer
,
232 const GLenum nonTfTarget
,
233 const uint32_t nonTfId
) const {
235 const auto& tfAttribs
= mBoundTransformFeedback
->mIndexedBindings
;
236 for (const auto& cur
: tfAttribs
) {
237 dupe
|= (&nonTfBuffer
== cur
.mBufferBinding
.get());
239 if (MOZ_LIKELY(!dupe
)) return true;
242 for (const auto tfId
: IntegerRange(tfAttribs
.size())) {
243 const auto& tfBuffer
= tfAttribs
[tfId
].mBufferBinding
;
244 if (&nonTfBuffer
== tfBuffer
) {
246 GenErrorIllegalUse(nonTfTarget
, nonTfId
,
247 LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
, tfId
);
254 bool WebGLContext::ValidateBuffersForTf(
255 const WebGLTransformFeedback
& tfo
,
256 const webgl::LinkedProgramInfo
& linkInfo
) const {
258 switch (linkInfo
.transformFeedbackBufferMode
) {
259 case LOCAL_GL_INTERLEAVED_ATTRIBS
:
263 case LOCAL_GL_SEPARATE_ATTRIBS
:
264 numUsed
= linkInfo
.active
.activeTfVaryings
.size();
271 std::vector
<webgl::BufferAndIndex
> tfBuffers
;
272 tfBuffers
.reserve(numUsed
);
273 for (const auto i
: IntegerRange(numUsed
)) {
274 tfBuffers
.push_back({tfo
.mIndexedBindings
[i
].mBufferBinding
.get(),
275 static_cast<uint32_t>(i
)});
278 return ValidateBuffersForTf(tfBuffers
);
281 bool WebGLContext::ValidateBuffersForTf(
282 const std::vector
<webgl::BufferAndIndex
>& tfBuffers
) const {
284 const auto fnCheck
= [&](const WebGLBuffer
* const nonTf
,
285 const GLenum nonTfTarget
, const uint32_t nonTfId
) {
286 for (const auto& tf
: tfBuffers
) {
287 dupe
|= (nonTf
&& tf
.buffer
== nonTf
);
290 if (MOZ_LIKELY(!dupe
)) return false;
292 for (const auto& tf
: tfBuffers
) {
293 if (nonTf
&& tf
.buffer
== nonTf
) {
295 GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
, tf
.id
,
296 nonTfTarget
, nonTfId
);
302 fnCheck(mBoundArrayBuffer
.get(), LOCAL_GL_ARRAY_BUFFER
, -1);
303 fnCheck(mBoundCopyReadBuffer
.get(), LOCAL_GL_COPY_READ_BUFFER
, -1);
304 fnCheck(mBoundCopyWriteBuffer
.get(), LOCAL_GL_COPY_WRITE_BUFFER
, -1);
305 fnCheck(mBoundPixelPackBuffer
.get(), LOCAL_GL_PIXEL_PACK_BUFFER
, -1);
306 fnCheck(mBoundPixelUnpackBuffer
.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER
, -1);
307 // fnCheck(mBoundTransformFeedbackBuffer.get(),
308 // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
309 fnCheck(mBoundUniformBuffer
.get(), LOCAL_GL_UNIFORM_BUFFER
, -1);
311 for (const auto i
: IntegerRange(mIndexedUniformBufferBindings
.size())) {
312 const auto& cur
= mIndexedUniformBufferBindings
[i
];
313 fnCheck(cur
.mBufferBinding
.get(), LOCAL_GL_UNIFORM_BUFFER
, i
);
316 fnCheck(mBoundVertexArray
->mElementArrayBuffer
.get(),
317 LOCAL_GL_ELEMENT_ARRAY_BUFFER
, -1);
318 for (const auto i
: IntegerRange(MaxVertexAttribs())) {
319 const auto& binding
= mBoundVertexArray
->AttribBinding(i
);
320 fnCheck(binding
.buffer
.get(), LOCAL_GL_ARRAY_BUFFER
, i
);
326 ////////////////////////////////////////
328 template <typename T
>
329 static bool DoSetsIntersect(const std::set
<T
>& a
, const std::set
<T
>& b
) {
330 std::vector
<T
> intersection
;
331 std::set_intersection(a
.begin(), a
.end(), b
.begin(), b
.end(),
332 std::back_inserter(intersection
));
333 return bool(intersection
.size());
337 static size_t FindFirstOne(const std::bitset
<N
>& bs
) {
338 MOZ_ASSERT(bs
.any());
339 // We don't need this to be fast, so don't bother with CLZ intrinsics.
340 for (const auto i
: IntegerRange(N
)) {
346 const webgl::CachedDrawFetchLimits
* ValidateDraw(WebGLContext
* const webgl
,
348 const uint32_t instanceCount
) {
349 if (!webgl
->BindCurFBForDraw()) return nullptr;
351 const auto& fb
= webgl
->mBoundDrawFramebuffer
;
353 const auto& info
= *fb
->GetCompletenessInfo();
354 const auto isF32WithBlending
= info
.isAttachmentF32
& webgl
->mBlendEnabled
;
355 if (isF32WithBlending
.any()) {
356 if (!webgl
->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend
)) {
357 const auto first
= FindFirstOne(isF32WithBlending
);
358 webgl
->ErrorInvalidOperation(
359 "Attachment %u is float32 with blending enabled, which requires "
364 webgl
->WarnIfImplicit(WebGLExtensionID::EXT_float_blend
);
369 case LOCAL_GL_TRIANGLES
:
370 case LOCAL_GL_TRIANGLE_STRIP
:
371 case LOCAL_GL_TRIANGLE_FAN
:
372 case LOCAL_GL_POINTS
:
373 case LOCAL_GL_LINE_STRIP
:
374 case LOCAL_GL_LINE_LOOP
:
378 webgl
->ErrorInvalidEnumInfo("mode", mode
);
382 if (!webgl
->ValidateStencilParamsForDrawCall()) return nullptr;
384 if (!webgl
->mActiveProgramLinkInfo
) {
385 webgl
->ErrorInvalidOperation("The current program is not linked.");
388 const auto& linkInfo
= webgl
->mActiveProgramLinkInfo
;
393 for (const auto i
: IntegerRange(linkInfo
->uniformBlocks
.size())) {
394 const auto& cur
= linkInfo
->uniformBlocks
[i
];
395 const auto& dataSize
= cur
.info
.dataSize
;
396 const auto& binding
= cur
.binding
;
398 webgl
->ErrorInvalidOperation("Buffer for uniform block is null.");
402 const auto availByteCount
= binding
->ByteCount();
403 if (dataSize
> availByteCount
) {
404 webgl
->ErrorInvalidOperation(
405 "Buffer for uniform block is smaller"
406 " than UNIFORM_BLOCK_DATA_SIZE.");
410 if (!webgl
->ValidateBufferForNonTf(binding
->mBufferBinding
,
411 LOCAL_GL_UNIFORM_BUFFER
, i
))
417 const auto& tfo
= webgl
->mBoundTransformFeedback
;
418 if (tfo
&& tfo
->IsActiveAndNotPaused()) {
420 const auto& info
= *fb
->GetCompletenessInfo();
421 if (info
.isMultiview
) {
422 webgl
->ErrorInvalidOperation(
423 "Cannot render to multiview with transform feedback.");
428 if (!webgl
->ValidateBuffersForTf(*tfo
, *linkInfo
)) return nullptr;
433 const auto& fragOutputs
= linkInfo
->fragOutputs
;
434 const auto fnValidateFragOutputType
=
435 [&](const uint8_t loc
, const webgl::TextureBaseType dstBaseType
) {
436 const auto itr
= fragOutputs
.find(loc
);
437 MOZ_DIAGNOSTIC_ASSERT(itr
!= fragOutputs
.end());
439 const auto& info
= itr
->second
;
440 const auto& srcBaseType
= info
.baseType
;
441 if (MOZ_UNLIKELY(dstBaseType
!= srcBaseType
)) {
442 const auto& srcStr
= ToString(srcBaseType
);
443 const auto& dstStr
= ToString(dstBaseType
);
444 webgl
->ErrorInvalidOperation(
445 "Program frag output at location %u is type %s,"
446 " but destination draw buffer is type %s.",
447 uint32_t(loc
), srcStr
, dstStr
);
453 if (!webgl
->mRasterizerDiscardEnabled
) {
454 uint8_t fbZLayerCount
= 1;
455 auto hasAttachment
= std::bitset
<webgl::kMaxDrawBuffers
>(1);
456 auto drawBufferEnabled
= std::bitset
<webgl::kMaxDrawBuffers
>();
458 drawBufferEnabled
= fb
->DrawBufferEnabled();
459 const auto& info
= *fb
->GetCompletenessInfo();
460 fbZLayerCount
= info
.zLayerCount
;
461 hasAttachment
= info
.hasAttachment
;
463 drawBufferEnabled
[0] = (webgl
->mDefaultFB_DrawBuffer0
== LOCAL_GL_BACK
);
466 if (fbZLayerCount
!= linkInfo
->zLayerCount
) {
467 webgl
->ErrorInvalidOperation(
468 "Multiview count mismatch: shader: %u, framebuffer: %u",
469 uint32_t{linkInfo
->zLayerCount
}, uint32_t{fbZLayerCount
});
473 const auto writable
=
474 hasAttachment
& drawBufferEnabled
& webgl
->mColorWriteMaskNonzero
;
475 if (writable
.any()) {
476 // Do we have any undefined outputs with real attachments that
477 // aren't masked-out by color write mask or drawBuffers?
478 const auto wouldWriteUndefined
= ~linkInfo
->hasOutput
& writable
;
479 if (wouldWriteUndefined
.any()) {
480 const auto first
= FindFirstOne(wouldWriteUndefined
);
481 webgl
->ErrorInvalidOperation(
482 "Program has no frag output at location %u, the"
483 " destination draw buffer has an attached"
484 " image, and its color write mask is not all false,"
485 " and DRAW_BUFFER%u is not NONE.",
486 uint32_t(first
), uint32_t(first
));
490 const auto outputWrites
= linkInfo
->hasOutput
& writable
;
493 for (const auto& attach
: fb
->ColorDrawBuffers()) {
495 uint8_t(attach
->mAttachmentPoint
- LOCAL_GL_COLOR_ATTACHMENT0
);
496 if (!outputWrites
[i
]) continue;
497 const auto& imageInfo
= attach
->GetImageInfo();
498 if (!imageInfo
) continue;
499 const auto& dstBaseType
= imageInfo
->mFormat
->format
->baseType
;
500 if (!fnValidateFragOutputType(i
, dstBaseType
)) return nullptr;
503 if (outputWrites
[0]) {
504 if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float
))
513 const auto fetchLimits
= linkInfo
->GetDrawFetchLimits();
514 if (!fetchLimits
) return nullptr;
516 if (instanceCount
> fetchLimits
->maxInstances
) {
517 webgl
->ErrorInvalidOperation(
518 "Instance fetch requires %u, but attribs only"
520 instanceCount
, uint32_t(fetchLimits
->maxInstances
));
525 for (const auto& used
: fetchLimits
->usedBuffers
) {
526 MOZ_ASSERT(used
.buffer
);
527 if (!webgl
->ValidateBufferForNonTf(*used
.buffer
, LOCAL_GL_ARRAY_BUFFER
,
535 webgl
->RunContextLossTimer();
540 ////////////////////////////////////////
542 class ScopedFakeVertexAttrib0 final
{
543 WebGLContext
* const mWebGL
;
544 bool mDidFake
= false;
547 ScopedFakeVertexAttrib0(WebGLContext
* const webgl
, const uint64_t vertexCount
,
548 bool* const out_error
)
552 if (!mWebGL
->DoFakeVertexAttrib0(vertexCount
)) {
559 ~ScopedFakeVertexAttrib0() {
561 mWebGL
->UndoFakeVertexAttrib0();
566 ////////////////////////////////////////
568 static uint32_t UsedVertsForTFDraw(GLenum mode
, uint32_t vertCount
) {
569 uint8_t vertsPerPrim
;
572 case LOCAL_GL_POINTS
:
578 case LOCAL_GL_TRIANGLES
:
585 return vertCount
/ vertsPerPrim
* vertsPerPrim
;
588 class ScopedDrawWithTransformFeedback final
{
589 WebGLContext
* const mWebGL
;
590 WebGLTransformFeedback
* const mTFO
;
595 ScopedDrawWithTransformFeedback(WebGLContext
* webgl
, GLenum mode
,
596 uint32_t vertCount
, uint32_t instanceCount
,
597 bool* const out_error
)
599 mTFO(mWebGL
->mBoundTransformFeedback
),
600 mWithTF(mTFO
&& mTFO
->mIsActive
&& !mTFO
->mIsPaused
),
603 if (!mWithTF
) return;
605 if (mode
!= mTFO
->mActive_PrimMode
) {
606 mWebGL
->ErrorInvalidOperation(
607 "Drawing with transform feedback requires"
608 " `mode` to match BeginTransformFeedback's"
609 " `primitiveMode`.");
614 const auto usedVertsPerInstance
= UsedVertsForTFDraw(mode
, vertCount
);
615 const auto usedVerts
=
616 CheckedInt
<uint32_t>(usedVertsPerInstance
) * instanceCount
;
618 const auto remainingCapacity
=
619 mTFO
->mActive_VertCapacity
- mTFO
->mActive_VertPosition
;
620 if (!usedVerts
.isValid() || usedVerts
.value() > remainingCapacity
) {
621 mWebGL
->ErrorInvalidOperation(
622 "Insufficient buffer capacity remaining for"
623 " transform feedback.");
628 mUsedVerts
= usedVerts
.value();
631 void Advance() const {
632 if (!mWithTF
) return;
634 mTFO
->mActive_VertPosition
+= mUsedVerts
;
636 for (const auto& cur
: mTFO
->mIndexedBindings
) {
637 const auto& buffer
= cur
.mBufferBinding
;
639 buffer
->ResetLastUpdateFenceId();
645 static bool HasInstancedDrawing(const WebGLContext
& webgl
) {
646 return webgl
.IsWebGL2() ||
647 webgl
.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays
);
650 ////////////////////////////////////////
652 void WebGLContext::DrawArraysInstanced(GLenum mode
, GLint first
,
654 GLsizei instanceCount
) {
655 const FuncScope
funcScope(*this, "drawArraysInstanced");
656 AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS
);
657 if (IsContextLost()) return;
658 const gl::GLContext::TlsScope
inTls(gl
);
662 if (!ValidateNonNegative("first", first
) ||
663 !ValidateNonNegative("vertCount", vertCount
) ||
664 !ValidateNonNegative("instanceCount", instanceCount
)) {
668 if (IsWebGL2() && !gl
->IsSupported(gl::GLFeature::prim_restart_fixed
)) {
669 MOZ_ASSERT(gl
->IsSupported(gl::GLFeature::prim_restart
));
670 if (mPrimRestartTypeBytes
!= 0) {
671 mPrimRestartTypeBytes
= 0;
673 // OSX appears to have severe perf issues with leaving this enabled.
674 gl
->fDisable(LOCAL_GL_PRIMITIVE_RESTART
);
680 const auto fetchLimits
= ValidateDraw(this, mode
, instanceCount
);
681 if (!fetchLimits
) return;
685 const auto totalVertCount_safe
= CheckedInt
<uint32_t>(first
) + vertCount
;
686 if (!totalVertCount_safe
.isValid()) {
687 ErrorOutOfMemory("`first+vertCount` out of range.");
690 auto totalVertCount
= totalVertCount_safe
.value();
692 if (vertCount
&& instanceCount
&& totalVertCount
> fetchLimits
->maxVerts
) {
693 ErrorInvalidOperation(
694 "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount
,
695 uint32_t(fetchLimits
->maxVerts
));
702 const ScopedFakeVertexAttrib0
attrib0(this, totalVertCount
, &error
);
705 const ScopedResolveTexturesForDraw
scopedResolve(this, &error
);
708 const ScopedDrawWithTransformFeedback
scopedTF(this, mode
, vertCount
,
709 instanceCount
, &error
);
713 ScopedDrawCallWrapper
wrapper(*this);
714 if (vertCount
&& instanceCount
) {
715 AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS
);
716 if (HasInstancedDrawing(*this)) {
717 gl
->fDrawArraysInstanced(mode
, first
, vertCount
, instanceCount
);
719 MOZ_ASSERT(instanceCount
== 1);
720 gl
->fDrawArrays(mode
, first
, vertCount
);
729 ////////////////////////////////////////
731 WebGLBuffer
* WebGLContext::DrawElements_check(const GLsizei rawIndexCount
,
733 const WebGLintptr byteOffset
,
734 const GLsizei instanceCount
) {
735 if (mBoundTransformFeedback
&& mBoundTransformFeedback
->mIsActive
&&
736 !mBoundTransformFeedback
->mIsPaused
) {
737 ErrorInvalidOperation(
738 "DrawElements* functions are incompatible with"
739 " transform feedback.");
743 if (!ValidateNonNegative("vertCount", rawIndexCount
) ||
744 !ValidateNonNegative("byteOffset", byteOffset
) ||
745 !ValidateNonNegative("instanceCount", instanceCount
)) {
748 const auto indexCount
= uint32_t(rawIndexCount
);
750 uint8_t bytesPerIndex
= 0;
752 case LOCAL_GL_UNSIGNED_BYTE
:
756 case LOCAL_GL_UNSIGNED_SHORT
:
760 case LOCAL_GL_UNSIGNED_INT
:
762 IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint
)) {
767 if (!bytesPerIndex
) {
768 ErrorInvalidEnumInfo("type", type
);
771 if (byteOffset
% bytesPerIndex
!= 0) {
772 ErrorInvalidOperation(
773 "`byteOffset` must be a multiple of the size of `type`");
779 if (IsWebGL2() && !gl
->IsSupported(gl::GLFeature::prim_restart_fixed
)) {
780 MOZ_ASSERT(gl
->IsSupported(gl::GLFeature::prim_restart
));
781 if (mPrimRestartTypeBytes
!= bytesPerIndex
) {
782 mPrimRestartTypeBytes
= bytesPerIndex
;
784 const uint32_t ones
= UINT32_MAX
>> (32 - 8 * mPrimRestartTypeBytes
);
785 gl
->fEnable(LOCAL_GL_PRIMITIVE_RESTART
);
786 gl
->fPrimitiveRestartIndex(ones
);
793 const auto& indexBuffer
= mBoundVertexArray
->mElementArrayBuffer
;
795 ErrorInvalidOperation("Index buffer not bound.");
799 const size_t availBytes
= indexBuffer
->ByteLength();
800 const auto availIndices
=
801 AvailGroups(availBytes
, byteOffset
, bytesPerIndex
, bytesPerIndex
);
802 if (instanceCount
&& indexCount
> availIndices
) {
803 ErrorInvalidOperation("Index buffer too small.");
807 return indexBuffer
.get();
810 static void HandleDrawElementsErrors(
811 WebGLContext
* webgl
, gl::GLContext::LocalErrorScope
& errorScope
) {
812 const auto err
= errorScope
.GetError();
813 if (err
== LOCAL_GL_INVALID_OPERATION
) {
814 webgl
->ErrorInvalidOperation(
815 "Driver rejected indexed draw call, possibly"
816 " due to out-of-bounds indices.");
822 webgl
->ErrorImplementationBug(
823 "Unexpected driver error during indexed draw"
824 " call. Please file a bug.");
829 void WebGLContext::DrawElementsInstanced(GLenum mode
, GLsizei indexCount
,
830 GLenum type
, WebGLintptr byteOffset
,
831 GLsizei instanceCount
) {
832 const FuncScope
funcScope(*this, "drawElementsInstanced");
833 AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS
);
834 if (IsContextLost()) return;
836 const gl::GLContext::TlsScope
inTls(gl
);
838 const auto indexBuffer
=
839 DrawElements_check(indexCount
, type
, byteOffset
, instanceCount
);
840 if (!indexBuffer
) return;
844 const auto fetchLimits
= ValidateDraw(this, mode
, instanceCount
);
845 if (!fetchLimits
) return;
847 bool collapseToDrawArrays
= false;
848 auto fakeVertCount
= fetchLimits
->maxVerts
;
849 if (fetchLimits
->maxVerts
== UINT64_MAX
) {
850 // This isn't observable, and keeps FakeVertexAttrib0 sane.
851 collapseToDrawArrays
= true;
858 uint64_t indexCapacity
= indexBuffer
->ByteLength();
860 case LOCAL_GL_UNSIGNED_BYTE
:
862 case LOCAL_GL_UNSIGNED_SHORT
:
865 case LOCAL_GL_UNSIGNED_INT
:
870 uint32_t maxVertId
= 0;
871 const auto isFetchValid
= [&]() {
872 if (!indexCount
|| !instanceCount
) return true;
874 const auto globalMaxVertId
=
875 indexBuffer
->GetIndexedFetchMaxVert(type
, 0, indexCapacity
);
876 if (!globalMaxVertId
) return true;
877 if (globalMaxVertId
.value() < fetchLimits
->maxVerts
) return true;
879 const auto exactMaxVertId
=
880 indexBuffer
->GetIndexedFetchMaxVert(type
, byteOffset
, indexCount
);
881 maxVertId
= exactMaxVertId
.value();
882 return maxVertId
< fetchLimits
->maxVerts
;
885 ErrorInvalidOperation(
886 "Indexed vertex fetch requires %u vertices, but"
887 " attribs only supply %u.",
888 maxVertId
+ 1, uint32_t(fetchLimits
->maxVerts
));
896 const ScopedFakeVertexAttrib0
attrib0(this, fakeVertCount
, &error
);
899 const ScopedResolveTexturesForDraw
scopedResolve(this, &error
);
903 ScopedDrawCallWrapper
wrapper(*this);
905 UniquePtr
<gl::GLContext::LocalErrorScope
> errorScope
;
906 if (MOZ_UNLIKELY(gl
->IsANGLE() &&
908 gl::GLContext::DebugFlagAbortOnError
)) {
909 // ANGLE does range validation even when it doesn't need to.
910 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
911 errorScope
.reset(new gl::GLContext::LocalErrorScope(*gl
));
914 if (indexCount
&& instanceCount
) {
915 AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS
);
916 if (HasInstancedDrawing(*this)) {
917 if (MOZ_UNLIKELY(collapseToDrawArrays
)) {
918 gl
->fDrawArraysInstanced(mode
, 0, 1, instanceCount
);
920 gl
->fDrawElementsInstanced(mode
, indexCount
, type
,
921 reinterpret_cast<GLvoid
*>(byteOffset
),
925 MOZ_ASSERT(instanceCount
== 1);
926 if (MOZ_UNLIKELY(collapseToDrawArrays
)) {
927 gl
->fDrawArrays(mode
, 0, 1);
929 gl
->fDrawElements(mode
, indexCount
, type
,
930 reinterpret_cast<GLvoid
*>(byteOffset
));
936 HandleDrawElementsErrors(this, *errorScope
);
944 ////////////////////////////////////////
946 void WebGLContext::Draw_cleanup() {
947 if (gl
->WorkAroundDriverBugs()) {
948 if (gl
->Renderer() == gl::GLRenderer::Tegra
) {
949 mDrawCallsSinceLastFlush
++;
951 if (mDrawCallsSinceLastFlush
>= MAX_DRAW_CALLS_SINCE_FLUSH
) {
953 mDrawCallsSinceLastFlush
= 0;
958 // Let's check for a really common error: Viewport is larger than the actual
959 // destination framebuffer.
962 if (mBoundDrawFramebuffer
) {
963 const auto& info
= mBoundDrawFramebuffer
->GetCompletenessInfo();
964 destWidth
= info
->width
;
965 destHeight
= info
->height
;
967 destWidth
= mDefaultFB
->mSize
.width
;
968 destHeight
= mDefaultFB
->mSize
.height
;
971 if (mViewportWidth
> int32_t(destWidth
) ||
972 mViewportHeight
> int32_t(destHeight
)) {
973 if (!mAlreadyWarnedAboutViewportLargerThanDest
) {
975 "Drawing to a destination rect smaller than the viewport"
976 " rect. (This warning will only be given once)");
977 mAlreadyWarnedAboutViewportLargerThanDest
= true;
982 WebGLVertexAttrib0Status
WebGLContext::WhatDoesVertexAttrib0Need() const {
983 MOZ_ASSERT(mCurrentProgram
);
984 MOZ_ASSERT(mActiveProgramLinkInfo
);
986 bool legacyAttrib0
= gl
->IsCompatibilityProfile();
988 if (gl
->WorkAroundDriverBugs()) {
989 // Failures in conformance/attribs/gl-disabled-vertex-attrib.
990 // Even in Core profiles on NV. Sigh.
991 legacyAttrib0
|= (gl
->Vendor() == gl::GLVendor::NVIDIA
);
993 // Also programs with no attribs:
994 // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
995 legacyAttrib0
|= !mActiveProgramLinkInfo
->active
.activeAttribs
.size();
999 if (!legacyAttrib0
) return WebGLVertexAttrib0Status::Default
;
1001 if (!mActiveProgramLinkInfo
->attrib0Active
) {
1002 // Ensure that the legacy code has enough buffer.
1003 return WebGLVertexAttrib0Status::EmulatedUninitializedArray
;
1006 const auto& isAttribArray0Enabled
=
1007 mBoundVertexArray
->AttribBinding(0).layout
.isArray
;
1008 return isAttribArray0Enabled
1009 ? WebGLVertexAttrib0Status::Default
1010 : WebGLVertexAttrib0Status::EmulatedInitializedArray
;
1013 bool WebGLContext::DoFakeVertexAttrib0(const uint64_t vertexCount
) {
1014 const auto whatDoesAttrib0Need
= WhatDoesVertexAttrib0Need();
1015 if (MOZ_LIKELY(whatDoesAttrib0Need
== WebGLVertexAttrib0Status::Default
))
1018 if (!mAlreadyWarnedAboutFakeVertexAttrib0
) {
1020 "Drawing without vertex attrib 0 array enabled forces the browser "
1021 "to do expensive emulation work when running on desktop OpenGL "
1022 "platforms, for example on Mac. It is preferable to always draw "
1023 "with vertex attrib 0 array enabled, by using bindAttribLocation "
1024 "to bind some always-used attribute to location 0.");
1025 mAlreadyWarnedAboutFakeVertexAttrib0
= true;
1028 gl
->fEnableVertexAttribArray(0);
1030 if (!mFakeVertexAttrib0BufferObject
) {
1031 gl
->fGenBuffers(1, &mFakeVertexAttrib0BufferObject
);
1032 mFakeVertexAttrib0BufferObjectSize
= 0;
1034 gl
->fBindBuffer(LOCAL_GL_ARRAY_BUFFER
, mFakeVertexAttrib0BufferObject
);
1038 switch (mGenericVertexAttribTypes
[0]) {
1039 case webgl::AttribBaseType::Boolean
:
1040 case webgl::AttribBaseType::Float
:
1041 gl
->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT
, false, 0, 0);
1044 case webgl::AttribBaseType::Int
:
1045 gl
->fVertexAttribIPointer(0, 4, LOCAL_GL_INT
, 0, 0);
1048 case webgl::AttribBaseType::Uint
:
1049 gl
->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT
, 0, 0);
1055 const auto bytesPerVert
= sizeof(mFakeVertexAttrib0Data
);
1056 const auto checked_dataSize
= CheckedUint32(vertexCount
) * bytesPerVert
;
1057 if (!checked_dataSize
.isValid()) {
1059 "Integer overflow trying to construct a fake vertex attrib 0"
1060 " array for a draw-operation with %" PRIu64
1062 " reducing the number of vertices.",
1066 const auto dataSize
= checked_dataSize
.value();
1068 if (mFakeVertexAttrib0BufferObjectSize
< dataSize
) {
1069 gl
->fBufferData(LOCAL_GL_ARRAY_BUFFER
, dataSize
, nullptr,
1070 LOCAL_GL_DYNAMIC_DRAW
);
1071 mFakeVertexAttrib0BufferObjectSize
= dataSize
;
1072 mFakeVertexAttrib0DataDefined
= false;
1075 if (whatDoesAttrib0Need
==
1076 WebGLVertexAttrib0Status::EmulatedUninitializedArray
)
1081 if (mFakeVertexAttrib0DataDefined
&&
1082 memcmp(mFakeVertexAttrib0Data
, mGenericVertexAttrib0Data
, bytesPerVert
) ==
1089 const UniqueBuffer
data(malloc(dataSize
));
1091 ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 array.");
1094 auto itr
= (uint8_t*)data
.get();
1095 const auto itrEnd
= itr
+ dataSize
;
1096 while (itr
!= itrEnd
) {
1097 memcpy(itr
, mGenericVertexAttrib0Data
, bytesPerVert
);
1098 itr
+= bytesPerVert
;
1102 gl::GLContext::LocalErrorScope
errorScope(*gl
);
1104 gl
->fBufferSubData(LOCAL_GL_ARRAY_BUFFER
, 0, dataSize
, data
.get());
1106 const auto err
= errorScope
.GetError();
1108 ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
1115 memcpy(mFakeVertexAttrib0Data
, mGenericVertexAttrib0Data
, bytesPerVert
);
1116 mFakeVertexAttrib0DataDefined
= true;
1120 void WebGLContext::UndoFakeVertexAttrib0() {
1121 const auto whatDoesAttrib0Need
= WhatDoesVertexAttrib0Need();
1122 if (MOZ_LIKELY(whatDoesAttrib0Need
== WebGLVertexAttrib0Status::Default
))
1125 const auto& binding
= mBoundVertexArray
->AttribBinding(0);
1126 const auto& buffer
= binding
.buffer
;
1128 static_assert(IsBufferTargetLazilyBound(LOCAL_GL_ARRAY_BUFFER
));
1131 const auto& desc
= mBoundVertexArray
->AttribDesc(0);
1133 gl
->fBindBuffer(LOCAL_GL_ARRAY_BUFFER
, buffer
->mGLName
);
1134 DoVertexAttribPointer(*gl
, 0, desc
);
1135 gl
->fBindBuffer(LOCAL_GL_ARRAY_BUFFER
, 0);
1139 } // namespace mozilla