1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/canvas/src/WebGLTexelConversions.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,399 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "WebGLContext.h" 1.9 +#include "WebGLTexelConversions.h" 1.10 + 1.11 +namespace mozilla { 1.12 + 1.13 +using namespace WebGLTexelConversions; 1.14 + 1.15 +namespace { 1.16 + 1.17 +/** @class WebGLImageConverter 1.18 + * 1.19 + * This class is just a helper to implement WebGLContext::ConvertImage below. 1.20 + * 1.21 + * Design comments: 1.22 + * 1.23 + * WebGLContext::ConvertImage has to handle hundreds of format conversion paths. 1.24 + * It is important to minimize executable code size here. Instead of passing around 1.25 + * a large number of function parameters hundreds of times, we create a 1.26 + * WebGLImageConverter object once, storing these parameters, and then we call 1.27 + * the run() method on it. 1.28 + */ 1.29 +class WebGLImageConverter 1.30 +{ 1.31 + const size_t mWidth, mHeight; 1.32 + const void* const mSrcStart; 1.33 + void* const mDstStart; 1.34 + const ptrdiff_t mSrcStride, mDstStride; 1.35 + bool mAlreadyRun; 1.36 + bool mSuccess; 1.37 + 1.38 + /* 1.39 + * Returns sizeof(texel)/sizeof(type). The point is that we will iterate over 1.40 + * texels with typed pointers and this value will tell us by how much we need 1.41 + * to increment these pointers to advance to the next texel. 1.42 + */ 1.43 + template<MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) Format> 1.44 + static size_t NumElementsPerTexelForFormat() { 1.45 + switch (Format) { 1.46 + case WebGLTexelFormat::R8: 1.47 + case WebGLTexelFormat::A8: 1.48 + case WebGLTexelFormat::R16F: 1.49 + case WebGLTexelFormat::A16F: 1.50 + case WebGLTexelFormat::R32F: 1.51 + case WebGLTexelFormat::A32F: 1.52 + case WebGLTexelFormat::RGBA5551: 1.53 + case WebGLTexelFormat::RGBA4444: 1.54 + case WebGLTexelFormat::RGB565: 1.55 + return 1; 1.56 + case WebGLTexelFormat::RA8: 1.57 + case WebGLTexelFormat::RA16F: 1.58 + case WebGLTexelFormat::RA32F: 1.59 + return 2; 1.60 + case WebGLTexelFormat::RGB8: 1.61 + case WebGLTexelFormat::RGB16F: 1.62 + case WebGLTexelFormat::RGB32F: 1.63 + return 3; 1.64 + case WebGLTexelFormat::RGBA8: 1.65 + case WebGLTexelFormat::BGRA8: 1.66 + case WebGLTexelFormat::BGRX8: 1.67 + case WebGLTexelFormat::RGBA16F: 1.68 + case WebGLTexelFormat::RGBA32F: 1.69 + return 4; 1.70 + default: 1.71 + MOZ_ASSERT(false, "Unknown texel format. Coding mistake?"); 1.72 + return 0; 1.73 + } 1.74 + } 1.75 + 1.76 + /* 1.77 + * This is the completely format-specific templatized conversion function, 1.78 + * that will be instantiated hundreds of times for all different combinations. 1.79 + * It is important to avoid generating useless code here. In particular, many 1.80 + * instantiations of this function template will never be called, so we try 1.81 + * to return immediately in these cases to allow the compiler to avoid generating 1.82 + * useless code. 1.83 + */ 1.84 + template<MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) SrcFormat, 1.85 + MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) DstFormat, 1.86 + MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelPremultiplicationOp) PremultiplicationOp> 1.87 + void run() 1.88 + { 1.89 + // check for never-called cases. We early-return to allow the compiler 1.90 + // to avoid generating this code. It would be tempting to abort() instead, 1.91 + // as returning early does leave the destination surface with uninitialized 1.92 + // data, but that would not allow the compiler to avoid generating this code. 1.93 + // So instead, we return early, so Success() will return false, and the caller 1.94 + // must check that and abort in that case. See WebGLContext::ConvertImage. 1.95 + 1.96 + if (SrcFormat == DstFormat && 1.97 + PremultiplicationOp == WebGLTexelPremultiplicationOp::None) 1.98 + { 1.99 + // Should have used a fast exit path earlier, rather than entering this function. 1.100 + // we explicitly return here to allow the compiler to avoid generating this code 1.101 + return; 1.102 + } 1.103 + 1.104 + // Only textures uploaded from DOM elements or ImageData can allow DstFormat != SrcFormat. 1.105 + // DOM elements can only give BGRA8, BGRX8, A8, RGB565 formats. See DOMElementToImageSurface. 1.106 + // ImageData is always RGBA8. So all other SrcFormat will always satisfy DstFormat==SrcFormat, 1.107 + // so we can avoid compiling the code for all the unreachable paths. 1.108 + const bool CanSrcFormatComeFromDOMElementOrImageData 1.109 + = SrcFormat == WebGLTexelFormat::BGRA8 || 1.110 + SrcFormat == WebGLTexelFormat::BGRX8 || 1.111 + SrcFormat == WebGLTexelFormat::A8 || 1.112 + SrcFormat == WebGLTexelFormat::RGB565 || 1.113 + SrcFormat == WebGLTexelFormat::RGBA8; 1.114 + if (!CanSrcFormatComeFromDOMElementOrImageData && 1.115 + SrcFormat != DstFormat) 1.116 + { 1.117 + return; 1.118 + } 1.119 + 1.120 + // Likewise, only textures uploaded from DOM elements or ImageData can possibly have to be unpremultiplied. 1.121 + if (!CanSrcFormatComeFromDOMElementOrImageData && 1.122 + PremultiplicationOp == WebGLTexelPremultiplicationOp::Unpremultiply) 1.123 + { 1.124 + return; 1.125 + } 1.126 + 1.127 + // there is no point in premultiplication/unpremultiplication 1.128 + // in the following cases: 1.129 + // - the source format has no alpha 1.130 + // - the source format has no color 1.131 + // - the destination format has no color 1.132 + if (!HasAlpha(SrcFormat) || 1.133 + !HasColor(SrcFormat) || 1.134 + !HasColor(DstFormat)) 1.135 + { 1.136 + 1.137 + if (PremultiplicationOp != WebGLTexelPremultiplicationOp::None) 1.138 + { 1.139 + return; 1.140 + } 1.141 + } 1.142 + 1.143 + // end of early return cases. 1.144 + 1.145 + MOZ_ASSERT(!mAlreadyRun, "converter should be run only once!"); 1.146 + mAlreadyRun = true; 1.147 + 1.148 + // gather some compile-time meta-data about the formats at hand. 1.149 + 1.150 + typedef 1.151 + typename DataTypeForFormat<SrcFormat>::Type 1.152 + SrcType; 1.153 + typedef 1.154 + typename DataTypeForFormat<DstFormat>::Type 1.155 + DstType; 1.156 + 1.157 + const MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) IntermediateSrcFormat 1.158 + = IntermediateFormat<SrcFormat>::Value; 1.159 + const MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) IntermediateDstFormat 1.160 + = IntermediateFormat<DstFormat>::Value; 1.161 + typedef 1.162 + typename DataTypeForFormat<IntermediateSrcFormat>::Type 1.163 + IntermediateSrcType; 1.164 + typedef 1.165 + typename DataTypeForFormat<IntermediateDstFormat>::Type 1.166 + IntermediateDstType; 1.167 + 1.168 + const size_t NumElementsPerSrcTexel = NumElementsPerTexelForFormat<SrcFormat>(); 1.169 + const size_t NumElementsPerDstTexel = NumElementsPerTexelForFormat<DstFormat>(); 1.170 + const size_t MaxElementsPerTexel = 4; 1.171 + MOZ_ASSERT(NumElementsPerSrcTexel <= MaxElementsPerTexel, "unhandled format"); 1.172 + MOZ_ASSERT(NumElementsPerDstTexel <= MaxElementsPerTexel, "unhandled format"); 1.173 + 1.174 + // we assume that the strides are multiples of the sizeof of respective types. 1.175 + // this assumption will allow us to iterate over src and dst images using typed 1.176 + // pointers, e.g. uint8_t* or uint16_t* or float*, instead of untyped pointers. 1.177 + // So this assumption allows us to write cleaner and safer code, but it might 1.178 + // not be true forever and if it eventually becomes wrong, we'll have to revert 1.179 + // to always iterating using uint8_t* pointers regardless of the types at hand. 1.180 + MOZ_ASSERT(mSrcStride % sizeof(SrcType) == 0 && 1.181 + mDstStride % sizeof(DstType) == 0, 1.182 + "Unsupported: texture stride is not a multiple of sizeof(type)"); 1.183 + const ptrdiff_t srcStrideInElements = mSrcStride / sizeof(SrcType); 1.184 + const ptrdiff_t dstStrideInElements = mDstStride / sizeof(DstType); 1.185 + 1.186 + const SrcType *srcRowStart = static_cast<const SrcType*>(mSrcStart); 1.187 + DstType *dstRowStart = static_cast<DstType*>(mDstStart); 1.188 + 1.189 + // the loop performing the texture format conversion 1.190 + for (size_t i = 0; i < mHeight; ++i) { 1.191 + const SrcType *srcRowEnd = srcRowStart + mWidth * NumElementsPerSrcTexel; 1.192 + const SrcType *srcPtr = srcRowStart; 1.193 + DstType *dstPtr = dstRowStart; 1.194 + while (srcPtr != srcRowEnd) { 1.195 + // convert a single texel. We proceed in 3 steps: unpack the source texel 1.196 + // so the corresponding interchange format (e.g. unpack RGB565 to RGBA8), 1.197 + // convert the resulting data type to the destination type (e.g. convert 1.198 + // from RGBA8 to RGBA32F), and finally pack the destination texel 1.199 + // (e.g. pack RGBA32F to RGB32F). 1.200 + IntermediateSrcType unpackedSrc[MaxElementsPerTexel]; 1.201 + IntermediateDstType unpackedDst[MaxElementsPerTexel]; 1.202 + 1.203 + // unpack a src texel to corresponding intermediate src format. 1.204 + // for example, unpack RGB565 to RGBA8 1.205 + unpack<SrcFormat>(srcPtr, unpackedSrc); 1.206 + // convert the data type to the destination type, if needed. 1.207 + // for example, convert RGBA8 to RGBA32F 1.208 + convertType(unpackedSrc, unpackedDst); 1.209 + // pack the destination texel. 1.210 + // for example, pack RGBA32F to RGB32F 1.211 + pack<DstFormat, PremultiplicationOp>(unpackedDst, dstPtr); 1.212 + 1.213 + srcPtr += NumElementsPerSrcTexel; 1.214 + dstPtr += NumElementsPerDstTexel; 1.215 + } 1.216 + srcRowStart += srcStrideInElements; 1.217 + dstRowStart += dstStrideInElements; 1.218 + } 1.219 + 1.220 + mSuccess = true; 1.221 + return; 1.222 + } 1.223 + 1.224 + template<MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) SrcFormat, 1.225 + MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) DstFormat> 1.226 + void run(WebGLTexelPremultiplicationOp premultiplicationOp) 1.227 + { 1.228 + #define WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(PremultiplicationOp) \ 1.229 + case PremultiplicationOp: \ 1.230 + return run<SrcFormat, DstFormat, PremultiplicationOp>(); 1.231 + 1.232 + switch (premultiplicationOp) { 1.233 + WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(WebGLTexelPremultiplicationOp::None) 1.234 + WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(WebGLTexelPremultiplicationOp::Premultiply) 1.235 + WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP(WebGLTexelPremultiplicationOp::Unpremultiply) 1.236 + default: 1.237 + MOZ_ASSERT(false, "unhandled case. Coding mistake?"); 1.238 + } 1.239 + 1.240 + #undef WEBGLIMAGECONVERTER_CASE_PREMULTIPLICATIONOP 1.241 + } 1.242 + 1.243 + template<MOZ_ENUM_CLASS_ENUM_TYPE(WebGLTexelFormat) SrcFormat> 1.244 + void run(WebGLTexelFormat dstFormat, 1.245 + WebGLTexelPremultiplicationOp premultiplicationOp) 1.246 + { 1.247 + #define WEBGLIMAGECONVERTER_CASE_DSTFORMAT(DstFormat) \ 1.248 + case DstFormat: \ 1.249 + return run<SrcFormat, DstFormat>(premultiplicationOp); 1.250 + 1.251 + switch (dstFormat) { 1.252 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R8) 1.253 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A8) 1.254 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R16F) 1.255 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A16F) 1.256 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::R32F) 1.257 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::A32F) 1.258 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA8) 1.259 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA16F) 1.260 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RA32F) 1.261 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB8) 1.262 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB565) 1.263 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB16F) 1.264 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGB32F) 1.265 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA8) 1.266 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA5551) 1.267 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA4444) 1.268 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA16F) 1.269 + WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA32F) 1.270 + default: 1.271 + MOZ_ASSERT(false, "unhandled case. Coding mistake?"); 1.272 + } 1.273 + 1.274 + #undef WEBGLIMAGECONVERTER_CASE_DSTFORMAT 1.275 + } 1.276 + 1.277 +public: 1.278 + 1.279 + void run(WebGLTexelFormat srcFormat, 1.280 + WebGLTexelFormat dstFormat, 1.281 + WebGLTexelPremultiplicationOp premultiplicationOp) 1.282 + { 1.283 + #define WEBGLIMAGECONVERTER_CASE_SRCFORMAT(SrcFormat) \ 1.284 + case SrcFormat: \ 1.285 + return run<SrcFormat>(dstFormat, premultiplicationOp); 1.286 + 1.287 + switch (srcFormat) { 1.288 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R8) 1.289 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A8) 1.290 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R16F) 1.291 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A16F) 1.292 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::R32F) 1.293 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::A32F) 1.294 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA8) 1.295 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA16F) 1.296 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RA32F) 1.297 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB8) 1.298 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::BGRX8) // source format only 1.299 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB565) 1.300 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB16F) 1.301 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGB32F) 1.302 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA8) 1.303 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::BGRA8) 1.304 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA5551) 1.305 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA4444) 1.306 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA16F) 1.307 + WEBGLIMAGECONVERTER_CASE_SRCFORMAT(WebGLTexelFormat::RGBA32F) 1.308 + default: 1.309 + MOZ_ASSERT(false, "unhandled case. Coding mistake?"); 1.310 + } 1.311 + 1.312 + #undef WEBGLIMAGECONVERTER_CASE_SRCFORMAT 1.313 + } 1.314 + 1.315 + WebGLImageConverter(size_t width, size_t height, 1.316 + const void* srcStart, void* dstStart, 1.317 + ptrdiff_t srcStride, ptrdiff_t dstStride) 1.318 + : mWidth(width), mHeight(height), 1.319 + mSrcStart(srcStart), mDstStart(dstStart), 1.320 + mSrcStride(srcStride), mDstStride(dstStride), 1.321 + mAlreadyRun(false), mSuccess(false) 1.322 + {} 1.323 + 1.324 + bool Success() const { 1.325 + return mSuccess; 1.326 + } 1.327 +}; 1.328 + 1.329 +} // end anonymous namespace 1.330 + 1.331 +void 1.332 +WebGLContext::ConvertImage(size_t width, size_t height, size_t srcStride, size_t dstStride, 1.333 + const uint8_t* src, uint8_t *dst, 1.334 + WebGLTexelFormat srcFormat, bool srcPremultiplied, 1.335 + WebGLTexelFormat dstFormat, bool dstPremultiplied, 1.336 + size_t dstTexelSize) 1.337 +{ 1.338 + if (width <= 0 || height <= 0) 1.339 + return; 1.340 + 1.341 + const bool FormatsRequireNoPremultiplicationOp = 1.342 + !HasAlpha(srcFormat) || 1.343 + !HasColor(srcFormat) || 1.344 + !HasColor(dstFormat); 1.345 + 1.346 + if (srcFormat == dstFormat && 1.347 + (FormatsRequireNoPremultiplicationOp || srcPremultiplied == dstPremultiplied)) 1.348 + { 1.349 + // fast exit path: we just have to memcpy all the rows. 1.350 + // 1.351 + // The case where absolutely nothing needs to be done is supposed to have 1.352 + // been handled earlier (in TexImage2D_base, etc). 1.353 + // 1.354 + // So the case we're handling here is when even though no format conversion is needed, 1.355 + // we still might have to flip vertically and/or to adjust to a different stride. 1.356 + 1.357 + MOZ_ASSERT(mPixelStoreFlipY || srcStride != dstStride, "Performance trap -- should handle this case earlier, to avoid memcpy"); 1.358 + 1.359 + size_t row_size = width * dstTexelSize; // doesn't matter, src and dst formats agree 1.360 + const uint8_t* ptr = src; 1.361 + const uint8_t* src_end = src + height * srcStride; 1.362 + 1.363 + uint8_t* dst_row = mPixelStoreFlipY 1.364 + ? dst + (height-1) * dstStride 1.365 + : dst; 1.366 + ptrdiff_t dstStrideSigned(dstStride); 1.367 + ptrdiff_t dst_delta = mPixelStoreFlipY ? -dstStrideSigned : dstStrideSigned; 1.368 + 1.369 + while(ptr != src_end) { 1.370 + memcpy(dst_row, ptr, row_size); 1.371 + ptr += srcStride; 1.372 + dst_row += dst_delta; 1.373 + } 1.374 + return; 1.375 + } 1.376 + 1.377 + uint8_t* dstStart = dst; 1.378 + ptrdiff_t signedDstStride = dstStride; 1.379 + if (mPixelStoreFlipY) { 1.380 + dstStart = dst + (height - 1) * dstStride; 1.381 + signedDstStride = -signedDstStride; 1.382 + } 1.383 + 1.384 + WebGLImageConverter converter(width, height, src, dstStart, srcStride, signedDstStride); 1.385 + 1.386 + const WebGLTexelPremultiplicationOp premultiplicationOp 1.387 + = FormatsRequireNoPremultiplicationOp ? WebGLTexelPremultiplicationOp::None 1.388 + : (!srcPremultiplied && dstPremultiplied) ? WebGLTexelPremultiplicationOp::Premultiply 1.389 + : (srcPremultiplied && !dstPremultiplied) ? WebGLTexelPremultiplicationOp::Unpremultiply 1.390 + : WebGLTexelPremultiplicationOp::None; 1.391 + 1.392 + converter.run(srcFormat, dstFormat, premultiplicationOp); 1.393 + 1.394 + if (!converter.Success()) { 1.395 + // the dst image may be left uninitialized, so we better not try to 1.396 + // continue even in release builds. This should never happen anyway, 1.397 + // and would be a bug in our code. 1.398 + NS_RUNTIMEABORT("programming mistake in WebGL texture conversions"); 1.399 + } 1.400 +} 1.401 + 1.402 +} // end namespace mozilla