Coverage Report

Created: 2018-09-25 14:53

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