/* 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 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 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::Type SrcType; typedef typename DataTypeForFormat::Type DstType; const WebGLTexelFormat IntermediateSrcFormat = IntermediateFormat::Value; const WebGLTexelFormat IntermediateDstFormat = IntermediateFormat::Value; typedef typename DataTypeForFormat::Type IntermediateSrcType; typedef typename DataTypeForFormat::Type IntermediateDstType; const size_t NumElementsPerSrcTexel = NumElementsPerTexelForFormat(); const size_t NumElementsPerDstTexel = NumElementsPerTexelForFormat(); 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(mSrcStart); DstType* dstRowStart = static_cast(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(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(unpackedDst, dstPtr); srcPtr += NumElementsPerSrcTexel; dstPtr += NumElementsPerDstTexel; } srcRowStart += srcStrideInElements; dstRowStart += dstStrideInElements; } mSuccess = true; return; } template void run(WebGLTexelPremultiplicationOp premultiplicationOp) { #define WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(PremultiplicationOp) \ case PremultiplicationOp: \ return run(); 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 void run(WebGLTexelFormat dstFormat, WebGLTexelPremultiplicationOp premultiplicationOp) { #define WEBGLIMAGECONVERTER_CASE_DSTFORMAT(DstFormat) \ case DstFormat: \ return run(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(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. MOZ_CRASH("programming mistake in WebGL texture conversions"); } return true; } } // end namespace mozilla