Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/canvas/TexUnpackBlob.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 20; 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 "TexUnpackBlob.h"
7
8
#include "GLBlitHelper.h"
9
#include "GLContext.h"
10
#include "mozilla/dom/Element.h"
11
#include "mozilla/dom/HTMLCanvasElement.h"
12
#include "mozilla/RefPtr.h"
13
#include "nsLayoutUtils.h"
14
#include "WebGLBuffer.h"
15
#include "WebGLContext.h"
16
#include "WebGLTexelConversions.h"
17
#include "WebGLTexture.h"
18
19
namespace mozilla {
20
namespace webgl {
21
22
static bool
23
IsPIValidForDOM(const webgl::PackingInfo& pi)
24
0
{
25
0
    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
26
0
27
0
    // Just check for invalid individual formats and types, not combinations.
28
0
    switch (pi.format) {
29
0
    case LOCAL_GL_RGB:
30
0
    case LOCAL_GL_RGBA:
31
0
    case LOCAL_GL_LUMINANCE_ALPHA:
32
0
    case LOCAL_GL_LUMINANCE:
33
0
    case LOCAL_GL_ALPHA:
34
0
    case LOCAL_GL_RED:
35
0
    case LOCAL_GL_RED_INTEGER:
36
0
    case LOCAL_GL_RG:
37
0
    case LOCAL_GL_RG_INTEGER:
38
0
    case LOCAL_GL_RGB_INTEGER:
39
0
    case LOCAL_GL_RGBA_INTEGER:
40
0
        break;
41
0
42
0
    case LOCAL_GL_SRGB:
43
0
    case LOCAL_GL_SRGB_ALPHA:
44
0
        // Allowed in WebGL1+EXT_srgb
45
0
        break;
46
0
47
0
    default:
48
0
        return false;
49
0
    }
50
0
51
0
    switch (pi.type) {
52
0
    case LOCAL_GL_UNSIGNED_BYTE:
53
0
    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
54
0
    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
55
0
    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
56
0
    case LOCAL_GL_HALF_FLOAT:
57
0
    case LOCAL_GL_HALF_FLOAT_OES:
58
0
    case LOCAL_GL_FLOAT:
59
0
    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
60
0
        break;
61
0
62
0
    default:
63
0
        return false;
64
0
    }
65
0
66
0
    return true;
67
0
}
68
69
static bool
70
ValidatePIForDOM(WebGLContext* webgl, const webgl::PackingInfo& pi)
71
0
{
72
0
    if (!IsPIValidForDOM(pi)) {
73
0
        webgl->ErrorInvalidOperation("Format or type is invalid for DOM sources.");
74
0
        return false;
75
0
    }
76
0
    return true;
77
0
}
78
79
static WebGLTexelFormat
80
FormatForPackingInfo(const PackingInfo& pi)
81
0
{
82
0
    switch (pi.type) {
83
0
    case LOCAL_GL_UNSIGNED_BYTE:
84
0
        switch (pi.format) {
85
0
        case LOCAL_GL_RED:
86
0
        case LOCAL_GL_LUMINANCE:
87
0
        case LOCAL_GL_RED_INTEGER:
88
0
            return WebGLTexelFormat::R8;
89
0
90
0
        case LOCAL_GL_ALPHA:
91
0
            return WebGLTexelFormat::A8;
92
0
93
0
        case LOCAL_GL_LUMINANCE_ALPHA:
94
0
            return WebGLTexelFormat::RA8;
95
0
96
0
        case LOCAL_GL_RGB:
97
0
        case LOCAL_GL_RGB_INTEGER:
98
0
        case LOCAL_GL_SRGB:
99
0
            return WebGLTexelFormat::RGB8;
100
0
101
0
        case LOCAL_GL_RGBA:
102
0
        case LOCAL_GL_RGBA_INTEGER:
103
0
        case LOCAL_GL_SRGB_ALPHA:
104
0
            return WebGLTexelFormat::RGBA8;
105
0
106
0
        case LOCAL_GL_RG:
107
0
        case LOCAL_GL_RG_INTEGER:
108
0
            return WebGLTexelFormat::RG8;
109
0
110
0
        default:
111
0
            break;
112
0
        }
113
0
        break;
114
0
115
0
    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
116
0
        if (pi.format == LOCAL_GL_RGB)
117
0
            return WebGLTexelFormat::RGB565;
118
0
        break;
119
0
120
0
    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
121
0
        if (pi.format == LOCAL_GL_RGBA)
122
0
            return WebGLTexelFormat::RGBA5551;
123
0
        break;
124
0
125
0
    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
126
0
        if (pi.format == LOCAL_GL_RGBA)
127
0
            return WebGLTexelFormat::RGBA4444;
128
0
        break;
129
0
130
0
    case LOCAL_GL_HALF_FLOAT:
131
0
    case LOCAL_GL_HALF_FLOAT_OES:
132
0
        switch (pi.format) {
133
0
        case LOCAL_GL_RED:
134
0
        case LOCAL_GL_LUMINANCE:
135
0
            return WebGLTexelFormat::R16F;
136
0
137
0
        case LOCAL_GL_ALPHA:           return WebGLTexelFormat::A16F;
138
0
        case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA16F;
139
0
        case LOCAL_GL_RG:              return WebGLTexelFormat::RG16F;
140
0
        case LOCAL_GL_RGB:             return WebGLTexelFormat::RGB16F;
141
0
        case LOCAL_GL_RGBA:            return WebGLTexelFormat::RGBA16F;
142
0
143
0
        default:
144
0
            break;
145
0
        }
146
0
        break;
147
0
148
0
    case LOCAL_GL_FLOAT:
149
0
        switch (pi.format) {
150
0
        case LOCAL_GL_RED:
151
0
        case LOCAL_GL_LUMINANCE:
152
0
            return WebGLTexelFormat::R32F;
153
0
154
0
        case LOCAL_GL_ALPHA:           return WebGLTexelFormat::A32F;
155
0
        case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA32F;
156
0
        case LOCAL_GL_RG:              return WebGLTexelFormat::RG32F;
157
0
        case LOCAL_GL_RGB:             return WebGLTexelFormat::RGB32F;
158
0
        case LOCAL_GL_RGBA:            return WebGLTexelFormat::RGBA32F;
159
0
160
0
        default:
161
0
            break;
162
0
        }
163
0
        break;
164
0
165
0
    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
166
0
        if (pi.format == LOCAL_GL_RGB)
167
0
            return WebGLTexelFormat::RGB11F11F10F;
168
0
        break;
169
0
170
0
    default:
171
0
        break;
172
0
    }
173
0
174
0
    return WebGLTexelFormat::FormatNotSupportingAnyConversion;
175
0
}
176
177
////////////////////
178
179
static bool
180
ValidateUnpackPixels(WebGLContext* webgl, uint32_t fullRows,
181
                     uint32_t tailPixels, webgl::TexUnpackBlob* blob)
182
0
{
183
0
    if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
184
0
        return true;
185
0
186
0
    const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
187
0
    if (!usedPixelsPerRow.isValid() || usedPixelsPerRow.value() > blob->mRowLength) {
188
0
        webgl->ErrorInvalidOperation("UNPACK_SKIP_PIXELS + width >"
189
0
                                     " UNPACK_ROW_LENGTH.");
190
0
        return false;
191
0
    }
192
0
193
0
    if (blob->mHeight > blob->mImageHeight) {
194
0
        webgl->ErrorInvalidOperation("height > UNPACK_IMAGE_HEIGHT.");
195
0
        return false;
196
0
    }
197
0
198
0
    //////
199
0
200
0
    // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
201
0
    auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
202
0
    skipFullRows += blob->mSkipRows;
203
0
204
0
    MOZ_ASSERT(blob->mDepth >= 1);
205
0
    MOZ_ASSERT(blob->mHeight >= 1);
206
0
    auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight;
207
0
    usedFullRows += blob->mHeight - 1; // Full rows in the final image, excluding the tail.
208
0
209
0
    const auto fullRowsNeeded = skipFullRows + usedFullRows;
210
0
    if (!fullRowsNeeded.isValid()) {
211
0
        webgl->ErrorOutOfMemory("Invalid calculation for required row count.");
212
0
        return false;
213
0
    }
214
0
215
0
    if (fullRows > fullRowsNeeded.value())
216
0
        return true;
217
0
218
0
    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value()) {
219
0
        blob->mNeedsExactUpload = true;
220
0
        return true;
221
0
    }
222
0
223
0
    webgl->ErrorInvalidOperation("Desired upload requires more data than is"
224
0
                                 " available: (%u rows plus %u pixels needed, %u rows"
225
0
                                 " plus %u pixels available)",
226
0
                                 fullRowsNeeded.value(),
227
0
                                 usedPixelsPerRow.value(), fullRows, tailPixels);
228
0
    return false;
229
0
}
230
231
static bool
232
ValidateUnpackBytes(WebGLContext* webgl,
233
                    const webgl::PackingInfo& pi, size_t availByteCount,
234
                    webgl::TexUnpackBlob* blob)
235
0
{
236
0
    if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
237
0
        return true;
238
0
239
0
    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
240
0
    const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
241
0
    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
242
0
243
0
    const auto fullRows = availByteCount / rowStride;
244
0
    if (!fullRows.isValid()) {
245
0
        webgl->ErrorOutOfMemory("Unacceptable upload size calculated.");
246
0
        return false;
247
0
    }
248
0
249
0
    const auto bodyBytes = fullRows.value() * rowStride.value();
250
0
    const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
251
0
252
0
    return ValidateUnpackPixels(webgl, fullRows.value(), tailPixels, blob);
253
0
}
254
255
////////////////////
256
257
static uint32_t
258
ZeroOn2D(TexImageTarget target, uint32_t val)
259
0
{
260
0
    return (IsTarget3D(target) ? val : 0);
261
0
}
262
263
static uint32_t
264
FallbackOnZero(uint32_t val, uint32_t fallback)
265
0
{
266
0
    return (val ? val : fallback);
267
0
}
268
269
TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
270
                             uint32_t rowLength, uint32_t width, uint32_t height,
271
                             uint32_t depth, gfxAlphaType srcAlphaType)
272
    : mAlignment(webgl->mPixelStore_UnpackAlignment)
273
    , mRowLength(rowLength)
274
    , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight),
275
                                  height))
276
277
    , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
278
    , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
279
    , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
280
281
    , mWidth(width)
282
    , mHeight(height)
283
    , mDepth(depth)
284
285
    , mSrcAlphaType(srcAlphaType)
286
287
    , mNeedsExactUpload(false)
288
0
{
289
0
    MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
290
0
}
291
292
static bool
293
HasColorAndAlpha(const WebGLTexelFormat format)
294
{
295
    switch (format) {
296
    case WebGLTexelFormat::RA8:
297
    case WebGLTexelFormat::RA16F:
298
    case WebGLTexelFormat::RA32F:
299
    case WebGLTexelFormat::RGBA8:
300
    case WebGLTexelFormat::RGBA5551:
301
    case WebGLTexelFormat::RGBA4444:
302
    case WebGLTexelFormat::RGBA16F:
303
    case WebGLTexelFormat::RGBA32F:
304
    case WebGLTexelFormat::BGRA8:
305
        return true;
306
    default:
307
        return false;
308
    }
309
}
310
311
bool
312
TexUnpackBlob::ConvertIfNeeded(WebGLContext* webgl,
313
                               const uint32_t rowLength, const uint32_t rowCount,
314
                               WebGLTexelFormat srcFormat,
315
                               const uint8_t* const srcBegin, const ptrdiff_t srcStride,
316
                               WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
317
                               const uint8_t** const out_begin,
318
                               UniqueBuffer* const out_anchoredBuffer) const
319
0
{
320
0
    MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
321
0
    MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
322
0
323
0
    *out_begin = srcBegin;
324
0
325
0
    if (!rowLength || !rowCount)
326
0
        return true;
327
0
328
0
    const auto srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
329
0
    const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
330
0
    const auto fnHasPremultMismatch = [&]() {
331
0
        if (mSrcAlphaType == gfxAlphaType::Opaque)
332
0
            return false;
333
0
334
0
        if (!HasColorAndAlpha(srcFormat))
335
0
            return false;
336
0
337
0
        return srcIsPremult != dstIsPremult;
338
0
    };
339
0
340
0
    const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
341
0
                                                     : gl::OriginPos::BottomLeft);
342
0
    const auto dstOrigin = gl::OriginPos::BottomLeft;
343
0
344
0
    if (srcFormat != dstFormat) {
345
0
        webgl->GeneratePerfWarning("Conversion requires pixel reformatting. (%u->%u)",
346
0
                                   uint32_t(srcFormat),
347
0
                                   uint32_t(dstFormat));
348
0
    } else if (fnHasPremultMismatch()) {
349
0
        webgl->GeneratePerfWarning("Conversion requires change in"
350
0
                                   " alpha-premultiplication.");
351
0
    } else if (srcOrigin != dstOrigin) {
352
0
        webgl->GeneratePerfWarning("Conversion requires y-flip.");
353
0
    } else if (srcStride != dstStride) {
354
0
        webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
355
0
                                   uint32_t(srcStride), uint32_t(dstStride));
356
0
    } else {
357
0
        return true;
358
0
    }
359
0
360
0
    ////
361
0
362
0
    const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
363
0
    if (!dstTotalBytes.isValid()) {
364
0
        webgl->ErrorOutOfMemory("Calculation failed.");
365
0
        return false;
366
0
    }
367
0
368
0
    UniqueBuffer dstBuffer = calloc(1, dstTotalBytes.value());
369
0
    if (!dstBuffer.get()) {
370
0
        webgl->ErrorOutOfMemory("Failed to allocate dest buffer.");
371
0
        return false;
372
0
    }
373
0
    const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
374
0
375
0
    ////
376
0
377
0
    // And go!:
378
0
    bool wasTrivial;
379
0
    if (!ConvertImage(rowLength, rowCount,
380
0
                      srcBegin, srcStride, srcOrigin, srcFormat, srcIsPremult,
381
0
                      dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult,
382
0
                      &wasTrivial))
383
0
    {
384
0
        webgl->ErrorImplementationBug("ConvertImage failed.");
385
0
        return false;
386
0
    }
387
0
388
0
    *out_begin = dstBegin;
389
0
    *out_anchoredBuffer = std::move(dstBuffer);
390
0
    return true;
391
0
}
392
393
static GLenum
394
DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
395
                const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
396
                GLsizei width, GLsizei height, GLsizei depth, const void* data)
397
0
{
398
0
    if (isSubImage) {
399
0
        return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
400
0
                             depth, dui->ToPacking(), data);
401
0
    } else {
402
0
        return DoTexImage(gl, target, level, dui, width, height, depth, data);
403
0
    }
404
0
}
405
406
//////////////////////////////////////////////////////////////////////////////////////////
407
// TexUnpackBytes
408
409
TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
410
                               uint32_t width, uint32_t height, uint32_t depth,
411
                               bool isClientData, const uint8_t* ptr, size_t availBytes)
412
    : TexUnpackBlob(webgl, target,
413
                    FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
414
                    width, height, depth, gfxAlphaType::NonPremult)
415
    , mIsClientData(isClientData)
416
    , mPtr(ptr)
417
    , mAvailBytes(availBytes)
418
0
{ }
419
420
bool
421
TexUnpackBytes::Validate(WebGLContext* webgl, const webgl::PackingInfo& pi)
422
0
{
423
0
    if (mIsClientData && !mPtr)
424
0
        return true;
425
0
426
0
    return ValidateUnpackBytes(webgl, pi, mAvailBytes, this);
427
0
}
428
429
bool
430
TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
431
                              WebGLTexture* tex, TexImageTarget target, GLint level,
432
                              const webgl::DriverUnpackInfo* dui, GLint xOffset,
433
                              GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi,
434
                              GLenum* const out_error) const
435
0
{
436
0
    WebGLContext* webgl = tex->mContext;
437
0
438
0
    const auto format = FormatForPackingInfo(pi);
439
0
    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
440
0
441
0
    const uint8_t* uploadPtr = mPtr;
442
0
    UniqueBuffer tempBuffer;
443
0
444
0
    do {
445
0
        if (!mIsClientData || !mPtr)
446
0
            break;
447
0
448
0
        if (!webgl->mPixelStore_FlipY &&
449
0
            !webgl->mPixelStore_PremultiplyAlpha)
450
0
        {
451
0
            break;
452
0
        }
453
0
454
0
        if (webgl->mPixelStore_UnpackImageHeight ||
455
0
            webgl->mPixelStore_UnpackSkipImages ||
456
0
            webgl->mPixelStore_UnpackRowLength ||
457
0
            webgl->mPixelStore_UnpackSkipRows ||
458
0
            webgl->mPixelStore_UnpackSkipPixels)
459
0
        {
460
0
            webgl->ErrorInvalidOperation("Non-DOM-Element uploads with alpha-premult"
461
0
                                         " or y-flip do not support subrect selection.");
462
0
            return false;
463
0
        }
464
0
465
0
        webgl->GenerateWarning("Alpha-premult and y-flip are deprecated for"
466
0
                               " non-DOM-Element uploads.");
467
0
468
0
        const uint32_t rowLength = mWidth;
469
0
        const uint32_t rowCount = mHeight * mDepth;
470
0
        const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment);
471
0
        if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, mPtr, stride,
472
0
                             format, stride, &uploadPtr, &tempBuffer))
473
0
        {
474
0
            return false;
475
0
        }
476
0
    } while (false);
477
0
478
0
    //////
479
0
480
0
    const auto& gl = webgl->gl;
481
0
482
0
    bool useParanoidHandling = false;
483
0
    if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
484
0
        webgl->GenerateWarning("Uploads from a buffer with a final row with a byte"
485
0
                               " count smaller than the row stride can incur extra"
486
0
                               " overhead.");
487
0
488
0
        if (gl->WorkAroundDriverBugs()) {
489
0
            useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
490
0
        }
491
0
    }
492
0
493
0
    if (!useParanoidHandling) {
494
0
        if (webgl->mBoundPixelUnpackBuffer) {
495
0
            gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
496
0
                            webgl->mBoundPixelUnpackBuffer->mGLName);
497
0
        }
498
0
499
0
        *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
500
0
                                     zOffset, mWidth, mHeight, mDepth, uploadPtr);
501
0
502
0
        if (webgl->mBoundPixelUnpackBuffer) {
503
0
            gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
504
0
        }
505
0
        return true;
506
0
    }
507
0
508
0
    //////
509
0
510
0
    MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
511
0
512
0
    if (!isSubImage) {
513
0
        // Alloc first to catch OOMs.
514
0
        AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
515
0
        *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
516
0
                                     zOffset, mWidth, mHeight, mDepth, nullptr);
517
0
        if (*out_error)
518
0
            return true;
519
0
    }
520
0
521
0
    const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
522
0
                                 webgl->mBoundPixelUnpackBuffer);
523
0
524
0
    //////
525
0
526
0
    // Make our sometimes-implicit values explicit. Also this keeps them constant when we
527
0
    // ask for height=mHeight-1 and such.
528
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
529
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mImageHeight);
530
0
531
0
    if (mDepth > 1) {
532
0
        *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
533
0
                                     zOffset, mWidth, mHeight, mDepth-1, uploadPtr);
534
0
    }
535
0
536
0
    // Skip the images we uploaded.
537
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mSkipImages + mDepth - 1);
538
0
539
0
    if (mHeight > 1) {
540
0
        *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
541
0
                                     zOffset+mDepth-1, mWidth, mHeight-1, 1, uploadPtr);
542
0
    }
543
0
544
0
    const auto totalSkipRows = CheckedUint32(mSkipImages) * mImageHeight + mSkipRows;
545
0
    const auto totalFullRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1;
546
0
    const auto tailOffsetRows = totalSkipRows + totalFullRows;
547
0
548
0
    const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
549
0
    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
550
0
    if (!rowStride.isValid()) {
551
0
        MOZ_CRASH("Should be checked earlier.");
552
0
    }
553
0
    const auto tailOffsetBytes = tailOffsetRows * rowStride;
554
0
555
0
    uploadPtr += tailOffsetBytes.value();
556
0
557
0
    //////
558
0
559
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);   // No stride padding.
560
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);  // No padding in general.
561
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
562
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0);   // or rows.
563
0
                                                      // Keep skipping pixels though!
564
0
565
0
    *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
566
0
                                 yOffset+mHeight-1, zOffset+mDepth-1, mWidth, 1, 1,
567
0
                                 uploadPtr);
568
0
569
0
    // Reset all our modified state.
570
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
571
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, webgl->mPixelStore_UnpackImageHeight);
572
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
573
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, webgl->mPixelStore_UnpackSkipImages);
574
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, webgl->mPixelStore_UnpackSkipRows);
575
0
576
0
    return true;
577
0
}
578
579
////////////////////////////////////////////////////////////////////////////////
580
////////////////////////////////////////////////////////////////////////////////
581
// TexUnpackImage
582
583
TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
584
                               uint32_t width, uint32_t height, uint32_t depth,
585
                               layers::Image* image, gfxAlphaType srcAlphaType)
586
    : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
587
                    srcAlphaType)
588
    , mImage(image)
589
0
{ }
590
591
TexUnpackImage::~TexUnpackImage()
592
0
{ }
593
594
bool
595
TexUnpackImage::Validate(WebGLContext* webgl, const webgl::PackingInfo& pi)
596
0
{
597
0
    if (!ValidatePIForDOM(webgl, pi))
598
0
        return false;
599
0
600
0
    const auto fullRows = mImage->GetSize().height;
601
0
    return ValidateUnpackPixels(webgl, fullRows, 0, this);
602
0
}
603
604
bool
605
TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
606
                              WebGLTexture* tex, TexImageTarget target, GLint level,
607
                              const webgl::DriverUnpackInfo* dui, GLint xOffset,
608
                              GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi,
609
                              GLenum* const out_error) const
610
0
{
611
0
    MOZ_ASSERT_IF(needsRespec, !isSubImage);
612
0
613
0
    WebGLContext* webgl = tex->mContext;
614
0
615
0
    gl::GLContext* gl = webgl->GL();
616
0
617
0
    if (needsRespec) {
618
0
        *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
619
0
                                     yOffset, zOffset, mWidth, mHeight, mDepth,
620
0
                                     nullptr);
621
0
        if (*out_error)
622
0
            return true;
623
0
    }
624
0
625
0
    const char* fallbackReason;
626
0
    do {
627
0
        if (mDepth != 1) {
628
0
            fallbackReason = "depth is not 1";
629
0
            break;
630
0
        }
631
0
        if (xOffset != 0 || yOffset != 0 || zOffset != 0) {
632
0
            fallbackReason = "x/y/zOffset is not 0";
633
0
            break;
634
0
        }
635
0
636
0
        if (webgl->mPixelStore_UnpackSkipPixels ||
637
0
            webgl->mPixelStore_UnpackSkipRows ||
638
0
            webgl->mPixelStore_UnpackSkipImages)
639
0
        {
640
0
            fallbackReason = "non-zero UNPACK_SKIP_* not yet supported";
641
0
            break;
642
0
        }
643
0
644
0
        const auto fnHasPremultMismatch = [&]() {
645
0
            if (mSrcAlphaType == gfxAlphaType::Opaque)
646
0
                return false;
647
0
648
0
            const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
649
0
            const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
650
0
            if (srcIsPremult == dstIsPremult)
651
0
                return false;
652
0
653
0
            if (dstIsPremult) {
654
0
                fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
655
0
            } else {
656
0
                fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
657
0
            }
658
0
            return true;
659
0
        };
660
0
        if (fnHasPremultMismatch())
661
0
            break;
662
0
663
0
        if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) {
664
0
            fallbackReason = "`format` is not RGB or RGBA";
665
0
            break;
666
0
        }
667
0
668
0
        if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) {
669
0
            fallbackReason = "`type` is not UNSIGNED_BYTE";
670
0
            break;
671
0
        }
672
0
673
0
        gl::ScopedFramebuffer scopedFB(gl);
674
0
        gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
675
0
676
0
        {
677
0
            gl::GLContext::LocalErrorScope errorScope(*gl);
678
0
679
0
            gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
680
0
                                      target.get(), tex->mGLName, level);
681
0
682
0
            if (errorScope.GetError()) {
683
0
                fallbackReason = "bug: failed to attach to FB for blit";
684
0
                break;
685
0
            }
686
0
        }
687
0
688
0
        const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
689
0
        if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
690
0
            fallbackReason = "bug: failed to confirm FB for blit";
691
0
            break;
692
0
        }
693
0
694
0
        const gfx::IntSize dstSize(mWidth, mHeight);
695
0
        const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
696
0
                                                         : gl::OriginPos::BottomLeft);
697
0
        if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, dstSize, dstOrigin)) {
698
0
            fallbackReason = "likely bug: failed to blit";
699
0
            break;
700
0
        }
701
0
702
0
        // Blitting was successful, so we're done!
703
0
        *out_error = 0;
704
0
        return true;
705
0
    } while (false);
706
0
707
0
    const nsPrintfCString perfMsg("Failed to hit GPU-copy fast-path: %s (src type %u)",
708
0
                                  fallbackReason, uint32_t(mImage->GetFormat()));
709
0
710
0
    if (webgl->mPixelStore_RequireFastPath) {
711
0
        webgl->ErrorInvalidOperation("%s", perfMsg.BeginReading());
712
0
        return false;
713
0
    }
714
0
715
0
    webgl->GeneratePerfWarning("%s Falling back to CPU upload.",
716
0
                               perfMsg.BeginReading());
717
0
718
0
    const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface();
719
0
720
0
    RefPtr<gfx::DataSourceSurface> dataSurf;
721
0
    if (surf) {
722
0
        // WARNING: OSX can lose our MakeCurrent here.
723
0
        dataSurf = surf->GetDataSurface();
724
0
    }
725
0
    if (!dataSurf) {
726
0
        webgl->ErrorOutOfMemory("GetAsSourceSurface or GetDataSurface failed after"
727
0
                                " blit failed for TexUnpackImage.");
728
0
        return false;
729
0
    }
730
0
731
0
    const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
732
0
                                    mSrcAlphaType);
733
0
734
0
    return surfBlob.TexOrSubImage(isSubImage, needsRespec, tex, target, level,
735
0
                                  dui, xOffset, yOffset, zOffset, pi, out_error);
736
0
}
737
738
////////////////////////////////////////////////////////////////////////////////
739
////////////////////////////////////////////////////////////////////////////////
740
// TexUnpackSurface
741
742
TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
743
                                   uint32_t width, uint32_t height, uint32_t depth,
744
                                   gfx::DataSourceSurface* surf,
745
                                   gfxAlphaType srcAlphaType)
746
    : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
747
                    srcAlphaType)
748
    , mSurf(surf)
749
0
{ }
750
751
//////////
752
753
static bool
754
GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat,
755
                 uint8_t* const out_bpp)
756
0
{
757
0
    const auto surfFormat = surf->GetFormat();
758
0
    switch (surfFormat) {
759
0
    case gfx::SurfaceFormat::B8G8R8A8:
760
0
        *out_texelFormat = WebGLTexelFormat::BGRA8;
761
0
        *out_bpp = 4;
762
0
        return true;
763
0
764
0
    case gfx::SurfaceFormat::B8G8R8X8:
765
0
        *out_texelFormat = WebGLTexelFormat::BGRX8;
766
0
        *out_bpp = 4;
767
0
        return true;
768
0
769
0
    case gfx::SurfaceFormat::R8G8B8A8:
770
0
        *out_texelFormat = WebGLTexelFormat::RGBA8;
771
0
        *out_bpp = 4;
772
0
        return true;
773
0
774
0
    case gfx::SurfaceFormat::R8G8B8X8:
775
0
        *out_texelFormat = WebGLTexelFormat::RGBX8;
776
0
        *out_bpp = 4;
777
0
        return true;
778
0
779
0
    case gfx::SurfaceFormat::R5G6B5_UINT16:
780
0
        *out_texelFormat = WebGLTexelFormat::RGB565;
781
0
        *out_bpp = 2;
782
0
        return true;
783
0
784
0
    case gfx::SurfaceFormat::A8:
785
0
        *out_texelFormat = WebGLTexelFormat::A8;
786
0
        *out_bpp = 1;
787
0
        return true;
788
0
789
0
    case gfx::SurfaceFormat::YUV:
790
0
        // Ugh...
791
0
        NS_ERROR("We don't handle uploads from YUV sources yet.");
792
0
        // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
793
0
        // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
794
0
        return false;
795
0
796
0
    default:
797
0
        return false;
798
0
    }
799
0
}
800
801
//////////
802
803
bool
804
TexUnpackSurface::Validate(WebGLContext* webgl, const webgl::PackingInfo& pi)
805
0
{
806
0
    if (!ValidatePIForDOM(webgl, pi))
807
0
        return false;
808
0
809
0
    const auto fullRows = mSurf->GetSize().height;
810
0
    return ValidateUnpackPixels(webgl, fullRows, 0, this);
811
0
}
812
813
bool
814
TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
815
                                WebGLTexture* tex, TexImageTarget target, GLint level,
816
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
817
                                GLint yOffset, GLint zOffset, const webgl::PackingInfo& dstPI,
818
                                GLenum* const out_error) const
819
0
{
820
0
    const auto& webgl = tex->mContext;
821
0
822
0
    ////
823
0
824
0
    const auto rowLength = mSurf->GetSize().width;
825
0
    const auto rowCount = mSurf->GetSize().height;
826
0
827
0
    const auto& dstBPP = webgl::BytesPerPixel(dstPI);
828
0
    const auto dstFormat = FormatForPackingInfo(dstPI);
829
0
830
0
    ////
831
0
832
0
    WebGLTexelFormat srcFormat;
833
0
    uint8_t srcBPP;
834
0
    if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
835
0
        webgl->ErrorImplementationBug("GetFormatForSurf failed for"
836
0
                                      " WebGLTexelFormat::%u.",
837
0
                                      uint32_t(mSurf->GetFormat()));
838
0
        return false;
839
0
    }
840
0
841
0
    gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ);
842
0
    if (!map.IsMapped()) {
843
0
        webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
844
0
        return false;
845
0
    }
846
0
847
0
    const auto& srcBegin = map.GetData();
848
0
    const auto& srcStride = map.GetStride();
849
0
850
0
    ////
851
0
852
0
    const auto srcRowLengthBytes = rowLength * srcBPP;
853
0
854
0
    const uint8_t maxGLAlignment = 8;
855
0
    uint8_t srcAlignment = 1;
856
0
    for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) {
857
0
        const auto strideGuess = RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment);
858
0
        if (strideGuess == srcStride)
859
0
            break;
860
0
    }
861
0
    const uint32_t dstAlignment = (srcAlignment > maxGLAlignment) ? 1 : srcAlignment;
862
0
863
0
    const auto dstRowLengthBytes = rowLength * dstBPP;
864
0
    const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
865
0
866
0
    ////
867
0
868
0
    const uint8_t* dstBegin = srcBegin;
869
0
    UniqueBuffer tempBuffer;
870
0
    if (!ConvertIfNeeded(webgl, rowLength, rowCount, srcFormat, srcBegin,
871
0
                         srcStride, dstFormat, dstStride, &dstBegin, &tempBuffer))
872
0
    {
873
0
        return false;
874
0
    }
875
0
876
0
    ////
877
0
878
0
    const auto& gl = webgl->gl;
879
0
    if (!gl->MakeCurrent()) {
880
0
        *out_error = LOCAL_GL_CONTEXT_LOST;
881
0
        return true;
882
0
    }
883
0
884
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
885
0
    if (webgl->IsWebGL2()) {
886
0
        gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
887
0
    }
888
0
889
0
    *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
890
0
                                 yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
891
0
892
0
    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
893
0
    if (webgl->IsWebGL2()) {
894
0
        gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
895
0
    }
896
0
897
0
    return true;
898
0
}
899
900
} // namespace webgl
901
} // namespace mozilla