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