summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLTexelConversions.cpp
blob: 279d8a1cc251402e25b8df0aaa9d1160eb0dc611 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WebGLContext.h"
#include "WebGLTexelConversions.h"

namespace mozilla {

using namespace WebGLTexelConversions;

namespace {

/** @class WebGLImageConverter
 *
 * This class is just a helper to implement WebGLContext::ConvertImage below.
 *
 * Design comments:
 *
 * WebGLContext::ConvertImage has to handle hundreds of format conversion paths.
 * It is important to minimize executable code size here. Instead of passing
 * around a large number of function parameters hundreds of times, we create a
 * WebGLImageConverter object once, storing these parameters, and then we call
 * the run() method on it.
 */
class WebGLImageConverter {
  const size_t mWidth, mHeight;
  const void* const mSrcStart;
  void* const mDstStart;
  const ptrdiff_t mSrcStride, mDstStride;
  bool mAlreadyRun;
  bool mSuccess;

  /*
   * Returns sizeof(texel)/sizeof(type). The point is that we will iterate over
   * texels with typed pointers and this value will tell us by how much we need
   * to increment these pointers to advance to the next texel.
   */
  template <WebGLTexelFormat Format>
  static size_t NumElementsPerTexelForFormat() {
    switch (Format) {
      case WebGLTexelFormat::A8:
      case WebGLTexelFormat::A16F:
      case WebGLTexelFormat::A32F:
      case WebGLTexelFormat::R8:
      case WebGLTexelFormat::R16F:
      case WebGLTexelFormat::R32F:
      case WebGLTexelFormat::RGB565:
      case WebGLTexelFormat::RGB11F11F10F:
      case WebGLTexelFormat::RGBA4444:
      case WebGLTexelFormat::RGBA5551:
        return 1;
      case WebGLTexelFormat::RA8:
      case WebGLTexelFormat::RA16F:
      case WebGLTexelFormat::RA32F:
      case WebGLTexelFormat::RG8:
      case WebGLTexelFormat::RG16F:
      case WebGLTexelFormat::RG32F:
        return 2;
      case WebGLTexelFormat::RGB8:
      case WebGLTexelFormat::RGB16F:
      case WebGLTexelFormat::RGB32F:
        return 3;
      case WebGLTexelFormat::RGBA8:
      case WebGLTexelFormat::RGBA16F:
      case WebGLTexelFormat::RGBA32F:
      case WebGLTexelFormat::BGRX8:
      case WebGLTexelFormat::BGRA8:
        return 4;
      default:
        MOZ_ASSERT(false, "Unknown texel format. Coding mistake?");
        return 0;
    }
  }

  /*
   * This is the completely format-specific templatized conversion function,
   * that will be instantiated hundreds of times for all different combinations.
   * It is important to avoid generating useless code here. In particular, many
   * instantiations of this function template will never be called, so we try
   * to return immediately in these cases to allow the compiler to avoid
   * generating useless code.
   */
  template <WebGLTexelFormat SrcFormat, WebGLTexelFormat DstFormat,
            WebGLTexelPremultiplicationOp PremultiplicationOp>
  void run() {
    // check for never-called cases. We early-return to allow the compiler
    // to avoid generating this code. It would be tempting to abort() instead,
    // as returning early does leave the destination surface with uninitialized
    // data, but that would not allow the compiler to avoid generating this
    // code. So instead, we return early, so Success() will return false, and
    // the caller must check that and abort in that case. See
    // WebGLContext::ConvertImage.

    if (SrcFormat == DstFormat &&
        PremultiplicationOp == WebGLTexelPremultiplicationOp::None) {
      // Should have used a fast exit path earlier, rather than entering this
      // function. we explicitly return here to allow the compiler to avoid
      // generating this code
      return;
    }

    // Only textures uploaded from DOM elements or ImageData can allow DstFormat
    // != SrcFormat. DOM elements can only give BGRA8, BGRX8, A8, RGB565
    // formats. See DOMElementToImageSurface. ImageData is always RGBA8. So all
    // other SrcFormat will always satisfy DstFormat==SrcFormat, so we can avoid
    // compiling the code for all the unreachable paths.
    const bool CanSrcFormatComeFromDOMElementOrImageData =
        SrcFormat == WebGLTexelFormat::BGRA8 ||
        SrcFormat == WebGLTexelFormat::BGRX8 ||
        SrcFormat == WebGLTexelFormat::A8 ||
        SrcFormat == WebGLTexelFormat::RGB565 ||
        SrcFormat == WebGLTexelFormat::RGBA8;
    if (!CanSrcFormatComeFromDOMElementOrImageData && SrcFormat != DstFormat) {
      return;
    }

    // Likewise, only textures uploaded from DOM elements or ImageData can
    // possibly have to be unpremultiplied.
    if (!CanSrcFormatComeFromDOMElementOrImageData &&
        PremultiplicationOp == WebGLTexelPremultiplicationOp::Unpremultiply) {
      return;
    }

    // there is no point in premultiplication/unpremultiplication
    // in the following cases:
    //  - the source format has no alpha
    //  - the source format has no color
    //  - the destination format has no color
    if (!HasAlpha(SrcFormat) || !HasColor(SrcFormat) || !HasColor(DstFormat)) {
      if (PremultiplicationOp != WebGLTexelPremultiplicationOp::None) {
        return;
      }
    }

    // end of early return cases.

    MOZ_ASSERT(!mAlreadyRun, "converter should be run only once!");
    mAlreadyRun = true;

    // gather some compile-time meta-data about the formats at hand.

    using SrcType = typename DataTypeForFormat<SrcFormat>::Type;
    using DstType = typename DataTypeForFormat<DstFormat>::Type;

    const WebGLTexelFormat IntermediateSrcFormat =
        IntermediateFormat<SrcFormat>::Value;
    const WebGLTexelFormat IntermediateDstFormat =
        IntermediateFormat<DstFormat>::Value;
    using IntermediateSrcType =
        typename DataTypeForFormat<IntermediateSrcFormat>::Type;
    using IntermediateDstType =
        typename DataTypeForFormat<IntermediateDstFormat>::Type;

    const size_t NumElementsPerSrcTexel =
        NumElementsPerTexelForFormat<SrcFormat>();
    const size_t NumElementsPerDstTexel =
        NumElementsPerTexelForFormat<DstFormat>();
    const size_t MaxElementsPerTexel = 4;
    MOZ_ASSERT(NumElementsPerSrcTexel <= MaxElementsPerTexel,
               "unhandled format");
    MOZ_ASSERT(NumElementsPerDstTexel <= MaxElementsPerTexel,
               "unhandled format");

    // we assume that the strides are multiples of the sizeof of respective
    // types. this assumption will allow us to iterate over src and dst images
    // using typed pointers, e.g. uint8_t* or uint16_t* or float*, instead of
    // untyped pointers. So this assumption allows us to write cleaner and safer
    // code, but it might not be true forever and if it eventually becomes
    // wrong, we'll have to revert to always iterating using uint8_t* pointers
    // regardless of the types at hand.
    MOZ_ASSERT(
        mSrcStride % sizeof(SrcType) == 0 && mDstStride % sizeof(DstType) == 0,
        "Unsupported: texture stride is not a multiple of sizeof(type)");
    const ptrdiff_t srcStrideInElements =
        mSrcStride / static_cast<ptrdiff_t>(sizeof(SrcType));
    const ptrdiff_t dstStrideInElements =
        mDstStride / static_cast<ptrdiff_t>(sizeof(DstType));
    // Pop quiz: What's `ptrdiff_t(-16) / sizeof(int32_t)`?
    // Did you guess +4611686018427387900?
    MOZ_ASSERT(bool(srcStrideInElements < 0) == bool(mSrcStride < 0));
    MOZ_ASSERT(bool(dstStrideInElements < 0) == bool(mDstStride < 0));

    const SrcType* srcRowStart = static_cast<const SrcType*>(mSrcStart);
    DstType* dstRowStart = static_cast<DstType*>(mDstStart);

    // the loop performing the texture format conversion
    for (size_t i = 0; i < mHeight; ++i) {
      const SrcType* srcRowEnd = srcRowStart + mWidth * NumElementsPerSrcTexel;
      const SrcType* srcPtr = srcRowStart;
      DstType* dstPtr = dstRowStart;
      while (srcPtr != srcRowEnd) {
        // convert a single texel. We proceed in 3 steps: unpack the source
        // texel so the corresponding interchange format (e.g. unpack RGB565 to
        // RGBA8), convert the resulting data type to the destination type (e.g.
        // convert from RGBA8 to RGBA32F), and finally pack the destination
        // texel (e.g. pack RGBA32F to RGB32F).
        IntermediateSrcType unpackedSrc[MaxElementsPerTexel];
        IntermediateDstType unpackedDst[MaxElementsPerTexel];

        // unpack a src texel to corresponding intermediate src format.
        // for example, unpack RGB565 to RGBA8
        unpack<SrcFormat>(srcPtr, unpackedSrc);
        // convert the data type to the destination type, if needed.
        // for example, convert RGBA8 to RGBA32F
        convertType(unpackedSrc, unpackedDst);
        // pack the destination texel.
        // for example, pack RGBA32F to RGB32F
        pack<DstFormat, PremultiplicationOp>(unpackedDst, dstPtr);

        srcPtr += NumElementsPerSrcTexel;
        dstPtr += NumElementsPerDstTexel;
      }
      srcRowStart += srcStrideInElements;
      dstRowStart += dstStrideInElements;
    }

    mSuccess = true;
  }

  template <WebGLTexelFormat SrcFormat, WebGLTexelFormat DstFormat>
  void run(WebGLTexelPremultiplicationOp premultiplicationOp) {
#define WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(PremultiplicationOp) \
  case PremultiplicationOp:                                               \
    return run<SrcFormat, DstFormat, PremultiplicationOp>();

    switch (premultiplicationOp) {
      WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(
          WebGLTexelPremultiplicationOp::None)
      WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(
          WebGLTexelPremultiplicationOp::Premultiply)
      WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(
          WebGLTexelPremultiplicationOp::Unpremultiply)
      default:
        MOZ_ASSERT(false, "unhandled case. Coding mistake?");
    }

#undef WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP
  }

  template <WebGLTexelFormat SrcFormat>
  void run(WebGLTexelFormat dstFormat,
           WebGLTexelPremultiplicationOp premultiplicationOp) {
#define WEBGLIMAGECONVERTER_CASE_DSTFORMAT(DstFormat) \
  case DstFormat:                                     \
    return run<SrcFormat, DstFormat>(premultiplicationOp);

    switch (dstFormat) {
      // 1-channel formats
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A8)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A16F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A32F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R8)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R16F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R32F)
      // 2-channel formats
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA8)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA16F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA32F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RG8)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RG16F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RG32F)
      // 3-channel formats
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB565)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB8)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB11F11F10F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB16F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB32F)
      // 4-channel formats
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA4444)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA5551)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA8)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA16F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA32F)
      WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::BGRA8)

      default:
        MOZ_ASSERT(false, "unhandled case. Coding mistake?");
    }

#undef WEBGLIMAGECONVERTER_CASE_DSTFORMAT
  }

 public:
  void run(WebGLTexelFormat srcFormat, WebGLTexelFormat dstFormat,
           WebGLTexelPremultiplicationOp premultiplicationOp) {
#define WEBGLIMAGECONVERTER_CASE_SRCFORMAT(SrcFormat) \
  case SrcFormat:                                     \
    return run<SrcFormat>(dstFormat, premultiplicationOp);

    switch (srcFormat) {
      // 1-channel formats
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A8)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A16F)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A32F)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R8)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R16F)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R32F)
      // 2-channel formats
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA8)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA16F)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA32F)
      // 3-channel formats
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB565)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB8)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB16F)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB32F)
      // 4-channel formats
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA4444)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA5551)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA8)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA16F)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA32F)
      // DOM element source formats
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::BGRX8)
      WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::BGRA8)

      default:
        MOZ_ASSERT(false, "unhandled case. Coding mistake?");
    }

#undef WEBGLIMAGECONVERTER_CASE_SRCFORMAT
  }

  WebGLImageConverter(size_t width, size_t height, const void* srcStart,
                      void* dstStart, ptrdiff_t srcStride, ptrdiff_t dstStride)
      : mWidth(width),
        mHeight(height),
        mSrcStart(srcStart),
        mDstStart(dstStart),
        mSrcStride(srcStride),
        mDstStride(dstStride),
        mAlreadyRun(false),
        mSuccess(false) {}

  bool Success() const { return mSuccess; }
};

}  // end anonymous namespace

bool ConvertImage(size_t width, size_t height, const void* srcBegin,
                  size_t srcStride, gl::OriginPos srcOrigin,
                  WebGLTexelFormat srcFormat, bool srcPremultiplied,
                  void* dstBegin, size_t dstStride, gl::OriginPos dstOrigin,
                  WebGLTexelFormat dstFormat, bool dstPremultiplied,
                  bool* const out_wasTrivial) {
  *out_wasTrivial = true;

  if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion ||
      dstFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
    return false;
  }

  if (!width || !height) return true;

  const bool shouldYFlip = (srcOrigin != dstOrigin);

  const bool canSkipPremult =
      (!HasAlpha(srcFormat) || !HasColor(srcFormat) || !HasColor(dstFormat));

  WebGLTexelPremultiplicationOp premultOp;
  if (canSkipPremult) {
    premultOp = WebGLTexelPremultiplicationOp::None;
  } else if (!srcPremultiplied && dstPremultiplied) {
    premultOp = WebGLTexelPremultiplicationOp::Premultiply;
  } else if (srcPremultiplied && !dstPremultiplied) {
    premultOp = WebGLTexelPremultiplicationOp::Unpremultiply;
  } else {
    premultOp = WebGLTexelPremultiplicationOp::None;
  }

  const uint8_t* srcItr = (const uint8_t*)srcBegin;
  const uint8_t* const srcEnd = srcItr + srcStride * height;
  uint8_t* dstItr = (uint8_t*)dstBegin;
  ptrdiff_t dstItrStride = dstStride;
  if (shouldYFlip) {
    dstItr = dstItr + dstStride * (height - 1);
    dstItrStride = -dstItrStride;
  }

  if (srcFormat == dstFormat &&
      premultOp == WebGLTexelPremultiplicationOp::None) {
    // Fast exit path: we just have to memcpy all the rows.

    const auto bytesPerPixel = TexelBytesForFormat(srcFormat);
    const size_t bytesPerRow = bytesPerPixel * width;

    while (srcItr != srcEnd) {
      memcpy(dstItr, srcItr, bytesPerRow);
      srcItr += srcStride;
      dstItr += dstItrStride;
    }
    return true;
  }

  *out_wasTrivial = false;

  WebGLImageConverter converter(width, height, srcItr, dstItr, srcStride,
                                dstItrStride);
  converter.run(srcFormat, dstFormat, premultOp);

  if (!converter.Success()) {
    // the dst image may be left uninitialized, so we better not try to
    // continue even in release builds. This should never happen anyway,
    // and would be a bug in our code.
    MOZ_CRASH("programming mistake in WebGL texture conversions");
  }

  return true;
}

}  // end namespace mozilla