1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/. */
5 
6 #include "WebGLContext.h"
7 
8 #include "GeckoProfiler.h"
9 #include "MozFramebuffer.h"
10 #include "GLContext.h"
11 #include "mozilla/CheckedInt.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"
23 #include "WebGLVertexAttribData.h"
24 
25 #include <algorithm>
26 
27 namespace mozilla {
28 
29 // For a Tegra workaround.
30 static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
31 
32 ////////////////////////////////////////
33 
34 class ScopedResolveTexturesForDraw {
35   struct TexRebindRequest {
36     uint32_t texUnit;
37     WebGLTexture* tex;
38   };
39 
40   WebGLContext* const mWebGL;
41   std::vector<TexRebindRequest> mRebindRequests;
42 
43  public:
44   ScopedResolveTexturesForDraw(WebGLContext* webgl, const char* funcName,
45                                bool* const out_error);
46   ~ScopedResolveTexturesForDraw();
47 };
48 
IsFeedback(WebGLContext * webgl,const char * funcName,uint32_t texUnit,const std::vector<const WebGLFBAttachPoint * > & fbAttachments) const49 bool WebGLTexture::IsFeedback(
50     WebGLContext* webgl, const char* funcName, uint32_t texUnit,
51     const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const {
52   auto itr = fbAttachments.cbegin();
53   for (; itr != fbAttachments.cend(); ++itr) {
54     const auto& attach = *itr;
55     if (attach->Texture() == this) break;
56   }
57 
58   if (itr == fbAttachments.cend()) return false;
59 
60   ////
61 
62   const auto minLevel = mBaseMipmapLevel;
63   uint32_t maxLevel;
64   if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) {
65     // No valid mips. Will need fake-black.
66     return false;
67   }
68 
69   ////
70 
71   for (; itr != fbAttachments.cend(); ++itr) {
72     const auto& attach = *itr;
73     if (attach->Texture() != this) continue;
74 
75     const auto dstLevel = attach->MipLevel();
76 
77     if (minLevel <= dstLevel && dstLevel <= maxLevel) {
78       webgl->ErrorInvalidOperation(
79           "%s: Feedback loop detected between tex target"
80           " 0x%04x, tex unit %u, levels %u-%u; and"
81           " framebuffer attachment 0x%04x, level %u.",
82           funcName, mTarget.get(), texUnit, minLevel, maxLevel,
83           attach->mAttachmentPoint, dstLevel);
84       return true;
85     }
86   }
87 
88   return false;
89 }
90 
ScopedResolveTexturesForDraw(WebGLContext * webgl,const char * funcName,bool * const out_error)91 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
92     WebGLContext* webgl, const char* funcName, bool* const out_error)
93     : mWebGL(webgl) {
94   MOZ_ASSERT(mWebGL->gl->IsCurrent());
95 
96   const std::vector<const WebGLFBAttachPoint*>* attachList = nullptr;
97   const auto& fb = mWebGL->mBoundDrawFramebuffer;
98   if (fb) {
99     attachList = &(fb->ResolvedCompleteData()->texDrawBuffers);
100   }
101 
102   MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
103   const auto& uniformSamplers = mWebGL->mActiveProgramLinkInfo->uniformSamplers;
104   for (const auto& uniform : uniformSamplers) {
105     const auto& texList = *(uniform->mSamplerTexList);
106 
107     for (const auto& texUnit : uniform->mSamplerValues) {
108       if (texUnit >= texList.Length()) continue;
109 
110       const auto& tex = texList[texUnit];
111       if (!tex) continue;
112 
113       if (attachList &&
114           tex->IsFeedback(mWebGL, funcName, texUnit, *attachList)) {
115         *out_error = true;
116         return;
117       }
118 
119       FakeBlackType fakeBlack;
120       if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack)) {
121         mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.",
122                                  funcName);
123         *out_error = true;
124         return;
125       }
126 
127       if (fakeBlack == FakeBlackType::None) continue;
128 
129       if (!mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack)) {
130         mWebGL->ErrorOutOfMemory("%s: Failed to create fake black texture.",
131                                  funcName);
132         *out_error = true;
133         return;
134       }
135 
136       mRebindRequests.push_back({texUnit, tex});
137     }
138   }
139 
140   *out_error = false;
141 }
142 
~ScopedResolveTexturesForDraw()143 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
144   if (mRebindRequests.empty()) return;
145 
146   gl::GLContext* gl = mWebGL->gl;
147 
148   for (const auto& itr : mRebindRequests) {
149     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
150     gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
151   }
152 
153   gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
154 }
155 
BindFakeBlack(uint32_t texUnit,TexTarget target,FakeBlackType fakeBlack)156 bool WebGLContext::BindFakeBlack(uint32_t texUnit, TexTarget target,
157                                  FakeBlackType fakeBlack) {
158   MOZ_ASSERT(fakeBlack == FakeBlackType::RGBA0000 ||
159              fakeBlack == FakeBlackType::RGBA0001);
160 
161   const auto fnGetSlot = [this, target,
162                           fakeBlack]() -> UniquePtr<FakeBlackTexture>* {
163     switch (fakeBlack) {
164       case FakeBlackType::RGBA0000:
165         switch (target.get()) {
166           case LOCAL_GL_TEXTURE_2D:
167             return &mFakeBlack_2D_0000;
168           case LOCAL_GL_TEXTURE_CUBE_MAP:
169             return &mFakeBlack_CubeMap_0000;
170           case LOCAL_GL_TEXTURE_3D:
171             return &mFakeBlack_3D_0000;
172           case LOCAL_GL_TEXTURE_2D_ARRAY:
173             return &mFakeBlack_2D_Array_0000;
174           default:
175             return nullptr;
176         }
177 
178       case FakeBlackType::RGBA0001:
179         switch (target.get()) {
180           case LOCAL_GL_TEXTURE_2D:
181             return &mFakeBlack_2D_0001;
182           case LOCAL_GL_TEXTURE_CUBE_MAP:
183             return &mFakeBlack_CubeMap_0001;
184           case LOCAL_GL_TEXTURE_3D:
185             return &mFakeBlack_3D_0001;
186           case LOCAL_GL_TEXTURE_2D_ARRAY:
187             return &mFakeBlack_2D_Array_0001;
188           default:
189             return nullptr;
190         }
191 
192       default:
193         return nullptr;
194     }
195   };
196 
197   UniquePtr<FakeBlackTexture>* slot = fnGetSlot();
198   if (!slot) {
199     MOZ_CRASH("GFX: fnGetSlot failed.");
200   }
201   UniquePtr<FakeBlackTexture>& fakeBlackTex = *slot;
202 
203   if (!fakeBlackTex) {
204     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
205     if (IsWebGL2()) {
206       gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, 0);
207       gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0);
208       gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0);
209     }
210 
211     fakeBlackTex = FakeBlackTexture::Create(gl, target, fakeBlack);
212 
213     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mPixelStore_UnpackAlignment);
214     if (IsWebGL2()) {
215       gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS,
216                        mPixelStore_UnpackSkipPixels);
217       gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, mPixelStore_UnpackSkipRows);
218       gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES,
219                        mPixelStore_UnpackSkipImages);
220     }
221     if (!fakeBlackTex) {
222       return false;
223     }
224   }
225 
226   gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
227   gl->fBindTexture(target.get(), fakeBlackTex->mGLName);
228   gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
229   return true;
230 }
231 
232 ////////////////////////////////////////
233 
234 template <typename T>
DoSetsIntersect(const std::set<T> & a,const std::set<T> & b)235 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
236   std::vector<T> intersection;
237   std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
238                         std::back_inserter(intersection));
239   return bool(intersection.size());
240 }
241 
242 class ScopedDrawHelper final {
243   WebGLContext* const mWebGL;
244   bool mDidFake;
245 
246  public:
ScopedDrawHelper(WebGLContext * const webgl,const char * const funcName,const GLenum mode,const Maybe<uint32_t> & lastRequiredVertex,const uint32_t instanceCount,bool * const out_error)247   ScopedDrawHelper(WebGLContext* const webgl, const char* const funcName,
248                    const GLenum mode, const Maybe<uint32_t>& lastRequiredVertex,
249                    const uint32_t instanceCount, bool* const out_error)
250       : mWebGL(webgl), mDidFake(false) {
251     MOZ_ASSERT(mWebGL->gl->IsCurrent());
252 
253     if (!mWebGL->BindCurFBForDraw(funcName)) {
254       *out_error = true;
255       return;
256     }
257 
258     if (!mWebGL->ValidateDrawModeEnum(mode, funcName)) {
259       *out_error = true;
260       return;
261     }
262 
263     if (!mWebGL->ValidateStencilParamsForDrawCall()) {
264       *out_error = true;
265       return;
266     }
267 
268     if (!mWebGL->mActiveProgramLinkInfo) {
269       mWebGL->ErrorInvalidOperation("%s: The current program is not linked.",
270                                     funcName);
271       *out_error = true;
272       return;
273     }
274     const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
275 
276     ////
277     // Check UBO sizes.
278 
279     for (const auto& cur : linkInfo->uniformBlocks) {
280       const auto& dataSize = cur->mDataSize;
281       const auto& binding = cur->mBinding;
282       if (!binding) {
283         mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
284                                       funcName);
285         *out_error = true;
286         return;
287       }
288 
289       const auto availByteCount = binding->ByteCount();
290       if (dataSize > availByteCount) {
291         mWebGL->ErrorInvalidOperation(
292             "%s: Buffer for uniform block is smaller"
293             " than UNIFORM_BLOCK_DATA_SIZE.",
294             funcName);
295         *out_error = true;
296         return;
297       }
298 
299       if (binding->mBufferBinding->IsBoundForTF()) {
300         mWebGL->ErrorInvalidOperation(
301             "%s: Buffer for uniform block is bound or"
302             " in use for transform feedback.",
303             funcName);
304         *out_error = true;
305         return;
306       }
307     }
308 
309     ////
310 
311     const auto& tfo = mWebGL->mBoundTransformFeedback;
312     if (tfo && tfo->IsActiveAndNotPaused()) {
313       uint32_t numUsed;
314       switch (linkInfo->transformFeedbackBufferMode) {
315         case LOCAL_GL_INTERLEAVED_ATTRIBS:
316           numUsed = 1;
317           break;
318 
319         case LOCAL_GL_SEPARATE_ATTRIBS:
320           numUsed = linkInfo->transformFeedbackVaryings.size();
321           break;
322 
323         default:
324           MOZ_CRASH();
325       }
326 
327       for (uint32_t i = 0; i < numUsed; ++i) {
328         const auto& buffer = tfo->mIndexedBindings[i].mBufferBinding;
329         if (buffer->IsBoundForNonTF()) {
330           mWebGL->ErrorInvalidOperation(
331               "%s: Transform feedback varying %u's"
332               " buffer is bound for"
333               " non-transform-feedback.",
334               funcName, i);
335           *out_error = true;
336           return;
337         }
338 
339         // Technically we don't know that this will be updated yet, but we can
340         // speculatively mark it.
341         buffer->ResetLastUpdateFenceId();
342       }
343     }
344 
345     ////
346 
347     const auto& fetchLimits = linkInfo->GetDrawFetchLimits(funcName);
348     if (!fetchLimits) {
349       *out_error = true;
350       return;
351     }
352 
353     if (lastRequiredVertex && instanceCount) {
354       if (lastRequiredVertex.value() >= fetchLimits->maxVerts) {
355         mWebGL->ErrorInvalidOperation(
356             "%s: Vertex fetch requires vertex #%u, but"
357             " attribs only supply %" PRIu64 ".",
358             funcName, lastRequiredVertex.value(), fetchLimits->maxVerts);
359         *out_error = true;
360         return;
361       }
362       if (instanceCount > fetchLimits->maxInstances) {
363         mWebGL->ErrorInvalidOperation(
364             "%s: Instance fetch requires %u, but"
365             " attribs only supply %" PRIu64 ".",
366             funcName, instanceCount, fetchLimits->maxInstances);
367         *out_error = true;
368         return;
369       }
370     }
371 
372     ////
373 
374     if (lastRequiredVertex) {
375       if (!mWebGL->DoFakeVertexAttrib0(funcName, lastRequiredVertex.value())) {
376         *out_error = true;
377         return;
378       }
379       mDidFake = true;
380     }
381 
382     ////
383 
384     mWebGL->RunContextLossTimer();
385   }
386 
~ScopedDrawHelper()387   ~ScopedDrawHelper() {
388     if (mDidFake) {
389       mWebGL->UndoFakeVertexAttrib0();
390     }
391   }
392 };
393 
394 ////////////////////////////////////////
395 
UsedVertsForTFDraw(GLenum mode,uint32_t vertCount)396 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
397   uint8_t vertsPerPrim;
398 
399   switch (mode) {
400     case LOCAL_GL_POINTS:
401       vertsPerPrim = 1;
402       break;
403     case LOCAL_GL_LINES:
404       vertsPerPrim = 2;
405       break;
406     case LOCAL_GL_TRIANGLES:
407       vertsPerPrim = 3;
408       break;
409     default:
410       MOZ_CRASH("`mode`");
411   }
412 
413   return vertCount / vertsPerPrim * vertsPerPrim;
414 }
415 
416 class ScopedDrawWithTransformFeedback final {
417   WebGLContext* const mWebGL;
418   WebGLTransformFeedback* const mTFO;
419   const bool mWithTF;
420   uint32_t mUsedVerts;
421 
422  public:
ScopedDrawWithTransformFeedback(WebGLContext * webgl,const char * funcName,GLenum mode,uint32_t vertCount,uint32_t instanceCount,bool * const out_error)423   ScopedDrawWithTransformFeedback(WebGLContext* webgl, const char* funcName,
424                                   GLenum mode, uint32_t vertCount,
425                                   uint32_t instanceCount, bool* const out_error)
426       : mWebGL(webgl),
427         mTFO(mWebGL->mBoundTransformFeedback),
428         mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
429         mUsedVerts(0) {
430     *out_error = false;
431     if (!mWithTF) return;
432 
433     if (mode != mTFO->mActive_PrimMode) {
434       mWebGL->ErrorInvalidOperation(
435           "%s: Drawing with transform feedback requires"
436           " `mode` to match BeginTransformFeedback's"
437           " `primitiveMode`.",
438           funcName);
439       *out_error = true;
440       return;
441     }
442 
443     const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
444     const auto usedVerts =
445         CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
446 
447     const auto remainingCapacity =
448         mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
449     if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
450       mWebGL->ErrorInvalidOperation(
451           "%s: Insufficient buffer capacity remaining for"
452           " transform feedback.",
453           funcName);
454       *out_error = true;
455       return;
456     }
457 
458     mUsedVerts = usedVerts.value();
459   }
460 
Advance() const461   void Advance() const {
462     if (!mWithTF) return;
463 
464     mTFO->mActive_VertPosition += mUsedVerts;
465   }
466 };
467 
HasInstancedDrawing(const WebGLContext & webgl)468 static bool HasInstancedDrawing(const WebGLContext& webgl) {
469   return webgl.IsWebGL2() ||
470          webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
471 }
472 
473 ////////////////////////////////////////
474 
DrawArrays_check(const char * const funcName,const GLint first,const GLsizei vertCount,const GLsizei instanceCount,Maybe<uint32_t> * const out_lastVert)475 bool WebGLContext::DrawArrays_check(const char* const funcName,
476                                     const GLint first, const GLsizei vertCount,
477                                     const GLsizei instanceCount,
478                                     Maybe<uint32_t>* const out_lastVert) {
479   if (!ValidateNonNegative(funcName, "first", first) ||
480       !ValidateNonNegative(funcName, "vertCount", vertCount) ||
481       !ValidateNonNegative(funcName, "instanceCount", instanceCount)) {
482     return false;
483   }
484 
485   if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
486     MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
487     if (mPrimRestartTypeBytes != 0) {
488       mPrimRestartTypeBytes = 0;
489 
490       // OSX appears to have severe perf issues with leaving this enabled.
491       gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
492     }
493   }
494 
495   if (!vertCount) {
496     *out_lastVert = Nothing();
497   } else {
498     const auto lastVert_checked = CheckedInt<uint32_t>(first) + vertCount - 1;
499     if (!lastVert_checked.isValid()) {
500       ErrorOutOfMemory("%s: `first+vertCount` out of range.", funcName);
501       return false;
502     }
503     *out_lastVert = Some(lastVert_checked.value());
504   }
505   return true;
506 }
507 
DrawArraysInstanced(GLenum mode,GLint first,GLsizei vertCount,GLsizei instanceCount,const char * const funcName)508 void WebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
509                                        GLsizei vertCount, GLsizei instanceCount,
510                                        const char* const funcName) {
511   AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
512   if (IsContextLost()) return;
513 
514   const gl::GLContext::TlsScope inTls(gl);
515 
516   Maybe<uint32_t> lastVert;
517   if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert))
518     return;
519 
520   bool error = false;
521   const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert,
522                                       instanceCount, &error);
523   if (error) return;
524 
525   const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
526   if (error) return;
527 
528   const ScopedDrawWithTransformFeedback scopedTF(
529       this, funcName, mode, vertCount, instanceCount, &error);
530   if (error) return;
531 
532   {
533     ScopedDrawCallWrapper wrapper(*this);
534     if (vertCount && instanceCount) {
535       AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS);
536       if (HasInstancedDrawing(*this)) {
537         gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
538       } else {
539         MOZ_ASSERT(instanceCount == 1);
540         gl->fDrawArrays(mode, first, vertCount);
541       }
542     }
543   }
544 
545   Draw_cleanup(funcName);
546   scopedTF.Advance();
547 }
548 
549 ////////////////////////////////////////
550 
DrawElements_check(const char * const funcName,const GLsizei rawIndexCount,const GLenum type,const WebGLintptr byteOffset,const GLsizei instanceCount,Maybe<uint32_t> * const out_lastVert)551 bool WebGLContext::DrawElements_check(const char* const funcName,
552                                       const GLsizei rawIndexCount,
553                                       const GLenum type,
554                                       const WebGLintptr byteOffset,
555                                       const GLsizei instanceCount,
556                                       Maybe<uint32_t>* const out_lastVert) {
557   if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
558       !mBoundTransformFeedback->mIsPaused) {
559     ErrorInvalidOperation(
560         "%s: DrawElements* functions are incompatible with"
561         " transform feedback.",
562         funcName);
563     return false;
564   }
565 
566   if (!ValidateNonNegative(funcName, "vertCount", rawIndexCount) ||
567       !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
568       !ValidateNonNegative(funcName, "instanceCount", instanceCount)) {
569     return false;
570   }
571   const auto indexCount = uint32_t(rawIndexCount);
572 
573   uint8_t bytesPerIndex = 0;
574   switch (type) {
575     case LOCAL_GL_UNSIGNED_BYTE:
576       bytesPerIndex = 1;
577       break;
578 
579     case LOCAL_GL_UNSIGNED_SHORT:
580       bytesPerIndex = 2;
581       break;
582 
583     case LOCAL_GL_UNSIGNED_INT:
584       if (IsWebGL2() ||
585           IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
586         bytesPerIndex = 4;
587       }
588       break;
589   }
590   if (!bytesPerIndex) {
591     ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
592     return false;
593   }
594   if (byteOffset % bytesPerIndex != 0) {
595     ErrorInvalidOperation(
596         "%s: `byteOffset` must be a multiple of the size of `type`", funcName);
597     return false;
598   }
599 
600   ////
601 
602   if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
603     MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
604     if (mPrimRestartTypeBytes != bytesPerIndex) {
605       mPrimRestartTypeBytes = bytesPerIndex;
606 
607       const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
608       gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
609       gl->fPrimitiveRestartIndex(ones);
610     }
611   }
612 
613   ////
614   // Index fetching
615 
616   if (!indexCount || !instanceCount) {
617     *out_lastVert = Nothing();
618     return true;
619   }
620 
621   const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
622 
623   size_t availBytes = 0;
624   if (indexBuffer) {
625     MOZ_ASSERT(!indexBuffer->IsBoundForTF(), "This should be impossible.");
626     availBytes = indexBuffer->ByteLength();
627   }
628   const auto availIndices =
629       AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
630   if (indexCount > availIndices) {
631     ErrorInvalidOperation("%s: Index buffer too small.", funcName);
632     return false;
633   }
634 
635   *out_lastVert =
636       indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
637   return true;
638 }
639 
HandleDrawElementsErrors(WebGLContext * webgl,const char * funcName,gl::GLContext::LocalErrorScope & errorScope)640 static void HandleDrawElementsErrors(
641     WebGLContext* webgl, const char* funcName,
642     gl::GLContext::LocalErrorScope& errorScope) {
643   const auto err = errorScope.GetError();
644   if (err == LOCAL_GL_INVALID_OPERATION) {
645     webgl->ErrorInvalidOperation(
646         "%s: Driver rejected indexed draw call, possibly"
647         " due to out-of-bounds indices.",
648         funcName);
649     return;
650   }
651 
652   MOZ_ASSERT(!err);
653   if (err) {
654     webgl->ErrorImplementationBug(
655         "%s: Unexpected driver error during indexed draw"
656         " call. Please file a bug.",
657         funcName);
658     return;
659   }
660 }
661 
DrawElementsInstanced(GLenum mode,GLsizei indexCount,GLenum type,WebGLintptr byteOffset,GLsizei instanceCount,const char * const funcName)662 void WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount,
663                                          GLenum type, WebGLintptr byteOffset,
664                                          GLsizei instanceCount,
665                                          const char* const funcName) {
666   AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
667   if (IsContextLost()) return;
668 
669   const gl::GLContext::TlsScope inTls(gl);
670 
671   Maybe<uint32_t> lastVert;
672   if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount,
673                           &lastVert)) {
674     return;
675   }
676 
677   bool error = false;
678   const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert,
679                                       instanceCount, &error);
680   if (error) return;
681 
682   const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
683   if (error) return;
684 
685   {
686     ScopedDrawCallWrapper wrapper(*this);
687     {
688       UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
689 
690       if (gl->IsANGLE()) {
691         errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
692       }
693 
694       if (indexCount && instanceCount) {
695         AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
696         if (HasInstancedDrawing(*this)) {
697           gl->fDrawElementsInstanced(mode, indexCount, type,
698                                      reinterpret_cast<GLvoid*>(byteOffset),
699                                      instanceCount);
700         } else {
701           MOZ_ASSERT(instanceCount == 1);
702           gl->fDrawElements(mode, indexCount, type,
703                             reinterpret_cast<GLvoid*>(byteOffset));
704         }
705       }
706 
707       if (errorScope) {
708         HandleDrawElementsErrors(this, funcName, *errorScope);
709       }
710     }
711   }
712 
713   Draw_cleanup(funcName);
714 }
715 
716 ////////////////////////////////////////
717 
Draw_cleanup(const char * funcName)718 void WebGLContext::Draw_cleanup(const char* funcName) {
719   if (gl->WorkAroundDriverBugs()) {
720     if (gl->Renderer() == gl::GLRenderer::Tegra) {
721       mDrawCallsSinceLastFlush++;
722 
723       if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
724         gl->fFlush();
725         mDrawCallsSinceLastFlush = 0;
726       }
727     }
728   }
729 
730   // Let's check for a really common error: Viewport is larger than the actual
731   // destination framebuffer.
732   uint32_t destWidth = mViewportWidth;
733   uint32_t destHeight = mViewportHeight;
734 
735   if (mBoundDrawFramebuffer) {
736     const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
737     for (const auto& cur : drawBuffers) {
738       if (!cur->IsDefined()) continue;
739       cur->Size(&destWidth, &destHeight);
740       break;
741     }
742   } else {
743     destWidth = mDefaultFB->mSize.width;
744     destHeight = mDefaultFB->mSize.height;
745   }
746 
747   if (mViewportWidth > int32_t(destWidth) ||
748       mViewportHeight > int32_t(destHeight)) {
749     if (!mAlreadyWarnedAboutViewportLargerThanDest) {
750       GenerateWarning(
751           "%s: Drawing to a destination rect smaller than the viewport"
752           " rect. (This warning will only be given once)",
753           funcName);
754       mAlreadyWarnedAboutViewportLargerThanDest = true;
755     }
756   }
757 }
758 
WhatDoesVertexAttrib0Need() const759 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
760   MOZ_ASSERT(mCurrentProgram);
761   MOZ_ASSERT(mActiveProgramLinkInfo);
762 
763   bool legacyAttrib0 = gl->IsCompatibilityProfile();
764 #ifdef XP_MACOSX
765   if (gl->WorkAroundDriverBugs()) {
766     // Failures in conformance/attribs/gl-disabled-vertex-attrib.
767     // Even in Core profiles on NV. Sigh.
768     legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
769   }
770 #endif
771 
772   if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
773 
774   if (!mActiveProgramLinkInfo->attrib0Active) {
775     // Ensure that the legacy code has enough buffer.
776     return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
777   }
778 
779   const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
780   return isAttribArray0Enabled
781              ? WebGLVertexAttrib0Status::Default
782              : WebGLVertexAttrib0Status::EmulatedInitializedArray;
783 }
784 
DoFakeVertexAttrib0(const char * const funcName,const uint32_t lastVert)785 bool WebGLContext::DoFakeVertexAttrib0(const char* const funcName,
786                                        const uint32_t lastVert) {
787   const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
788   if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
789     return true;
790 
791   if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
792     GenerateWarning(
793         "Drawing without vertex attrib 0 array enabled forces the browser "
794         "to do expensive emulation work when running on desktop OpenGL "
795         "platforms, for example on Mac. It is preferable to always draw "
796         "with vertex attrib 0 array enabled, by using bindAttribLocation "
797         "to bind some always-used attribute to location 0.");
798     mAlreadyWarnedAboutFakeVertexAttrib0 = true;
799   }
800 
801   gl->fEnableVertexAttribArray(0);
802 
803   if (!mFakeVertexAttrib0BufferObject) {
804     gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
805     mFakeVertexAttrib0BufferObjectSize = 0;
806   }
807   gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
808 
809   ////
810 
811   switch (mGenericVertexAttribTypes[0]) {
812     case LOCAL_GL_FLOAT:
813       gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
814       break;
815 
816     case LOCAL_GL_INT:
817       gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
818       break;
819 
820     case LOCAL_GL_UNSIGNED_INT:
821       gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
822       break;
823 
824     default:
825       MOZ_CRASH();
826   }
827 
828   ////
829 
830   const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
831   const auto checked_dataSize = (CheckedUint32(lastVert) + 1) * bytesPerVert;
832   if (!checked_dataSize.isValid()) {
833     ErrorOutOfMemory(
834         "Integer overflow trying to construct a fake vertex attrib 0"
835         " array for a draw-operation with %" PRIu64
836         " vertices. Try"
837         " reducing the number of vertices.",
838         uint64_t(lastVert) + 1);
839     return false;
840   }
841   const auto dataSize = checked_dataSize.value();
842 
843   if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
844     gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
845                     LOCAL_GL_DYNAMIC_DRAW);
846     mFakeVertexAttrib0BufferObjectSize = dataSize;
847     mFakeVertexAttrib0DataDefined = false;
848   }
849 
850   if (whatDoesAttrib0Need ==
851       WebGLVertexAttrib0Status::EmulatedUninitializedArray)
852     return true;
853 
854   ////
855 
856   if (mFakeVertexAttrib0DataDefined &&
857       memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) ==
858           0) {
859     return true;
860   }
861 
862   ////
863 
864   const UniqueBuffer data(malloc(dataSize));
865   if (!data) {
866     ErrorOutOfMemory("%s: Failed to allocate fake vertex attrib 0 array.",
867                      funcName);
868     return false;
869   }
870   auto itr = (uint8_t*)data.get();
871   const auto itrEnd = itr + dataSize;
872   while (itr != itrEnd) {
873     memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
874     itr += bytesPerVert;
875   }
876 
877   {
878     gl::GLContext::LocalErrorScope errorScope(*gl);
879 
880     gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
881 
882     const auto err = errorScope.GetError();
883     if (err) {
884       ErrorOutOfMemory("%s: Failed to upload fake vertex attrib 0 data.",
885                        funcName);
886       return false;
887     }
888   }
889 
890   ////
891 
892   memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
893   mFakeVertexAttrib0DataDefined = true;
894   return true;
895 }
896 
UndoFakeVertexAttrib0()897 void WebGLContext::UndoFakeVertexAttrib0() {
898   const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
899   if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
900     return;
901 
902   if (mBoundVertexArray->mAttribs[0].mBuf) {
903     const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0];
904     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.mBuf->mGLName);
905     attrib0.DoVertexAttribPointer(gl, 0);
906   } else {
907     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
908   }
909 
910   gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER,
911                   mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
912 }
913 
CreateGLTexture(gl::GLContext * gl)914 static GLuint CreateGLTexture(gl::GLContext* gl) {
915   MOZ_ASSERT(gl->IsCurrent());
916   GLuint ret = 0;
917   gl->fGenTextures(1, &ret);
918   return ret;
919 }
920 
921 UniquePtr<WebGLContext::FakeBlackTexture>
Create(gl::GLContext * gl,TexTarget target,FakeBlackType type)922 WebGLContext::FakeBlackTexture::Create(gl::GLContext* gl, TexTarget target,
923                                        FakeBlackType type) {
924   GLenum texFormat;
925   switch (type) {
926     case FakeBlackType::RGBA0000:
927       texFormat = LOCAL_GL_RGBA;
928       break;
929 
930     case FakeBlackType::RGBA0001:
931       texFormat = LOCAL_GL_RGB;
932       break;
933 
934     default:
935       MOZ_CRASH("GFX: bad type");
936   }
937 
938   UniquePtr<FakeBlackTexture> result(new FakeBlackTexture(gl));
939   gl::ScopedBindTexture scopedBind(gl, result->mGLName, target.get());
940 
941   gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
942                      LOCAL_GL_NEAREST);
943   gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MAG_FILTER,
944                      LOCAL_GL_NEAREST);
945 
946   const webgl::DriverUnpackInfo dui = {texFormat, texFormat,
947                                        LOCAL_GL_UNSIGNED_BYTE};
948   UniqueBuffer zeros = moz_xcalloc(1, 4);  // Infallible allocation.
949 
950   MOZ_ASSERT(gl->IsCurrent());
951 
952   if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
953     for (int i = 0; i < 6; ++i) {
954       const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
955       const GLenum error =
956           DoTexImage(gl, curTarget.get(), 0, &dui, 1, 1, 1, zeros.get());
957       if (error) {
958         return nullptr;
959       }
960     }
961   } else {
962     const GLenum error =
963         DoTexImage(gl, target.get(), 0, &dui, 1, 1, 1, zeros.get());
964     if (error) {
965       return nullptr;
966     }
967   }
968 
969   return result;
970 }
971 
FakeBlackTexture(gl::GLContext * gl)972 WebGLContext::FakeBlackTexture::FakeBlackTexture(gl::GLContext* gl)
973     : mGL(gl), mGLName(CreateGLTexture(gl)) {}
974 
~FakeBlackTexture()975 WebGLContext::FakeBlackTexture::~FakeBlackTexture() {
976   mGL->fDeleteTextures(1, &mGLName);
977 }
978 
979 }  // namespace mozilla
980