michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebGLContext.h" michael@0: #include "WebGLTexelConversions.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace WebGLTexelConversions; michael@0: michael@0: namespace { michael@0: michael@0: /** @class WebGLImageConverter michael@0: * michael@0: * This class is just a helper to implement WebGLContext::ConvertImage below. michael@0: * michael@0: * Design comments: michael@0: * michael@0: * WebGLContext::ConvertImage has to handle hundreds of format conversion paths. michael@0: * It is important to minimize executable code size here. Instead of passing around michael@0: * a large number of function parameters hundreds of times, we create a michael@0: * WebGLImageConverter object once, storing these parameters, and then we call michael@0: * the run() method on it. michael@0: */ michael@0: class WebGLImageConverter michael@0: { michael@0: const size_t mWidth, mHeight; michael@0: const void* const mSrcStart; michael@0: void* const mDstStart; michael@0: const ptrdiff_t mSrcStride, mDstStride; michael@0: bool mAlreadyRun; michael@0: bool mSuccess; michael@0: michael@0: /* michael@0: * Returns sizeof(texel)/sizeof(type). The point is that we will iterate over michael@0: * texels with typed pointers and this value will tell us by how much we need michael@0: * to increment these pointers to advance to the next texel. michael@0: */ michael@0: template michael@0: static size_t NumElementsPerTexelForFormat() { michael@0: switch (Format) { michael@0: case WebGLTexelFormat::R8: michael@0: case WebGLTexelFormat::A8: michael@0: case WebGLTexelFormat::R16F: michael@0: case WebGLTexelFormat::A16F: michael@0: case WebGLTexelFormat::R32F: michael@0: case WebGLTexelFormat::A32F: michael@0: case WebGLTexelFormat::RGBA5551: michael@0: case WebGLTexelFormat::RGBA4444: michael@0: case WebGLTexelFormat::RGB565: michael@0: return 1; michael@0: case WebGLTexelFormat::RA8: michael@0: case WebGLTexelFormat::RA16F: michael@0: case WebGLTexelFormat::RA32F: michael@0: return 2; michael@0: case WebGLTexelFormat::RGB8: michael@0: case WebGLTexelFormat::RGB16F: michael@0: case WebGLTexelFormat::RGB32F: michael@0: return 3; michael@0: case WebGLTexelFormat::RGBA8: michael@0: case WebGLTexelFormat::BGRA8: michael@0: case WebGLTexelFormat::BGRX8: michael@0: case WebGLTexelFormat::RGBA16F: michael@0: case WebGLTexelFormat::RGBA32F: michael@0: return 4; michael@0: default: michael@0: MOZ_ASSERT(false, "Unknown texel format. Coding mistake?"); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * This is the completely format-specific templatized conversion function, michael@0: * that will be instantiated hundreds of times for all different combinations. michael@0: * It is important to avoid generating useless code here. In particular, many michael@0: * instantiations of this function template will never be called, so we try michael@0: * to return immediately in these cases to allow the compiler to avoid generating michael@0: * useless code. michael@0: */ michael@0: template michael@0: void run() michael@0: { michael@0: // check for never-called cases. We early-return to allow the compiler michael@0: // to avoid generating this code. It would be tempting to abort() instead, michael@0: // as returning early does leave the destination surface with uninitialized michael@0: // data, but that would not allow the compiler to avoid generating this code. michael@0: // So instead, we return early, so Success() will return false, and the caller michael@0: // must check that and abort in that case. See WebGLContext::ConvertImage. michael@0: michael@0: if (SrcFormat == DstFormat && michael@0: PremultiplicationOp == WebGLTexelPremultiplicationOp::None) michael@0: { michael@0: // Should have used a fast exit path earlier, rather than entering this function. michael@0: // we explicitly return here to allow the compiler to avoid generating this code michael@0: return; michael@0: } michael@0: michael@0: // Only textures uploaded from DOM elements or ImageData can allow DstFormat != SrcFormat. michael@0: // DOM elements can only give BGRA8, BGRX8, A8, RGB565 formats. See DOMElementToImageSurface. michael@0: // ImageData is always RGBA8. So all other SrcFormat will always satisfy DstFormat==SrcFormat, michael@0: // so we can avoid compiling the code for all the unreachable paths. michael@0: const bool CanSrcFormatComeFromDOMElementOrImageData michael@0: = SrcFormat == WebGLTexelFormat::BGRA8 || michael@0: SrcFormat == WebGLTexelFormat::BGRX8 || michael@0: SrcFormat == WebGLTexelFormat::A8 || michael@0: SrcFormat == WebGLTexelFormat::RGB565 || michael@0: SrcFormat == WebGLTexelFormat::RGBA8; michael@0: if (!CanSrcFormatComeFromDOMElementOrImageData && michael@0: SrcFormat != DstFormat) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: // Likewise, only textures uploaded from DOM elements or ImageData can possibly have to be unpremultiplied. michael@0: if (!CanSrcFormatComeFromDOMElementOrImageData && michael@0: PremultiplicationOp == WebGLTexelPremultiplicationOp::Unpremultiply) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: // there is no point in premultiplication/unpremultiplication michael@0: // in the following cases: michael@0: // - the source format has no alpha michael@0: // - the source format has no color michael@0: // - the destination format has no color michael@0: if (!HasAlpha(SrcFormat) || michael@0: !HasColor(SrcFormat) || michael@0: !HasColor(DstFormat)) michael@0: { michael@0: michael@0: if (PremultiplicationOp != WebGLTexelPremultiplicationOp::None) michael@0: { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // end of early return cases. michael@0: michael@0: MOZ_ASSERT(!mAlreadyRun, "converter should be run only once!"); michael@0: mAlreadyRun = true; michael@0: michael@0: // gather some compile-time meta-data about the formats at hand. michael@0: michael@0: typedef michael@0: typename DataTypeForFormat::Type michael@0: SrcType; michael@0: typedef michael@0: typename DataTypeForFormat::Type michael@0: DstType; michael@0: michael@0: const MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) IntermediateSrcFormat michael@0: = IntermediateFormat::Value; michael@0: const MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) IntermediateDstFormat michael@0: = IntermediateFormat::Value; michael@0: typedef michael@0: typename DataTypeForFormat::Type michael@0: IntermediateSrcType; michael@0: typedef michael@0: typename DataTypeForFormat::Type michael@0: IntermediateDstType; michael@0: michael@0: const size_t NumElementsPerSrcTexel = NumElementsPerTexelForFormat(); michael@0: const size_t NumElementsPerDstTexel = NumElementsPerTexelForFormat(); michael@0: const size_t MaxElementsPerTexel = 4; michael@0: MOZ_ASSERT(NumElementsPerSrcTexel <= MaxElementsPerTexel, "unhandled format"); michael@0: MOZ_ASSERT(NumElementsPerDstTexel <= MaxElementsPerTexel, "unhandled format"); michael@0: michael@0: // we assume that the strides are multiples of the sizeof of respective types. michael@0: // this assumption will allow us to iterate over src and dst images using typed michael@0: // pointers, e.g. uint8_t* or uint16_t* or float*, instead of untyped pointers. michael@0: // So this assumption allows us to write cleaner and safer code, but it might michael@0: // not be true forever and if it eventually becomes wrong, we'll have to revert michael@0: // to always iterating using uint8_t* pointers regardless of the types at hand. michael@0: MOZ_ASSERT(mSrcStride % sizeof(SrcType) == 0 && michael@0: mDstStride % sizeof(DstType) == 0, michael@0: "Unsupported: texture stride is not a multiple of sizeof(type)"); michael@0: const ptrdiff_t srcStrideInElements = mSrcStride / sizeof(SrcType); michael@0: const ptrdiff_t dstStrideInElements = mDstStride / sizeof(DstType); michael@0: michael@0: const SrcType *srcRowStart = static_cast(mSrcStart); michael@0: DstType *dstRowStart = static_cast(mDstStart); michael@0: michael@0: // the loop performing the texture format conversion michael@0: for (size_t i = 0; i < mHeight; ++i) { michael@0: const SrcType *srcRowEnd = srcRowStart + mWidth * NumElementsPerSrcTexel; michael@0: const SrcType *srcPtr = srcRowStart; michael@0: DstType *dstPtr = dstRowStart; michael@0: while (srcPtr != srcRowEnd) { michael@0: // convert a single texel. We proceed in 3 steps: unpack the source texel michael@0: // so the corresponding interchange format (e.g. unpack RGB565 to RGBA8), michael@0: // convert the resulting data type to the destination type (e.g. convert michael@0: // from RGBA8 to RGBA32F), and finally pack the destination texel michael@0: // (e.g. pack RGBA32F to RGB32F). michael@0: IntermediateSrcType unpackedSrc[MaxElementsPerTexel]; michael@0: IntermediateDstType unpackedDst[MaxElementsPerTexel]; michael@0: michael@0: // unpack a src texel to corresponding intermediate src format. michael@0: // for example, unpack RGB565 to RGBA8 michael@0: unpack(srcPtr, unpackedSrc); michael@0: // convert the data type to the destination type, if needed. michael@0: // for example, convert RGBA8 to RGBA32F michael@0: convertType(unpackedSrc, unpackedDst); michael@0: // pack the destination texel. michael@0: // for example, pack RGBA32F to RGB32F michael@0: pack(unpackedDst, dstPtr); michael@0: michael@0: srcPtr += NumElementsPerSrcTexel; michael@0: dstPtr += NumElementsPerDstTexel; michael@0: } michael@0: srcRowStart += srcStrideInElements; michael@0: dstRowStart += dstStrideInElements; michael@0: } michael@0: michael@0: mSuccess = true; michael@0: return; michael@0: } michael@0: michael@0: template michael@0: void run(WebGLTexelPremultiplicationOp premultiplicationOp) michael@0: { michael@0: #define WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(PremultiplicationOp) \ michael@0: case PremultiplicationOp: \ michael@0: return run(); michael@0: michael@0: switch (premultiplicationOp) { michael@0: WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(WebGLTexelPremultiplicationOp::None) michael@0: WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(WebGLTexelPremultiplicationOp::Premultiply) michael@0: WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(WebGLTexelPremultiplicationOp::Unpremultiply) michael@0: default: michael@0: MOZ_ASSERT(false, "unhandled case. Coding mistake?"); michael@0: } michael@0: michael@0: #undef WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP michael@0: } michael@0: michael@0: template michael@0: void run(WebGLTexelFormat dstFormat, michael@0: WebGLTexelPremultiplicationOp premultiplicationOp) michael@0: { michael@0: #define WEBGLIMAGECONVERTER_CASE_DSTFORMAT(DstFormat) \ michael@0: case DstFormat: \ michael@0: return run(premultiplicationOp); michael@0: michael@0: switch (dstFormat) { michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R8) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A8) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R16F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A16F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R32F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A32F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA8) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA16F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA32F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB8) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB565) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB16F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB32F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA8) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA5551) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA4444) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA16F) michael@0: WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA32F) michael@0: default: michael@0: MOZ_ASSERT(false, "unhandled case. Coding mistake?"); michael@0: } michael@0: michael@0: #undef WEBGLIMAGECONVERTER_CASE_DSTFORMAT michael@0: } michael@0: michael@0: public: michael@0: michael@0: void run(WebGLTexelFormat srcFormat, michael@0: WebGLTexelFormat dstFormat, michael@0: WebGLTexelPremultiplicationOp premultiplicationOp) michael@0: { michael@0: #define WEBGLIMAGECONVERTER_CASE_SRCFORMAT(SrcFormat) \ michael@0: case SrcFormat: \ michael@0: return run(dstFormat, premultiplicationOp); michael@0: michael@0: switch (srcFormat) { michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R8) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A8) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R16F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A16F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R32F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A32F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA8) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA16F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA32F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB8) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::BGRX8) // source format only michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB565) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB16F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB32F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA8) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::BGRA8) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA5551) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA4444) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA16F) michael@0: WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA32F) michael@0: default: michael@0: MOZ_ASSERT(false, "unhandled case. Coding mistake?"); michael@0: } michael@0: michael@0: #undef WEBGLIMAGECONVERTER_CASE_SRCFORMAT michael@0: } michael@0: michael@0: WebGLImageConverter(size_t width, size_t height, michael@0: const void* srcStart, void* dstStart, michael@0: ptrdiff_t srcStride, ptrdiff_t dstStride) michael@0: : mWidth(width), mHeight(height), michael@0: mSrcStart(srcStart), mDstStart(dstStart), michael@0: mSrcStride(srcStride), mDstStride(dstStride), michael@0: mAlreadyRun(false), mSuccess(false) michael@0: {} michael@0: michael@0: bool Success() const { michael@0: return mSuccess; michael@0: } michael@0: }; michael@0: michael@0: } // end anonymous namespace michael@0: michael@0: void michael@0: WebGLContext::ConvertImage(size_t width, size_t height, size_t srcStride, size_t dstStride, michael@0: const uint8_t* src, uint8_t *dst, michael@0: WebGLTexelFormat srcFormat, bool srcPremultiplied, michael@0: WebGLTexelFormat dstFormat, bool dstPremultiplied, michael@0: size_t dstTexelSize) michael@0: { michael@0: if (width <= 0 || height <= 0) michael@0: return; michael@0: michael@0: const bool FormatsRequireNoPremultiplicationOp = michael@0: !HasAlpha(srcFormat) || michael@0: !HasColor(srcFormat) || michael@0: !HasColor(dstFormat); michael@0: michael@0: if (srcFormat == dstFormat && michael@0: (FormatsRequireNoPremultiplicationOp || srcPremultiplied == dstPremultiplied)) michael@0: { michael@0: // fast exit path: we just have to memcpy all the rows. michael@0: // michael@0: // The case where absolutely nothing needs to be done is supposed to have michael@0: // been handled earlier (in TexImage2D_base, etc). michael@0: // michael@0: // So the case we're handling here is when even though no format conversion is needed, michael@0: // we still might have to flip vertically and/or to adjust to a different stride. michael@0: michael@0: MOZ_ASSERT(mPixelStoreFlipY || srcStride != dstStride, "Performance trap -- should handle this case earlier, to avoid memcpy"); michael@0: michael@0: size_t row_size = width * dstTexelSize; // doesn't matter, src and dst formats agree michael@0: const uint8_t* ptr = src; michael@0: const uint8_t* src_end = src + height * srcStride; michael@0: michael@0: uint8_t* dst_row = mPixelStoreFlipY michael@0: ? dst + (height-1) * dstStride michael@0: : dst; michael@0: ptrdiff_t dstStrideSigned(dstStride); michael@0: ptrdiff_t dst_delta = mPixelStoreFlipY ? -dstStrideSigned : dstStrideSigned; michael@0: michael@0: while(ptr != src_end) { michael@0: memcpy(dst_row, ptr, row_size); michael@0: ptr += srcStride; michael@0: dst_row += dst_delta; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: uint8_t* dstStart = dst; michael@0: ptrdiff_t signedDstStride = dstStride; michael@0: if (mPixelStoreFlipY) { michael@0: dstStart = dst + (height - 1) * dstStride; michael@0: signedDstStride = -signedDstStride; michael@0: } michael@0: michael@0: WebGLImageConverter converter(width, height, src, dstStart, srcStride, signedDstStride); michael@0: michael@0: const WebGLTexelPremultiplicationOp premultiplicationOp michael@0: = FormatsRequireNoPremultiplicationOp ? WebGLTexelPremultiplicationOp::None michael@0: : (!srcPremultiplied && dstPremultiplied) ? WebGLTexelPremultiplicationOp::Premultiply michael@0: : (srcPremultiplied && !dstPremultiplied) ? WebGLTexelPremultiplicationOp::Unpremultiply michael@0: : WebGLTexelPremultiplicationOp::None; michael@0: michael@0: converter.run(srcFormat, dstFormat, premultiplicationOp); michael@0: michael@0: if (!converter.Success()) { michael@0: // the dst image may be left uninitialized, so we better not try to michael@0: // continue even in release builds. This should never happen anyway, michael@0: // and would be a bug in our code. michael@0: NS_RUNTIMEABORT("programming mistake in WebGL texture conversions"); michael@0: } michael@0: } michael@0: michael@0: } // end namespace mozilla