summaryrefslogtreecommitdiff
path: root/dom/canvas/WebGLTexelConversions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/WebGLTexelConversions.cpp')
-rw-r--r--dom/canvas/WebGLTexelConversions.cpp435
1 files changed, 435 insertions, 0 deletions
diff --git a/dom/canvas/WebGLTexelConversions.cpp b/dom/canvas/WebGLTexelConversions.cpp
new file mode 100644
index 0000000000..f6ffe0cae4
--- /dev/null
+++ b/dom/canvas/WebGLTexelConversions.cpp
@@ -0,0 +1,435 @@
+/* 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.
+
+ typedef
+ typename DataTypeForFormat<SrcFormat>::Type
+ SrcType;
+ typedef
+ typename DataTypeForFormat<DstFormat>::Type
+ DstType;
+
+ const WebGLTexelFormat IntermediateSrcFormat
+ = IntermediateFormat<SrcFormat>::Value;
+ const WebGLTexelFormat IntermediateDstFormat
+ = IntermediateFormat<DstFormat>::Value;
+ typedef
+ typename DataTypeForFormat<IntermediateSrcFormat>::Type
+ IntermediateSrcType;
+ typedef
+ typename DataTypeForFormat<IntermediateDstFormat>::Type
+ IntermediateDstType;
+
+ 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 / sizeof(SrcType);
+ const ptrdiff_t dstStrideInElements = mDstStride / sizeof(DstType);
+
+ 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;
+ return;
+ }
+
+ 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)
+
+ 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.
+ //
+ // The case where absolutely nothing needs to be done is supposed to have
+ // been handled earlier (in TexImage2D_base, etc).
+ //
+ // So the case we're handling here is when even though no format conversion is
+ // needed, we still might have to flip vertically and/or to adjust to a different
+ // stride.
+
+ // We ignore canSkipPremult for this perf trap, since it's an avoidable perf cliff
+ // under the WebGL API user's control.
+ MOZ_ASSERT((srcPremultiplied != dstPremultiplied ||
+ shouldYFlip ||
+ srcStride != dstStride),
+ "Performance trap -- should handle this case earlier to avoid memcpy");
+
+ 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.
+ NS_RUNTIMEABORT("programming mistake in WebGL texture conversions");
+ }
+
+ return true;
+}
+
+} // end namespace mozilla