From b75b514a746e0d88a46556d1c50bc7e3f78d6acb Mon Sep 17 00:00:00 2001 From: roytam1 Date: Mon, 16 May 2022 21:07:53 +0800 Subject: [PATCH 1/4] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1194059 (Part 1) - Ensure that metadata decode progress is always delivered atomically. r=tn (07f0441600) - Bug 1191090 - Use the normal PNG decoder for PNG metadata decodes. r=tn (ce3fe1be5f) - Bug 1191114 (Part 1) - Always detect HAS_TRANSPARENCY during the metadata decode. r=tn (3841132932) - Bug 1191114 (Part 2) - Add support for creating an anonymous metadata decoder, for use in tests. r=tn (2cdcc0c278) - Bug 1191114 (Part 3) - Add flags to image test cases. r=tn (4a6f5a5230) - Bug 1191114 (Part 4) - Add tests for metadata decoding, including that we always deliver HAS_TRANSPARENCY during the metadata decode. r=tn (b9c5d1cd4a) - Bug 1126330 - Remove the check for non-looping animations. r=seth (828dabba24) - Bug 1194059 (Part 2) - Always detect IS_ANIMATED during the metadatadecode. r=tn (0ba5bf38f1) - Bug 1194059 (Part 3) - Ensure the nsIInputStream LoadImage() returns is always buffered. r=tn (ed2b02205b) - Bug 1194059 (Part 4) - Add tests that we detect IS_ANIMATED during the metadata decode. r=tn (298f14a7c9) - Bug 1188705 (part 1) - Remove gfxASurface::GetMemoryLocation(). r=seth. (1f0da73a08) - Bug 1188705 (part 2) - Remove unused SizeOfDecodedWithComputedFallbackIfHeap declaration. r=seth. (3356dbed06) - Bug 1188705 (part 3) - Simplify imgFrame::SizeOfExcludingThis(). r=seth. (563262a834) - Bug 1155252 - Don't allocate X11TextureClients bigger than xlib's maximum surface size. r=jrmuizel (3f11590667) - Bug 1143994 - Fix some -Wunreachable-code and -Wswitch warnings in imagelib. r=seth (008becc7e2) - Bug 1060609 (Part 1) - Disable downscale-during-decode when HQ scaling is disabled. r=tn (6da77e3cad) - Bug 1187569 - PNGs getting stuck in a pixelated state. r=seth (da305ef99c) - Bug 1194900 - Stop deciding when to send invalidations in nsPNGDecoder and let Decoder handle it. r=tn (50fa14a984) - Bug 1151694 - Part 1 - Move CommonAnimationManager::sLayerAnimationInfo into LayerAnimationInfo.(cpp|h). r=bbirtles (9f93e0d569) - Bug 1151694 - Part 2 - imgTools should be inside mozilla::image namespace. r=bbirtles (8dfc3f2e4b) - Bug 1196066 (Part 1) - Fix bad directory entries in two of our ICO reftests. r=tn (9e4c70d2b4) - Bug 1196065 - Add sanity tests for image decoders. r=tn (557b9131cb) - Bug 1194912 (Part 1) - Add CopyOnWrite to support automatic copy-on-write for recursive writes to data structures. r=tn (b081a50716) - Bug 1196066 (Part 2) - Add a streaming lexing framework to ImageLib. r=tn (59eb634ea5) - Bug 1196476 - Replace ProgressTracker::FirstObserverIs() with a simpler mechanism on imgRequest. r=tn (db9ecc65ef) - missing part of Bug 1139225 (Part 2) - Dispatch OnImageAvailable to the main thread manually in imgRequest. r=tn (e7b22db614) - Bug 1194912 (Part 2) - Store ProgressTracker observers in a copy-on-write hash table, and dispatch notifications to them using a template. r=tn (5efd7b38b3) - Bug 1180225. Make convolver more like upstream. r=seth (18e3c168fc) - Bug 1149318 - Fix the calling convention on SkGetUserDefaultLocaleNameProc. r=eihrul (7b750d4e4e) - Bug 1210493 - enlarge stroke bounds by line width when doing a quick-reject in SkDraw::drawRect. r=jmuizelaar (e8b5d0fe2d) - Bug 1188206 - Fix more constructors in gfx; r=jrmuizel (944ea9938c) --- gfx/2d/RecordedEvent.h | 2 +- gfx/2d/convolver.cpp | 92 +++-- gfx/2d/convolverSSE2.cpp | 77 ++-- gfx/2d/convolverSSE2.h | 4 +- gfx/gl/HeapCopyOfStackArray.h | 4 +- gfx/layers/basic/TextureClientX11.cpp | 4 +- gfx/skia/skia/src/core/SkDraw.cpp | 26 +- gfx/skia/skia/src/utils/win/SkDWrite.h | 2 +- gfx/thebes/gfxASurface.cpp | 6 - gfx/thebes/gfxASurface.h | 6 - gfx/thebes/gfxTypes.h | 11 - gfx/thebes/gfxWindowsSurface.cpp | 6 - gfx/thebes/gfxWindowsSurface.h | 4 - gfx/thebes/gfxXlibSurface.cpp | 11 - gfx/thebes/gfxXlibSurface.h | 10 +- image/CopyOnWrite.h | 250 ++++++++++++ image/DecodePool.cpp | 5 +- image/Decoder.cpp | 38 +- image/Decoder.h | 37 +- image/DecoderFactory.cpp | 66 +++- image/DecoderFactory.h | 64 +++- image/Downscaler.cpp | 6 +- image/FrameAnimator.cpp | 26 +- image/FrameAnimator.h | 10 + image/Image.h | 2 +- image/ImageMetadata.cpp | 44 --- image/ImageMetadata.h | 42 ++- image/ProgressTracker.cpp | 178 +++++---- image/ProgressTracker.h | 61 +-- image/RasterImage.cpp | 177 +++++---- image/RasterImage.h | 33 +- image/SourceBuffer.cpp | 4 - image/StreamingLexer.h | 355 ++++++++++++++++++ image/SurfaceCache.cpp | 9 +- image/SurfaceCache.h | 2 +- image/decoders/GIF2.h | 2 - image/decoders/nsBMPDecoder.cpp | 9 + image/decoders/nsGIFDecoder2.cpp | 79 ++-- image/decoders/nsGIFDecoder2.h | 1 + image/decoders/nsICODecoder.cpp | 8 +- image/decoders/nsJPEGDecoder.cpp | 7 - image/decoders/nsPNGDecoder.cpp | 209 +++++------ image/decoders/nsPNGDecoder.h | 3 + image/imgFrame.cpp | 42 +-- image/imgFrame.h | 4 +- image/imgRequest.cpp | 10 +- image/imgRequest.h | 4 + image/imgTools.cpp | 7 +- image/imgTools.h | 7 + image/moz.build | 1 - image/test/gtest/Common.cpp | 74 +++- image/test/gtest/Common.h | 24 +- image/test/gtest/TestCopyOnWrite.cpp | 235 ++++++++++++ image/test/gtest/TestDecodeToSurface.cpp | 3 +- image/test/gtest/TestDecoders.cpp | 249 ++++++++++++ image/test/gtest/TestMetadata.cpp | 254 +++++++++++++ image/test/gtest/TestStreamingLexer.cpp | 266 +++++++++++++ image/test/gtest/first-frame-padding.gif | 0 image/test/gtest/moz.build | 11 + image/test/gtest/no-frame-delay.gif | Bin 0 -> 317 bytes image/test/gtest/rle4.bmp | Bin 0 -> 3686 bytes image/test/gtest/rle8.bmp | Bin 0 -> 1288 bytes image/test/gtest/transparent.bmp | Bin 0 -> 4234 bytes image/test/gtest/transparent.gif | Bin 0 -> 355 bytes image/test/gtest/transparent.png | Bin 0 -> 419 bytes .../test/mochitest/test_has_transparency.html | 6 +- .../reftest/ico/ico-png/ico-size-1x1-png.ico | Bin 92 -> 92 bytes .../ico/ico-png/ico-size-256x256-png.ico | Bin 5934 -> 5934 bytes layout/base/RestyleManager.cpp | 11 +- layout/build/nsLayoutStatics.cpp | 3 +- layout/style/AnimationCommon.cpp | 66 +--- layout/style/AnimationCommon.h | 16 - layout/style/LayerAnimationInfo.cpp | 53 +++ layout/style/LayerAnimationInfo.h | 33 ++ layout/style/moz.build | 2 + 75 files changed, 2601 insertions(+), 772 deletions(-) create mode 100644 image/CopyOnWrite.h delete mode 100644 image/ImageMetadata.cpp create mode 100644 image/StreamingLexer.h create mode 100644 image/test/gtest/TestCopyOnWrite.cpp create mode 100644 image/test/gtest/TestDecoders.cpp create mode 100644 image/test/gtest/TestMetadata.cpp create mode 100644 image/test/gtest/TestStreamingLexer.cpp create mode 100644 image/test/gtest/first-frame-padding.gif create mode 100644 image/test/gtest/no-frame-delay.gif create mode 100644 image/test/gtest/rle4.bmp create mode 100644 image/test/gtest/rle8.bmp create mode 100644 image/test/gtest/transparent.bmp create mode 100644 image/test/gtest/transparent.gif create mode 100644 image/test/gtest/transparent.png create mode 100644 layout/style/LayerAnimationInfo.cpp create mode 100644 layout/style/LayerAnimationInfo.h diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h index 5412478d2c..242bff632a 100644 --- a/gfx/2d/RecordedEvent.h +++ b/gfx/2d/RecordedEvent.h @@ -36,7 +36,7 @@ struct ReferencePtr {} template - ReferencePtr(const RefPtr& aPtr) + MOZ_IMPLICIT ReferencePtr(const RefPtr& aPtr) : mLongPtr(uint64_t(aPtr.get())) {} diff --git a/gfx/2d/convolver.cpp b/gfx/2d/convolver.cpp index b02552d052..b4a23133f3 100644 --- a/gfx/2d/convolver.cpp +++ b/gfx/2d/convolver.cpp @@ -175,11 +175,11 @@ class CircularRowBuffer { // |src_data| and continues for the [begin, end) of the filter. template void ConvolveHorizontally(const unsigned char* src_data, - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row) { + int num_values = filter.num_values(); // Loop over each pixel on this row in the output image. - for (int out_x = begin; out_x < end; out_x++) { + for (int out_x = 0; out_x < num_values; out_x++) { // Get the filter that determines the current output pixel. int filter_offset, filter_length; const ConvolutionFilter1D::Fixed* filter_values = @@ -220,17 +220,18 @@ void ConvolveHorizontally(const unsigned char* src_data, // Does vertical convolution to produce one output row. The filter values and // length are given in the first two parameters. These are applied to each // of the rows pointed to in the |source_data_rows| array, with each row -// being |end - begin| wide. +// being |pixel_width| wide. // -// The output must have room for |(end - begin) * 4| bytes. +// The output must have room for |pixel_width * 4| bytes. template void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, unsigned char* out_row) { + int pixel_width, + unsigned char* out_row) { // We go through each column in the output and do a vertical convolution, // generating one output pixel each time. - for (int out_x = begin; out_x < end; out_x++) { + for (int out_x = 0; out_x < pixel_width; out_x++) { // Compute the number of bytes over in each row that the current column // we're convolving starts at. The pixel will cover the next 4 bytes. int byte_offset = out_x * 4; @@ -288,28 +289,29 @@ void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int width, unsigned char* out_row, + int pixel_width, unsigned char* out_row, bool has_alpha, bool use_simd) { - int processed = 0; #if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) // If the binary was not built with SSE2 support, we had to fallback to C version. - int simd_width = width & ~3; - if (use_simd && simd_width) { + if (use_simd) { ConvolveVertically_SIMD(filter_values, filter_length, - source_data_rows, 0, simd_width, + source_data_rows, + pixel_width, out_row, has_alpha); - processed = simd_width; - } + } else #endif - - if (width > processed) { + { if (has_alpha) { - ConvolveVertically(filter_values, filter_length, source_data_rows, - processed, width, out_row); + ConvolveVertically(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row); } else { - ConvolveVertically(filter_values, filter_length, source_data_rows, - processed, width, out_row); + ConvolveVertically(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row); } } } @@ -326,16 +328,16 @@ void ConvolveHorizontally(const unsigned char* src_data, // SIMD implementation works with 4 pixels at a time. // Therefore we process as much as we can using SSE and then use // C implementation for leftovers - ConvolveHorizontally_SSE2(src_data, 0, simd_width, filter, out_row); + ConvolveHorizontally_SSE2(src_data, filter, out_row); processed = simd_width; } #endif if (width > processed) { if (has_alpha) { - ConvolveHorizontally(src_data, processed, width, filter, out_row); + ConvolveHorizontally(src_data, filter, out_row); } else { - ConvolveHorizontally(src_data, processed, width, filter, out_row); + ConvolveHorizontally(src_data, filter, out_row); } } } @@ -457,9 +459,23 @@ void BGRAConvolve2D(const unsigned char* source_data, int num_output_rows = filter_y.num_values(); int pixel_width = filter_x.num_values(); + // We need to check which is the last line to convolve before we advance 4 // lines in one iteration. int last_filter_offset, last_filter_length; + // SSE2 can access up to 3 extra pixels past the end of the + // buffer. At the bottom of the image, we have to be careful + // not to access data past the end of the buffer. Normally + // we fall back to the C++ implementation for the last row. + // If the last row is less than 3 pixels wide, we may have to fall + // back to the C++ version for more rows. Compute how many + // rows we need to avoid the SSE implementation for here. + filter_x.FilterForValue(filter_x.num_values() - 1, &last_filter_offset, + &last_filter_length); +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) + int avoid_simd_rows = 1 + 3 / + (last_filter_offset + last_filter_length); +#endif filter_y.FilterForValue(num_output_rows - 1, &last_filter_offset, &last_filter_length); @@ -473,36 +489,32 @@ void BGRAConvolve2D(const unsigned char* source_data, // We don't want to process too much rows in batches of 4 because // we can go out-of-bounds at the end while (next_x_row < filter_offset + filter_length) { - if (next_x_row + 3 < last_filter_offset + last_filter_length - 3) { + if (next_x_row + 3 < last_filter_offset + last_filter_length - + avoid_simd_rows) { const unsigned char* src[4]; unsigned char* out_row[4]; for (int i = 0; i < 4; ++i) { src[i] = &source_data[(next_x_row + i) * source_byte_row_stride]; out_row[i] = row_buffer.AdvanceRow(); } - ConvolveHorizontally4_SIMD(src, 0, pixel_width, filter_x, out_row); + ConvolveHorizontally4_SIMD(src, filter_x, out_row); next_x_row += 4; } else { - unsigned char* buffer = row_buffer.AdvanceRow(); - - // For last rows, SSE2 load possibly to access data beyond the - // image area. therefore we use cobined C+SSE version here - int simd_width = pixel_width & ~3; - if (simd_width) { + // Check if we need to avoid SSE2 for this row. + if (next_x_row < last_filter_offset + last_filter_length - + avoid_simd_rows) { ConvolveHorizontally_SIMD( &source_data[next_x_row * source_byte_row_stride], - 0, simd_width, filter_x, buffer); - } - - if (pixel_width > simd_width) { + filter_x, row_buffer.AdvanceRow()); + } else { if (source_has_alpha) { ConvolveHorizontally( &source_data[next_x_row * source_byte_row_stride], - simd_width, pixel_width, filter_x, buffer); + filter_x, row_buffer.AdvanceRow()); } else { ConvolveHorizontally( &source_data[next_x_row * source_byte_row_stride], - simd_width, pixel_width, filter_x, buffer); + filter_x, row_buffer.AdvanceRow()); } } next_x_row++; @@ -513,12 +525,12 @@ void BGRAConvolve2D(const unsigned char* source_data, while (next_x_row < filter_offset + filter_length) { if (source_has_alpha) { ConvolveHorizontally( - &source_data[next_x_row * source_byte_row_stride], - 0, pixel_width, filter_x, row_buffer.AdvanceRow()); + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); } else { ConvolveHorizontally( - &source_data[next_x_row * source_byte_row_stride], - 0, pixel_width, filter_x, row_buffer.AdvanceRow()); + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); } next_x_row++; } diff --git a/gfx/2d/convolverSSE2.cpp b/gfx/2d/convolverSSE2.cpp index a1b223703e..20ddd45f1f 100644 --- a/gfx/2d/convolverSSE2.cpp +++ b/gfx/2d/convolverSSE2.cpp @@ -35,26 +35,24 @@ namespace skia { // Convolves horizontally along a single row. The row data is given in -// |src_data| and continues for the [begin, end) of the filter. +// |src_data| and continues for the num_values() of the filter. void ConvolveHorizontally_SSE2(const unsigned char* src_data, - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row) { + int num_values = filter.num_values(); int filter_offset, filter_length; __m128i zero = _mm_setzero_si128(); - __m128i mask[3]; + __m128i mask[4]; // |mask| will be used to decimate all extra filter coefficients that are // loaded by SIMD when |filter_length| is not divisible by 4. - mask[0] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); - mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); - mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); - - // This buffer is used for tails - __m128i buffer; + // mask[0] is not used in following algorithm. + mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); + mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); + mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); // Output one pixel each iteration, calculating all channels (RGBA) together. - for (int out_x = begin; out_x < end; out_x++) { + for (int out_x = 0; out_x < num_values; out_x++) { const ConvolutionFilter1D::Fixed* filter_values = filter.FilterForValue(out_x, &filter_offset, &filter_length); @@ -117,22 +115,21 @@ void ConvolveHorizontally_SSE2(const unsigned char* src_data, // When |filter_length| is not divisible by 4, we need to decimate some of // the filter coefficient that was loaded incorrectly to zero; Other than - // that the algorithm is same with above, except that the 4th pixel will be + // that the algorithm is same with above, exceot that the 4th pixel will be // always absent. - int r = filter_length & 3; + int r = filter_length&3; if (r) { - memcpy(&buffer, row_to_filter, r * 4); // Note: filter_values must be padded to align_up(filter_offset, 8). __m128i coeff, coeff16; coeff = _mm_loadl_epi64(reinterpret_cast(filter_values)); // Mask out extra filter taps. - coeff = _mm_and_si128(coeff, mask[r-1]); + coeff = _mm_and_si128(coeff, mask[r]); coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); // Note: line buffer must be padded to align_up(filter_offset, 16). - // We resolve this by temporary buffer - __m128i src8 = _mm_loadu_si128(&buffer); + // We resolve this by use C-version for the last horizontal line. + __m128i src8 = _mm_loadu_si128(row_to_filter); __m128i src16 = _mm_unpacklo_epi8(src8, zero); __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); @@ -165,24 +162,26 @@ void ConvolveHorizontally_SSE2(const unsigned char* src_data, } // Convolves horizontally along four rows. The row data is given in -// |src_data| and continues for the [begin, end) of the filter. +// |src_data| and continues for the num_values() of the filter. // The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please // refer to that function for detailed comments. void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row[4]) { + int num_values = filter.num_values(); + int filter_offset, filter_length; __m128i zero = _mm_setzero_si128(); - __m128i mask[3]; + __m128i mask[4]; // |mask| will be used to decimate all extra filter coefficients that are // loaded by SIMD when |filter_length| is not divisible by 4. - mask[0] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); - mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); - mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); + // mask[0] is not used in following algorithm. + mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); + mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); + mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); // Output one pixel each iteration, calculating all channels (RGBA) together. - for (int out_x = begin; out_x < end; out_x++) { + for (int out_x = 0; out_x < num_values; out_x++) { const ConvolutionFilter1D::Fixed* filter_values = filter.FilterForValue(out_x, &filter_offset, &filter_length); @@ -240,7 +239,7 @@ void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], __m128i coeff; coeff = _mm_loadl_epi64(reinterpret_cast(filter_values)); // Mask out extra filter taps. - coeff = _mm_and_si128(coeff, mask[r-1]); + coeff = _mm_and_si128(coeff, mask[r]); __m128i coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); /* c1 c1 c1 c1 c0 c0 c0 c0 */ @@ -284,21 +283,22 @@ void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], // Does vertical convolution to produce one output row. The filter values and // length are given in the first two parameters. These are applied to each // of the rows pointed to in the |source_data_rows| array, with each row -// being |end - begin| wide. +// being |pixel_width| wide. // -// The output must have room for |(end - begin) * 4| bytes. +// The output must have room for |pixel_width * 4| bytes. template void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, + int pixel_width, unsigned char* out_row) { + int width = pixel_width & ~3; + __m128i zero = _mm_setzero_si128(); __m128i accum0, accum1, accum2, accum3, coeff16; const __m128i* src; - int out_x; // Output four pixels per iteration (16 bytes). - for (out_x = begin; out_x + 3 < end; out_x += 4) { + for (int out_x = 0; out_x < width; out_x += 4) { // Accumulated result for each pixel. 32 bits per RGBA channel. accum0 = _mm_setzero_si128(); @@ -391,11 +391,7 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value // When the width of the output is not divisible by 4, We need to save one // pixel (4 bytes) each time. And also the fourth pixel is always absent. - int r = end - out_x; - if (r > 0) { - // Since accum3 is never used here, we'll use it as a buffer - __m128i *buffer = &accum3; - + if (pixel_width & 3) { accum0 = _mm_setzero_si128(); accum1 = _mm_setzero_si128(); accum2 = _mm_setzero_si128(); @@ -403,9 +399,8 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value coeff16 = _mm_set1_epi16(filter_values[filter_y]); // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 src = reinterpret_cast( - &source_data_rows[filter_y][out_x * 4]); - memcpy(buffer, src, r * 4); - __m128i src8 = _mm_loadu_si128(buffer); + &source_data_rows[filter_y][width<<2]); + __m128i src8 = _mm_loadu_si128(src); // [16] a1 b1 g1 r1 a0 b0 g0 r0 __m128i src16 = _mm_unpacklo_epi8(src8, zero); __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); @@ -451,7 +446,7 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value accum0 = _mm_or_si128(accum0, mask); } - for (; out_x < end; out_x++) { + for (int out_x = width; out_x < pixel_width; out_x++) { *(reinterpret_cast(out_row)) = _mm_cvtsi128_si32(accum0); accum0 = _mm_srli_si128(accum0, 4); out_row += 4; @@ -462,14 +457,14 @@ void ConvolveVertically_SSE2_impl(const ConvolutionFilter1D::Fixed* filter_value void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, + int pixel_width, unsigned char* out_row, bool has_alpha) { if (has_alpha) { ConvolveVertically_SSE2_impl(filter_values, filter_length, - source_data_rows, begin, end, out_row); + source_data_rows, pixel_width, out_row); } else { ConvolveVertically_SSE2_impl(filter_values, filter_length, - source_data_rows, begin, end, out_row); + source_data_rows, pixel_width, out_row); } } diff --git a/gfx/2d/convolverSSE2.h b/gfx/2d/convolverSSE2.h index 916c1eddd3..a54ce676bc 100644 --- a/gfx/2d/convolverSSE2.h +++ b/gfx/2d/convolverSSE2.h @@ -40,7 +40,6 @@ namespace skia { // Convolves horizontally along a single row. The row data is given in // |src_data| and continues for the [begin, end) of the filter. void ConvolveHorizontally_SSE2(const unsigned char* src_data, - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row); @@ -49,7 +48,6 @@ void ConvolveHorizontally_SSE2(const unsigned char* src_data, // The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please // refer to that function for detailed comments. void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], - int begin, int end, const ConvolutionFilter1D& filter, unsigned char* out_row[4]); @@ -62,7 +60,7 @@ void ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, int filter_length, unsigned char* const* source_data_rows, - int begin, int end, + int pixel_width, unsigned char* out_row, bool has_alpha); } // namespace skia diff --git a/gfx/gl/HeapCopyOfStackArray.h b/gfx/gl/HeapCopyOfStackArray.h index df4c050ed4..4a95c95828 100644 --- a/gfx/gl/HeapCopyOfStackArray.h +++ b/gfx/gl/HeapCopyOfStackArray.h @@ -23,7 +23,7 @@ class HeapCopyOfStackArray { public: template - HeapCopyOfStackArray(ElemType (&array)[N]) + MOZ_IMPLICIT HeapCopyOfStackArray(ElemType (&array)[N]) : mArrayLength(N) , mArrayData(new ElemType[N]) { @@ -44,4 +44,4 @@ private: } // namespace mozilla -#endif // HEAPCOPYOFSTACKARRAY_H_ \ No newline at end of file +#endif // HEAPCOPYOFSTACKARRAY_H_ diff --git a/gfx/layers/basic/TextureClientX11.cpp b/gfx/layers/basic/TextureClientX11.cpp index 3bd53bf49c..3748177ef9 100644 --- a/gfx/layers/basic/TextureClientX11.cpp +++ b/gfx/layers/basic/TextureClientX11.cpp @@ -112,7 +112,9 @@ TextureClientX11::AllocateForSurface(IntSize aSize, TextureAllocationFlags aText //MOZ_ASSERT(mFormat != gfx::FORMAT_YUV, "This TextureClient cannot use YCbCr data"); MOZ_ASSERT(aSize.width >= 0 && aSize.height >= 0); - if (aSize.width <= 0 || aSize.height <= 0) { + if (aSize.width <= 0 || aSize.height <= 0 || + aSize.width > XLIB_IMAGE_SIDE_SIZE_LIMIT || + aSize.height > XLIB_IMAGE_SIDE_SIZE_LIMIT) { gfxDebug() << "Asking for X11 surface of invalid size " << aSize.width << "x" << aSize.height; return false; } diff --git a/gfx/skia/skia/src/core/SkDraw.cpp b/gfx/skia/skia/src/core/SkDraw.cpp index b77eb430c7..d9c44a2b6f 100644 --- a/gfx/skia/skia/src/core/SkDraw.cpp +++ b/gfx/skia/skia/src/core/SkDraw.cpp @@ -734,6 +734,16 @@ void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count, } } +static inline SkPoint compute_stroke_size(const SkPaint& paint, const SkMatrix& matrix) { + SkASSERT(matrix.rectStaysRect()); + SkASSERT(SkPaint::kFill_Style != paint.getStyle()); + + SkVector size; + SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() }; + matrix.mapVectors(&size, &pt, 1); + return SkPoint::Make(SkScalarAbs(size.fX), SkScalarAbs(size.fY)); +} + static bool easy_rect_join(const SkPaint& paint, const SkMatrix& matrix, SkPoint* strokeSize) { if (SkPaint::kMiter_Join != paint.getStrokeJoin() || @@ -812,12 +822,22 @@ void SkDraw::drawRect(const SkRect& rect, const SkPaint& paint) const { devRect.sort(); // look for the quick exit, before we build a blitter - SkIRect ir; - devRect.roundOut(&ir); + SkRect bbox = devRect; if (paint.getStyle() != SkPaint::kFill_Style) { // extra space for hairlines - ir.inset(-1, -1); + if (paint.getStrokeWidth() == 0) { + bbox.outset(1, 1); + } else { + // For kStroke_RectType, strokeSize is already computed. + const SkPoint& ssize = (kStroke_RectType == rtype) + ? strokeSize + : compute_stroke_size(paint, *fMatrix); + bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y())); + } } + + SkIRect ir; + bbox.roundOut(&ir); if (fRC->quickReject(ir)) { return; } diff --git a/gfx/skia/skia/src/utils/win/SkDWrite.h b/gfx/skia/skia/src/utils/win/SkDWrite.h index 9ca157e647..fb81fef560 100644 --- a/gfx/skia/skia/src/utils/win/SkDWrite.h +++ b/gfx/skia/skia/src/utils/win/SkDWrite.h @@ -42,7 +42,7 @@ HRESULT sk_wchar_to_skstring(WCHAR* name, SkString* skname); void sk_get_locale_string(IDWriteLocalizedStrings* names, const WCHAR* preferedLocale, SkString* skname); -typedef int (*SkGetUserDefaultLocaleNameProc)(LPWSTR, int); +typedef int (WINAPI *SkGetUserDefaultLocaleNameProc)(LPWSTR, int); HRESULT SkGetGetUserDefaultLocaleNameProc(SkGetUserDefaultLocaleNameProc* proc); //////////////////////////////////////////////////////////////////////////////// diff --git a/gfx/thebes/gfxASurface.cpp b/gfx/thebes/gfxASurface.cpp index 2587f979b9..07d4739adf 100644 --- a/gfx/thebes/gfxASurface.cpp +++ b/gfx/thebes/gfxASurface.cpp @@ -499,12 +499,6 @@ gfxASurface::GetSubpixelAntialiasingEnabled() #endif } -gfxMemoryLocation -gfxASurface::GetMemoryLocation() const -{ - return gfxMemoryLocation::IN_PROCESS_HEAP; -} - int32_t gfxASurface::BytePerPixelFromFormat(gfxImageFormat format) { diff --git a/gfx/thebes/gfxASurface.h b/gfx/thebes/gfxASurface.h index bee57feb07..fef2de0f7a 100644 --- a/gfx/thebes/gfxASurface.h +++ b/gfx/thebes/gfxASurface.h @@ -159,12 +159,6 @@ public: // to a sub-class of gfxASurface.) virtual bool SizeOfIsMeasured() const { return false; } - /** - * Where does this surface's memory live? By default, we say it's in this - * process's heap. - */ - virtual gfxMemoryLocation GetMemoryLocation() const; - static int32_t BytePerPixelFromFormat(gfxImageFormat format); virtual const mozilla::gfx::IntSize GetSize() const; diff --git a/gfx/thebes/gfxTypes.h b/gfx/thebes/gfxTypes.h index 2b01cd8a4d..2c923e78eb 100644 --- a/gfx/thebes/gfxTypes.h +++ b/gfx/thebes/gfxTypes.h @@ -92,15 +92,4 @@ enum class gfxContentType { SENTINEL = 0xffff }; -/** - * The memory used by a gfxASurface (as reported by KnownMemoryUsed()) can - * either live in this process's heap, in this process but outside the - * heap, or in another process altogether. - */ -enum class gfxMemoryLocation { - IN_PROCESS_HEAP, - IN_PROCESS_NONHEAP, - OUT_OF_PROCESS -}; - #endif /* GFX_TYPES_H */ diff --git a/gfx/thebes/gfxWindowsSurface.cpp b/gfx/thebes/gfxWindowsSurface.cpp index 89b8d193c5..c093099119 100644 --- a/gfx/thebes/gfxWindowsSurface.cpp +++ b/gfx/thebes/gfxWindowsSurface.cpp @@ -300,9 +300,3 @@ gfxWindowsSurface::GetSize() const return mozilla::gfx::IntSize(cairo_win32_surface_get_width(mSurface), cairo_win32_surface_get_height(mSurface)); } - -gfxMemoryLocation -gfxWindowsSurface::GetMemoryLocation() const -{ - return gfxMemoryLocation::IN_PROCESS_NONHEAP; -} diff --git a/gfx/thebes/gfxWindowsSurface.h b/gfx/thebes/gfxWindowsSurface.h index c2d3e5c2d2..7e8b51746d 100644 --- a/gfx/thebes/gfxWindowsSurface.h +++ b/gfx/thebes/gfxWindowsSurface.h @@ -65,10 +65,6 @@ public: const mozilla::gfx::IntSize GetSize() const; - // The memory used by this surface lives in this process's address space, - // but not in the heap. - virtual gfxMemoryLocation GetMemoryLocation() const; - private: void MakeInvalid(mozilla::gfx::IntSize& size); diff --git a/gfx/thebes/gfxXlibSurface.cpp b/gfx/thebes/gfxXlibSurface.cpp index ea8658e925..9a5a7b651a 100644 --- a/gfx/thebes/gfxXlibSurface.cpp +++ b/gfx/thebes/gfxXlibSurface.cpp @@ -21,11 +21,6 @@ using namespace mozilla; -// Although the dimension parameters in the xCreatePixmapReq wire protocol are -// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if -// either dimension cannot be represented by a 16-bit *signed* integer. -#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff - gfxXlibSurface::gfxXlibSurface(Display *dpy, Drawable drawable, Visual *visual) : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable) #if defined(GL_PROVIDER_GLX) @@ -619,9 +614,3 @@ gfxXlibSurface::BindGLXPixmap(GLXPixmap aPixmap) } #endif - -gfxMemoryLocation -gfxXlibSurface::GetMemoryLocation() const -{ - return gfxMemoryLocation::OUT_OF_PROCESS; -} diff --git a/gfx/thebes/gfxXlibSurface.h b/gfx/thebes/gfxXlibSurface.h index 694d29f667..8c5e8f9a36 100644 --- a/gfx/thebes/gfxXlibSurface.h +++ b/gfx/thebes/gfxXlibSurface.h @@ -17,6 +17,12 @@ #include "nsSize.h" +// Although the dimension parameters in the xCreatePixmapReq wire protocol are +// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if +// either dimension cannot be represented by a 16-bit *signed* integer. +#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff + + class gfxXlibSurface final : public gfxASurface { public: // construct a wrapper around the specified drawable with dpy/visual. @@ -79,10 +85,6 @@ public: // Find a visual and colormap pair suitable for rendering to this surface. bool GetColormapAndVisual(Colormap* colormap, Visual **visual); - // This surface is a wrapper around X pixmaps, which are stored in the X - // server, not the main application. - virtual gfxMemoryLocation GetMemoryLocation() const override; - #if defined(GL_PROVIDER_GLX) GLXPixmap GetGLXPixmap(); // Binds a GLXPixmap backed by this context's surface. diff --git a/image/CopyOnWrite.h b/image/CopyOnWrite.h new file mode 100644 index 0000000000..5f9c88bb94 --- /dev/null +++ b/image/CopyOnWrite.h @@ -0,0 +1,250 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * CopyOnWrite allows code to safely read from a data structure without + * worrying that reentrant code will modify it. + */ + +#ifndef mozilla_image_CopyOnWrite_h +#define mozilla_image_CopyOnWrite_h + +#include "mozilla/nsRefPtr.h" +#include "MainThreadUtils.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// Implementation Details +/////////////////////////////////////////////////////////////////////////////// + +namespace detail { + +template +class CopyOnWriteValue final +{ +public: + NS_INLINE_DECL_REFCOUNTING(CopyOnWriteValue) + + explicit CopyOnWriteValue(T* aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(already_AddRefed& aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(already_AddRefed&& aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(const nsRefPtr& aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(nsRefPtr&& aValue) : mValue(aValue) { } + + T* get() { return mValue.get(); } + const T* get() const { return mValue.get(); } + + bool HasReaders() const { return mReaders > 0; } + bool HasWriter() const { return mWriter; } + bool HasUsers() const { return HasReaders() || HasWriter(); } + + void LockForReading() { MOZ_ASSERT(!HasWriter()); mReaders++; } + void UnlockForReading() { MOZ_ASSERT(HasReaders()); mReaders--; } + + struct MOZ_STACK_CLASS AutoReadLock + { + explicit AutoReadLock(CopyOnWriteValue* aValue) + : mValue(aValue) + { + mValue->LockForReading(); + } + ~AutoReadLock() { mValue->UnlockForReading(); } + CopyOnWriteValue* mValue; + }; + + void LockForWriting() { MOZ_ASSERT(!HasUsers()); mWriter = true; } + void UnlockForWriting() { MOZ_ASSERT(HasWriter()); mWriter = false; } + + struct MOZ_STACK_CLASS AutoWriteLock + { + explicit AutoWriteLock(CopyOnWriteValue* aValue) + : mValue(aValue) + { + mValue->LockForWriting(); + } + ~AutoWriteLock() { mValue->UnlockForWriting(); } + CopyOnWriteValue* mValue; + }; + +private: + CopyOnWriteValue(const CopyOnWriteValue&) = delete; + CopyOnWriteValue(CopyOnWriteValue&&) = delete; + + ~CopyOnWriteValue() { } + + nsRefPtr mValue; + uint64_t mReaders = 0; + bool mWriter = false; +}; + +} // namespace detail + + +/////////////////////////////////////////////////////////////////////////////// +// Public API +/////////////////////////////////////////////////////////////////////////////// + +/** + * CopyOnWrite allows code to safely read from a data structure without + * worrying that reentrant code will modify it. If reentrant code would modify + * the data structure while other code is reading from it, a copy is made so + * that readers can continue to use the old version. + * + * Note that it's legal to nest a writer inside any number of readers, but + * nothing can be nested inside a writer. This is because it's assumed that the + * state of the contained data structure may not be consistent during the write. + * + * This is a main-thread-only data structure. + * + * To work with CopyOnWrite, a type T needs to be reference counted and to + * support copy construction. + */ +template +class CopyOnWrite final +{ + typedef detail::CopyOnWriteValue CopyOnWriteValue; + +public: + explicit CopyOnWrite(T* aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(already_AddRefed& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(already_AddRefed&& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(const nsRefPtr& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(nsRefPtr&& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + /// @return true if it's safe to read at this time. + bool CanRead() const { return !mValue->HasWriter(); } + + /** + * Read from the contained data structure using the function @aReader. + * @aReader will be passed a pointer of type |const T*|. It's not legal to + * call this while a writer is active. + * + * @return whatever value @aReader returns, or nothing if @aReader is a void + * function. + */ + template + auto Read(ReadFunc aReader) const + -> decltype(aReader(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(CanRead()); + + // Run the provided function while holding a read lock. + nsRefPtr cowValue = mValue; + typename CopyOnWriteValue::AutoReadLock lock(cowValue); + return aReader(cowValue->get()); + } + + /** + * Read from the contained data structure using the function @aReader. + * @aReader will be passed a pointer of type |const T*|. If it's currently not + * possible to read because a writer is currently active, @aOnError will be + * called instead. + * + * @return whatever value @aReader or @aOnError returns (their return types + * must be consistent), or nothing if the provided functions are void. + */ + template + auto Read(ReadFunc aReader, ErrorFunc aOnError) const + -> decltype(aReader(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!CanRead()) { + return aOnError(); + } + + return Read(aReader); + } + + /// @return true if it's safe to write at this time. + bool CanWrite() const { return !mValue->HasWriter(); } + + /** + * Write to the contained data structure using the function @aWriter. + * @aWriter will be passed a pointer of type |T*|. It's not legal to call this + * while another writer is active. + * + * If readers are currently active, they will be able to continue reading from + * a copy of the old version of the data structure. The copy will be destroyed + * when all its readers finish. Later readers and writers will see the + * version of the data structure produced by the most recent call to Write(). + * + * @return whatever value @aWriter returns, or nothing if @aWriter is a void + * function. + */ + template + auto Write(WriteFunc aWriter) + -> decltype(aWriter(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(CanWrite()); + + // If there are readers, we need to copy first. + if (mValue->HasReaders()) { + mValue = new CopyOnWriteValue(new T(*mValue->get())); + } + + // Run the provided function while holding a write lock. + nsRefPtr cowValue = mValue; + typename CopyOnWriteValue::AutoWriteLock lock(cowValue); + return aWriter(cowValue->get()); + } + + /** + * Write to the contained data structure using the function @aWriter. + * @aWriter will be passed a pointer of type |T*|. If it's currently not + * possible to write because a writer is currently active, @aOnError will be + * called instead. + * + * If readers are currently active, they will be able to continue reading from + * a copy of the old version of the data structure. The copy will be destroyed + * when all its readers finish. Later readers and writers will see the + * version of the data structure produced by the most recent call to Write(). + * + * @return whatever value @aWriter or @aOnError returns (their return types + * must be consistent), or nothing if the provided functions are void. + */ + template + auto Write(WriteFunc aWriter, ErrorFunc aOnError) + -> decltype(aWriter(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!CanWrite()) { + return aOnError(); + } + + return Write(aWriter); + } + +private: + CopyOnWrite(const CopyOnWrite&) = delete; + CopyOnWrite(CopyOnWrite&&) = delete; + + nsRefPtr mValue; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_CopyOnWrite_h diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp index 09ed7e965f..209b481bf0 100644 --- a/image/DecodePool.cpp +++ b/image/DecodePool.cpp @@ -451,7 +451,10 @@ DecodePool::Decode(Decoder* aDecoder) nsresult rv = aDecoder->Decode(); if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) { - if (aDecoder->HasProgress()) { + // If this isn't a metadata decode, notify for the progress we've made so + // far. It's important that metadata decode results are delivered + // atomically, so for those decodes we wait until NotifyDecodeComplete. + if (aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()) { NotifyProgress(aDecoder); } // The decoder will ensure that a new worker gets enqueued to continue diff --git a/image/Decoder.cpp b/image/Decoder.cpp index 1d95d65f86..b6199bc62c 100644 --- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -37,10 +37,8 @@ Decoder::Decoder(RasterImage* aImage) , mMetadataDecode(false) , mSendPartialInvalidations(false) , mImageIsTransient(false) - , mImageIsLocked(false) , mFirstFrameDecode(false) , mInFrame(false) - , mIsAnimated(false) , mDataDone(false) , mDecodeDone(false) , mDataError(false) @@ -94,15 +92,18 @@ Decoder::Init() } nsresult -Decoder::Decode() +Decoder::Decode(IResumable* aOnResume) { MOZ_ASSERT(mInitialized, "Should be initialized here"); MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); + // If no IResumable was provided, default to |this|. + IResumable* onResume = aOnResume ? aOnResume : this; + // We keep decoding chunks until the decode completes or there are no more // chunks available. while (!GetDecodeDone() && !HasError()) { - auto newState = mIterator->AdvanceOrScheduleResume(this); + auto newState = mIterator->AdvanceOrScheduleResume(onResume); if (newState == SourceBufferIterator::WAITING) { // We can't continue because the rest of the data hasn't arrived from the @@ -237,7 +238,7 @@ Decoder::CompleteDecode() // If this image wasn't animated and isn't a transient image, mark its frame // as optimizable. We don't support optimizing animated images and // optimizing transient images isn't worth it. - if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) { + if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) { mCurrentFrame->SetOptimizable(); } } @@ -260,7 +261,12 @@ Decoder::AllocateFrame(uint32_t aFrameNum, mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize); if (aFrameNum + 1 == mFrameCount) { - PostFrameStart(); + // If we're past the first frame, PostIsAnimated() should've been called. + MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation()); + + // Update our state to reflect the new frame + MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); + mInFrame = true; } } else { PostDataError(); @@ -406,19 +412,11 @@ Decoder::PostHasTransparency() } void -Decoder::PostFrameStart() +Decoder::PostIsAnimated(int32_t aFirstFrameTimeout) { - // We shouldn't already be mid-frame - MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); - - // Update our state to reflect the new frame - mInFrame = true; - - // If we just became animated, record that fact. - if (mFrameCount > 1) { - mIsAnimated = true; - mProgress |= FLAG_IS_ANIMATED; - } + mProgress |= FLAG_IS_ANIMATED; + mImageMetadata.SetHasAnimation(); + mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout); } void @@ -442,7 +440,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */, // If we're not sending partial invalidations, then we send an invalidation // here when the first frame is complete. - if (!mSendPartialInvalidations && !mIsAnimated) { + if (!mSendPartialInvalidations && !HasAnimation()) { mInvalidRect.UnionRect(mInvalidRect, gfx::IntRect(gfx::IntPoint(0, 0), GetSize())); } @@ -459,7 +457,7 @@ Decoder::PostInvalidation(const nsIntRect& aRect, // Record this invalidation, unless we're not sending partial invalidations // or we're past the first frame. - if (mSendPartialInvalidations && !mIsAnimated) { + if (mSendPartialInvalidations && !HasAnimation()) { mInvalidRect.UnionRect(mInvalidRect, aRect); mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect)); } diff --git a/image/Decoder.h b/image/Decoder.h index 9ec512c9e4..7e3d8d896a 100644 --- a/image/Decoder.h +++ b/image/Decoder.h @@ -34,13 +34,16 @@ public: void Init(); /** - * Decodes, reading all data currently available in the SourceBuffer. If more - * data is needed, Decode() automatically ensures that it will be called again - * on a DecodePool thread when the data becomes available. + * Decodes, reading all data currently available in the SourceBuffer. + * + * If more data is needed, Decode() will schedule @aOnResume to be called when + * more data is available. If @aOnResume is null or unspecified, the default + * implementation resumes decoding on a DecodePool thread. Most callers should + * use the default implementation. * * Any errors are reported by setting the appropriate state on the decoder. */ - nsresult Decode(); + nsresult Decode(IResumable* aOnResume = nullptr); /** * Given a maximum number of bytes we're willing to decode, @aByteLimit, @@ -181,20 +184,6 @@ public: mImageIsTransient = aIsTransient; } - /** - * Set whether the image is locked for the lifetime of this decoder. We lock - * the image during our initial decode to ensure that we don't evict any - * surfaces before we realize that the image is animated. - */ - void SetImageIsLocked() - { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mImageIsLocked = true; - } - - bool ImageIsLocked() const { return mImageIsLocked; } - - /** * Set whether we should stop decoding after the first frame. */ @@ -225,7 +214,7 @@ public: } // Did we discover that the image we're decoding is animated? - bool HasAnimation() const { return mIsAnimated; } + bool HasAnimation() const { return mImageMetadata.HasAnimation(); } // Error tracking bool HasError() const { return HasDataError() || HasDecoderError(); } @@ -344,9 +333,11 @@ protected: // actual contents of the frame and give a more accurate result. void PostHasTransparency(); - // Called by decoders when they begin a frame. Informs the image, sends - // notifications, and does internal book-keeping. - void PostFrameStart(); + // Called by decoders if they determine that the image is animated. + // + // @param aTimeout The time for which the first frame should be shown before + // we advance to the next frame. + void PostIsAnimated(int32_t aFirstFrameTimeout); // Called by decoders when they end a frame. Informs the image, sends // notifications, and does internal book-keeping. @@ -451,10 +442,8 @@ private: bool mMetadataDecode : 1; bool mSendPartialInvalidations : 1; bool mImageIsTransient : 1; - bool mImageIsLocked : 1; bool mFirstFrameDecode : 1; bool mInFrame : 1; - bool mIsAnimated : 1; bool mDataDone : 1; bool mDecodeDone : 1; bool mDataError : 1; diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 16e328f36c..79c2690e34 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -138,8 +138,7 @@ DecoderFactory::CreateDecoder(DecoderType aType, int aSampleSize, const IntSize& aResolution, bool aIsRedecode, - bool aImageIsTransient, - bool aImageIsLocked) + bool aImageIsTransient) { if (aType == DecoderType::UNKNOWN) { return nullptr; @@ -156,10 +155,7 @@ DecoderFactory::CreateDecoder(DecoderType aType, decoder->SetResolution(aResolution); decoder->SetSendPartialInvalidations(!aIsRedecode); decoder->SetImageIsTransient(aImageIsTransient); - - if (aImageIsLocked) { - decoder->SetImageIsLocked(); - } + decoder->SetIsFirstFrameDecode(); // Set a target size for downscale-during-decode if applicable. if (aTargetSize) { @@ -177,6 +173,39 @@ DecoderFactory::CreateDecoder(DecoderType aType, return decoder.forget(); } +/* static */ already_AddRefed +DecoderFactory::CreateAnimationDecoder(DecoderType aType, + RasterImage* aImage, + SourceBuffer* aSourceBuffer, + uint32_t aFlags, + const IntSize& aResolution) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG, + "Calling CreateAnimationDecoder for non-animating DecoderType"); + + nsRefPtr decoder = + GetDecoder(aType, aImage, /* aIsRedecode = */ true); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(false); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetFlags(aFlags); + decoder->SetResolution(aResolution); + decoder->SetSendPartialInvalidations(false); + + decoder->Init(); + if (NS_FAILED(decoder->GetDecoderError())) { + return nullptr; + } + + return decoder.forget(); +} + /* static */ already_AddRefed DecoderFactory::CreateMetadataDecoder(DecoderType aType, RasterImage* aImage, @@ -240,5 +269,30 @@ DecoderFactory::CreateAnonymousDecoder(DecoderType aType, return decoder.forget(); } +/* static */ already_AddRefed +DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType, + SourceBuffer* aSourceBuffer) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + nsRefPtr decoder = + GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(true); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetIsFirstFrameDecode(); + + decoder->Init(); + if (NS_FAILED(decoder->GetDecoderError())) { + return nullptr; + } + + return decoder.forget(); +} + } // namespace image } // namespace mozilla diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index b90e3c3343..2fc55b6fb6 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -40,12 +40,13 @@ public: static DecoderType GetDecoderType(const char* aMimeType); /** - * Creates and initializes a decoder of type @aType. The decoder will send - * notifications to @aImage. + * Creates and initializes a decoder for non-animated images of type @aType. + * (If the image *is* animated, only the first frame will be decoded.) The + * decoder will send notifications to @aImage. * - * XXX(seth): @aIsRedecode, @aImageIsTransient, and @aImageIsLocked should - * really be part of @aFlags. This requires changes to the way that decoder - * flags work, though. See bug 1185800. + * XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of + * @aFlags. This requires changes to the way that decoder flags work, though. + * See bug 1185800. * * @param aType Which type of decoder to create - JPEG, PNG, etc. * @param aImage The image will own the decoder and which should receive @@ -64,9 +65,6 @@ public: * empty rect if none). * @param aIsRedecode Specify 'true' if this image has been decoded before. * @param aImageIsTransient Specify 'true' if this image is transient. - * @param aImageIsLocked Specify 'true' if this image is locked for the - * lifetime of this decoder, and should be unlocked - * when the decoder finishes. */ static already_AddRefed CreateDecoder(DecoderType aType, @@ -77,8 +75,28 @@ public: int aSampleSize, const gfx::IntSize& aResolution, bool aIsRedecode, - bool aImageIsTransient, - bool aImageIsLocked); + bool aImageIsTransient); + + /** + * Creates and initializes a decoder for animated images of type @aType. + * The decoder will send notifications to @aImage. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aImage The image will own the decoder and which should receive + * notifications as decoding progresses. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aFlags Flags specifying what type of output the decoder should + * produce; see GetDecodeFlags() in RasterImage.h. + * @param aResolution The resolution requested using #-moz-resolution (or an + * empty rect if none). + */ + static already_AddRefed + CreateAnimationDecoder(DecoderType aType, + RasterImage* aImage, + SourceBuffer* aSourceBuffer, + uint32_t aFlags, + const gfx::IntSize& aResolution); /** * Creates and initializes a metadata decoder of type @aType. This decoder @@ -103,11 +121,37 @@ public: int aSampleSize, const gfx::IntSize& aResolution); + /** + * Creates and initializes an anonymous decoder (one which isn't associated + * with an Image object). Only the first frame of the image will be decoded. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aFlags Flags specifying what type of output the decoder should + * produce; see GetDecodeFlags() in RasterImage.h. + */ static already_AddRefed CreateAnonymousDecoder(DecoderType aType, SourceBuffer* aSourceBuffer, uint32_t aFlags); + /** + * Creates and initializes an anonymous metadata decoder (one which isn't + * associated with an Image object). This decoder will only decode the image's + * header, extracting metadata like the size of the image. No actual image + * data will be decoded and no surfaces will be allocated. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aFlags Flags specifying what type of output the decoder should + * produce; see GetDecodeFlags() in RasterImage.h. + */ + static already_AddRefed + CreateAnonymousMetadataDecoder(DecoderType aType, + SourceBuffer* aSourceBuffer); + private: virtual ~DecoderFactory() = 0; diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp index 6cfb260ecf..efe2e02ad0 100644 --- a/image/Downscaler.cpp +++ b/image/Downscaler.cpp @@ -91,8 +91,9 @@ Downscaler::BeginFrame(const nsIntSize& aOriginalSize, mYFilter.get()); // Allocate the buffer, which contains scanlines of the original image. + // pad by 15 to handle overreads by the simd code size_t bufferLen = mOriginalSize.width * sizeof(uint32_t); - mRowBuffer = MakeUnique(bufferLen); + mRowBuffer = MakeUnique(mOriginalSize.width * sizeof(uint32_t) + 15); if (MOZ_UNLIKELY(!mRowBuffer)) { return NS_ERROR_OUT_OF_MEMORY; } @@ -111,7 +112,8 @@ Downscaler::BeginFrame(const nsIntSize& aOriginalSize, } bool anyAllocationFailed = false; - const int rowSize = mTargetSize.width * sizeof(uint32_t); + // pad by 15 to handle overreads by the simd code + const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15; for (int32_t i = 0; i < mWindowCapacity; ++i) { mWindow[i] = new uint8_t[rowSize]; anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr; diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index 45cd8443f7..316e2e1151 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -291,14 +291,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum) int32_t FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const { + int32_t rawTimeout = 0; + RawAccessFrameRef frame = GetRawFrame(aFrameNum); - if (!frame) { + if (frame) { + AnimationData data = frame->GetAnimationData(); + rawTimeout = data.mRawTimeout; + } else if (aFrameNum == 0) { + rawTimeout = mFirstFrameTimeout; + } else { NS_WARNING("No frame; called GetTimeoutForFrame too early?"); return 100; } - AnimationData data = frame->GetAnimationData(); - // Ensure a minimal time between updates so we don't throttle the UI thread. // consider 0 == unspecified and make it fast but not too fast. Unless we // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug @@ -312,11 +317,11 @@ FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const // It seems that there are broken tools out there that set a 0ms or 10ms // timeout when they really want a "default" one. So munge values in that // range. - if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10 && mLoopCount != 0) { + if (rawTimeout >= 0 && rawTimeout <= 10) { return 100; } - return data.mRawTimeout; + return rawTimeout; } static void @@ -334,12 +339,9 @@ DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface, SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType); // Extract the surface's memory usage information. - size_t heap = aSurface - ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_HEAP, aMallocSizeOf); + size_t heap = 0, nonHeap = 0; + aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap); counter.Values().SetDecodedHeap(heap); - - size_t nonHeap = aSurface - ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr); counter.Values().SetDecodedNonHeap(nonHeap); // Record it. @@ -436,6 +438,7 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect, // Calculate area that needs updating switch (prevFrameData.mDisposalMethod) { default: + MOZ_ASSERT_UNREACHABLE("Unexpected DisposalMethod"); case DisposalMethod::NOT_SPECIFIED: case DisposalMethod::KEEP: *aDirtyRect = nextFrameData.mRect; @@ -569,6 +572,9 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect, break; default: + MOZ_ASSERT_UNREACHABLE("Unexpected DisposalMethod"); + case DisposalMethod::NOT_SPECIFIED: + case DisposalMethod::KEEP: // Copy previous frame into compositingFrame before we put the new // frame on top // Assumes that the previous frame represents a full frame (it could be diff --git a/image/FrameAnimator.h b/image/FrameAnimator.h index a404d01c71..95a2a13b91 100644 --- a/image/FrameAnimator.h +++ b/image/FrameAnimator.h @@ -33,6 +33,7 @@ public: , mLoopRemainingCount(-1) , mLastCompositedFrameIndex(-1) , mLoopCount(-1) + , mFirstFrameTimeout(0) , mAnimationMode(aAnimationMode) , mDoneDecoding(false) { } @@ -148,6 +149,12 @@ public: void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; } int32_t LoopCount() const { return mLoopCount; } + /* + * Set the timeout for the first frame. This is used to allow animation + * scheduling even before a full decode runs for this image. + */ + void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; } + /** * Collect an accounting of the memory occupied by the compositing surfaces we * use during animation playback. All of the actual animation frames are @@ -277,6 +284,9 @@ private: // data //! The total number of loops for the image. int32_t mLoopCount; + //! The timeout for the first frame of this image. + int32_t mFirstFrameTimeout; + //! The animation mode of this image. Constants defined in imgIContainer. uint16_t mAnimationMode; diff --git a/image/Image.h b/image/Image.h index 130d484423..67ac064f4b 100644 --- a/image/Image.h +++ b/image/Image.h @@ -8,7 +8,7 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/TimeStamp.h" -#include "gfx2DGlue.h" // for gfxMemoryLocation +#include "gfx2DGlue.h" #include "imgIContainer.h" #include "ImageURL.h" #include "nsStringFwd.h" diff --git a/image/ImageMetadata.cpp b/image/ImageMetadata.cpp deleted file mode 100644 index 236228520a..0000000000 --- a/image/ImageMetadata.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * 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 "ImageMetadata.h" - -#include "RasterImage.h" -#include "nsComponentManagerUtils.h" -#include "nsISupportsPrimitives.h" -#include "nsXPCOMCID.h" - -namespace mozilla { -namespace image { - -nsresult -ImageMetadata::SetOnImage(RasterImage* aImage) -{ - nsresult rv = NS_OK; - - if (mHotspotX != -1 && mHotspotY != -1) { - nsCOMPtr intwrapx = - do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); - nsCOMPtr intwrapy = - do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); - intwrapx->SetData(mHotspotX); - intwrapy->SetData(mHotspotY); - aImage->Set("hotspotX", intwrapx); - aImage->Set("hotspotY", intwrapy); - } - - aImage->SetLoopCount(mLoopCount); - - if (HasSize()) { - MOZ_ASSERT(HasOrientation(), "Should have orientation"); - rv = aImage->SetSize(GetWidth(), GetHeight(), GetOrientation()); - } - - return rv; -} - -} // namespace image -} // namespace mozilla diff --git a/image/ImageMetadata.h b/image/ImageMetadata.h index c257612e02..defa56af4c 100644 --- a/image/ImageMetadata.h +++ b/image/ImageMetadata.h @@ -22,23 +22,26 @@ class ImageMetadata { public: ImageMetadata() - : mHotspotX(-1) - , mHotspotY(-1) - , mLoopCount(-1) + : mLoopCount(-1) + , mFirstFrameTimeout(0) + , mHasAnimation(false) { } - // Set the metadata this object represents on an image. - nsresult SetOnImage(RasterImage* aImage); - - void SetHotspot(uint16_t hotspotx, uint16_t hotspoty) + void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY) { - mHotspotX = hotspotx; - mHotspotY = hotspoty; + mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY)); } + gfx::IntPoint GetHotspot() const { return *mHotspot; } + bool HasHotspot() const { return mHotspot.isSome(); } + void SetLoopCount(int32_t loopcount) { mLoopCount = loopcount; } + int32_t GetLoopCount() const { return mLoopCount; } + + void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; } + int32_t GetFirstFrameTimeout() const { return mFirstFrameTimeout; } void SetSize(int32_t width, int32_t height, Orientation orientation) { @@ -47,25 +50,28 @@ public: mOrientation.emplace(orientation); } } - + nsIntSize GetSize() const { return *mSize; } + Orientation GetOrientation() const { return *mOrientation; } bool HasSize() const { return mSize.isSome(); } bool HasOrientation() const { return mOrientation.isSome(); } - int32_t GetWidth() const { return mSize->width; } - int32_t GetHeight() const { return mSize->height; } - nsIntSize GetSize() const { return *mSize; } - Orientation GetOrientation() const { return *mOrientation; } + void SetHasAnimation() { mHasAnimation = true; } + bool HasAnimation() const { return mHasAnimation; } private: - // The hotspot found on cursors, or -1 if none was found. - int32_t mHotspotX; - int32_t mHotspotY; + /// The hotspot found on cursors, if present. + Maybe mHotspot; - // The loop count for animated images, or -1 for infinite loop. + /// The loop count for animated images, or -1 for infinite loop. int32_t mLoopCount; + /// The timeout of an animated image's first frame. + int32_t mFirstFrameTimeout; + Maybe mSize; Maybe mOrientation; + + bool mHasAnimation : 1; }; } // namespace image diff --git a/image/ProgressTracker.cpp b/image/ProgressTracker.cpp index 47b3265d8f..b0a76a762e 100644 --- a/image/ProgressTracker.cpp +++ b/image/ProgressTracker.cpp @@ -243,33 +243,76 @@ ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver) NS_DispatchToCurrentThread(ev); } -#define NOTIFY_IMAGE_OBSERVERS(OBSERVERS, FUNC) \ - do { \ - ObserverArray::ForwardIterator iter(OBSERVERS); \ - while (iter.HasMore()) { \ - nsRefPtr observer = iter.GetNext().get(); \ - if (observer && !observer->NotificationsDeferred()) { \ - observer->FUNC; \ - } \ - } \ - } while (false); - -/* static */ void -ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers, - bool aHasImage, - Progress aProgress, - const nsIntRect& aDirtyRect) +/** + * ImageObserverNotifier is a helper type that abstracts over the difference + * between sending notifications to all of the observers in an ObserverTable, + * and sending them to a single observer. This allows the same notification code + * to be used for both cases. + */ +template struct ImageObserverNotifier; + +template <> +struct MOZ_STACK_CLASS ImageObserverNotifier +{ + explicit ImageObserverNotifier(const ObserverTable* aObservers, + bool aIgnoreDeferral = false) + : mObservers(aObservers) + , mIgnoreDeferral(aIgnoreDeferral) + { } + + template + void operator()(Lambda aFunc) + { + for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) { + nsRefPtr observer = iter.Data().get(); + if (observer && + (mIgnoreDeferral || !observer->NotificationsDeferred())) { + aFunc(observer); + } + } + } + +private: + const ObserverTable* mObservers; + const bool mIgnoreDeferral; +}; + +template <> +struct MOZ_STACK_CLASS ImageObserverNotifier +{ + explicit ImageObserverNotifier(IProgressObserver* aObserver) + : mObserver(aObserver) + { } + + template + void operator()(Lambda aFunc) + { + if (mObserver && !mObserver->NotificationsDeferred()) { + aFunc(mObserver); + } + } + +private: + IProgressObserver* mObserver; +}; + +template void +SyncNotifyInternal(const T& aObservers, + bool aHasImage, + Progress aProgress, + const nsIntRect& aDirtyRect) { MOZ_ASSERT(NS_IsMainThread()); typedef imgINotificationObserver I; + ImageObserverNotifier notify(aObservers); if (aProgress & FLAG_SIZE_AVAILABLE) { - NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::SIZE_AVAILABLE)); + notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); }); } if (aProgress & FLAG_ONLOAD_BLOCKED) { - NOTIFY_IMAGE_OBSERVERS(aObservers, BlockOnload()); + notify([](IProgressObserver* aObs) { aObs->BlockOnload(); }); } if (aHasImage) { @@ -278,19 +321,21 @@ ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers, // vector images, true for raster images that have decoded at // least one frame) then send OnFrameUpdate. if (!aDirtyRect.IsEmpty()) { - NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::FRAME_UPDATE, &aDirtyRect)); + notify([&](IProgressObserver* aObs) { + aObs->Notify(I::FRAME_UPDATE, &aDirtyRect); + }); } if (aProgress & FLAG_FRAME_COMPLETE) { - NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::FRAME_COMPLETE)); + notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); }); } if (aProgress & FLAG_HAS_TRANSPARENCY) { - NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::HAS_TRANSPARENCY)); + notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); }); } if (aProgress & FLAG_IS_ANIMATED) { - NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::IS_ANIMATED)); + notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); }); } } @@ -298,17 +343,18 @@ ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers, // observers that can fire events when they receive those notifications to do // so then, instead of being forced to wait for UnblockOnload. if (aProgress & FLAG_ONLOAD_UNBLOCKED) { - NOTIFY_IMAGE_OBSERVERS(aObservers, UnblockOnload()); + notify([](IProgressObserver* aObs) { aObs->UnblockOnload(); }); } if (aProgress & FLAG_DECODE_COMPLETE) { MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?"); - NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::DECODE_COMPLETE)); + notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); }); } if (aProgress & FLAG_LOAD_COMPLETE) { - NOTIFY_IMAGE_OBSERVERS(aObservers, - OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE)); + notify([=](IProgressObserver* aObs) { + aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE); + }); } } @@ -340,7 +386,9 @@ ProgressTracker::SyncNotifyProgress(Progress aProgress, CheckProgressConsistency(mProgress); // Send notifications. - SyncNotifyInternal(mObservers, HasImage(), progress, aInvalidRect); + mObservers.Read([&](const ObserverTable* aTable) { + SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect); + }); if (progress & FLAG_HAS_ERROR) { FireFailureNotification(); @@ -370,9 +418,7 @@ ProgressTracker::SyncNotify(IProgressObserver* aObserver) } } - ObserverArray array; - array.AppendElement(aObserver); - SyncNotifyInternal(array, !!image, mProgress, rect); + SyncNotifyInternal(aObserver, !!image, mProgress, rect); } void @@ -395,7 +441,14 @@ void ProgressTracker::AddObserver(IProgressObserver* aObserver) { MOZ_ASSERT(NS_IsMainThread()); - mObservers.AppendElementUnlessExists(aObserver); + + mObservers.Write([=](ObserverTable* aTable) { + MOZ_ASSERT(!aTable->Get(aObserver, nullptr), + "Adding duplicate entry for image observer"); + + WeakPtr weakPtr = aObserver; + aTable->Put(aObserver, weakPtr); + }); } bool @@ -404,7 +457,11 @@ ProgressTracker::RemoveObserver(IProgressObserver* aObserver) MOZ_ASSERT(NS_IsMainThread()); // Remove the observer from the list. - bool removed = mObservers.RemoveElement(aObserver); + bool removed = mObservers.Write([=](ObserverTable* aTable) { + bool removed = aTable->Get(aObserver, nullptr); + aTable->Remove(aObserver); + return removed; + }); // Observers can get confused if they don't get all the proper teardown // notifications. Part ways on good terms. @@ -425,26 +482,25 @@ ProgressTracker::RemoveObserver(IProgressObserver* aObserver) return removed; } -bool -ProgressTracker::FirstObserverIs(IProgressObserver* aObserver) +uint32_t +ProgressTracker::ObserverCount() const { - MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only"); - ObserverArray::ForwardIterator iter(mObservers); - while (iter.HasMore()) { - nsRefPtr observer = iter.GetNext().get(); - if (observer) { - return observer.get() == aObserver; - } - } - return false; + MOZ_ASSERT(NS_IsMainThread()); + return mObservers.Read([](const ObserverTable* aTable) { + return aTable->Count(); + }); } void ProgressTracker::OnUnlockedDraw() { MOZ_ASSERT(NS_IsMainThread()); - NOTIFY_IMAGE_OBSERVERS(mObservers, - Notify(imgINotificationObserver::UNLOCKED_DRAW)); + mObservers.Read([](const ObserverTable* aTable) { + ImageObserverNotifier notify(aTable); + notify([](IProgressObserver* aObs) { + aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW); + }); + }); } void @@ -459,30 +515,26 @@ void ProgressTracker::OnDiscard() { MOZ_ASSERT(NS_IsMainThread()); - NOTIFY_IMAGE_OBSERVERS(mObservers, - Notify(imgINotificationObserver::DISCARD)); + mObservers.Read([](const ObserverTable* aTable) { + ImageObserverNotifier notify(aTable); + notify([](IProgressObserver* aObs) { + aObs->Notify(imgINotificationObserver::DISCARD); + }); + }); } void ProgressTracker::OnImageAvailable() { - if (!NS_IsMainThread()) { - // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter - // so subsequent calls or dispatches which Unlock or Decrement~ should - // be issued after this to avoid race conditions. - NS_DispatchToMainThread( - NS_NewRunnableMethod(this, &ProgressTracker::OnImageAvailable)); - return; - } - + MOZ_ASSERT(NS_IsMainThread()); // Notify any imgRequestProxys that are observing us that we have an Image. - ObserverArray::ForwardIterator iter(mObservers); - while (iter.HasMore()) { - nsRefPtr observer = iter.GetNext().get(); - if (observer) { - observer->SetHasImage(); - } - } + mObservers.Read([](const ObserverTable* aTable) { + ImageObserverNotifier + notify(aTable, /* aIgnoreDeferral = */ true); + notify([](IProgressObserver* aObs) { + aObs->SetHasImage(); + }); + }); } void diff --git a/image/ProgressTracker.h b/image/ProgressTracker.h index 299f903607..1cb0c116d8 100644 --- a/image/ProgressTracker.h +++ b/image/ProgressTracker.h @@ -7,9 +7,11 @@ #ifndef mozilla_image_ProgressTracker_h #define mozilla_image_ProgressTracker_h +#include "CopyOnWrite.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" #include "mozilla/WeakPtr.h" +#include "nsDataHashtable.h" #include "nsCOMPtr.h" #include "nsTObserverArray.h" #include "nsThreadUtils.h" @@ -57,6 +59,37 @@ inline Progress LoadCompleteProgress(bool aLastPart, return progress; } +/** + * ProgressTracker stores its observers in an ObserverTable, which is a hash + * table mapping raw pointers to WeakPtr's to the same objects. This sounds like + * unnecessary duplication of information, but it's necessary for stable hash + * values since WeakPtr's lose the knowledge of which object they used to point + * to when that object is destroyed. + * + * ObserverTable subclasses nsDataHashtable to add reference counting support + * and a copy constructor, both of which are needed for use with CopyOnWrite. + */ +class ObserverTable + : public nsDataHashtable, + WeakPtr> +{ +public: + NS_INLINE_DECL_REFCOUNTING(ObserverTable); + + ObserverTable() = default; + + ObserverTable(const ObserverTable& aOther) + { + NS_WARNING("Forced to copy ObserverTable due to nested notifications"); + for (auto iter = aOther.ConstIter(); !iter.Done(); iter.Next()) { + this->Put(iter.Key(), iter.Data()); + } + } + +private: + ~ObserverTable() { } +}; + /** * ProgressTracker is a class that records an Image's progress through the * loading and decoding process, and makes it possible to send notifications to @@ -78,6 +111,7 @@ public: ProgressTracker() : mImageMutex("ProgressTracker::mImage") , mImage(nullptr) + , mObservers(new ObserverTable) , mProgress(NoProgress) { } @@ -126,7 +160,7 @@ public: void ResetForNewRequest(); // Stateless notifications. These are dispatched and immediately forgotten - // about. All except OnImageAvailable are main thread only. + // about. All of these notifications are main thread only. void OnDiscard(); void OnUnlockedDraw(); void OnImageAvailable(); @@ -150,22 +184,13 @@ public: // with its loading progress. Weak pointers. void AddObserver(IProgressObserver* aObserver); bool RemoveObserver(IProgressObserver* aObserver); - size_t ObserverCount() const { - MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only"); - return mObservers.Length(); - } - - // This is intentionally non-general because its sole purpose is to support - // some obscure network priority logic in imgRequest. That stuff could - // probably be improved, but it's too scary to mess with at the moment. - bool FirstObserverIs(IProgressObserver* aObserver); + uint32_t ObserverCount() const; // Resets our weak reference to our image. Image subclasses should call this // in their destructor. void ResetImage(); private: - typedef nsTObserverArray> ObserverArray; friend class AsyncNotifyRunnable; friend class AsyncNotifyCurrentStateRunnable; friend class ImageFactory; @@ -183,12 +208,7 @@ private: // Main thread only because it deals with the observer service. void FireFailureNotification(); - // Main thread only, since notifications are expected on the main thread, and - // mObservers is not threadsafe. - static void SyncNotifyInternal(ObserverArray& aObservers, - bool aHasImage, Progress aProgress, - const nsIntRect& aInvalidRect); - + // The runnable, if any, that we've scheduled to deliver async notifications. nsCOMPtr mRunnable; // mImage is a weak ref; it should be set to null when the image goes out of @@ -196,10 +216,9 @@ private: mutable Mutex mImageMutex; Image* mImage; - // List of observers attached to the image. Each observer represents a - // consumer using the image. Array and/or individual elements should only be - // accessed on the main thread. - ObserverArray mObservers; + // Hashtable of observers attached to the image. Each observer represents a + // consumer using the image. Main thread only. + CopyOnWrite mObservers; Progress mProgress; }; diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index e75e6d4738..8417347290 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -24,6 +24,7 @@ #include "nsIConsoleService.h" #include "nsIInputStream.h" #include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" #include "nsPresContext.h" #include "SourceBuffer.h" #include "SurfaceCache.h" @@ -487,7 +488,7 @@ RasterImage::LookupFrame(uint32_t aFrameNum, // We don't have a copy of this frame, and there's no decoder working on // one. (Or we're sync decoding and the existing decoder hasn't even started // yet.) Trigger decoding so it'll be available next time. - MOZ_ASSERT(!mAnim, "Animated frames should be locked"); + MOZ_ASSERT(!mAnim || GetNumFrames() < 1, "Animated frames should be locked"); Decode(requestedSize, aFlags); @@ -538,7 +539,7 @@ RasterImage::GetRequestedFrameIndex(uint32_t aWhichFrame) const IntRect RasterImage::GetFirstFrameRect() { - if (mAnim) { + if (mAnim && mHasBeenDecoded) { return mAnim->GetFirstFrameRefreshArea(); } @@ -591,7 +592,10 @@ RasterImage::GetAnimated(bool* aAnimated) } // Otherwise, we need to have been decoded to know for sure, since if we were - // decoded at least once mAnim would have been created for animated images + // decoded at least once mAnim would have been created for animated images. + // This is true even though we check for animation during the metadata decode, + // because we may still discover animation only during the full decode for + // corrupt images. if (!mHasBeenDecoded) { return NS_ERROR_NOT_AVAILABLE; } @@ -923,21 +927,9 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount, mFrameCount = aNewFrameCount; if (aNewFrameCount == 2) { - // We're becoming animated, so initialize animation stuff. - MOZ_ASSERT(!mAnim, "Already have animation state?"); - mAnim = MakeUnique(this, mSize, mAnimationMode); - - // We don't support discarding animated images (See bug 414259). - // Lock the image and throw away the key. - // - // Note that this is inefficient, since we could get rid of the source - // data too. However, doing this is actually hard, because we're probably - // mid-decode, and thus we're decoding out of the source buffer. Since - // we're going to fix this anyway later, and since we didn't kill the - // source data in the old world either, locking is acceptable for the - // moment. - LockImage(); + MOZ_ASSERT(mAnim, "Should already have animation state"); + // We may be able to start animating. if (mPendingAnimation && ShouldAnimate()) { StartAnimation(); } @@ -949,7 +941,8 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount, } nsresult -RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) +RasterImage::SetMetadata(const ImageMetadata& aMetadata, + bool aFromMetadataDecode) { MOZ_ASSERT(NS_IsMainThread()); @@ -957,26 +950,64 @@ RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) return NS_ERROR_FAILURE; } - // Ensure that we have positive values - // XXX - Why isn't the size unsigned? Should this be changed? - if ((aWidth < 0) || (aHeight < 0)) { - return NS_ERROR_INVALID_ARG; + if (aMetadata.HasSize()) { + IntSize size = aMetadata.GetSize(); + if (size.width < 0 || size.height < 0) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(aMetadata.HasOrientation()); + Orientation orientation = aMetadata.GetOrientation(); + + // If we already have a size, check the new size against the old one. + if (mHasSize && (size != mSize || orientation != mOrientation)) { + NS_WARNING("Image changed size or orientation on redecode! " + "This should not happen!"); + DoError(); + return NS_ERROR_UNEXPECTED; + } + + // Set the size and flag that we have it. + mSize = size; + mOrientation = orientation; + mHasSize = true; } - // if we already have a size, check the new size against the old one - if (mHasSize && - ((aWidth != mSize.width) || - (aHeight != mSize.height) || - (aOrientation != mOrientation))) { - NS_WARNING("Image changed size on redecode! This should not happen!"); - DoError(); - return NS_ERROR_UNEXPECTED; + if (mHasSize && aMetadata.HasAnimation() && !mAnim) { + // We're becoming animated, so initialize animation stuff. + mAnim = MakeUnique(this, mSize, mAnimationMode); + + // We don't support discarding animated images (See bug 414259). + // Lock the image and throw away the key. + LockImage(); + + if (!aFromMetadataDecode) { + // The metadata decode reported that this image isn't animated, but we + // discovered that it actually was during the full decode. This is a + // rare failure that only occurs for corrupt images. To recover, we need + // to discard all existing surfaces and redecode. + RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT); + } } - // Set the size and flag that we have it - mSize.SizeTo(aWidth, aHeight); - mOrientation = aOrientation; - mHasSize = true; + if (mAnim) { + mAnim->SetLoopCount(aMetadata.GetLoopCount()); + mAnim->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout()); + } + + if (aMetadata.HasHotspot()) { + IntPoint hotspot = aMetadata.GetHotspot(); + + nsCOMPtr intwrapx = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); + nsCOMPtr intwrapy = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); + intwrapx->SetData(hotspot.x); + intwrapy->SetData(hotspot.y); + + Set("hotspotX", intwrapx); + Set("hotspotY", intwrapy); + } return NS_OK; } @@ -1001,10 +1032,9 @@ RasterImage::StartAnimation() MOZ_ASSERT(ShouldAnimate(), "Should not animate!"); - // If we don't have mAnim yet, then we're not ready to animate. Setting - // mPendingAnimation will cause us to start animating as soon as we have a - // second frame, which causes mAnim to be constructed. - mPendingAnimation = !mAnim; + // If we're not ready to animate, then set mPendingAnimation, which will cause + // us to start animating if and when we do become ready. + mPendingAnimation = !mAnim || GetNumFrames() < 2; if (mPendingAnimation) { return NS_OK; } @@ -1093,19 +1123,6 @@ RasterImage::GetFrameIndex(uint32_t aWhichFrame) : mAnim->GetCurrentAnimationFrameIndex(); } -void -RasterImage::SetLoopCount(int32_t aLoopCount) -{ - if (mError) { - return; - } - - // No need to set this if we're not an animation. - if (mAnim) { - mAnim->SetLoopCount(aLoopCount); - } -} - NS_IMETHODIMP_(IntRect) RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect) { @@ -1393,20 +1410,19 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags) Maybe targetSize = mSize != aSize ? Some(aSize) : Nothing(); - bool imageIsLocked = false; - if (!mHasBeenDecoded) { - // Lock the image while we're decoding, so that it doesn't get evicted from - // the SurfaceCache before we have a chance to realize that it's animated. - // The corresponding unlock happens in FinalizeDecoder. - LockImage(); - imageIsLocked = true; - } - // Create a decoder. - nsRefPtr decoder = - DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, targetSize, - aFlags, mRequestedSampleSize, mRequestedResolution, - mHasBeenDecoded, mTransient, imageIsLocked); + nsRefPtr decoder; + if (mAnim) { + decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this, + mSourceBuffer, aFlags, + mRequestedResolution); + } else { + decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, + targetSize, aFlags, + mRequestedSampleSize, + mRequestedResolution, + mHasBeenDecoded, mTransient); + } // Make sure DecoderFactory was able to create a decoder successfully. if (!decoder) { @@ -1556,6 +1572,7 @@ RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags) // image, we have all the source data and know our size, the flags allow us to // do it, and a 'good' filter is being used. if (!mDownscaleDuringDecode || !mHasSize || + !gfxPrefs::ImageHQDownscalingEnabled() || !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) { return false; } @@ -1931,7 +1948,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) } // Record all the metadata the decoder gathered about this image. - nsresult rv = aDecoder->GetImageMetadata().SetOnImage(this); + nsresult rv = SetMetadata(aDecoder->GetImageMetadata(), + aDecoder->IsMetadataDecode()); if (NS_FAILED(rv)) { aDecoder->PostResizeError(); } @@ -1942,17 +1960,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) if (aDecoder->GetDecodeTotallyDone() && !mError) { // Flag that we've been decoded before. mHasBeenDecoded = true; - - if (aDecoder->HasAnimation()) { - if (mAnim) { - mAnim->SetDoneDecoding(true); - } else { - // The OnAddedFrame event that will create mAnim is still in the event - // queue. Wait for it. - nsCOMPtr runnable = - NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded); - NS_DispatchToMainThread(runnable); - } + if (mAnim) { + mAnim->SetDoneDecoding(true); } } @@ -1985,11 +1994,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) } } - if (aDecoder->ImageIsLocked()) { - // Unlock the image, balancing the LockImage call we made in CreateDecoder. - UnlockImage(); - } - // If we were a metadata decode and a full decode was requested, do it. if (done && wasMetadata && mWantFullDecode) { mWantFullDecode = false; @@ -1997,17 +2001,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) } } -void -RasterImage::MarkAnimationDecoded() -{ - MOZ_ASSERT(mAnim, "Should have an animation now"); - if (!mAnim) { - return; - } - - mAnim->SetDoneDecoding(true); -} - void RasterImage::ReportDecoderError(Decoder* aDecoder) { diff --git a/image/RasterImage.h b/image/RasterImage.h index b1cd253ec0..36fefa6d6d 100644 --- a/image/RasterImage.h +++ b/image/RasterImage.h @@ -132,6 +132,7 @@ namespace image { class Decoder; class FrameAnimator; +class ImageMetadata; class SourceBuffer; /** @@ -188,18 +189,6 @@ public: void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea); - /** Sets the size and inherent orientation of the container. This should only - * be called by the decoder. This function may be called multiple times, but - * will throw an error if subsequent calls do not match the first. - */ - nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation); - - /** - * Number of times to loop the image. - * @note -1 means forever. - */ - void SetLoopCount(int32_t aLoopCount); - /** * Sends the provided progress notifications to ProgressTracker. * @@ -222,8 +211,7 @@ public: */ void FinalizeDecoder(Decoder* aDecoder); - // Helper methods for FinalizeDecoder. - void MarkAnimationDecoded(); + // Helper method for FinalizeDecoder. void ReportDecoderError(Decoder* aDecoder); @@ -301,10 +289,6 @@ private: nsIntRect GetFirstFrameRect(); - size_t - SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, - MallocSizeOf aMallocSizeOf) const; - Pair> GetCurrentImage(layers::ImageContainer* aContainer, uint32_t aFlags); @@ -343,6 +327,19 @@ private: */ NS_IMETHOD DecodeMetadata(uint32_t aFlags); + /** + * Sets the size, inherent orientation, animation metadata, and other + * information about the image gathered during decoding. + * + * This function may be called multiple times, but will throw an error if + * subsequent calls do not match the first. + * + * @param aMetadata The metadata to set on this image. + * @param aFromMetadataDecode True if this metadata came from a metadata + * decode; false if it came from a full decode. + */ + nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode); + /** * In catastrophic circumstances like a GPU driver crash, we may lose our * frames even if they're locked. RecoverFromLossOfFrames discards all diff --git a/image/SourceBuffer.cpp b/image/SourceBuffer.cpp index f283c7f79f..ce008967b4 100644 --- a/image/SourceBuffer.cpp +++ b/image/SourceBuffer.cpp @@ -213,10 +213,6 @@ SourceBuffer::AddWaitingConsumer(IResumable* aConsumer) MOZ_ASSERT(!mStatus, "Waiting when we're complete?"); - if (MOZ_UNLIKELY(NS_IsMainThread())) { - NS_WARNING("SourceBuffer consumer on the main thread needed to wait"); - } - mWaitingConsumers.AppendElement(aConsumer); } diff --git a/image/StreamingLexer.h b/image/StreamingLexer.h new file mode 100644 index 0000000000..b3fcce5250 --- /dev/null +++ b/image/StreamingLexer.h @@ -0,0 +1,355 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * StreamingLexer is a lexing framework designed to make it simple to write + * image decoders without worrying about the details of how the data is arriving + * from the network. + */ + +#ifndef mozilla_image_StreamingLexer_h +#define mozilla_image_StreamingLexer_h + +#include +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" +#include "mozilla/Vector.h" + +namespace mozilla { +namespace image { + +/// Buffering behaviors for StreamingLexer transitions. +enum class BufferingStrategy +{ + BUFFERED, // Data will be buffered and processed in one chunk. + UNBUFFERED // Data will be processed as it arrives, in multiple chunks. +}; + +/// @return true if @aState is a terminal state. +template +bool IsTerminalState(State aState) +{ + return aState == State::SUCCESS || + aState == State::FAILURE; +} + +/** + * LexerTransition is a type used to give commands to the lexing framework. + * Code that uses StreamingLexer can create LexerTransition values using the + * static methods on Transition, and then return them to the lexing framework + * for execution. + */ +template +class LexerTransition +{ +public: + State NextState() const { return mNextState; } + State UnbufferedState() const { return *mUnbufferedState; } + size_t Size() const { return mSize; } + BufferingStrategy Buffering() const { return mBufferingStrategy; } + +private: + friend struct Transition; + + LexerTransition(const State& aNextState, + const Maybe& aUnbufferedState, + size_t aSize, + BufferingStrategy aBufferingStrategy) + : mNextState(aNextState) + , mUnbufferedState(aUnbufferedState) + , mSize(aSize) + , mBufferingStrategy(aBufferingStrategy) + { + MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED, + mUnbufferedState); + MOZ_ASSERT_IF(mUnbufferedState, + mBufferingStrategy == BufferingStrategy::UNBUFFERED); + } + + State mNextState; + Maybe mUnbufferedState; + size_t mSize; + BufferingStrategy mBufferingStrategy; +}; + +struct Transition +{ + /// Transition to @aNextState, buffering @aSize bytes of data. + template + static LexerTransition + To(const State& aNextState, size_t aSize) + { + MOZ_ASSERT(!IsTerminalState(aNextState)); + return LexerTransition(aNextState, Nothing(), aSize, + BufferingStrategy::BUFFERED); + } + + /** + * Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of + * data unbuffered. + * + * The unbuffered data will be delivered in state @aUnbufferedState, which may + * be invoked repeatedly until all @aSize bytes have been delivered. Then, + * @aNextState will be invoked with no data. No state transitions are allowed + * from @aUnbufferedState except for transitions to a terminal state, so + * @aNextState will always be reached unless lexing terminates early. + */ + template + static LexerTransition + ToUnbuffered(const State& aNextState, + const State& aUnbufferedState, + size_t aSize) + { + MOZ_ASSERT(!IsTerminalState(aNextState)); + MOZ_ASSERT(!IsTerminalState(aUnbufferedState)); + return LexerTransition(aNextState, Some(aUnbufferedState), aSize, + BufferingStrategy::UNBUFFERED); + } + + /** + * Continue receiving unbuffered data. @aUnbufferedState should be the same + * state as the @aUnbufferedState specified in the preceding call to + * ToUnbuffered(). + * + * This should be used during an unbuffered read initiated by ToUnbuffered(). + */ + template + static LexerTransition + ContinueUnbuffered(const State& aUnbufferedState) + { + MOZ_ASSERT(!IsTerminalState(aUnbufferedState)); + return LexerTransition(aUnbufferedState, Nothing(), 0, + BufferingStrategy::BUFFERED); + } + + /** + * Terminate lexing, ending up in terminal state @aFinalState. + * + * No more data will be delivered after Terminate() is used. + */ + template + static LexerTransition + Terminate(const State& aFinalState) + { + MOZ_ASSERT(IsTerminalState(aFinalState)); + return LexerTransition(aFinalState, Nothing(), 0, + BufferingStrategy::BUFFERED); + } + +private: + Transition(); +}; + +/** + * StreamingLexer is a lexing framework designed to make it simple to write + * image decoders without worrying about the details of how the data is arriving + * from the network. + * + * To use StreamingLexer: + * + * - Create a State type. This should be an |enum class| listing all of the + * states that you can be in while lexing the image format you're trying to + * read. It must contain the two terminal states SUCCESS and FAILURE. + * + * - Add an instance of StreamingLexer to your decoder class. Initialize + * it with a Transition::To() the state that you want to start lexing in. + * + * - In your decoder's WriteInternal method(), call Lex(), passing in the input + * data and length that are passed to WriteInternal(). You also need to pass + * a lambda which dispatches to lexing code for each state based on the State + * value that's passed in. The lambda generally should just continue a + * |switch| statement that calls different methods for each State value. Each + * method should return a LexerTransition, which the lambda should + * return in turn. + * + * - Write the methods that actually implement lexing for your image format. + * These methods should return either Transition::To(), to move on to another + * state, or Transition::Terminate(), if lexing has terminated in either + * success or failure. (There are also additional transitions for unbuffered + * reads; see below.) + * + * That's all there is to it. The StreamingLexer will track your position in the + * input and buffer enough data so that your lexing methods can process + * everything in one pass. Lex() returns Nothing() if more data is needed, in + * which case you should just return from WriteInternal(). If lexing reaches a + * terminal state, Lex() returns Some(State::SUCCESS) or Some(State::FAILURE), + * and you can check which one to determine if lexing succeeded or failed and do + * any necessary cleanup. + * + * There's one more wrinkle: some lexers may want to *avoid* buffering in some + * cases, and just process the data as it comes in. This is useful if, for + * example, you just want to skip over a large section of data; there's no point + * in buffering data you're just going to ignore. + * + * You can begin an unbuffered read with Transition::ToUnbuffered(). This works + * a little differently than Transition::To() in that you specify *two* states. + * The @aUnbufferedState argument specifies a state that will be called + * repeatedly with unbuffered data, as soon as it arrives. The implementation + * for that state should return either a transition to a terminal state, or + * Transition::ContinueUnbuffered(). Once the amount of data requested in the + * original call to Transition::ToUnbuffered() has been delivered, Lex() will + * transition to the @aNextState state specified via Transition::ToUnbuffered(). + * That state will be invoked with *no* data; it's just called to signal that + * the unbuffered read is over. + * + * XXX(seth): We should be able to get of the |State| stuff totally once bug + * 1198451 lands, since we can then just return a function representing the next + * state directly. + */ +template +class StreamingLexer +{ +public: + explicit StreamingLexer(LexerTransition aStartState) + : mTransition(aStartState) + , mToReadUnbuffered(0) + { } + + template + Maybe Lex(const char* aInput, size_t aLength, Func aFunc) + { + if (IsTerminalState(mTransition.NextState())) { + // We've already reached a terminal state. We never deliver any more data + // in this case; just return the terminal state again immediately. + return Some(mTransition.NextState()); + } + + if (mToReadUnbuffered > 0) { + // We're continuing an unbuffered read. + + MOZ_ASSERT(mBuffer.empty(), + "Shouldn't be continuing an unbuffered read and a buffered " + "read at the same time"); + + size_t toRead = std::min(mToReadUnbuffered, aLength); + + // Call aFunc with the unbuffered state to indicate that we're in the middle + // of an unbuffered read. We enforce that any state transition passed back + // to us is either a terminal states or takes us back to the unbuffered + // state. + LexerTransition unbufferedTransition = + aFunc(mTransition.UnbufferedState(), aInput, toRead); + if (IsTerminalState(unbufferedTransition.NextState())) { + mTransition = unbufferedTransition; + return Some(mTransition.NextState()); // Done! + } + MOZ_ASSERT(mTransition.UnbufferedState() == + unbufferedTransition.NextState()); + + aInput += toRead; + aLength -= toRead; + mToReadUnbuffered -= toRead; + if (mToReadUnbuffered != 0) { + return Nothing(); // Need more input. + } + + // We're done with the unbuffered read, so transition to the next state. + mTransition = aFunc(mTransition.NextState(), nullptr, 0); + if (IsTerminalState(mTransition.NextState())) { + return Some(mTransition.NextState()); // Done! + } + } else if (0 < mBuffer.length()) { + // We're continuing a buffered read. + + MOZ_ASSERT(mToReadUnbuffered == 0, + "Shouldn't be continuing an unbuffered read and a buffered " + "read at the same time"); + MOZ_ASSERT(mBuffer.length() < mTransition.Size(), + "Buffered more than we needed?"); + + size_t toRead = std::min(aLength, mTransition.Size() - mBuffer.length()); + + mBuffer.append(aInput, toRead); + aInput += toRead; + aLength -= toRead; + if (mBuffer.length() != mTransition.Size()) { + return Nothing(); // Need more input. + } + + // We've buffered everything, so transition to the next state. + mTransition = + aFunc(mTransition.NextState(), mBuffer.begin(), mBuffer.length()); + mBuffer.clear(); + if (IsTerminalState(mTransition.NextState())) { + return Some(mTransition.NextState()); // Done! + } + } + + MOZ_ASSERT(mToReadUnbuffered == 0); + MOZ_ASSERT(mBuffer.empty()); + + // Process states as long as we continue to have enough input to do so. + while (mTransition.Size() <= aLength) { + size_t toRead = mTransition.Size(); + + if (mTransition.Buffering() == BufferingStrategy::BUFFERED) { + mTransition = aFunc(mTransition.NextState(), aInput, toRead); + } else { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED); + + // Call aFunc with the unbuffered state to indicate that we're in the + // middle of an unbuffered read. We enforce that any state transition + // passed back to us is either a terminal states or takes us back to the + // unbuffered state. + LexerTransition unbufferedTransition = + aFunc(mTransition.UnbufferedState(), aInput, toRead); + if (IsTerminalState(unbufferedTransition.NextState())) { + mTransition = unbufferedTransition; + return Some(mTransition.NextState()); // Done! + } + MOZ_ASSERT(mTransition.UnbufferedState() == + unbufferedTransition.NextState()); + + // We're done with the unbuffered read, so transition to the next state. + mTransition = aFunc(mTransition.NextState(), nullptr, 0); + } + + aInput += toRead; + aLength -= toRead; + + if (IsTerminalState(mTransition.NextState())) { + return Some(mTransition.NextState()); // Done! + } + } + + if (aLength == 0) { + // We finished right at a transition point. Just wait for more data. + return Nothing(); + } + + // If the next state is unbuffered, deliver what we can and then wait. + if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) { + LexerTransition unbufferedTransition = + aFunc(mTransition.UnbufferedState(), aInput, aLength); + if (IsTerminalState(unbufferedTransition.NextState())) { + mTransition = unbufferedTransition; + return Some(mTransition.NextState()); // Done! + } + MOZ_ASSERT(mTransition.UnbufferedState() == + unbufferedTransition.NextState()); + + mToReadUnbuffered = mTransition.Size() - aLength; + return Nothing(); // Need more input. + } + + // If the next state is buffered, buffer what we can and then wait. + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED); + if (!mBuffer.reserve(mTransition.Size())) { + return Some(State::FAILURE); // Done due to allocation failure. + } + mBuffer.append(aInput, aLength); + return Nothing(); // Need more input. + } + +private: + Vector mBuffer; + LexerTransition mTransition; + size_t mToReadUnbuffered; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_StreamingLexer_h diff --git a/image/SurfaceCache.cpp b/image/SurfaceCache.cpp index a3aeb119fc..31e9f6bd21 100644 --- a/image/SurfaceCache.cpp +++ b/image/SurfaceCache.cpp @@ -206,13 +206,10 @@ public: if (aCachedSurface->mSurface) { counter.SubframeSize() = Some(aCachedSurface->mSurface->GetSize()); - size_t heap = aCachedSurface->mSurface - ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_HEAP, - mMallocSizeOf); + size_t heap = 0, nonHeap = 0; + aCachedSurface->mSurface->AddSizeOfExcludingThis(mMallocSizeOf, + heap, nonHeap); counter.Values().SetDecodedHeap(heap); - - size_t nonHeap = aCachedSurface->mSurface - ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr); counter.Values().SetDecodedNonHeap(nonHeap); } diff --git a/image/SurfaceCache.h b/image/SurfaceCache.h index 32d61b068c..772badee0f 100644 --- a/image/SurfaceCache.h +++ b/image/SurfaceCache.h @@ -14,7 +14,7 @@ #include "mozilla/Maybe.h" // for Maybe #include "mozilla/MemoryReporting.h" // for MallocSizeOf #include "mozilla/HashFunctions.h" // for HashGeneric and AddToHash -#include "gfx2DGlue.h" // for gfxMemoryLocation +#include "gfx2DGlue.h" #include "gfxPoint.h" // for gfxSize #include "nsCOMPtr.h" // for already_AddRefed #include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize diff --git a/image/decoders/GIF2.h b/image/decoders/GIF2.h index 49cc39a4cd..fb6291db7c 100644 --- a/image/decoders/GIF2.h +++ b/image/decoders/GIF2.h @@ -31,7 +31,6 @@ typedef enum { gif_image_header, gif_image_header_continue, gif_image_colormap, - gif_image_body, gif_lzw_start, gif_lzw, gif_sub_block, @@ -107,4 +106,3 @@ typedef struct gif_struct { } gif_struct; #endif // mozilla_image_decoders_GIF2_H - diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp index 084e974470..d049b3c2fe 100644 --- a/image/decoders/nsBMPDecoder.cpp +++ b/image/decoders/nsBMPDecoder.cpp @@ -369,6 +369,15 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) return; } + // We treat BMPs as transparent if they're 32bpp and alpha is enabled, but + // also if they use RLE encoding, because the 'delta' mode can skip pixels + // and cause implicit transparency. + if ((mBIH.compression == BMPINFOHEADER::RLE8) || + (mBIH.compression == BMPINFOHEADER::RLE4) || + (mBIH.bpp == 32 && mUseAlphaData)) { + PostHasTransparency(); + } + // We have the size. If we're doing a metadata decode, we're done. if (IsMetadataDecode()) { return; diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp index bfdf8c5060..46bb35442e 100644 --- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -50,6 +50,8 @@ mailing address. #include #include "mozilla/Telemetry.h" +using namespace mozilla::gfx; + namespace mozilla { namespace image { @@ -161,6 +163,27 @@ nsGIFDecoder2::BeginGIF() PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); } +void +nsGIFDecoder2::CheckForTransparency(IntRect aFrameRect) +{ + // Check if the image has a transparent color in its palette. + if (mGIFStruct.is_transparent) { + PostHasTransparency(); + return; + } + + if (mGIFStruct.images_decoded > 0) { + return; // We only care about first frame padding below. + } + + // If we need padding on the first frame, that means we don't draw into part + // of the image at all. Report that as transparency. + IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height); + if (!imageRect.IsEqualEdges(aFrameRect)) { + PostHasTransparency(); + } +} + //****************************************************************************** nsresult nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) @@ -170,13 +193,14 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) gfx::SurfaceFormat format; if (mGIFStruct.is_transparent) { format = gfx::SurfaceFormat::B8G8R8A8; - PostHasTransparency(); } else { format = gfx::SurfaceFormat::B8G8R8X8; } - nsIntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset, - mGIFStruct.width, mGIFStruct.height); + IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset, + mGIFStruct.width, mGIFStruct.height); + + CheckForTransparency(frameRect); // Use correct format, RGB for first frame, PAL for following frames // and include transparency to allow for optimization of opaque images @@ -186,12 +210,6 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(), frameRect, format, aDepth); } else { - if (!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) { - // We need padding on the first frame, which means that we don't draw into - // part of the image at all. Report that as transparency. - PostHasTransparency(); - } - // Regardless of depth of input, the first frame is decoded into 24bit RGB. rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(), frameRect, format); @@ -689,12 +707,6 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) mGIFStruct.screen_height = GETINT16(q + 2); mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1; - if (IsMetadataDecode()) { - MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point"); - PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); - return; - } - // screen_bgcolor is not used //mGIFStruct.screen_bgcolor = q[5]; // q[6] = Pixel Aspect Ratio @@ -731,6 +743,9 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) case gif_image_start: switch (*q) { case GIF_TRAILER: + if (IsMetadataDecode()) { + return; + } mGIFStruct.state = gif_done; break; @@ -837,6 +852,11 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) } mGIFStruct.delay_time = GETINT16(q + 1) * 10; + + if (mGIFStruct.delay_time > 0) { + PostIsAnimated(mGIFStruct.delay_time); + } + GETN(1, gif_consume_block); break; @@ -901,11 +921,20 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) break; case gif_image_header: { - if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) { - // We're about to get a second frame, but we only want the first. Stop - // decoding now. - mGIFStruct.state = gif_done; - break; + if (mGIFStruct.images_decoded == 1) { + if (!HasAnimation()) { + // We should've already called PostIsAnimated(); this must be a + // corrupt animated image with a first frame timeout of zero. Signal + // that we're animated now, before the first-frame decode early exit + // below, so that RasterImage can detect that this happened. + PostIsAnimated(/* aFirstFrameTimeout = */ 0); + } + if (IsFirstFrameDecode()) { + // We're about to get a second frame, but we only want the first. Stop + // decoding now. + mGIFStruct.state = gif_done; + break; + } } // Get image offsets, with respect to the screen origin @@ -938,6 +967,9 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) // If we were doing a metadata decode, we're done. if (IsMetadataDecode()) { + IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset, + mGIFStruct.width, mGIFStruct.height); + CheckForTransparency(frameRect); return; } } @@ -1108,7 +1140,9 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) // We shouldn't ever get here. default: - break; + MOZ_ASSERT_UNREACHABLE("Unexpected mGIFStruct.state"); + PostDecoderError(NS_ERROR_UNEXPECTED); + return; } } @@ -1145,8 +1179,6 @@ done: mLastFlushedRow = mCurrentRow; mLastFlushedPass = mCurrentPass; } - - return; } bool @@ -1179,6 +1211,5 @@ nsGIFDecoder2::SpeedHistogram() return Telemetry::IMAGE_DECODE_SPEED_GIF; } - } // namespace image } // namespace mozilla diff --git a/image/decoders/nsGIFDecoder2.h b/image/decoders/nsGIFDecoder2.h index ca5a4223a6..44aff60b8b 100644 --- a/image/decoders/nsGIFDecoder2.h +++ b/image/decoders/nsGIFDecoder2.h @@ -47,6 +47,7 @@ private: bool DoLzw(const uint8_t* q); bool SetHold(const uint8_t* buf, uint32_t count, const uint8_t* buf2 = nullptr, uint32_t count2 = 0); + void CheckForTransparency(gfx::IntRect aFrameRect); inline int ClearCode() const { return 1 << mGIFStruct.datasize; } diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index 6e0e2e50df..6bab3e5d24 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -378,8 +378,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) } if (!HasSize() && mContainedDecoder->HasSize()) { - PostSize(mContainedDecoder->GetImageMetadata().GetWidth(), - mContainedDecoder->GetImageMetadata().GetHeight()); + nsIntSize size = mContainedDecoder->GetSize(); + PostSize(size.width, size.height); } mPos += aCount; @@ -474,8 +474,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) return; } - PostSize(mContainedDecoder->GetImageMetadata().GetWidth(), - mContainedDecoder->GetImageMetadata().GetHeight()); + nsIntSize size = mContainedDecoder->GetSize(); + PostSize(size.width, size.height); // We have the size. If we're doing a metadata decode, we're done. if (IsMetadataDecode()) { diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp index 0b3f452599..ed1eb9aa83 100644 --- a/image/decoders/nsJPEGDecoder.cpp +++ b/image/decoders/nsJPEGDecoder.cpp @@ -80,7 +80,6 @@ METHODDEF(void) my_error_exit (j_common_ptr cinfo); // Normal JFIF markers can't have more bytes than this. #define MAX_JPEG_MARKER_LENGTH (((uint32_t)1 << 16) - 1) - nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle) : Decoder(aImage) @@ -390,7 +389,6 @@ nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_LOG(GetJPEGDecoderAccountingLog(), LogLevel::Debug, ("} (unknown colorpsace (3))")); return; - break; } } @@ -423,7 +421,6 @@ nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) } } - MOZ_LOG(GetJPEGDecoderAccountingLog(), LogLevel::Debug, (" JPEGDecoderAccounting: nsJPEGDecoder::" "Write -- created image frame with %ux%u pixels", @@ -453,7 +450,6 @@ nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) return; // I/O suspension } - // If this is a progressive JPEG ... mState = mInfo.buffered_image ? JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL; @@ -737,7 +733,6 @@ nsJPEGDecoder::OutputScanlines(bool* suspend) } } - // Override the standard error method in the IJG JPEG decoder code. METHODDEF(void) my_error_exit (j_common_ptr cinfo) @@ -841,7 +836,6 @@ skip_input_data (j_decompress_ptr jd, long num_bytes) } } - /******************************************************************************/ /* data source manager method This is called whenever bytes_in_buffer has reached zero and more @@ -961,7 +955,6 @@ term_source (j_decompress_ptr jd) } // namespace image } // namespace mozilla - ///*************** Inverted CMYK -> RGB conversion ************************* /// Input is (Inverted) CMYK stored as 4 bytes per pixel. /// Output is RGB stored as 3 bytes per pixel. diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp index 56ec234b00..6763ae56e8 100644 --- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -19,6 +19,8 @@ #include +using namespace mozilla::gfx; + namespace mozilla { namespace image { @@ -54,11 +56,6 @@ GetPNGDecoderAccountingLog() # define MOZ_PNG_MAX_PIX 268435456 // 256 Mpix = 16Ki x 16Ki #endif -// For metadata decodes. -#define WIDTH_OFFSET 16 -#define HEIGHT_OFFSET (WIDTH_OFFSET + 4) -#define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4) - nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() : mDispose(DisposalMethod::KEEP) , mBlend(BlendMethod::OVER) @@ -66,32 +63,33 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() { } #ifdef PNG_APNG_SUPPORTED + +int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo) +{ + // Delay, in seconds, is delayNum / delayDen. + png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo); + png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo); + + if (delayNum == 0) { + return 0; // SetFrameTimeout() will set to a minimum. + } + + if (delayDen == 0) { + delayDen = 100; // So says the APNG spec. + } + + // Need to cast delay_num to float to have a proper division and + // the result to int to avoid a compiler warning. + return static_cast(static_cast(delayNum) * 1000 / delayDen); +} + nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) : mDispose(DisposalMethod::KEEP) , mBlend(BlendMethod::OVER) , mTimeout(0) { - png_uint_16 delay_num, delay_den; - // delay, in seconds is delay_num/delay_den - png_byte dispose_op; - png_byte blend_op; - delay_num = png_get_next_frame_delay_num(aPNG, aInfo); - delay_den = png_get_next_frame_delay_den(aPNG, aInfo); - dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); - blend_op = png_get_next_frame_blend_op(aPNG, aInfo); - - if (delay_num == 0) { - mTimeout = 0; // SetFrameTimeout() will set to a minimum - } else { - if (delay_den == 0) { - delay_den = 100; // so says the APNG spec - } - - // Need to cast delay_num to float to have a proper division and - // the result to int to avoid compiler warning - mTimeout = static_cast(static_cast(delay_num) * - 1000 / delay_den); - } + png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); + png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo); if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { mDispose = DisposalMethod::RESTORE_PREVIOUS; @@ -106,6 +104,8 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) } else { mBlend = BlendMethod::OVER; } + + mTimeout = GetNextFrameDelay(aPNG, aInfo); } #endif @@ -148,6 +148,20 @@ nsPNGDecoder::~nsPNGDecoder() } } +void +nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat, + const IntRect& aFrameRect) +{ + // Check if the image has a transparent color in its palette. + if (aFormat == SurfaceFormat::B8G8R8A8) { + PostHasTransparency(); + } + + // PNGs shouldn't have first-frame padding. + MOZ_ASSERT_IF(mNumFrames == 0, + IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect)); +} + // CreateFrame() is used for both simple and animated images nsresult nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset, @@ -155,18 +169,10 @@ nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset, gfx::SurfaceFormat aFormat) { MOZ_ASSERT(HasSize()); + MOZ_ASSERT(!IsMetadataDecode()); - if (aFormat == gfx::SurfaceFormat::B8G8R8A8) { - PostHasTransparency(); - } - - nsIntRect frameRect(aXOffset, aYOffset, aWidth, aHeight); - if (mNumFrames == 0 && - !nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) { - // We need padding on the first frame, which means that we don't draw into - // part of the image at all. Report that as transparency. - PostHasTransparency(); - } + IntRect frameRect(aXOffset, aYOffset, aWidth, aHeight); + CheckForTransparency(aFormat, frameRect); // XXX(seth): Some tests depend on the first frame of PNGs being B8G8R8A8. // This is something we should fix. @@ -217,15 +223,6 @@ nsPNGDecoder::EndImageFrame() opacity = Opacity::OPAQUE; } -#ifdef PNG_APNG_SUPPORTED - uint32_t numFrames = GetFrameCount(); - - // We can't use mPNG->num_frames_read as it may be one ahead. - if (numFrames > 1) { - PostInvalidation(mFrameRect); - } -#endif - PostFrameStop(opacity, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend); } @@ -233,11 +230,6 @@ nsPNGDecoder::EndImageFrame() void nsPNGDecoder::InitInternal() { - // For metadata decodes, we don't need to initialize the PNG decoder. - if (IsMetadataDecode()) { - return; - } - mCMSMode = gfxPlatform::GetCMSMode(); if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) { mCMSMode = eCMSMode_Off; @@ -264,8 +256,6 @@ nsPNGDecoder::InitInternal() 122, 84, 88, 116, '\0'}; // zTXt #endif - // For full decodes, do png init stuff - // Initialize the container's source image header // Always decode to 24 bit pixdepth @@ -286,7 +276,7 @@ nsPNGDecoder::InitInternal() #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED // Ignore unused chunks - if (mCMSMode == eCMSMode_Off) { + if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) { png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2); } @@ -337,73 +327,24 @@ nsPNGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) { MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!"); - // If we only want width/height, we don't need to go through libpng. - if (IsMetadataDecode()) { - - // Are we done? - if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) { - return; - } - - // Scan the header for the width and height bytes - uint32_t pos = 0; - const uint8_t* bptr = (uint8_t*)aBuffer; - - while (pos < aCount && mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS) { - // Verify the signature bytes - if (mHeaderBytesRead < sizeof(pngSignatureBytes)) { - if (bptr[pos] != nsPNGDecoder::pngSignatureBytes[mHeaderBytesRead]) { - PostDataError(); - return; - } - } - - // Get width and height bytes into the buffer - if ((mHeaderBytesRead >= WIDTH_OFFSET) && - (mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS)) { - mSizeBytes[mHeaderBytesRead - WIDTH_OFFSET] = bptr[pos]; - } - pos ++; - mHeaderBytesRead ++; - } - - // If we're done now, verify the data and set up the container - if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) { - - // Grab the width and height, accounting for endianness (thanks libpng!) - uint32_t width = png_get_uint_32(mSizeBytes); - uint32_t height = png_get_uint_32(mSizeBytes + 4); - - // Check sizes against cap limits - if ((width > MOZ_PNG_MAX_WIDTH) || (height > MOZ_PNG_MAX_HEIGHT)) { - PostDataError(); - return; - } + // libpng uses setjmp/longjmp for error handling. Set it up. + if (setjmp(png_jmpbuf(mPNG))) { - // Post our size to the superclass - PostSize(width, height); + // We exited early. If mSuccessfulEarlyFinish isn't true, then we + // encountered an error. We might not really know what caused it, but it + // makes more sense to blame the data. + if (!mSuccessfulEarlyFinish && !HasError()) { + PostDataError(); } - // Otherwise, we're doing a standard decode - } else { - - // libpng uses setjmp/longjmp for error handling - set the buffer - if (setjmp(png_jmpbuf(mPNG))) { - - // We might not really know what caused the error, but it makes more - // sense to blame the data. - if (!mSuccessfulEarlyFinish && !HasError()) { - PostDataError(); - } - - png_destroy_read_struct(&mPNG, &mInfo, nullptr); - return; - } - - // Pass the data off to libpng - png_process_data(mPNG, mInfo, (unsigned char*)aBuffer, aCount); - + png_destroy_read_struct(&mPNG, &mInfo, nullptr); + return; } + + // Pass the data off to libpng. + png_process_data(mPNG, mInfo, + reinterpret_cast(const_cast((aBuffer))), + aCount); } // Sets up gamma pre-correction in libpng before our callback gets called. @@ -667,7 +608,24 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) } #ifdef PNG_APNG_SUPPORTED - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { + bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL); + if (isAnimated) { + decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr)); + } +#endif + + if (decoder->IsMetadataDecode()) { + decoder->CheckForTransparency(decoder->format, + IntRect(0, 0, width, height)); + + // We have the metadata we're looking for, so we don't need to decode any + // further. + decoder->mSuccessfulEarlyFinish = true; + png_longjmp(decoder->mPNG, 1); + } + +#ifdef PNG_APNG_SUPPORTED + if (isAnimated) { png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, nullptr); } @@ -744,7 +702,7 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, return; } - if (row_num >= (png_uint_32) decoder->mFrameRect.height) { + if (row_num >= static_cast(decoder->mFrameRect.height)) { return; } @@ -823,11 +781,14 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, png_longjmp(decoder->mPNG, 1); } - if (decoder->mNumFrames <= 1) { - // Only do incremental image display for the first frame - // XXXbholley - this check should be handled in the superclass - nsIntRect r(0, row_num, width, 1); - decoder->PostInvalidation(r); + if (!decoder->interlacebuf) { + // Do line-by-line partial invalidations for non-interlaced images + decoder->PostInvalidation(IntRect(0, row_num, width, 1)); + } else if (row_num == + static_cast(decoder->mFrameRect.height - 1)) { + // Do only one full image invalidation for each pass (Bug 1187569) + decoder->PostInvalidation(IntRect(0, 0, width, + decoder->mFrameRect.height)); } } } diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h index af8fdf365d..fce64d1f1e 100644 --- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -35,6 +35,9 @@ public: gfx::SurfaceFormat aFormat); void EndImageFrame(); + void CheckForTransparency(gfx::SurfaceFormat aFormat, + const gfx::IntRect& aFrameRect); + // Check if PNG is valid ICO (32bpp RGBA) // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx bool IsValidICO() const diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp index 11e2f7d71d..8e5b31d73e 100644 --- a/image/imgFrame.cpp +++ b/image/imgFrame.cpp @@ -1121,42 +1121,28 @@ imgFrame::SetCompositingFailed(bool val) mCompositingFailed = val; } -size_t -imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation, - MallocSizeOf aMallocSizeOf) const +void +imgFrame::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const { MonitorAutoLock lock(mMonitor); - // aMallocSizeOf is only used if aLocation is - // gfxMemoryLocation::IN_PROCESS_HEAP. It - // should be nullptr otherwise. - MOZ_ASSERT( - (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && aMallocSizeOf) || - (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf), - "mismatch between aLocation and aMallocSizeOf"); - - size_t n = 0; - - if (mPalettedImageData && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { - n += aMallocSizeOf(mPalettedImageData); - } - if (mImageSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { - n += aMallocSizeOf(mImageSurface); + if (mPalettedImageData) { + aHeapSizeOut += aMallocSizeOf(mPalettedImageData); } - if (mOptSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { - n += aMallocSizeOf(mOptSurface); + if (mImageSurface) { + aHeapSizeOut += aMallocSizeOf(mImageSurface); } - - if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { - n += aMallocSizeOf(mVBuf); - n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); + if (mOptSurface) { + aHeapSizeOut += aMallocSizeOf(mOptSurface); } - if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_NONHEAP) { - n += mVBuf->NonHeapSizeOfExcludingThis(); + if (mVBuf) { + aHeapSizeOut += aMallocSizeOf(mVBuf); + aHeapSizeOut += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); + aNonHeapSizeOut += mVBuf->NonHeapSizeOfExcludingThis(); } - - return n; } } // namespace image diff --git a/image/imgFrame.h b/image/imgFrame.h index 769fa13201..89724c46f4 100644 --- a/image/imgFrame.h +++ b/image/imgFrame.h @@ -264,8 +264,8 @@ public: already_AddRefed GetSurface(); already_AddRefed GetDrawTarget(); - size_t SizeOfExcludingThis(gfxMemoryLocation aLocation, - MallocSizeOf aMallocSizeOf) const; + void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const; private: // methods diff --git a/image/imgRequest.cpp b/image/imgRequest.cpp index a9168609e9..e1606283af 100644 --- a/image/imgRequest.cpp +++ b/image/imgRequest.cpp @@ -64,6 +64,7 @@ imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey) : mLoader(aLoader) , mCacheKey(aCacheKey) , mLoadId(nullptr) + , mFirstProxy(nullptr) , mValidator(nullptr) , mInnerWindowId(0) , mCORSMode(imgIRequest::CORS_NONE) @@ -218,6 +219,12 @@ imgRequest::AddProxy(imgRequestProxy* proxy) NS_PRECONDITION(proxy, "null imgRequestProxy passed in"); LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy); + if (!mFirstProxy) { + // Save a raw pointer to the first proxy we see, for use in the network + // priority logic. + mFirstProxy = proxy; + } + // If we're empty before adding, we have to tell the loader we now have // proxies. nsRefPtr progressTracker = GetProgressTracker(); @@ -535,8 +542,7 @@ imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta) // concern though is that image loads remain lower priority than other pieces // of content such as link clicks, CSS, and JS. // - nsRefPtr progressTracker = GetProgressTracker(); - if (!progressTracker->FirstObserverIs(proxy)) { + if (!mFirstProxy || proxy != mFirstProxy) { return; } diff --git a/image/imgRequest.h b/image/imgRequest.h index dc592fd3a5..5361934f79 100644 --- a/image/imgRequest.h +++ b/image/imgRequest.h @@ -256,6 +256,10 @@ private: void* mLoadId; + /// Raw pointer to the first proxy that was added to this imgRequest. Use only + /// pointer comparisons; there's no guarantee this will remain valid. + void* mFirstProxy; + imgCacheValidator* mValidator; nsCOMPtr mRedirectCallback; nsCOMPtr mNewRedirectChannel; diff --git a/image/imgTools.cpp b/image/imgTools.cpp index 5fd101fa4e..6a2688b3a7 100644 --- a/image/imgTools.cpp +++ b/image/imgTools.cpp @@ -25,10 +25,10 @@ #include "imgIScriptedNotificationObserver.h" #include "gfxPlatform.h" -using namespace mozilla; -using namespace mozilla::image; using namespace mozilla::gfx; +namespace mozilla { +namespace image { /* ========== imgITools implementation ========== */ @@ -349,3 +349,6 @@ imgTools::GetImgCacheForDocument(nsIDOMDocument* aDoc, imgICache** aCache) NS_ENSURE_SUCCESS(rv, rv); return CallQueryInterface(loader, aCache); } + +} // namespace image +} // namespace mozilla diff --git a/image/imgTools.h b/image/imgTools.h index e82af9624a..52d9d74435 100644 --- a/image/imgTools.h +++ b/image/imgTools.h @@ -17,6 +17,9 @@ {0xbd, 0xef, 0x2c, 0x7a, 0xe2, 0x49, 0x96, 0x7a} \ } +namespace mozilla { +namespace image { + class imgTools final : public imgITools { public: @@ -28,4 +31,8 @@ public: private: virtual ~imgTools(); }; + +} // namespace image +} // namespace mozilla + #endif // mozilla_image_imgITools_h diff --git a/image/moz.build b/image/moz.build index 6cd8237742..605a30eec9 100644 --- a/image/moz.build +++ b/image/moz.build @@ -61,7 +61,6 @@ UNIFIED_SOURCES += [ 'Image.cpp', 'ImageCacheKey.cpp', 'ImageFactory.cpp', - 'ImageMetadata.cpp', 'ImageOps.cpp', 'ImageWrapper.cpp', 'imgFrame.cpp', diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp index 0cd30c61ce..40946a9815 100644 --- a/image/test/gtest/Common.cpp +++ b/image/test/gtest/Common.cpp @@ -15,6 +15,7 @@ #include "nsIProperties.h" #include "nsNetUtil.h" #include "mozilla/nsRefPtr.h" +#include "nsStreamUtils.h" #include "nsString.h" namespace mozilla { @@ -71,6 +72,15 @@ LoadFile(const char* aRelativePath) rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file); ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + // Ensure the resulting input stream is buffered. + if (!NS_InputStreamIsBuffered(inputStream)) { + nsCOMPtr bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), + inputStream, 1024); + ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + inputStream = bufStream; + } + return inputStream.forget(); } @@ -126,7 +136,7 @@ ImageTestCase GreenGIFTestCase() ImageTestCase GreenJPGTestCase() { return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100), - /* aFuzzy = */ true); + TEST_CASE_IS_FUZZY); } ImageTestCase GreenBMPTestCase() @@ -136,22 +146,76 @@ ImageTestCase GreenBMPTestCase() ImageTestCase GreenICOTestCase() { - return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100)); + // This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default + // when the BMP is embedded in an ICO, so it's transparent. + return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100), + TEST_CASE_IS_TRANSPARENT); } ImageTestCase GreenFirstFrameAnimatedGIFTestCase() { - return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100)); + return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100), + TEST_CASE_IS_ANIMATED); } ImageTestCase GreenFirstFrameAnimatedPNGTestCase() { - return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100)); + return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100), + TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED); } ImageTestCase CorruptTestCase() { - return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100)); + return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100), + TEST_CASE_HAS_ERROR); +} + +ImageTestCase TransparentPNGTestCase() +{ + return ImageTestCase("transparent.png", "image/png", IntSize(32, 32), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase TransparentGIFTestCase() +{ + return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase FirstFramePaddingGIFTestCase() +{ + return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase() +{ + // Note that we only decode this test case as transparent when the BMP decoder + // is set to use alpha data. (That's not the default, which is why it's not marked + // TEST_CASE_IS_TRANSPARENT; tests that want to treat this testcase as + // transparent need to handle this case manually.) + return ImageTestCase("transparent.bmp", "image/bmp", IntSize(32, 32)); +} + +ImageTestCase RLE4BMPTestCase() +{ + return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase RLE8BMPTestCase() +{ + return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase NoFrameDelayGIFTestCase() +{ + // This is an invalid (or at least, questionably valid) GIF that's animated + // even though it specifies a frame delay of zero. It's animated, but it's not + // marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that + // it's animated. + return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100)); } } // namespace mozilla diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h index b9d8efa814..6864c09a2b 100644 --- a/image/test/gtest/Common.h +++ b/image/test/gtest/Common.h @@ -17,22 +17,31 @@ namespace mozilla { // Types /////////////////////////////////////////////////////////////////////////////// +enum TestCaseFlags +{ + TEST_CASE_DEFAULT_FLAGS = 0, + TEST_CASE_IS_FUZZY = 1 << 0, + TEST_CASE_HAS_ERROR = 1 << 1, + TEST_CASE_IS_TRANSPARENT = 1 << 2, + TEST_CASE_IS_ANIMATED = 1 << 3, +}; + struct ImageTestCase { ImageTestCase(const char* aPath, const char* aMimeType, gfx::IntSize aSize, - bool aFuzzy = false) + uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS) : mPath(aPath) , mMimeType(aMimeType) , mSize(aSize) - , mFuzzy(aFuzzy) + , mFlags(aFlags) { } const char* mPath; const char* mMimeType; gfx::IntSize mSize; - bool mFuzzy; + uint32_t mFlags; }; struct BGRAColor @@ -86,6 +95,15 @@ ImageTestCase GreenFirstFrameAnimatedPNGTestCase(); ImageTestCase CorruptTestCase(); +ImageTestCase TransparentPNGTestCase(); +ImageTestCase TransparentGIFTestCase(); +ImageTestCase FirstFramePaddingGIFTestCase(); +ImageTestCase NoFrameDelayGIFTestCase(); + +ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase(); +ImageTestCase RLE4BMPTestCase(); +ImageTestCase RLE8BMPTestCase(); + } // namespace mozilla #endif // mozilla_image_test_gtest_Common_h diff --git a/image/test/gtest/TestCopyOnWrite.cpp b/image/test/gtest/TestCopyOnWrite.cpp new file mode 100644 index 0000000000..0d420b6722 --- /dev/null +++ b/image/test/gtest/TestCopyOnWrite.cpp @@ -0,0 +1,235 @@ +/* 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 "gtest/gtest.h" + +#include "CopyOnWrite.h" + +using namespace mozilla; +using namespace mozilla::image; + +struct ValueStats +{ + int32_t mCopies = 0; + int32_t mFrees = 0; + int32_t mCalls = 0; + int32_t mConstCalls = 0; + int32_t mSerial = 0; +}; + +struct Value +{ + NS_INLINE_DECL_REFCOUNTING(Value) + + explicit Value(ValueStats& aStats) + : mStats(aStats) + , mSerial(mStats.mSerial++) + { } + + Value(const Value& aOther) + : mStats(aOther.mStats) + , mSerial(mStats.mSerial++) + { + mStats.mCopies++; + } + + void Go() { mStats.mCalls++; } + void Go() const { mStats.mConstCalls++; } + + int32_t Serial() const { return mSerial; } + +protected: + ~Value() { mStats.mFrees++; } + +private: + ValueStats& mStats; + int32_t mSerial; +}; + +TEST(ImageCopyOnWrite, Read) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + }); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + } + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); +} + +TEST(ImageCopyOnWrite, RecursiveRead) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + // Make sure that Read() inside a Read() succeeds. + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + }, []() { + // This gets called if we can't read. We shouldn't get here. + EXPECT_TRUE(false); + }); + }); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + } + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); +} + +TEST(ImageCopyOnWrite, Write) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + cow.Write([&](Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(!cow.CanRead()); + EXPECT_TRUE(!cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(0, stats.mConstCalls); + }); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(0, stats.mConstCalls); + } + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); +} + +TEST(ImageCopyOnWrite, WriteRecursive) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + // Make sure Write() inside a Read() succeeds. + cow.Write([&](Value* aValue) { + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(1, aValue->Serial()); + EXPECT_TRUE(!cow.CanRead()); + EXPECT_TRUE(!cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(0, stats.mConstCalls); + + // Make sure Read() inside a Write() fails. + cow.Read([](const Value* aValue) { + // This gets called if we can read. We shouldn't get here. + EXPECT_TRUE(false); + }, []() { + // This gets called if we can't read. We *should* get here. + EXPECT_TRUE(true); + }); + + // Make sure Write() inside a Write() fails. + cow.Write([](Value* aValue) { + // This gets called if we can write. We shouldn't get here. + EXPECT_TRUE(false); + }, []() { + // This gets called if we can't write. We *should* get here. + EXPECT_TRUE(true); + }); + }, []() { + // This gets called if we can't write. We shouldn't get here. + EXPECT_TRUE(false); + }); + + aValue->Go(); + + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + }); + + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + } + + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(2, stats.mFrees); +} diff --git a/image/test/gtest/TestDecodeToSurface.cpp b/image/test/gtest/TestDecodeToSurface.cpp index 5a4696f8cd..f48cff6906 100644 --- a/image/test/gtest/TestDecodeToSurface.cpp +++ b/image/test/gtest/TestDecodeToSurface.cpp @@ -61,7 +61,8 @@ public: surface->GetFormat() == SurfaceFormat::B8G8R8A8); EXPECT_EQ(mTestCase.mSize, surface->GetSize()); - EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), mTestCase.mFuzzy)); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), + mTestCase.mFlags & TEST_CASE_IS_FUZZY)); } private: diff --git a/image/test/gtest/TestDecoders.cpp b/image/test/gtest/TestDecoders.cpp new file mode 100644 index 0000000000..4fa7b906a6 --- /dev/null +++ b/image/test/gtest/TestDecoders.cpp @@ -0,0 +1,249 @@ +/* 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 "gtest/gtest.h" + +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "decoders/nsBMPDecoder.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "ImageFactory.h" +#include "mozilla/gfx/2D.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "mozilla/nsRefPtr.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "ProgressTracker.h" +#include "SourceBuffer.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +TEST(ImageDecoders, ImageModuleAvailable) +{ + // We can run into problems if XPCOM modules get initialized in the wrong + // order. It's important that this test run first, both as a sanity check and + // to ensure we get the module initialization order we want. + nsCOMPtr imgTools = + do_CreateInstance("@mozilla.org/image/tools;1"); + EXPECT_TRUE(imgTools != nullptr); +} + +static void +CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder) +{ + EXPECT_TRUE(aDecoder->GetDecodeDone()); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), + aDecoder->HasError()); + EXPECT_TRUE(!aDecoder->WasAborted()); + + // Verify that the decoder made the expected progress. + Progress progress = aDecoder->TakeProgress(); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), + bool(progress & FLAG_HAS_ERROR)); + + if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { + return; // That's all we can check for bad images. + } + + EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE)); + EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE)); + EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE)); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT), + bool(progress & FLAG_HAS_TRANSPARENCY)); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), + bool(progress & FLAG_IS_ANIMATED)); + + // The decoder should get the correct size. + IntSize size = aDecoder->GetSize(); + EXPECT_EQ(aTestCase.mSize.width, size.width); + EXPECT_EQ(aTestCase.mSize.height, size.height); + + // Get the current frame, which is always the first frame of the image + // because CreateAnonymousDecoder() forces a first-frame-only decode. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + nsRefPtr surface = currentFrame->GetSurface(); + + // Verify that the resulting surfaces matches our expectations. + EXPECT_EQ(SurfaceType::DATA, surface->GetType()); + EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 || + surface->GetFormat() == SurfaceFormat::B8G8R8A8); + EXPECT_EQ(aTestCase.mSize, surface->GetSize()); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), + aTestCase.mFlags & TEST_CASE_IS_FUZZY)); +} + +static void +CheckDecoderSingleChunk(const ImageTestCase& aTestCase) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into a SourceBuffer. + nsRefPtr sourceBuffer = new SourceBuffer(); + sourceBuffer->ExpectLength(length); + rv = sourceBuffer->AppendFromInputStream(inputStream, length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + sourceBuffer->Complete(NS_OK); + + // Create a decoder. + DecoderType decoderType = + DecoderFactory::GetDecoderType(aTestCase.mMimeType); + nsRefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, + DefaultSurfaceFlags()); + ASSERT_TRUE(decoder != nullptr); + + // Run the full decoder synchronously. + decoder->Decode(); + + CheckDecoderResults(aTestCase, decoder); +} + +class NoResume : public IResumable +{ +public: + NS_INLINE_DECL_REFCOUNTING(NoResume, override) + virtual void Resume() override { } + +private: + ~NoResume() { } +}; + +static void +CheckDecoderMultiChunk(const ImageTestCase& aTestCase) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Create a SourceBuffer and a decoder. + nsRefPtr sourceBuffer = new SourceBuffer(); + sourceBuffer->ExpectLength(length); + DecoderType decoderType = + DecoderFactory::GetDecoderType(aTestCase.mMimeType); + nsRefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, + DefaultSurfaceFlags()); + ASSERT_TRUE(decoder != nullptr); + + // Decode synchronously, using a |NoResume| IResumable so the Decoder doesn't + // attempt to schedule itself on a nonexistent DecodePool when we write more + // data into the SourceBuffer. + nsRefPtr noResume = new NoResume(); + for (uint64_t read = 0; read < length ; ++read) { + uint64_t available = 0; + rv = inputStream->Available(&available); + ASSERT_TRUE(available > 0); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = sourceBuffer->AppendFromInputStream(inputStream, 1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + decoder->Decode(noResume); + } + + sourceBuffer->Complete(NS_OK); + decoder->Decode(noResume); + + CheckDecoderResults(aTestCase, decoder); +} + +TEST(ImageDecoders, PNGSingleChunk) +{ + CheckDecoderSingleChunk(GreenPNGTestCase()); +} + +TEST(ImageDecoders, PNGMultiChunk) +{ + CheckDecoderMultiChunk(GreenPNGTestCase()); +} + +TEST(ImageDecoders, GIFSingleChunk) +{ + CheckDecoderSingleChunk(GreenGIFTestCase()); +} + +TEST(ImageDecoders, GIFMultiChunk) +{ + CheckDecoderMultiChunk(GreenGIFTestCase()); +} + +TEST(ImageDecoders, JPGSingleChunk) +{ + CheckDecoderSingleChunk(GreenJPGTestCase()); +} + +TEST(ImageDecoders, JPGMultiChunk) +{ + CheckDecoderMultiChunk(GreenJPGTestCase()); +} + +TEST(ImageDecoders, BMPSingleChunk) +{ + CheckDecoderSingleChunk(GreenBMPTestCase()); +} + +TEST(ImageDecoders, BMPMultiChunk) +{ + CheckDecoderMultiChunk(GreenBMPTestCase()); +} + +TEST(ImageDecoders, ICOSingleChunk) +{ + CheckDecoderSingleChunk(GreenICOTestCase()); +} + +// XXX(seth): Disabled. We'll fix this in bug 1196066. +TEST(ImageDecoders, DISABLED_ICOMultiChunk) +{ + CheckDecoderMultiChunk(GreenICOTestCase()); +} + +TEST(ImageDecoders, AnimatedGIFSingleChunk) +{ + CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST(ImageDecoders, AnimatedGIFMultiChunk) +{ + CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST(ImageDecoders, AnimatedPNGSingleChunk) +{ + CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST(ImageDecoders, AnimatedPNGMultiChunk) +{ + CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST(ImageDecoders, CorruptSingleChunk) +{ + CheckDecoderSingleChunk(CorruptTestCase()); +} + +TEST(ImageDecoders, CorruptMultiChunk) +{ + CheckDecoderMultiChunk(CorruptTestCase()); +} diff --git a/image/test/gtest/TestMetadata.cpp b/image/test/gtest/TestMetadata.cpp new file mode 100644 index 0000000000..a01833104e --- /dev/null +++ b/image/test/gtest/TestMetadata.cpp @@ -0,0 +1,254 @@ +/* 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 "gtest/gtest.h" + +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "decoders/nsBMPDecoder.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "ImageFactory.h" +#include "mozilla/gfx/2D.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "mozilla/nsRefPtr.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "ProgressTracker.h" +#include "SourceBuffer.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +TEST(ImageMetadata, ImageModuleAvailable) +{ + // We can run into problems if XPCOM modules get initialized in the wrong + // order. It's important that this test run first, both as a sanity check and + // to ensure we get the module initialization order we want. + nsCOMPtr imgTools = + do_CreateInstance("@mozilla.org/image/tools;1"); + EXPECT_TRUE(imgTools != nullptr); +} + +enum class BMPAlpha +{ + DISABLED, + ENABLED +}; + +static void +CheckMetadata(const ImageTestCase& aTestCase, + BMPAlpha aBMPAlpha = BMPAlpha::DISABLED) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into a SourceBuffer. + nsRefPtr sourceBuffer = new SourceBuffer(); + sourceBuffer->ExpectLength(length); + rv = sourceBuffer->AppendFromInputStream(inputStream, length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + sourceBuffer->Complete(NS_OK); + + // Create a metadata decoder. + DecoderType decoderType = + DecoderFactory::GetDecoderType(aTestCase.mMimeType); + nsRefPtr decoder = + DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer); + ASSERT_TRUE(decoder != nullptr); + + if (aBMPAlpha == BMPAlpha::ENABLED) { + static_cast(decoder.get())->SetUseAlphaData(true); + } + + // Run the metadata decoder synchronously. + decoder->Decode(); + + // Ensure that the metadata decoder didn't make progress it shouldn't have + // (which would indicate that it decoded past the header of the image). + Progress metadataProgress = decoder->TakeProgress(); + EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE | + FLAG_HAS_TRANSPARENCY | + FLAG_IS_ANIMATED))); + + // If the test case is corrupt, assert what we can and return early. + if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { + EXPECT_TRUE(decoder->GetDecodeDone()); + EXPECT_TRUE(decoder->HasError()); + return; + } + + EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); + + // Check that we got the expected metadata. + EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE); + + IntSize metadataSize = decoder->GetSize(); + EXPECT_EQ(aTestCase.mSize.width, metadataSize.width); + EXPECT_EQ(aTestCase.mSize.height, metadataSize.height); + + bool expectTransparency = aBMPAlpha == BMPAlpha::ENABLED + ? true + : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT); + EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY)); + + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), + bool(metadataProgress & FLAG_IS_ANIMATED)); + + // Create a full decoder, so we can compare the result. + decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, + imgIContainer::DECODE_FLAGS_DEFAULT); + ASSERT_TRUE(decoder != nullptr); + + if (aBMPAlpha == BMPAlpha::ENABLED) { + static_cast(decoder.get())->SetUseAlphaData(true); + } + + // Run the full decoder synchronously. + decoder->Decode(); + + EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); + Progress fullProgress = decoder->TakeProgress(); + + // If the metadata decoder set a progress bit, the full decoder should also + // have set the same bit. + EXPECT_EQ(fullProgress, metadataProgress | fullProgress); + + // The full decoder and the metadata decoder should agree on the image's size. + IntSize fullSize = decoder->GetSize(); + EXPECT_EQ(metadataSize.width, fullSize.width); + EXPECT_EQ(metadataSize.height, fullSize.height); + + // We should not discover transparency during the full decode that we didn't + // discover during the metadata decode, unless the image is animated. + EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) || + (metadataProgress & FLAG_HAS_TRANSPARENCY) || + (fullProgress & FLAG_IS_ANIMATED)); +} + +TEST(ImageMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); } +TEST(ImageMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); } +TEST(ImageMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); } +TEST(ImageMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); } +TEST(ImageMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); } +TEST(ImageMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); } +TEST(ImageMetadata, ICO) { CheckMetadata(GreenICOTestCase()); } + +TEST(ImageMetadata, AnimatedGIF) +{ + CheckMetadata(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST(ImageMetadata, AnimatedPNG) +{ + CheckMetadata(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST(ImageMetadata, FirstFramePaddingGIF) +{ + CheckMetadata(FirstFramePaddingGIFTestCase()); +} + +TEST(ImageMetadata, TransparentBMPWithBMPAlphaOff) +{ + CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED); +} + +TEST(ImageMetadata, TransparentBMPWithBMPAlphaOn) +{ + CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED); +} + +TEST(ImageMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); } +TEST(ImageMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); } + +TEST(ImageMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); } + +TEST(ImageMetadata, NoFrameDelayGIF) +{ + CheckMetadata(NoFrameDelayGIFTestCase()); +} + +TEST(ImageMetadata, NoFrameDelayGIFFullDecode) +{ + ImageTestCase testCase = NoFrameDelayGIFTestCase(); + + // The previous test (NoFrameDelayGIF) verifies that we *don't* detect that + // this test case is animated, because it has a zero frame delay for the first + // frame. This test verifies that when we do a full decode, we detect the + // animation at that point and successfully decode all the frames. + + // Create an image. + nsRefPtr image = + ImageFactory::CreateAnonymousImage(nsAutoCString(testCase.mMimeType)); + ASSERT_TRUE(!image->HasError()); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into the image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, + static_cast(length)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Let the image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsRefPtr tracker = image->GetProgressTracker(); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + + // Use GetFrame() to force a sync decode of the image. + nsRefPtr surface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that the image's metadata meets our expectations. + IntSize imageSize(0, 0); + rv = image->GetWidth(&imageSize.width); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = image->GetHeight(&imageSize.height); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + EXPECT_EQ(testCase.mSize.width, imageSize.width); + EXPECT_EQ(testCase.mSize.height, imageSize.height); + + Progress imageProgress = tracker->GetProgress(); + + EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); + EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); + + // Ensure that we decoded both frames of the image. + LookupResult firstFrameLookupResult = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + imgIContainer::DECODE_FLAGS_DEFAULT, + /* aFrameNum = */ 0)); + EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type()); + + LookupResult secondFrameLookupResult = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + imgIContainer::DECODE_FLAGS_DEFAULT, + /* aFrameNum = */ 1)); + EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type()); +} diff --git a/image/test/gtest/TestStreamingLexer.cpp b/image/test/gtest/TestStreamingLexer.cpp new file mode 100644 index 0000000000..544e3947db --- /dev/null +++ b/image/test/gtest/TestStreamingLexer.cpp @@ -0,0 +1,266 @@ +/* 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 "gtest/gtest.h" + +#include "mozilla/Vector.h" +#include "StreamingLexer.h" + +using namespace mozilla; +using namespace mozilla::image; + +enum class TestState +{ + ONE, + TWO, + THREE, + UNBUFFERED, + SUCCESS, + FAILURE +}; + +void +CheckData(const char* aData, size_t aLength) +{ + EXPECT_TRUE(aLength == 3); + EXPECT_EQ(1, aData[0]); + EXPECT_EQ(2, aData[1]); + EXPECT_EQ(3, aData[2]); +} + +LexerTransition +DoLex(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckData(aData, aLength); + return Transition::To(TestState::TWO, 3); + case TestState::TWO: + CheckData(aData, aLength); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckData(aData, aLength); + return Transition::Terminate(TestState::SUCCESS); + default: + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::Terminate(TestState::FAILURE); + } +} + +LexerTransition +DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength, + Vector& aUnbufferedVector) +{ + switch (aState) { + case TestState::ONE: + CheckData(aData, aLength); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::UNBUFFERED: + EXPECT_TRUE(aLength <= 3); + aUnbufferedVector.append(aData, aLength); + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + case TestState::TWO: + CheckData(aUnbufferedVector.begin(), aUnbufferedVector.length()); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckData(aData, aLength); + return Transition::Terminate(TestState::SUCCESS); + default: + EXPECT_TRUE(false); + return Transition::Terminate(TestState::FAILURE); + } +} + +LexerTransition +DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckData(aData, aLength); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::UNBUFFERED: + return Transition::Terminate(TestState::SUCCESS); + default: + EXPECT_TRUE(false); + return Transition::Terminate(TestState::FAILURE); + } +} + +TEST(ImageStreamingLexer, SingleChunk) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + + // Test delivering all the data at once. + Maybe result = lexer.Lex(data, sizeof(data), DoLex); + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); +} + +TEST(ImageStreamingLexer, SingleChunkWithUnbuffered) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + Vector unbufferedVector; + + // Test delivering all the data at once. + Maybe result = + lexer.Lex(data, sizeof(data), + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); +} + +TEST(ImageStreamingLexer, ChunkPerState) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0 ; i < 3 ; ++i) { + Maybe result = lexer.Lex(data + 3 * i, 3, DoLex); + + if (i == 2) { + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); + } else { + EXPECT_TRUE(result.isNothing()); + } + } +} + +TEST(ImageStreamingLexer, ChunkPerStateWithUnbuffered) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + Vector unbufferedVector; + + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0 ; i < 3 ; ++i) { + Maybe result = + lexer.Lex(data + 3 * i, 3, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + if (i == 2) { + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); + } else { + EXPECT_TRUE(result.isNothing()); + } + } +} + +TEST(ImageStreamingLexer, OneByteChunks) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + + // Test delivering in one byte chunks. + for (unsigned i = 0 ; i < 9 ; ++i) { + Maybe result = lexer.Lex(data + i, 1, DoLex); + + if (i == 8) { + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); + } else { + EXPECT_TRUE(result.isNothing()); + } + } +} + +TEST(ImageStreamingLexer, OneByteChunksWithUnbuffered) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + Vector unbufferedVector; + + // Test delivering in one byte chunks. + for (unsigned i = 0 ; i < 9 ; ++i) { + Maybe result = + lexer.Lex(data + i, 1, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + if (i == 8) { + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); + } else { + EXPECT_TRUE(result.isNothing()); + } + } +} + +TEST(ImageStreamingLexer, TerminateSuccess) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + + // Test that Terminate is "sticky". + Maybe result = + lexer.Lex(data, sizeof(data), + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(aState == TestState::ONE); + return Transition::Terminate(TestState::SUCCESS); + }); + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); + + result = + lexer.Lex(data, sizeof(data), + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::Terminate(TestState::FAILURE); + }); + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); +} + +TEST(ImageStreamingLexer, TerminateFailure) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + + // Test that Terminate is "sticky". + Maybe result = + lexer.Lex(data, sizeof(data), + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(aState == TestState::ONE); + return Transition::Terminate(TestState::FAILURE); + }); + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::FAILURE, *result); + + result = + lexer.Lex(data, sizeof(data), + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::Terminate(TestState::FAILURE); + }); + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::FAILURE, *result); +} + +TEST(ImageStreamingLexer, TerminateUnbuffered) +{ + StreamingLexer lexer(Transition::To(TestState::ONE, 3)); + char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; + + // Test that Terminate works during an unbuffered read. + for (unsigned i = 0 ; i < 9 ; ++i) { + Maybe result = + lexer.Lex(data + i, 1, DoLexWithUnbufferedTerminate); + + if (i > 2) { + EXPECT_TRUE(result.isSome()); + EXPECT_EQ(TestState::SUCCESS, *result); + } else { + EXPECT_TRUE(result.isNothing()); + } + } +} diff --git a/image/test/gtest/first-frame-padding.gif b/image/test/gtest/first-frame-padding.gif new file mode 100644 index 0000000000..e69de29bb2 diff --git a/image/test/gtest/moz.build b/image/test/gtest/moz.build index a01292506c..0e768241de 100644 --- a/image/test/gtest/moz.build +++ b/image/test/gtest/moz.build @@ -8,18 +8,29 @@ Library('imagetest') UNIFIED_SOURCES = [ 'Common.cpp', + 'TestCopyOnWrite.cpp', + 'TestDecoders.cpp', 'TestDecodeToSurface.cpp', + 'TestMetadata.cpp', + 'TestStreamingLexer.cpp', ] TEST_HARNESS_FILES.gtest += [ 'corrupt.jpg', 'first-frame-green.gif', 'first-frame-green.png', + 'first-frame-padding.gif', 'green.bmp', 'green.gif', 'green.ico', 'green.jpg', 'green.png', + 'no-frame-delay.gif', + 'rle4.bmp', + 'rle8.bmp', + 'transparent.bmp', + 'transparent.gif', + 'transparent.png', ] LOCAL_INCLUDES += [ diff --git a/image/test/gtest/no-frame-delay.gif b/image/test/gtest/no-frame-delay.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c50b67431fb99d6542dbb5433b1a29641dc2c4a GIT binary patch literal 317 zcmZ?wbhEHbOkqf2_`txx@E?d76o0aSCbCU^SwR4`P&|OOncV3?A57l@3@bD^11%4_xZ1L-~aLRaJ3&<^s&Q7 zYxg(%)P8t~3SOWmpOg})TnSOb?Wf!dwrMno=60lzDo_`zO>KCiE@P;SOpL1E3%b;`U# znn#M1eTsxRq06jvR_saZ;BBwmmANiGt+7@v1MHa-=7fAgU)QM)7itBljFH#uGnWs< zex;}}ID<8{HFKuxggK$dyke|ZT%cPRmp(c!_v6Kbmn`x3jxlkyTG_${7t;&IM zXpkvrp8$Q)zLX&w-#pZJEEZpR$2TGyqOc|Qxj_?!ubh8+dXi^(k&1kjBeRyPMT(D) z&(G9heXmqte?;Na(NV3I`|r< z=I!mRoN%^YuOA<8T$w|@+~42JEu?*Naw2u(v+>rWnR_&)ZhqENaFkNf{7#P=Tetn! zjU|#hnaVXvt#+&3I6FH#J>}F^hc&#uhP2=`Xq{?{f8cBu&m@K&MR(C1WC|G$hu0gb zv899rcTZ)Aa=+DTH5-kFmoJbRBo1S`kYGC95dD6uNyH<<8$Qft;Vh2hC=Q~C$cXzP zee5)wUbEqPo~NI~3d0%f5Va@>g3)L?8ufa;PN&}?w}Q8A;FXz~Pk3!@->p z7Du2Rrvux&xVX?d+;GBA7>WLaqwFsr6iiH5d$FlP>yv!8ekVdRlzQ g<)weQQUC7U?6Bzy9s3Or$M^k99{!wv_mDLH0PnoJF8}}l literal 0 HcmV?d00001 diff --git a/image/test/gtest/transparent.bmp b/image/test/gtest/transparent.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c3ee2289598752d21f1f922520f5430f689382f3 GIT binary patch literal 4234 zcmciEF>ez=5CvdIK}b*`A=-!vr6x5^8kB+pYU=y}TDLTjtkjY63-SXj+R(bT=+e5N za7BIG7xKvJSnkfoAcDU_a zZ?WyeqmTD4Vzk2zW_plw$>X8sQexHc6US4pcFDu6Q=jd_PoA7z@bJb`GnU439`V=g zY&L5ai^bNTm9BOeeCm$n;ErBw`W9>2)zVuFer!#9>BDyueD9~G9VWluK^6>WJZkyj zAhrp9YUh3|9D6*}i$A_T9%J#nm$Gj5(VnxJmYQMbUDSB+jrm*-X%D{lk(KuaKXG_g z&g>Arzi(xJ_R+2$e(bo2SU+QWCK|CvqlVXFnBrk|c#jIY}l=b_%i z&rIRD^Ed6$$!4}}I7LHjv<+DtQ#0CAL+#8%FYObPiz`1HKRZ@CZr(?<>!YP!Y*(YR zp8356J3T9BAU>qKjF)xP$EVbT4;*=`)#|rqHJruVan~9%_Y$8LHA>9v)s43Gdi_V9 zSZ3(XwtxS_xyvKiw97FszVKu5V)ZF?N`0H|VbLA!YV*P6GhaON?!@G5tG&jLj_^?1 zT-CR?^vM|yG2Ci({?&&^`VB8yaiHJ(+vd$}$}2qFquIhoA0A43jU&v=T@Sp6UA(y( TR-X?KeK(K3yoZd@y{Yz}%|Rs` literal 0 HcmV?d00001 diff --git a/image/test/gtest/transparent.gif b/image/test/gtest/transparent.gif new file mode 100644 index 0000000000000000000000000000000000000000..48f5c7caf1ee52766257792fcf2c01ff277bdb39 GIT binary patch literal 355 zcmYk2p$@_@6h&{lu?<3&3dF<&xCsQZ1TzKUFUBA26S|L@!Zg86<7OsMNE~Xs(vH%a z=A`fR-k#1!xxWYqynwN7uOo09yAF5SegA+`cy8X|9Eh<6Epr#i2o{Q+Dw`lqb6Hnt zc$DT4rB;@#m15x{lQl|51}N}I6xE>Jl%@j^84t5lgZF@c&4c%VS>inmmKYDm>d&Kp zdBA5TAw4?f0Sg?N@-Q$*$df?&JY;sg$g{MbfO-|C7>N>xTrm=bj*uu5^i_us2CY`7 literal 0 HcmV?d00001 diff --git a/image/test/gtest/transparent.png b/image/test/gtest/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..fc8002053a39d720cec152a82a97ab068e14a366 GIT binary patch literal 419 zcmV;U0bKrxP)fvE$HuR_SJSS z55Et?JTvoPh0AulLIih*akytD27~Tb;Xe$LE%nD4!n3xHfZuZ4Pj*<5|d*T#H!ESYk7m-HU8@5jnVrEpCx9QzA>j^IKr#L|fDVP9W`Og2!1Mm~*q* z55G_oeZ|SW=ug`&$i3kho&-p?HH33nlfnslU>nkL46N&>6^EB7TYn&gqWCOVz#0Gm N002ovPDHLkV1ktEtrY+O literal 0 HcmV?d00001 diff --git a/image/test/mochitest/test_has_transparency.html b/image/test/mochitest/test_has_transparency.html index 393a0bddd8..652baf4ab3 100644 --- a/image/test/mochitest/test_has_transparency.html +++ b/image/test/mochitest/test_has_transparency.html @@ -48,8 +48,12 @@ function testFiles() { // GIFs with padding on the first frame are always transparent. yield ["first-frame-padding.gif", true]; - // JPEGs and BMPs are never transparent. + // JPEGs are never transparent. yield ["damon.jpg", false]; + + // Most BMPs are not transparent. (The TestMetadata GTest, which will + // eventually replace this test totally, has coverage for the kinds that can be + // transparent.) yield ["opaque.bmp", false]; // ICO files which contain BMPs have an additional type of transparency - the diff --git a/image/test/reftest/ico/ico-png/ico-size-1x1-png.ico b/image/test/reftest/ico/ico-png/ico-size-1x1-png.ico index 988a6201822f036d1d93c7916f3f3c4f9d7aaaf3..8eb80c7db2ee450eaf16449d307986f7b1736c46 100644 GIT binary patch delta 21 Xcma!v;b&lA1VKg+t-xS6k>3{p6afMl delta 21 Xcma!v;b&lA1VKg+t-xR~k>3{p6UhP! diff --git a/image/test/reftest/ico/ico-png/ico-size-256x256-png.ico b/image/test/reftest/ico/ico-png/ico-size-256x256-png.ico index 3968a13f7222d4eadf4784d71d5703c7f8feb88c..edbaf544349242589fc73db180b49af95c00f229 100644 GIT binary patch delta 25 ccmZ3dw@y!hfq@YS6%|3W0)v3~Mgci-05c8)O8@`> delta 25 ccmZ3dw@y!hfq@YS6%|3W0)vUrMgci-05i=4Y5)KL diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 36fd37ad3e..1c1de5c7c8 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -15,6 +15,7 @@ #include "AnimationCommon.h" // For GetLayerAnimationInfo #include "FrameLayerBuilder.h" #include "GeckoProfiler.h" +#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords #include "nsStyleChangeList.h" #include "nsRuleProcessorData.h" #include "nsStyleUtil.h" @@ -2635,10 +2636,10 @@ ElementRestyler::AddLayerChangesForAnimation() RestyleManager::GetMaxAnimationGenerationForFrame(mFrame); nsChangeHint hint = nsChangeHint(0); - const auto& layerInfo = CommonAnimationManager::sLayerAnimationInfo; - for (size_t i = 0; i < ArrayLength(layerInfo); i++) { + for (const LayerAnimationInfo::Record& layerInfo : + LayerAnimationInfo::sRecords) { Layer* layer = - FrameLayerBuilder::GetDedicatedLayer(mFrame, layerInfo[i].mLayerType); + FrameLayerBuilder::GetDedicatedLayer(mFrame, layerInfo.mLayerType); if (layer && frameGeneration > layer->GetAnimationGeneration()) { // If we have a transform layer but don't have any transform style, we // probably just removed the transform but haven't destroyed the layer @@ -2647,11 +2648,11 @@ ElementRestyler::AddLayerChangesForAnimation() // so we can skip adding any change hint here. (If we *were* to add // nsChangeHint_UpdateTransformLayer, ApplyRenderingChangeToTree would // complain that we're updating a transform layer without a transform). - if (layerInfo[i].mLayerType == nsDisplayItem::TYPE_TRANSFORM && + if (layerInfo.mLayerType == nsDisplayItem::TYPE_TRANSFORM && !mFrame->StyleDisplay()->HasTransformStyle()) { continue; } - NS_UpdateHint(hint, layerInfo[i].mChangeHint); + NS_UpdateHint(hint, layerInfo.mChangeHint); } } if (hint) { diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index a424cc4800..76c1bef4ce 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -68,6 +68,7 @@ #include "FrameLayerBuilder.h" #include "mozilla/dom/RequestSyncWifiService.h" #include "AnimationCommon.h" +#include "LayerAnimationInfo.h" #include "AudioChannelService.h" #include "mozilla/dom/DataStoreService.h" @@ -308,7 +309,7 @@ nsLayoutStatics::Initialize() #ifdef DEBUG nsStyleContext::Initialize(); - mozilla::CommonAnimationManager::Initialize(); + mozilla::LayerAnimationInfo::Initialize(); #endif MediaDecoder::InitStatics(); diff --git a/layout/style/AnimationCommon.cpp b/layout/style/AnimationCommon.cpp index f0d4a199e3..684ecf4eb5 100644 --- a/layout/style/AnimationCommon.cpp +++ b/layout/style/AnimationCommon.cpp @@ -18,6 +18,7 @@ #include "nsIFrame.h" #include "nsLayoutUtils.h" #include "mozilla/LookAndFeel.h" +#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords #include "Layers.h" #include "FrameLayerBuilder.h" #include "nsDisplayList.h" @@ -409,30 +410,6 @@ CommonAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, return collection->mStyleRule; } -/* static */ const CommonAnimationManager::LayerAnimationRecord - CommonAnimationManager::sLayerAnimationInfo[] = - { { eCSSProperty_transform, - nsDisplayItem::TYPE_TRANSFORM, - nsChangeHint_UpdateTransformLayer }, - { eCSSProperty_opacity, - nsDisplayItem::TYPE_OPACITY, - nsChangeHint_UpdateOpacityLayer } }; - -/* static */ const CommonAnimationManager::LayerAnimationRecord* -CommonAnimationManager::LayerAnimationRecordFor(nsCSSProperty aProperty) -{ - MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty, - CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), - "unexpected property"); - const auto& info = sLayerAnimationInfo; - for (size_t i = 0; i < ArrayLength(info); ++i) { - if (aProperty == info[i].mProperty) { - return &info[i]; - } - } - return nullptr; -} - /* virtual */ void CommonAnimationManager::WillRefresh(TimeStamp aTime) { @@ -458,42 +435,6 @@ CommonAnimationManager::WillRefresh(TimeStamp aTime) MaybeStartOrStopObservingRefreshDriver(); } -#ifdef DEBUG -/* static */ void -CommonAnimationManager::Initialize() -{ - const auto& info = CommonAnimationManager::sLayerAnimationInfo; - for (size_t i = 0; i < ArrayLength(info); i++) { - auto record = info[i]; - MOZ_ASSERT(nsCSSProps::PropHasFlags(record.mProperty, - CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), - "CSS property with entry in sLayerAnimationInfo does not " - "have the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag"); - } - - // Check that every property with the flag for animating on the - // compositor has an entry in sLayerAnimationInfo. - for (nsCSSProperty prop = nsCSSProperty(0); - prop < eCSSProperty_COUNT; - prop = nsCSSProperty(prop + 1)) { - if (nsCSSProps::PropHasFlags(prop, - CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) { - bool found = false; - for (size_t i = 0; i < ArrayLength(info); i++) { - auto record = info[i]; - if (record.mProperty == prop) { - found = true; - break; - } - } - MOZ_ASSERT(found, - "CSS property with the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR " - "flag does not have an entry in sLayerAnimationInfo"); - } - } -} -#endif - NS_IMPL_ISUPPORTS(AnimValuesStyleRule, nsIStyleRule) /* virtual */ void @@ -895,9 +836,8 @@ AnimationCollection::CanThrottleAnimation(TimeStamp aTime) return false; } - const auto& info = CommonAnimationManager::sLayerAnimationInfo; - for (size_t i = 0; i < ArrayLength(info); i++) { - auto record = info[i]; + for (const LayerAnimationInfo::Record& record : + LayerAnimationInfo::sRecords) { // We only need to worry about *current* animations here. // - If we have a newly-finished animation, Animation::CanThrottle will // detect that and force an unthrottled sample. diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h index 460ea9f377..cdbbd9f110 100644 --- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -64,10 +64,6 @@ public: // nsARefreshObserver void WillRefresh(TimeStamp aTime) override; -#ifdef DEBUG - static void Initialize(); -#endif - // NOTE: This can return null after Disconnect(). nsPresContext* PresContext() const { return mPresContext; } @@ -121,18 +117,6 @@ public: nsChangeHint mChangeHint; }; -protected: - static const size_t kLayerRecords = 2; - -public: - static const LayerAnimationRecord sLayerAnimationInfo[kLayerRecords]; - - // Will return non-null for any property with the - // CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag; should only be called - // on such properties. - static const LayerAnimationRecord* - LayerAnimationRecordFor(nsCSSProperty aProperty); - protected: virtual ~CommonAnimationManager(); diff --git a/layout/style/LayerAnimationInfo.cpp b/layout/style/LayerAnimationInfo.cpp new file mode 100644 index 0000000000..bebdb7d3de --- /dev/null +++ b/layout/style/LayerAnimationInfo.cpp @@ -0,0 +1,53 @@ +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ +/* 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 "LayerAnimationInfo.h" + +#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags + +namespace mozilla { + +/* static */ const LayerAnimationInfo::Record LayerAnimationInfo::sRecords[] = + { { eCSSProperty_transform, + nsDisplayItem::TYPE_TRANSFORM, + nsChangeHint_UpdateTransformLayer }, + { eCSSProperty_opacity, + nsDisplayItem::TYPE_OPACITY, + nsChangeHint_UpdateOpacityLayer } }; + +#ifdef DEBUG +/* static */ void +LayerAnimationInfo::Initialize() +{ + for (const Record& record : sRecords) { + MOZ_ASSERT(nsCSSProps::PropHasFlags(record.mProperty, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), + "CSS property with entry in LayerAnimation::sRecords does not " + "have the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag"); + } + + // Check that every property with the flag for animating on the + // compositor has an entry in LayerAnimationInfo::sRecords. + for (nsCSSProperty prop = nsCSSProperty(0); + prop < eCSSProperty_COUNT; + prop = nsCSSProperty(prop + 1)) { + if (nsCSSProps::PropHasFlags(prop, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) { + bool found = false; + for (const Record& record : sRecords) { + if (record.mProperty == prop) { + found = true; + break; + } + } + MOZ_ASSERT(found, + "CSS property with the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR " + "flag does not have an entry in LayerAnimationInfo::sRecords"); + } + } +} +#endif + +} // namespace mozilla diff --git a/layout/style/LayerAnimationInfo.h b/layout/style/LayerAnimationInfo.h new file mode 100644 index 0000000000..01a1ef42b3 --- /dev/null +++ b/layout/style/LayerAnimationInfo.h @@ -0,0 +1,33 @@ +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ +/* 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/. */ + +#ifndef mozilla_LayerAnimationInfo_h +#define mozilla_LayerAnimationInfo_h + +#include "nsChangeHint.h" +#include "nsCSSProperty.h" +#include "nsDisplayList.h" // For nsDisplayItem::Type + +namespace mozilla { + +struct LayerAnimationInfo { +#ifdef DEBUG + static void Initialize(); +#endif + // For CSS properties that may be animated on a separate layer, represents + // a record of the corresponding layer type and change hint. + struct Record { + nsCSSProperty mProperty; + nsDisplayItem::Type mLayerType; + nsChangeHint mChangeHint; + }; + + static const size_t kRecords = 2; + static const Record sRecords[kRecords]; +}; + +} // namespace mozilla + +#endif /* !defined(mozilla_LayerAnimationInfo_h) */ diff --git a/layout/style/moz.build b/layout/style/moz.build index a0c968b138..b3a3172934 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -85,6 +85,7 @@ EXPORTS.mozilla += [ 'CSSVariableResolver.h', 'CSSVariableValues.h', 'IncrementalClearCOMRuleArray.h', + 'LayerAnimationInfo.h', 'RuleNodeCacheConditions.h', 'RuleProcessorCache.h', 'StyleAnimationValue.h', @@ -130,6 +131,7 @@ UNIFIED_SOURCES += [ 'FontFaceSetIterator.cpp', 'ImageLoader.cpp', 'IncrementalClearCOMRuleArray.cpp', + 'LayerAnimationInfo.cpp', 'Loader.cpp', 'MediaQueryList.cpp', 'nsAnimationManager.cpp', From 00b0a024a4098402efedd6d69d09542d68bc88c1 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 18 May 2022 09:55:21 +0800 Subject: [PATCH 2/4] import changes from `dev' branch of rmottola/Arctic-Fox: - namespace (dd7e1c593d) - Bug 1158541 - Remove TiledTextureImage::mSize; r=jrmuizel This member is never initialized to anything so it always contains a (0,0) size, and it shadows the protected mSize member in the base class which is actually initialized to the correct size. (e2930b05a8) - Bug 1158542 - Remove TextureImage::mImageFormat; r=jrmuizel This is never initialized or accessed. (c5af3763b5) - Bug 1136428 - Create a different set and restore path of the draw buffer state in WebGLContext::ForceClearFramebufferWithDefaultValues depending on whether or not the default framebuffer is being used. r=jgilbert (e92fbcaa38) - Bug 1136428 - Change implementation of WebGLContext::DrawBuffers such that it goes through GLScreenBuffer to change the draw buffer. Added a variable to GLScreenBuffer to cache the draw buffer mode. r=jgilbert (eb80c3ac18) - Bug 1136428 - Ensure we never create more than the amount supported of color attachments in WebGLFrameBuffer or try to get an attachment point higher than the supported number. r=jgilbert (3050ce6979) - Bug 1185803 - Replace MOZ_CRASH with GenerateWarning. r=jgilbert (1b5ba983c6) - Bug 1188540 - Forward texture-related functions to WebGLTexture. - r=kamidphish (10f84c83e5) - Bug 1189903 - Don't use RGBA surfaces on GLX if surface sharing is not used. r=jgilbert (2edbca3d13) - Bug 1170842 - Part 3: Implement GetInternalformatParameter. r=jgilbert, r=smaug (18f0fdf5f9) - Bug 1170842 - Part 4: Implement FramebufferTextureLayer. r=jgilbert (2acd7963e7) - Bug 1062066 (Part 1) - Add support for vertically flipping downscaler output. r=tn (f23315cba2) - Bug 1204626 - Add a regression test. r=billm (aa3c694b83) - Bug 1062066 (Part 2) - Add a Downscaler API to clear a row. r=tn (7c77bf27b0) - Bug 1062066 (Part 3) - Only mark BMP surfaces as transparent if they actually have alpha data. r=tn (ed9bade04e) - Bug 1060609 (Part 2) - Add downscale-during-decode support for the PNG decoder. r=tn,f=glennrp (47259a797a) - hack to fix build (12266daf48) - Bug 1060609 (Part 3) - Add tests that interlaced and non-interlaced PNGs have the same downscaling behavior. r=tn (763b18b15e) - Bug 1062066 (Part 4) - Add downscale-during-decode support for the BMP decoder. r=tn (5a810ed168) - indentation fixes (872c66d3a8) --- dom/canvas/WebGL2Context.cpp | 13 +- dom/canvas/WebGL2Context.h | 24 +- dom/canvas/WebGL2ContextDraw.cpp | 2 +- dom/canvas/WebGL2ContextFramebuffers.cpp | 95 +- dom/canvas/WebGL2ContextRenderbuffers.cpp | 66 + dom/canvas/WebGL2ContextTextures.cpp | 485 +----- dom/canvas/WebGLContext.cpp | 190 +-- dom/canvas/WebGLContext.h | 274 ++-- .../WebGLContextFramebufferOperations.cpp | 13 +- dom/canvas/WebGLContextGL.cpp | 1173 +------------- dom/canvas/WebGLContextTextures.cpp | 469 ++++++ dom/canvas/WebGLContextUtils.cpp | 4 +- dom/canvas/WebGLContextUtils.h | 3 + dom/canvas/WebGLContextValidate.cpp | 34 - dom/canvas/WebGLFramebuffer.cpp | 80 +- dom/canvas/WebGLFramebuffer.h | 11 +- dom/canvas/WebGLTexture.cpp | 285 +++- dom/canvas/WebGLTexture.h | 206 ++- dom/canvas/WebGLTextureUpload.cpp | 1419 +++++++++++++++++ dom/canvas/moz.build | 3 + dom/webidl/WebGL2RenderingContext.webidl | 5 +- embedding/test/browser.ini | 6 + embedding/test/browser_bug1204626.js | 87 + embedding/test/bug1204626_doc0.html | 3 + embedding/test/bug1204626_doc1.html | 5 + embedding/test/moz.build | 1 + gfx/gl/GLScreenBuffer.cpp | 39 + gfx/gl/GLScreenBuffer.h | 2 + gfx/gl/GLTextureImage.h | 3 - gfx/gl/SharedSurface.h | 4 +- image/Downscaler.cpp | 35 +- image/Downscaler.h | 15 +- image/ImageFactory.cpp | 7 +- image/RasterImage.h | 6 +- image/decoders/nsBMPDecoder.cpp | 147 +- image/decoders/nsBMPDecoder.h | 10 +- image/decoders/nsICODecoder.cpp | 15 +- image/decoders/nsPNGDecoder.cpp | 90 +- image/decoders/nsPNGDecoder.h | 7 + .../reftest/downscaling/downscale-png.html | 31 + .../reftest/downscaling/png-interlaced.png | Bin 0 -> 806 bytes image/test/reftest/downscaling/png-normal.png | Bin 0 -> 421 bytes image/test/reftest/downscaling/reftest.list | 6 + .../nsUrlClassifierDBService.cpp | 2 +- 44 files changed, 3253 insertions(+), 2122 deletions(-) create mode 100644 dom/canvas/WebGL2ContextRenderbuffers.cpp create mode 100644 dom/canvas/WebGLContextTextures.cpp create mode 100644 dom/canvas/WebGLTextureUpload.cpp create mode 100644 embedding/test/browser.ini create mode 100644 embedding/test/browser_bug1204626.js create mode 100644 embedding/test/bug1204626_doc0.html create mode 100644 embedding/test/bug1204626_doc1.html create mode 100644 image/test/reftest/downscaling/downscale-png.html create mode 100644 image/test/reftest/downscaling/png-interlaced.png create mode 100644 image/test/reftest/downscaling/png-normal.png diff --git a/dom/canvas/WebGL2Context.cpp b/dom/canvas/WebGL2Context.cpp index c3276f4fbf..c519ce0739 100644 --- a/dom/canvas/WebGL2Context.cpp +++ b/dom/canvas/WebGL2Context.cpp @@ -77,8 +77,7 @@ static const gl::GLFeature kRequiredFeatures[] = { gl::GLFeature::element_index_uint, gl::GLFeature::frag_color_float, gl::GLFeature::frag_depth, - gl::GLFeature::framebuffer_blit, - gl::GLFeature::framebuffer_multisample, + gl::GLFeature::framebuffer_object, gl::GLFeature::get_integer_indexed, gl::GLFeature::get_integer64_indexed, gl::GLFeature::gpu_shader4, @@ -164,6 +163,16 @@ WebGLContext::InitWebGL2() gl->GetUIntegerv(LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS, &mGLMaxUniformBufferBindings); + if (MinCapabilityMode()) { + mGLMax3DTextureSize = MINVALUE_GL_MAX_3D_TEXTURE_SIZE; + mGLMaxArrayTextureLayers = MINVALUE_GL_MAX_ARRAY_TEXTURE_LAYERS; + } else { + gl->fGetIntegerv(LOCAL_GL_MAX_3D_TEXTURE_SIZE, + (GLint*) &mGLMax3DTextureSize); + gl->fGetIntegerv(LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS, + (GLint*) &mGLMaxArrayTextureLayers); + } + mBoundTransformFeedbackBuffers.SetLength(mGLMaxTransformFeedbackSeparateAttribs); mBoundUniformBuffers.SetLength(mGLMaxUniformBufferBindings); diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h index 475ed67eca..42591d8e0c 100644 --- a/dom/canvas/WebGL2Context.h +++ b/dom/canvas/WebGL2Context.h @@ -8,6 +8,12 @@ #include "WebGLContext.h" +/* + * Minimum value constants define in 6.2 State Tables of OpenGL ES - 3.0.4 + */ +#define MINVALUE_GL_MAX_3D_TEXTURE_SIZE 256 +#define MINVALUE_GL_MAX_ARRAY_TEXTURE_LAYERS 256 + namespace mozilla { class ErrorResult; @@ -55,13 +61,20 @@ public: void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); - void GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval); + void FramebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture* texture, GLint level, GLint layer); void InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments, ErrorResult& rv); void InvalidateSubFramebuffer (GLenum target, const dom::Sequence& attachments, GLint x, GLint y, GLsizei width, GLsizei height, ErrorResult& rv); void ReadBuffer(GLenum mode); + + + // ------------------------------------------------------------------------- + // Renderbuffer objects - WebGL2ContextRenderbuffers.cpp + + void GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, + GLenum pname, JS::MutableHandleValue retval, + ErrorResult& rv); void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); @@ -357,15 +370,10 @@ public: private: WebGL2Context(); - JS::Value GetTexParameterInternal(const TexTarget& target, GLenum pname) override; + virtual bool IsTexParamValid(GLenum pname) const override; void UpdateBoundQuery(GLenum target, WebGLQuery* query); - bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info); - bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, - const char* info); - // CreateVertexArrayImpl is assumed to be infallible. virtual WebGLVertexArray* CreateVertexArrayImpl() override; virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override; diff --git a/dom/canvas/WebGL2ContextDraw.cpp b/dom/canvas/WebGL2ContextDraw.cpp index 517f3f06fb..c930f8b084 100644 --- a/dom/canvas/WebGL2ContextDraw.cpp +++ b/dom/canvas/WebGL2ContextDraw.cpp @@ -13,7 +13,7 @@ namespace mozilla { void WebGL2Context::DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset) { - MOZ_CRASH("Not Implemented."); + GenerateWarning("drawRangeElements: Not Implemented."); } } // namespace mozilla diff --git a/dom/canvas/WebGL2ContextFramebuffers.cpp b/dom/canvas/WebGL2ContextFramebuffers.cpp index f8f3a621a7..3fd827961c 100644 --- a/dom/canvas/WebGL2ContextFramebuffers.cpp +++ b/dom/canvas/WebGL2ContextFramebuffers.cpp @@ -330,16 +330,92 @@ WebGL2Context::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY mask, filter); } -void -WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) +static bool +ValidateTextureLayerAttachment(GLenum attachment) { - MOZ_CRASH("Not Implemented."); + if (LOCAL_GL_COLOR_ATTACHMENT0 < attachment && + attachment <= LOCAL_GL_COLOR_ATTACHMENT15) + { + return true; + } + + switch (attachment) { + case LOCAL_GL_DEPTH_ATTACHMENT: + case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: + case LOCAL_GL_STENCIL_ATTACHMENT: + return true; + } + + return false; } void -WebGL2Context::GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval) +WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment, + WebGLTexture* texture, GLint level, GLint layer) { - MOZ_CRASH("Not Implemented."); + if (IsContextLost()) + return; + + if (!ValidateFramebufferTarget(target, "framebufferTextureLayer")) + return; + + if (!ValidateTextureLayerAttachment(attachment)) + return ErrorInvalidEnumInfo("framebufferTextureLayer: attachment:", attachment); + + if (texture) { + if (texture->IsDeleted()) { + return ErrorInvalidValue("framebufferTextureLayer: texture must be a valid " + "texture object."); + } + + if (level < 0) + return ErrorInvalidValue("framebufferTextureLayer: layer must be >= 0."); + + switch (texture->Target()) { + case LOCAL_GL_TEXTURE_3D: + if ((GLuint) layer >= mGLMax3DTextureSize) { + return ErrorInvalidValue("framebufferTextureLayer: layer must be < " + "MAX_3D_TEXTURE_SIZE"); + } + break; + + case LOCAL_GL_TEXTURE_2D_ARRAY: + if ((GLuint) layer >= mGLMaxArrayTextureLayers) { + return ErrorInvalidValue("framebufferTextureLayer: layer must be < " + "MAX_ARRAY_TEXTURE_LAYERS"); + } + break; + + default: + return ErrorInvalidOperation("framebufferTextureLayer: texture must be an " + "existing 3D texture, or a 2D texture array."); + } + } else { + return ErrorInvalidOperation("framebufferTextureLayer: texture must be an " + "existing 3D texture, or a 2D texture array."); + } + + WebGLFramebuffer* fb; + switch (target) { + case LOCAL_GL_FRAMEBUFFER: + case LOCAL_GL_DRAW_FRAMEBUFFER: + fb = mBoundDrawFramebuffer; + break; + + case LOCAL_GL_READ_FRAMEBUFFER: + fb = mBoundReadFramebuffer; + break; + + default: + MOZ_CRASH("Bad target."); + } + + if (!fb) { + return ErrorInvalidOperation("framebufferTextureLayer: cannot modify" + " framebuffer 0."); + } + + fb->FramebufferTextureLayer(attachment, texture, level, layer); } // Map attachments intended for the default buffer, to attachments for a non- @@ -522,13 +598,4 @@ WebGL2Context::ReadBuffer(GLenum mode) gl->Screen()->SetReadBuffer(mode); } -void -WebGL2Context::RenderbufferStorageMultisample(GLenum target, GLsizei samples, - GLenum internalFormat, - GLsizei width, GLsizei height) -{ - RenderbufferStorage_base("renderbufferStorageMultisample", target, samples, - internalFormat, width, height); -} - } // namespace mozilla diff --git a/dom/canvas/WebGL2ContextRenderbuffers.cpp b/dom/canvas/WebGL2ContextRenderbuffers.cpp new file mode 100644 index 0000000000..7ad686414a --- /dev/null +++ b/dom/canvas/WebGL2ContextRenderbuffers.cpp @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WebGL2Context.h" + +#include "GLContext.h" +#include "WebGLContextUtils.h" + +namespace mozilla { + +void +WebGL2Context::GetInternalformatParameter(JSContext* cx, GLenum target, + GLenum internalformat, GLenum pname, + JS::MutableHandleValue retval, + ErrorResult& rv) +{ + if (IsContextLost()) + return; + + if (target != LOCAL_GL_RENDERBUFFER) { + return ErrorInvalidEnumInfo("getInternalfomratParameter: target must be " + "RENDERBUFFER. Was:", target); + } + + // GL_INVALID_ENUM is generated if internalformat is not color-, + // depth-, or stencil-renderable. + // TODO: When format table queries lands. + + if (pname != LOCAL_GL_SAMPLES) { + return ErrorInvalidEnumInfo("getInternalformatParameter: pname must be SAMPLES. " + "Was:", pname); + } + + GLint* samples = nullptr; + GLint sampleCount = 0; + gl->fGetInternalformativ(LOCAL_GL_RENDERBUFFER, internalformat, + LOCAL_GL_NUM_SAMPLE_COUNTS, 1, &sampleCount); + if (sampleCount > 0) { + samples = new GLint[sampleCount]; + gl->fGetInternalformativ(LOCAL_GL_RENDERBUFFER, internalformat, LOCAL_GL_SAMPLES, + sampleCount, samples); + } + + JSObject* obj = dom::Int32Array::Create(cx, this, sampleCount, samples); + if (!obj) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + + delete[] samples; + + retval.setObjectOrNull(obj); +} + +void +WebGL2Context::RenderbufferStorageMultisample(GLenum target, GLsizei samples, + GLenum internalFormat, + GLsizei width, GLsizei height) +{ + RenderbufferStorage_base("renderbufferStorageMultisample", target, samples, + internalFormat, width, height); +} + +} // namespace mozilla diff --git a/dom/canvas/WebGL2ContextTextures.cpp b/dom/canvas/WebGL2ContextTextures.cpp index 33938666ad..45911cff98 100644 --- a/dom/canvas/WebGL2ContextTextures.cpp +++ b/dom/canvas/WebGL2ContextTextures.cpp @@ -10,485 +10,126 @@ namespace mozilla { -bool -WebGL2Context::ValidateSizedInternalFormat(GLenum internalformat, const char* info) -{ - switch (internalformat) { - // Sized Internal Formats - // https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml - case LOCAL_GL_R8: - case LOCAL_GL_R8_SNORM: - case LOCAL_GL_R16F: - case LOCAL_GL_R32F: - case LOCAL_GL_R8UI: - case LOCAL_GL_R8I: - case LOCAL_GL_R16UI: - case LOCAL_GL_R16I: - case LOCAL_GL_R32UI: - case LOCAL_GL_R32I: - case LOCAL_GL_RG8: - case LOCAL_GL_RG8_SNORM: - case LOCAL_GL_RG16F: - case LOCAL_GL_RG32F: - case LOCAL_GL_RG8UI: - case LOCAL_GL_RG8I: - case LOCAL_GL_RG16UI: - case LOCAL_GL_RG16I: - case LOCAL_GL_RG32UI: - case LOCAL_GL_RG32I: - case LOCAL_GL_RGB8: - case LOCAL_GL_SRGB8: - case LOCAL_GL_RGB565: - case LOCAL_GL_RGB8_SNORM: - case LOCAL_GL_R11F_G11F_B10F: - case LOCAL_GL_RGB9_E5: - case LOCAL_GL_RGB16F: - case LOCAL_GL_RGB32F: - case LOCAL_GL_RGB8UI: - case LOCAL_GL_RGB8I: - case LOCAL_GL_RGB16UI: - case LOCAL_GL_RGB16I: - case LOCAL_GL_RGB32UI: - case LOCAL_GL_RGB32I: - case LOCAL_GL_RGBA8: - case LOCAL_GL_SRGB8_ALPHA8: - case LOCAL_GL_RGBA8_SNORM: - case LOCAL_GL_RGB5_A1: - case LOCAL_GL_RGBA4: - case LOCAL_GL_RGB10_A2: - case LOCAL_GL_RGBA16F: - case LOCAL_GL_RGBA32F: - case LOCAL_GL_RGBA8UI: - case LOCAL_GL_RGBA8I: - case LOCAL_GL_RGB10_A2UI: - case LOCAL_GL_RGBA16UI: - case LOCAL_GL_RGBA16I: - case LOCAL_GL_RGBA32I: - case LOCAL_GL_RGBA32UI: - case LOCAL_GL_DEPTH_COMPONENT16: - case LOCAL_GL_DEPTH_COMPONENT24: - case LOCAL_GL_DEPTH_COMPONENT32F: - case LOCAL_GL_DEPTH24_STENCIL8: - case LOCAL_GL_DEPTH32F_STENCIL8: - return true; - } - - if (IsCompressedTextureFormat(internalformat)) - return true; - - nsCString name; - EnumName(internalformat, &name); - ErrorInvalidEnum("%s: invalid internal format %s", info, name.get()); - - return false; -} - -/** Validates parameters to texStorage{2D,3D} */ -bool -WebGL2Context::ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, - const char* info) -{ - // GL_INVALID_OPERATION is generated if the default texture object is curently bound to target. - WebGLTexture* tex = ActiveBoundTextureForTarget(target); - if (!tex) { - ErrorInvalidOperation("%s: no texture is bound to target %s", info, EnumName(target)); - return false; - } - - // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has - // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE. - if (tex->IsImmutable()) { - ErrorInvalidOperation("%s: texture bound to target %s is already immutable", info, EnumName(target)); - return false; - } - - // GL_INVALID_ENUM is generated if internalformat is not a valid sized internal format. - if (!ValidateSizedInternalFormat(internalformat, info)) - return false; - - // GL_INVALID_VALUE is generated if width, height or levels are less than 1. - if (width < 1) { ErrorInvalidValue("%s: width is < 1", info); return false; } - if (height < 1) { ErrorInvalidValue("%s: height is < 1", info); return false; } - if (depth < 1) { ErrorInvalidValue("%s: depth is < 1", info); return false; } - if (levels < 1) { ErrorInvalidValue("%s: levels is < 1", info); return false; } - - // GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1. - if (FloorLog2(std::max(std::max(width, height), depth)) + 1 < levels) { - ErrorInvalidOperation("%s: too many levels for given texture dimensions", info); - return false; - } - - return true; -} - -// ------------------------------------------------------------------------- -// Texture objects - void -WebGL2Context::TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) +WebGL2Context::TexStorage2D(GLenum rawTexTarget, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height) { - if (IsContextLost()) + const char funcName[] = "TexStorage2D"; + TexTarget texTarget; + WebGLTexture* tex; + if (!ValidateTexTarget(this, rawTexTarget, funcName, &texTarget, &tex)) return; - // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants. - if (target != LOCAL_GL_TEXTURE_2D && target != LOCAL_GL_TEXTURE_CUBE_MAP) - return ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP"); - - if (!ValidateTexStorage(target, levels, internalformat, width, height, 1, "texStorage2D")) - return; - - GetAndFlushUnderlyingGLErrors(); - gl->fTexStorage2D(target, levels, internalformat, width, height); - GLenum error = GetAndFlushUnderlyingGLErrors(); - if (error) { - return GenerateWarning("texStorage2D generated error %s", ErrorName(error)); - } - - WebGLTexture* tex = ActiveBoundTextureForTarget(target); - tex->SetImmutable(); - - const size_t facesCount = (target == LOCAL_GL_TEXTURE_2D) ? 1 : 6; - GLsizei w = width; - GLsizei h = height; - for (size_t l = 0; l < size_t(levels); l++) { - for (size_t f = 0; f < facesCount; f++) { - tex->SetImageInfo(TexImageTargetForTargetAndFace(target, f), - l, w, h, 1, - internalformat, - WebGLImageDataStatus::UninitializedImageData); - } - w = std::max(1, w / 2); - h = std::max(1, h / 2); - } + tex->TexStorage2D(texTarget, levels, internalFormat, width, height); } void -WebGL2Context::TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, +WebGL2Context::TexStorage3D(GLenum rawTexTarget, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth) { - if (IsContextLost()) + const char funcName[] = "texStorage3D"; + TexTarget texTarget; + WebGLTexture* tex; + if (!ValidateTexTarget(this, rawTexTarget, funcName, &texTarget, &tex)) return; - // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants. - if (target != LOCAL_GL_TEXTURE_3D) - return ErrorInvalidEnum("texStorage3D: target is not TEXTURE_3D"); - - if (!ValidateTexStorage(target, levels, internalformat, width, height, depth, "texStorage3D")) - return; - - GetAndFlushUnderlyingGLErrors(); - gl->fTexStorage3D(target, levels, internalformat, width, height, depth); - GLenum error = GetAndFlushUnderlyingGLErrors(); - if (error) { - return GenerateWarning("texStorage3D generated error %s", ErrorName(error)); - } - - WebGLTexture* tex = ActiveBoundTextureForTarget(target); - tex->SetImmutable(); - - GLsizei w = width; - GLsizei h = height; - GLsizei d = depth; - for (size_t l = 0; l < size_t(levels); l++) { - tex->SetImageInfo(TexImageTargetForTargetAndFace(target, 0), - l, w, h, d, - internalformat, - WebGLImageDataStatus::UninitializedImageData); - w = std::max(1, w >> 1); - h = std::max(1, h >> 1); - d = std::max(1, d >> 1); - } + tex->TexStorage3D(texTarget, levels, internalFormat, width, height, depth); } void -WebGL2Context::TexImage3D(GLenum target, GLint level, GLenum internalformat, +WebGL2Context::TexImage3D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, - GLint border, GLenum format, GLenum type, - const dom::Nullable &pixels, - ErrorResult& rv) + GLint border, GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult& out_rv) { - if (IsContextLost()) - return; - - void* data; - size_t dataLength; - js::Scalar::Type jsArrayType; - if (pixels.IsNull()) { - data = nullptr; - dataLength = 0; - jsArrayType = js::Scalar::MaxTypedArrayViewType; - } else { - const dom::ArrayBufferView& view = pixels.Value(); - view.ComputeLengthAndData(); - - data = view.Data(); - dataLength = view.Length(); - jsArrayType = view.Type(); - } - - const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D; - - if (!ValidateTexImageTarget(target, func, dims)) - return; - - TexImageTarget texImageTarget = target; - - if (!ValidateTexImage(texImageTarget, level, internalformat, - 0, 0, 0, - width, height, depth, - border, format, type, func, dims)) + const char funcName[] = "texImage3D"; + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, funcName, &texImageTarget, &tex)) { return; } - if (!ValidateTexInputData(type, jsArrayType, func, dims)) - return; - - TexInternalFormat effectiveInternalFormat = - EffectiveInternalFormatFromInternalFormatAndType(internalformat, type); - - if (effectiveInternalFormat == LOCAL_GL_NONE) { - return ErrorInvalidOperation("texImage3D: bad combination of internalformat and type"); - } - - // we need to find the exact sized format of the source data. Slightly abusing - // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format - // is the same thing as an unsized internalformat. - TexInternalFormat effectiveSourceFormat = - EffectiveInternalFormatFromInternalFormatAndType(format, type); - MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated format/type combo earlier - const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat); - MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here. - size_t srcTexelSize = srcbitsPerTexel / 8; - - CheckedUint32 checked_neededByteLength = - GetImageSize(height, width, depth, srcTexelSize, mPixelStoreUnpackAlignment); - - if (!checked_neededByteLength.isValid()) - return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); - - uint32_t bytesNeeded = checked_neededByteLength.value(); - - if (dataLength && dataLength < bytesNeeded) - return ErrorInvalidOperation("texImage3D: not enough data for operation (need %d, have %d)", - bytesNeeded, dataLength); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - - if (!tex) - return ErrorInvalidOperation("texImage3D: no texture is bound to this target"); - - if (tex->IsImmutable()) { - return ErrorInvalidOperation( - "texImage3D: disallowed because the texture " - "bound to this target has already been made immutable by texStorage3D"); - } - - GLenum driverType = LOCAL_GL_NONE; - GLenum driverInternalFormat = LOCAL_GL_NONE; - GLenum driverFormat = LOCAL_GL_NONE; - DriverFormatsFromEffectiveInternalFormat(gl, - effectiveInternalFormat, - &driverInternalFormat, - &driverFormat, - &driverType); - - MakeContextCurrent(); - GetAndFlushUnderlyingGLErrors(); - gl->fTexImage3D(texImageTarget.get(), level, - driverInternalFormat, - width, height, depth, - 0, driverFormat, driverType, - data); - GLenum error = GetAndFlushUnderlyingGLErrors(); - if (error) { - return GenerateWarning("texImage3D generated error %s", ErrorName(error)); - } - - tex->SetImageInfo(texImageTarget, level, - width, height, depth, - effectiveInternalFormat, - data ? WebGLImageDataStatus::InitializedImageData - : WebGLImageDataStatus::UninitializedImageData); + tex->TexImage3D(texImageTarget, level, internalFormat, width, height, depth, border, + unpackFormat, unpackType, maybeView, &out_rv); } void -WebGL2Context::TexSubImage3D(GLenum rawTarget, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, +WebGL2Context::TexSubImage3D(GLenum rawTexImageTarget, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLenum type, - const dom::Nullable& pixels, - ErrorResult& rv) + GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult& out_rv) { - if (IsContextLost()) - return; - - if (pixels.IsNull()) - return ErrorInvalidValue("texSubImage3D: pixels must not be null!"); - - const dom::ArrayBufferView& view = pixels.Value(); - view.ComputeLengthAndData(); - - const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D; - - if (!ValidateTexImageTarget(rawTarget, func, dims)) - return; - - TexImageTarget texImageTarget(rawTarget); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - if (!tex) { - return ErrorInvalidOperation("texSubImage3D: no texture bound on active texture unit"); - } - - if (!tex->HasImageInfoAt(texImageTarget, level)) { - return ErrorInvalidOperation("texSubImage3D: no previously defined texture image"); - } - - const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level); - const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat(); - TexInternalFormat existingUnsizedInternalFormat = LOCAL_GL_NONE; - TexType existingType = LOCAL_GL_NONE; - UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(existingEffectiveInternalFormat, - &existingUnsizedInternalFormat, - &existingType); - - if (!ValidateTexImage(texImageTarget, level, existingEffectiveInternalFormat.get(), - xoffset, yoffset, zoffset, - width, height, depth, - 0, format, type, func, dims)) + const char funcName[] = "texSubImage3D"; + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, funcName, &texImageTarget, &tex)) { return; } - if (type != existingType) { - return ErrorInvalidOperation("texSubImage3D: type differs from that of the existing image"); - } - - js::Scalar::Type jsArrayType = view.Type(); - void* data = view.Data(); - size_t dataLength = view.Length(); - - if (!ValidateTexInputData(type, jsArrayType, func, dims)) - return; - - const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat); - MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here. - size_t srcTexelSize = bitsPerTexel / 8; - - if (width == 0 || height == 0 || depth == 0) - return; // no effect, we better return right now - - CheckedUint32 checked_neededByteLength = - GetImageSize(height, width, depth, srcTexelSize, mPixelStoreUnpackAlignment); - - if (!checked_neededByteLength.isValid()) - return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); - - uint32_t bytesNeeded = checked_neededByteLength.value(); - - if (dataLength < bytesNeeded) - return ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, dataLength); - - if (imageInfo.HasUninitializedImageData()) { - bool coversWholeImage = xoffset == 0 && - yoffset == 0 && - zoffset == 0 && - width == imageInfo.Width() && - height == imageInfo.Height() && - depth == imageInfo.Depth(); - if (coversWholeImage) { - tex->SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); - } else { - if (!tex->EnsureInitializedImageData(texImageTarget, level)) - return; - } - } - - GLenum driverType = LOCAL_GL_NONE; - GLenum driverInternalFormat = LOCAL_GL_NONE; - GLenum driverFormat = LOCAL_GL_NONE; - DriverFormatsFromEffectiveInternalFormat(gl, - existingEffectiveInternalFormat, - &driverInternalFormat, - &driverFormat, - &driverType); - - MakeContextCurrent(); - gl->fTexSubImage3D(texImageTarget.get(), level, - xoffset, yoffset, zoffset, - width, height, depth, - driverFormat, driverType, data); + tex->TexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, width, height, + depth, unpackFormat, unpackType, maybeView, &out_rv); } void WebGL2Context::TexSubImage3D(GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, - GLenum format, GLenum type, dom::ImageData* data, - ErrorResult& rv) + GLint xOffset, GLint yOffset, GLint zOffset, + GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData, + ErrorResult& out_rv) { - MOZ_CRASH("Not Implemented."); + GenerateWarning("texSubImage3D: Not implemented."); } void WebGL2Context::CopyTexSubImage3D(GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, + GLint xOffset, GLint yOffset, GLint zOffset, GLint x, GLint y, GLsizei width, GLsizei height) { - MOZ_CRASH("Not Implemented."); + GenerateWarning("copyTexSubImage3D: Not implemented."); } void -WebGL2Context::CompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, +WebGL2Context::CompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, - GLint border, GLsizei imageSize, const dom::ArrayBufferView& data) + GLint border, GLsizei imageSize, const dom::ArrayBufferView& view) { - MOZ_CRASH("Not Implemented."); + GenerateWarning("compressedTexImage3D: Not implemented."); } void -WebGL2Context::CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, +WebGL2Context::CompressedTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLsizei imageSize, const dom::ArrayBufferView& data) + GLenum unpackFormat, GLsizei imageSize, const dom::ArrayBufferView& view) { - MOZ_CRASH("Not Implemented."); + GenerateWarning("compressedTexSubImage3D: Not implemented."); } -JS::Value -WebGL2Context::GetTexParameterInternal(const TexTarget& target, GLenum pname) +bool +WebGL2Context::IsTexParamValid(GLenum pname) const { switch (pname) { - case LOCAL_GL_TEXTURE_BASE_LEVEL: - case LOCAL_GL_TEXTURE_COMPARE_FUNC: - case LOCAL_GL_TEXTURE_COMPARE_MODE: - case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT: - case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS: - case LOCAL_GL_TEXTURE_MAX_LEVEL: - case LOCAL_GL_TEXTURE_SWIZZLE_A: - case LOCAL_GL_TEXTURE_SWIZZLE_B: - case LOCAL_GL_TEXTURE_SWIZZLE_G: - case LOCAL_GL_TEXTURE_SWIZZLE_R: - case LOCAL_GL_TEXTURE_WRAP_R: - { - GLint i = 0; - gl->fGetTexParameteriv(target.get(), pname, &i); - return JS::NumberValue(uint32_t(i)); - } + case LOCAL_GL_TEXTURE_BASE_LEVEL: + case LOCAL_GL_TEXTURE_COMPARE_FUNC: + case LOCAL_GL_TEXTURE_COMPARE_MODE: + case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT: + case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS: + case LOCAL_GL_TEXTURE_MAX_LEVEL: + case LOCAL_GL_TEXTURE_SWIZZLE_A: + case LOCAL_GL_TEXTURE_SWIZZLE_B: + case LOCAL_GL_TEXTURE_SWIZZLE_G: + case LOCAL_GL_TEXTURE_SWIZZLE_R: + case LOCAL_GL_TEXTURE_WRAP_R: + case LOCAL_GL_TEXTURE_MAX_LOD: + case LOCAL_GL_TEXTURE_MIN_LOD: + return true; - case LOCAL_GL_TEXTURE_MAX_LOD: - case LOCAL_GL_TEXTURE_MIN_LOD: - { - GLfloat f = 0.0f; - gl->fGetTexParameterfv(target.get(), pname, &f); - return JS::NumberValue(float(f)); - } + default: + return WebGLContext::IsTexParamValid(pname); } - - return WebGLContext::GetTexParameterInternal(target, pname); } } // namespace mozilla diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index 27f1cc34cd..2335603ce6 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -271,6 +271,8 @@ WebGLContext::WebGLContext() mGLMaxDrawBuffers = 1; mGLMaxTransformFeedbackSeparateAttribs = 0; mGLMaxUniformBufferBindings = 0; + mGLMax3DTextureSize = 0; + mGLMaxArrayTextureLayers = 0; // See OpenGL ES 2.0.25 spec, 6.2 State Tables, table 6.13 mPixelStorePackAlignment = 4; @@ -682,10 +684,14 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options, if (!baseCaps.alpha) baseCaps.premultAlpha = true; - if (gl->IsANGLE() || gl->GetContextType() == GLContextType::GLX) { + if (gl->IsANGLE() || + (gl->GetContextType() == GLContextType::GLX && + gfxPlatform::GetPlatform()->GetCompositorBackend() == LayersBackend::LAYERS_OPENGL)) + { // We can't use no-alpha formats on ANGLE yet because of: // https://code.google.com/p/angleproject/issues/detail?id=764 - // GLX only supports GL_RGBA pixmaps as well. + // GLX only supports GL_RGBA pixmaps as well. Since we can't blit from + // an RGB FB to GLX's RGBA FB, force RGBA when surface sharing. baseCaps.alpha = true; } @@ -1350,6 +1356,7 @@ WebGLContext::ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfiel bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT); bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers); bool shouldOverrideDrawBuffers = false; + bool usingDefaultFrameBuffer = !mBoundDrawFramebuffer; GLenum currentDrawBuffers[WebGLContext::kMaxColorAttachments]; @@ -1367,7 +1374,7 @@ WebGLContext::ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfiel GLenum drawBuffersCommand[WebGLContext::kMaxColorAttachments] = { LOCAL_GL_NONE }; - for(int32_t i = 0; i < mGLMaxDrawBuffers; i++) { + for (int32_t i = 0; i < mGLMaxDrawBuffers; i++) { GLint temp; gl->fGetIntegerv(LOCAL_GL_DRAW_BUFFER0 + i, &temp); currentDrawBuffers[i] = temp; @@ -1378,6 +1385,16 @@ WebGLContext::ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfiel if (currentDrawBuffers[i] != drawBuffersCommand[i]) shouldOverrideDrawBuffers = true; } + + // When clearing the default framebuffer, we must be clearing only + // GL_BACK, and nothing else, or else gl may return an error. We will + // only use the first element of currentDrawBuffers in this case. + if (usingDefaultFrameBuffer) { + gl->Screen()->SetDrawBuffer(LOCAL_GL_BACK); + if (currentDrawBuffers[0] == LOCAL_GL_COLOR_ATTACHMENT0) + currentDrawBuffers[0] = LOCAL_GL_BACK; + shouldOverrideDrawBuffers = false; + } // calling draw buffers can cause resolves on adreno drivers so // we try to avoid calling it if (shouldOverrideDrawBuffers) @@ -1423,8 +1440,13 @@ WebGLContext::ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfiel // Restore GL state after clearing. if (initializeColorBuffer) { - if (shouldOverrideDrawBuffers) { - gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers); + + if (drawBuffersIsEnabled) { + if (usingDefaultFrameBuffer) { + gl->Screen()->SetDrawBuffer(currentDrawBuffers[0]); + } else if (shouldOverrideDrawBuffers) { + gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers); + } } gl->fColorMask(mColorWriteMask[0], @@ -1806,158 +1828,6 @@ WebGLContext::DidRefresh() } } -bool -WebGLContext::TexImageFromVideoElement(const TexImageTarget texImageTarget, - GLint level, GLenum internalFormat, - GLenum format, GLenum type, - mozilla::dom::Element& elt) -{ - if (type == LOCAL_GL_HALF_FLOAT_OES && - !gl->IsExtensionSupported(gl::GLContext::OES_texture_half_float)) - { - type = LOCAL_GL_HALF_FLOAT; - } - - if (!ValidateTexImageFormatAndType(format, type, - WebGLTexImageFunc::TexImage, - WebGLTexDimensions::Tex2D)) - { - return false; - } - - HTMLVideoElement* video = HTMLVideoElement::FromContentOrNull(&elt); - if (!video) - return false; - - uint16_t readyState; - if (NS_SUCCEEDED(video->GetReadyState(&readyState)) && - readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) - { - //No frame inside, just return - return false; - } - - // If it doesn't have a principal, just bail - nsCOMPtr principal = video->GetCurrentPrincipal(); - if (!principal) - return false; - - mozilla::layers::ImageContainer* container = video->GetImageContainer(); - if (!container) - return false; - - if (video->GetCORSMode() == CORS_NONE) { - bool subsumes; - nsresult rv = mCanvasElement->NodePrincipal()->Subsumes(principal, &subsumes); - if (NS_FAILED(rv) || !subsumes) { - GenerateWarning("It is forbidden to load a WebGL texture from a cross-domain element that has not been validated with CORS. " - "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures"); - return false; - } - } - - AutoLockImage lockedImage(container); - Image* srcImage = lockedImage.GetImage(); - if (!srcImage) { - return false; - } - - gl->MakeCurrent(); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - - const WebGLTexture::ImageInfo& info = tex->ImageInfoAt(texImageTarget, 0); - bool dimensionsMatch = info.Width() == srcImage->GetSize().width && - info.Height() == srcImage->GetSize().height; - if (!dimensionsMatch) { - // we need to allocation - gl->fTexImage2D(texImageTarget.get(), level, internalFormat, - srcImage->GetSize().width, srcImage->GetSize().height, - 0, format, type, nullptr); - } - - const gl::OriginPos destOrigin = mPixelStoreFlipY ? gl::OriginPos::BottomLeft - : gl::OriginPos::TopLeft; - bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, - srcImage->GetSize(), - tex->mGLName, - texImageTarget.get(), - destOrigin); - if (ok) { - TexInternalFormat effectiveInternalFormat = - EffectiveInternalFormatFromInternalFormatAndType(internalFormat, - type); - MOZ_ASSERT(effectiveInternalFormat != LOCAL_GL_NONE); - tex->SetImageInfo(texImageTarget, level, srcImage->GetSize().width, - srcImage->GetSize().height, 1, - effectiveInternalFormat, - WebGLImageDataStatus::InitializedImageData); - tex->Bind(TexImageTargetToTexTarget(texImageTarget)); - } - return ok; -} - -void -WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xoffset, - GLint yoffset, GLenum format, GLenum type, - dom::Element* elt, ErrorResult* const out_rv) -{ - // TODO: Consolidate all the parameter validation - // checks. Instead of spreading out the cheks in multple - // places, consolidate into one spot. - - if (IsContextLost()) - return; - - if (!ValidateTexImageTarget(rawTexImageTarget, - WebGLTexImageFunc::TexSubImage, - WebGLTexDimensions::Tex2D)) - { - ErrorInvalidEnumInfo("texSubImage2D: target", rawTexImageTarget); - return; - } - - const TexImageTarget texImageTarget(rawTexImageTarget); - - if (level < 0) - return ErrorInvalidValue("texSubImage2D: level is negative"); - - const int32_t maxLevel = MaxTextureLevelForTexImageTarget(texImageTarget); - if (level > maxLevel) { - ErrorInvalidValue("texSubImage2D: level %d is too large, max is %d", - level, maxLevel); - return; - } - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - if (!tex) - return ErrorInvalidOperation("texSubImage2D: no texture bound on active texture unit"); - - const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level); - const TexInternalFormat internalFormat = imageInfo.EffectiveInternalFormat(); - - // Trying to handle the video by GPU directly first - if (TexImageFromVideoElement(texImageTarget, level, - internalFormat.get(), format, type, *elt)) - { - return; - } - - RefPtr data; - WebGLTexelFormat srcFormat; - nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(*elt); - *out_rv = SurfaceFromElementResultToImageSurface(res, data, &srcFormat); - if (out_rv->Failed() || !data) - return; - - gfx::IntSize size = data->GetSize(); - uint32_t byteLength = data->Stride() * size.height; - TexSubImage2D_base(texImageTarget.get(), level, xoffset, yoffset, size.width, - size.height, data->Stride(), format, type, data->GetData(), - byteLength, js::Scalar::MaxTypedArrayViewType, srcFormat, - res.mIsPremultiplied); -} - size_t RoundUpToMultipleOf(size_t value, size_t multiple) { @@ -1965,6 +1835,12 @@ RoundUpToMultipleOf(size_t value, size_t multiple) return overshoot - (overshoot % multiple); } +CheckedUint32 +RoundedToNextMultipleOf(CheckedUint32 x, CheckedUint32 y) +{ + return ((x + y - 1) / y) * y; +} + //////////////////////////////////////////////////////////////////////////////// WebGLContext::ScopedMaskWorkaround::ScopedMaskWorkaround(WebGLContext& webgl) diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index c80578dfb4..7bd055474a 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -281,9 +281,6 @@ public: // Returns hex formatted version of glenum if glenum is unknown. static void EnumName(GLenum glenum, nsACString* out_name); - bool IsCompressedTextureFormat(GLenum format); - bool IsTextureFormatCompressed(TexInternalFormat format); - void DummyFramebufferOperation(const char* funcName); WebGLTexture* ActiveBoundTextureForTarget(const TexTarget texTarget) const { @@ -373,13 +370,11 @@ public: dom::Nullable< nsTArray >& retval); void GetExtension(JSContext* cx, const nsAString& name, JS::MutableHandle retval, ErrorResult& rv); - void ActiveTexture(GLenum texture); void AttachShader(WebGLProgram* prog, WebGLShader* shader); void BindAttribLocation(WebGLProgram* prog, GLuint location, const nsAString& name); void BindFramebuffer(GLenum target, WebGLFramebuffer* fb); void BindRenderbuffer(GLenum target, WebGLRenderbuffer* fb); - void BindTexture(GLenum target, WebGLTexture* tex); void BindVertexArray(WebGLVertexArray* vao); void BlendColor(GLclampf r, GLclampf g, GLclampf b, GLclampf a); void BlendEquation(GLenum mode); @@ -396,24 +391,9 @@ public: void CompileShader(WebGLShader* shader); void CompileShaderANGLE(WebGLShader* shader); void CompileShaderBypass(WebGLShader* shader, const nsCString& shaderSource); - void CompressedTexImage2D(GLenum target, GLint level, - GLenum internalformat, GLsizei width, - GLsizei height, GLint border, - const dom::ArrayBufferView& view); - void CompressedTexSubImage2D(GLenum texImageTarget, GLint level, - GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, - const dom::ArrayBufferView& view); - void CopyTexImage2D(GLenum texImageTarget, GLint level, - GLenum internalformat, GLint x, GLint y, GLsizei width, - GLsizei height, GLint border); - void CopyTexSubImage2D(GLenum texImageTarget, GLint level, GLint xoffset, - GLint yoffset, GLint x, GLint y, GLsizei width, - GLsizei height); already_AddRefed CreateFramebuffer(); already_AddRefed CreateProgram(); already_AddRefed CreateRenderbuffer(); - already_AddRefed CreateTexture(); already_AddRefed CreateShader(GLenum type); already_AddRefed CreateVertexArray(); void CullFace(GLenum face); @@ -422,7 +402,6 @@ public: void DeleteRenderbuffer(WebGLRenderbuffer* rb); void DeleteShader(WebGLShader* shader); void DeleteVertexArray(WebGLVertexArray* vao); - void DeleteTexture(WebGLTexture* tex); void DepthFunc(GLenum func); void DepthMask(WebGLboolean b); void DepthRange(GLclampf zNear, GLclampf zFar); @@ -441,7 +420,6 @@ public: GLenum attachment, const char* funcName); void FrontFace(GLenum mode); - void GenerateMipmap(GLenum target); already_AddRefed GetActiveAttrib(WebGLProgram* prog, GLuint index); already_AddRefed GetActiveUniform(WebGLProgram* prog, @@ -507,13 +485,6 @@ public: void GetShaderInfoLog(WebGLShader* shader, nsAString& retval); void GetShaderSource(WebGLShader* shader, nsAString& retval); void GetShaderTranslatedSource(WebGLShader* shader, nsAString& retval); - JS::Value GetTexParameter(GLenum target, GLenum pname); - - void GetTexParameter(JSContext*, GLenum target, GLenum pname, - JS::MutableHandle retval) - { - retval.set(GetTexParameter(target, pname)); - } JS::Value GetUniform(JSContext* cx, WebGLProgram* prog, WebGLUniformLocation* loc); @@ -533,7 +504,6 @@ public: bool IsProgram(WebGLProgram* prog); bool IsRenderbuffer(WebGLRenderbuffer* rb); bool IsShader(WebGLShader* shader); - bool IsTexture(WebGLTexture* tex); bool IsVertexArray(WebGLVertexArray* vao); void LineWidth(GLfloat width); void LinkProgram(WebGLProgram* prog); @@ -560,105 +530,6 @@ public: void StencilOp(GLenum sfail, GLenum dpfail, GLenum dppass); void StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); - void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, - GLsizei width, GLsizei height, GLint border, GLenum format, - GLenum type, const dom::Nullable& pixels, - ErrorResult& rv); - void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, - GLenum format, GLenum type, dom::ImageData* pixels, - ErrorResult& rv); - // Allow whatever element types the bindings are willing to pass - // us in TexImage2D - bool TexImageFromVideoElement(TexImageTarget texImageTarget, GLint level, - GLenum internalFormat, GLenum format, - GLenum type, mozilla::dom::Element& image); - - template - void TexImage2D(GLenum rawTexImageTarget, GLint level, - GLenum internalFormat, GLenum format, GLenum type, - ElementType& elt, ErrorResult& rv) - { - if (IsContextLost()) - return; - - if (!ValidateTexImageTarget(rawTexImageTarget, - WebGLTexImageFunc::TexImage, - WebGLTexDimensions::Tex2D)) - { - ErrorInvalidEnumInfo("texSubImage2D: target", rawTexImageTarget); - return; - } - - const TexImageTarget texImageTarget(rawTexImageTarget); - - if (level < 0) - return ErrorInvalidValue("texImage2D: level is negative"); - - const int32_t maxLevel = MaxTextureLevelForTexImageTarget(texImageTarget); - if (level > maxLevel) { - ErrorInvalidValue("texImage2D: level %d is too large, max is %d", - level, maxLevel); - return; - } - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - - if (!tex) - return ErrorInvalidOperation("no texture is bound to this target"); - - // Trying to handle the video by GPU directly first - if (TexImageFromVideoElement(texImageTarget, level, internalFormat, - format, type, elt)) - { - return; - } - - RefPtr data; - WebGLTexelFormat srcFormat; - nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(elt); - rv = SurfaceFromElementResultToImageSurface(res, data, &srcFormat); - if (rv.Failed() || !data) - return; - - gfx::IntSize size = data->GetSize(); - uint32_t byteLength = data->Stride() * size.height; - return TexImage2D_base(texImageTarget, level, internalFormat, - size.width, size.height, data->Stride(), 0, - format, type, data->GetData(), byteLength, - js::Scalar::MaxTypedArrayViewType, srcFormat, - res.mIsPremultiplied); - } - - void TexParameterf(GLenum target, GLenum pname, GLfloat param) { - TexParameter_base(target, pname, nullptr, ¶m); - } - void TexParameteri(GLenum target, GLenum pname, GLint param) { - TexParameter_base(target, pname, ¶m, nullptr); - } - - void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xoffset, - GLint yoffset, GLsizei width, GLsizei height, - GLenum format, GLenum type, - const dom::Nullable& pixels, - ErrorResult& rv); - void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xoffset, - GLint yoffset, GLenum format, GLenum type, - dom::ImageData* pixels, ErrorResult& rv); - - void TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xoffset, - GLint yoffset, GLenum format, GLenum type, - dom::Element* elt, ErrorResult* const out_rv); - - // Allow whatever element types the bindings are willing to pass - // us in TexSubImage2D - template - void TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xoffset, - GLint yoffset, GLenum format, GLenum type, - ElementType& elt, ErrorResult& out_rv) - { - TexSubImage2D(rawTexImageTarget, level, xoffset, yoffset, format, type, &elt, - &out_rv); - } void Uniform1i(WebGLUniformLocation* loc, GLint x); void Uniform2i(WebGLUniformLocation* loc, GLint x, GLint y); @@ -939,6 +810,97 @@ private: bool ValidateCapabilityEnum(GLenum cap, const char* info); realGLboolean* GetStateTrackingSlot(GLenum cap); +// ----------------------------------------------------------------------------- +// Texture funcions (WebGLContextTextures.cpp) +public: + void ActiveTexture(GLenum texUnit); + void BindTexture(GLenum texTarget, WebGLTexture* tex); + already_AddRefed CreateTexture(); + void DeleteTexture(WebGLTexture* tex); + void GenerateMipmap(GLenum texTarget); + + void GetTexParameter(JSContext*, GLenum texTarget, GLenum pname, + JS::MutableHandle retval) + { + retval.set(GetTexParameter(texTarget, pname)); + } + + bool IsTexture(WebGLTexture* tex); + + void TexParameterf(GLenum texTarget, GLenum pname, GLfloat param) { + TexParameter_base(texTarget, pname, nullptr, ¶m); + } + + void TexParameteri(GLenum texTarget, GLenum pname, GLint param) { + TexParameter_base(texTarget, pname, ¶m, nullptr); + } + +protected: + JS::Value GetTexParameter(GLenum texTarget, GLenum pname); + void TexParameter_base(GLenum texTarget, GLenum pname, GLint* maybeIntParam, + GLfloat* maybeFloatParam); + + virtual bool IsTexParamValid(GLenum pname) const; + + // Upload funcs +public: + void CompressedTexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, + GLsizei width, GLsizei height, GLint border, + const dom::ArrayBufferView& view); + void CompressedTexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLsizei width, GLsizei height, + GLenum unpackFormat, const dom::ArrayBufferView& view); + + void CopyTexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, + GLint x, GLint y, GLsizei width, GLsizei height, GLint border); + void CopyTexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint x, GLint y, GLsizei width, + GLsizei height); + + void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, + GLsizei width, GLsizei height, GLint border, GLenum unpackFormat, + GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult& out_rv); + void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData, + ErrorResult& out_rv); + void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, dom::Element* elem, + ErrorResult* const out_rv); + + + void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset, + GLsizei width, GLsizei height, GLenum unpackFormat, + GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult& out_rv); + void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset, + GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData, + ErrorResult& out_rv); + void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset, + GLenum unpackFormat, GLenum unpackType, dom::Element* elem, + ErrorResult* const out_rv); + + // Allow whatever element unpackTypes the bindings are willing to pass + // us in Tex(Sub)Image2D + template + void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, ElementT& elem, + ErrorResult& out_rv) + { + TexImage2D(texImageTarget, level, internalFormat, unpackFormat, unpackType, &elem, + &out_rv); + } + template + void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset, + GLenum unpackFormat, GLenum unpackType, ElementT& elem, + ErrorResult& out_rv) + { + TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType, + &elem, &out_rv); + } + // ----------------------------------------------------------------------------- // Vertices Feature (WebGLContextVertices.cpp) public: @@ -1056,14 +1018,6 @@ protected: GLsizei depth, uint32_t pixelSize, uint32_t alignment); - virtual JS::Value GetTexParameterInternal(const TexTarget& target, - GLenum pname); - - // Returns x rounded to the next highest multiple of y. - static CheckedUint32 RoundedToNextMultipleOf(CheckedUint32 x, CheckedUint32 y) { - return ((x + y - 1) / y) * y; - } - inline void InvalidateBufferFetching() { mBufferFetchingIsVerified = false; @@ -1122,6 +1076,8 @@ protected: uint32_t mGLMaxTransformFeedbackSeparateAttribs; GLuint mGLMaxUniformBufferBindings; GLsizei mGLMaxSamples; + GLuint mGLMax3DTextureSize; + GLuint mGLMaxArrayTextureLayers; public: GLuint MaxVertexAttribs() const { @@ -1226,8 +1182,6 @@ protected: GLint width, GLint height, GLint depth, GLint border, GLenum format, GLenum type, WebGLTexImageFunc func, WebGLTexDimensions dims); - bool ValidateTexImageTarget(GLenum texImageTarget, WebGLTexImageFunc func, - WebGLTexDimensions dims); bool ValidateTexImageFormat(GLenum internalFormat, WebGLTexImageFunc func, WebGLTexDimensions dims); bool ValidateTexImageType(GLenum type, WebGLTexImageFunc func, @@ -1271,24 +1225,6 @@ protected: // helpers - // If jsArrayType is MaxTypedArrayViewType, it means no array. - void TexImage2D_base(TexImageTarget texImageTarget, GLint level, - GLenum internalFormat, GLsizei width, - GLsizei height, GLsizei srcStrideOrZero, GLint border, - GLenum format, GLenum type, void* data, - uint32_t byteLength, js::Scalar::Type jsArrayType, - WebGLTexelFormat srcFormat, bool srcPremultiplied); - void TexSubImage2D_base(GLenum texImageTarget, GLint level, - GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLsizei srcStrideOrZero, - GLenum format, GLenum type, void* pixels, - uint32_t byteLength, js::Scalar::Type jsArrayType, - WebGLTexelFormat srcFormat, bool srcPremultiplied); - - void TexParameter_base(GLenum target, GLenum pname, - GLint* const out_intParam, - GLfloat* const out_floatParam); - bool ConvertImage(size_t width, size_t height, size_t srcStride, size_t dstStride, const uint8_t* src, uint8_t* dst, WebGLTexelFormat srcFormat, bool srcPremultiplied, @@ -1320,11 +1256,6 @@ protected: RefPtr& imageOut, WebGLTexelFormat* format); - void CopyTexSubImage2D_base(TexImageTarget texImageTarget, - GLint level, TexInternalFormat internalFormat, - GLint xoffset, GLint yoffset, GLint x, GLint y, - GLsizei width, GLsizei height, bool isSub); - // Returns false if `object` is null or not valid. template bool ValidateObject(const char* info, ObjectType* object); @@ -1381,14 +1312,6 @@ protected: GLenum CheckedBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage); - /** Like glTexImage2D, but if the call may change the texture size, checks - * any GL error generated by this glTexImage2D call and returns it. - */ - GLenum CheckedTexImage2D(TexImageTarget texImageTarget, GLint level, - TexInternalFormat internalFormat, GLsizei width, - GLsizei height, GLint border, TexFormat format, - TexType type, const GLvoid* data); - void ForceLoseContext(bool simulateLoss = false); void ForceRestoreContext(); @@ -1690,6 +1613,17 @@ private: size_t RoundUpToMultipleOf(size_t value, size_t multiple); +bool +ValidateTexTarget(WebGLContext* webgl, GLenum rawTexTarget, const char* funcName, + TexTarget* const out_texTarget, WebGLTexture** const out_tex); +bool +ValidateTexImageTarget(WebGLContext* webgl, GLenum rawTexImageTarget, + const char* funcName, TexImageTarget* const out_texImageTarget, + WebGLTexture** const out_tex); + +// Returns x rounded to the next highest multiple of y. +CheckedUint32 RoundedToNextMultipleOf(CheckedUint32 x, CheckedUint32 y); + } // namespace mozilla #endif diff --git a/dom/canvas/WebGLContextFramebufferOperations.cpp b/dom/canvas/WebGLContextFramebufferOperations.cpp index 0c132d03d2..0ad3838935 100644 --- a/dom/canvas/WebGLContextFramebufferOperations.cpp +++ b/dom/canvas/WebGLContextFramebufferOperations.cpp @@ -8,6 +8,7 @@ #include "WebGLRenderbuffer.h" #include "WebGLFramebuffer.h" #include "GLContext.h" +#include "GLScreenBuffer.h" namespace mozilla { @@ -160,16 +161,8 @@ WebGLContext::DrawBuffers(const dom::Sequence& buffers) return ErrorInvalidValue("drawBuffers: invalid (main framebuffer: buffers.length must be 1)"); } - MakeContextCurrent(); - - if (buffers[0] == LOCAL_GL_NONE) { - const GLenum drawBuffersCommand = LOCAL_GL_NONE; - gl->fDrawBuffers(1, &drawBuffersCommand); - return; - } - else if (buffers[0] == LOCAL_GL_BACK) { - const GLenum drawBuffersCommand = LOCAL_GL_COLOR_ATTACHMENT0; - gl->fDrawBuffers(1, &drawBuffersCommand); + if (buffers[0] == LOCAL_GL_NONE || buffers[0] == LOCAL_GL_BACK) { + gl->Screen()->SetDrawBuffer(buffers[0]); return; } return ErrorInvalidOperation("drawBuffers: invalid operation (main framebuffer: buffers[0] must be GL_NONE or GL_BACK)"); diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp index f5802702a0..30094bdbf0 100644 --- a/dom/canvas/WebGLContextGL.cpp +++ b/dom/canvas/WebGLContextGL.cpp @@ -218,57 +218,6 @@ WebGLContext::BindRenderbuffer(GLenum target, WebGLRenderbuffer* wrb) mBoundRenderbuffer = wrb; } -void -WebGLContext::BindTexture(GLenum rawTarget, WebGLTexture* newTex) -{ - if (IsContextLost()) - return; - - if (!ValidateObjectAllowDeletedOrNull("bindTexture", newTex)) - return; - - // Need to check rawTarget first before comparing against newTex->Target() as - // newTex->Target() returns a TexTarget, which will assert on invalid value. - WebGLRefPtr* currentTexPtr = nullptr; - switch (rawTarget) { - case LOCAL_GL_TEXTURE_2D: - currentTexPtr = &mBound2DTextures[mActiveTexture]; - break; - case LOCAL_GL_TEXTURE_CUBE_MAP: - currentTexPtr = &mBoundCubeMapTextures[mActiveTexture]; - break; - case LOCAL_GL_TEXTURE_3D: - if (!IsWebGL2()) { - return ErrorInvalidEnum("bindTexture: target TEXTURE_3D is only available in WebGL version 2.0 or newer"); - } - currentTexPtr = &mBound3DTextures[mActiveTexture]; - break; - default: - return ErrorInvalidEnumInfo("bindTexture: target", rawTarget); - } - const TexTarget target(rawTarget); - - if (newTex) { - // silently ignore a deleted texture - if (newTex->IsDeleted()) - return; - - if (newTex->HasEverBeenBound() && newTex->Target() != rawTarget) - return ErrorInvalidOperation("bindTexture: this texture has already been bound to a different target"); - } - - *currentTexPtr = newTex; - - MakeContextCurrent(); - - if (newTex) { - SetFakeBlackStatus(WebGLContextFakeBlackStatus::Unknown); - newTex->Bind(target); - } else { - gl->fBindTexture(target.get(), 0); - } -} - void WebGLContext::BlendEquation(GLenum mode) { if (IsContextLost()) @@ -362,269 +311,6 @@ WebGLContext::CheckFramebufferStatus(GLenum target) return fb->CheckFramebufferStatus().get(); } -void -WebGLContext::CopyTexSubImage2D_base(TexImageTarget texImageTarget, GLint level, - TexInternalFormat internalformat, - GLint xoffset, GLint yoffset, GLint x, - GLint y, GLsizei width, GLsizei height, - bool sub) -{ - const WebGLRectangleObject* framebufferRect = CurValidReadFBRectObject(); - GLsizei framebufferWidth = framebufferRect ? framebufferRect->Width() : 0; - GLsizei framebufferHeight = framebufferRect ? framebufferRect->Height() : 0; - - WebGLTexImageFunc func = sub - ? WebGLTexImageFunc::CopyTexSubImage - : WebGLTexImageFunc::CopyTexImage; - WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; - const char* info = InfoFrom(func, dims); - - // TODO: This changes with color_buffer_float. Reassess when the - // patch lands. - if (!ValidateTexImage(texImageTarget, level, internalformat.get(), - xoffset, yoffset, 0, - width, height, 0, - 0, - LOCAL_GL_NONE, LOCAL_GL_NONE, - func, dims)) - { - return; - } - - if (!ValidateCopyTexImage(internalformat.get(), func, dims)) - return; - - if (!mBoundReadFramebuffer) - ClearBackbufferIfNeeded(); - - MakeContextCurrent(); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - - if (!tex) - return ErrorInvalidOperation("%s: no texture is bound to this target"); - - if (tex->IsImmutable()) { - if (!sub) { - return ErrorInvalidOperation("copyTexImage2D: disallowed because the texture bound to this target has already been made immutable by texStorage2D"); - } - } - - TexType framebuffertype = LOCAL_GL_NONE; - if (mBoundReadFramebuffer) { - TexInternalFormat framebuffereffectiveformat = mBoundReadFramebuffer->ColorAttachment(0).EffectiveInternalFormat(); - framebuffertype = TypeFromInternalFormat(framebuffereffectiveformat); - } else { - // FIXME - here we're assuming that the default framebuffer is backed by UNSIGNED_BYTE - // that might not always be true, say if we had a 16bpp default framebuffer. - framebuffertype = LOCAL_GL_UNSIGNED_BYTE; - } - - TexInternalFormat effectiveInternalFormat = - EffectiveInternalFormatFromUnsizedInternalFormatAndType(internalformat, framebuffertype); - - // this should never fail, validation happened earlier. - MOZ_ASSERT(effectiveInternalFormat != LOCAL_GL_NONE); - - const bool widthOrHeightIsZero = (width == 0 || height == 0); - if (gl->WorkAroundDriverBugs() && - sub && widthOrHeightIsZero) - { - // NV driver on Linux complains that CopyTexSubImage2D(level=0, - // xoffset=0, yoffset=2, x=0, y=0, width=0, height=0) from a 300x150 FB - // to a 0x2 texture. This a useless thing to do, but technically legal. - // NV331.38 generates INVALID_VALUE. - return DummyFramebufferOperation(info); - } - - // check if the memory size of this texture may change with this call - bool sizeMayChange = !sub; - if (!sub && tex->HasImageInfoAt(texImageTarget, level)) { - const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level); - sizeMayChange = width != imageInfo.Width() || - height != imageInfo.Height() || - effectiveInternalFormat != imageInfo.EffectiveInternalFormat(); - } - - if (sizeMayChange) - GetAndFlushUnderlyingGLErrors(); - - if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) { - if (sub) - gl->fCopyTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, x, y, width, height); - else - gl->fCopyTexImage2D(texImageTarget.get(), level, internalformat.get(), x, y, width, height, 0); - } else { - - // the rect doesn't fit in the framebuffer - - // first, we initialize the texture as black - if (!sub) { - tex->SetImageInfo(texImageTarget, level, width, height, 1, - effectiveInternalFormat, - WebGLImageDataStatus::UninitializedImageData); - if (!tex->EnsureInitializedImageData(texImageTarget, level)) - return; - } - - // if we are completely outside of the framebuffer, we can exit now with our black texture - if ( x >= framebufferWidth - || x+width <= 0 - || y >= framebufferHeight - || y+height <= 0) - { - // we are completely outside of range, can exit now with buffer filled with zeros - return DummyFramebufferOperation(info); - } - - GLint actual_x = clamped(x, 0, framebufferWidth); - GLint actual_x_plus_width = clamped(x + width, 0, framebufferWidth); - GLsizei actual_width = actual_x_plus_width - actual_x; - GLint actual_xoffset = xoffset + actual_x - x; - - GLint actual_y = clamped(y, 0, framebufferHeight); - GLint actual_y_plus_height = clamped(y + height, 0, framebufferHeight); - GLsizei actual_height = actual_y_plus_height - actual_y; - GLint actual_yoffset = yoffset + actual_y - y; - - gl->fCopyTexSubImage2D(texImageTarget.get(), level, actual_xoffset, actual_yoffset, actual_x, actual_y, actual_width, actual_height); - } - - if (sizeMayChange) { - GLenum error = GetAndFlushUnderlyingGLErrors(); - if (error) { - GenerateWarning("copyTexImage2D generated error %s", ErrorName(error)); - return; - } - } - - if (!sub) { - tex->SetImageInfo(texImageTarget, level, width, height, 1, - effectiveInternalFormat, - WebGLImageDataStatus::InitializedImageData); - } -} - -void -WebGLContext::CopyTexImage2D(GLenum rawTexImgTarget, - GLint level, - GLenum internalformat, - GLint x, - GLint y, - GLsizei width, - GLsizei height, - GLint border) -{ - if (IsContextLost()) - return; - - // copyTexImage2D only generates textures with type = UNSIGNED_BYTE - const WebGLTexImageFunc func = WebGLTexImageFunc::CopyTexImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; - - if (!ValidateTexImageTarget(rawTexImgTarget, func, dims)) - return; - - if (!ValidateTexImage(rawTexImgTarget, level, internalformat, - 0, 0, 0, - width, height, 0, - border, LOCAL_GL_NONE, LOCAL_GL_NONE, - func, dims)) - { - return; - } - - if (!ValidateCopyTexImage(internalformat, func, dims)) - return; - - if (!mBoundReadFramebuffer) - ClearBackbufferIfNeeded(); - - CopyTexSubImage2D_base(rawTexImgTarget, level, internalformat, 0, 0, x, y, width, height, false); -} - -void -WebGLContext::CopyTexSubImage2D(GLenum rawTexImgTarget, - GLint level, - GLint xoffset, - GLint yoffset, - GLint x, - GLint y, - GLsizei width, - GLsizei height) -{ - if (IsContextLost()) - return; - - switch (rawTexImgTarget) { - case LOCAL_GL_TEXTURE_2D: - case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: - case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: - case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: - case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: - case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: - case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: - break; - default: - return ErrorInvalidEnumInfo("copyTexSubImage2D: target", rawTexImgTarget); - } - - const TexImageTarget texImageTarget(rawTexImgTarget); - - if (level < 0) - return ErrorInvalidValue("copyTexSubImage2D: level may not be negative"); - - GLsizei maxTextureSize = MaxTextureSizeForTarget(TexImageTargetToTexTarget(texImageTarget)); - if (!(maxTextureSize >> level)) - return ErrorInvalidValue("copyTexSubImage2D: 2^level exceeds maximum texture size"); - - if (width < 0 || height < 0) - return ErrorInvalidValue("copyTexSubImage2D: width and height may not be negative"); - - if (xoffset < 0 || yoffset < 0) - return ErrorInvalidValue("copyTexSubImage2D: xoffset and yoffset may not be negative"); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - if (!tex) - return ErrorInvalidOperation("copyTexSubImage2D: no texture bound to this target"); - - if (!tex->HasImageInfoAt(texImageTarget, level)) - return ErrorInvalidOperation("copyTexSubImage2D: no texture image previously defined for this level and face"); - - const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level); - GLsizei texWidth = imageInfo.Width(); - GLsizei texHeight = imageInfo.Height(); - - if (xoffset + width > texWidth || xoffset + width < 0) - return ErrorInvalidValue("copyTexSubImage2D: xoffset+width is too large"); - - if (yoffset + height > texHeight || yoffset + height < 0) - return ErrorInvalidValue("copyTexSubImage2D: yoffset+height is too large"); - - if (!mBoundReadFramebuffer) - ClearBackbufferIfNeeded(); - - if (imageInfo.HasUninitializedImageData()) { - bool coversWholeImage = xoffset == 0 && - yoffset == 0 && - width == texWidth && - height == texHeight; - if (coversWholeImage) { - tex->SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); - } else { - if (!tex->EnsureInitializedImageData(texImageTarget, level)) - return; - } - } - - TexInternalFormat internalformat; - TexType type; - UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(imageInfo.EffectiveInternalFormat(), - &internalformat, &type); - return CopyTexSubImage2D_base(texImageTarget, level, internalformat, xoffset, yoffset, x, y, width, height, true); -} - - already_AddRefed WebGLContext::CreateProgram() { @@ -937,70 +623,6 @@ WebGLContext::FrontFace(GLenum mode) gl->fFrontFace(mode); } -void -WebGLContext::GenerateMipmap(GLenum rawTarget) -{ - if (IsContextLost()) - return; - - if (!ValidateTextureTargetEnum(rawTarget, "generateMipmap")) - return; - - const TexTarget target(rawTarget); - - WebGLTexture* tex = ActiveBoundTextureForTarget(target); - - if (!tex) - return ErrorInvalidOperation("generateMipmap: No texture is bound to this target."); - - const TexImageTarget imageTarget = (target == LOCAL_GL_TEXTURE_2D) - ? LOCAL_GL_TEXTURE_2D - : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X; - if (!tex->IsMipmapRangeValid()) - { - return ErrorInvalidOperation("generateMipmap: Texture does not have a valid mipmap range."); - } - if (!tex->HasImageInfoAt(imageTarget, tex->EffectiveBaseMipmapLevel())) - { - return ErrorInvalidOperation("generateMipmap: Level zero of texture is not defined."); - } - - if (!IsWebGL2() && !tex->IsFirstImagePowerOfTwo()) - return ErrorInvalidOperation("generateMipmap: Level zero of texture does not have power-of-two width and height."); - - TexInternalFormat internalformat = tex->ImageInfoAt(imageTarget, 0).EffectiveInternalFormat(); - if (IsTextureFormatCompressed(internalformat)) - return ErrorInvalidOperation("generateMipmap: Texture data at level zero is compressed."); - - if (IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) && - (IsGLDepthFormat(internalformat) || IsGLDepthStencilFormat(internalformat))) - { - return ErrorInvalidOperation("generateMipmap: " - "A texture that has a base internal format of " - "DEPTH_COMPONENT or DEPTH_STENCIL isn't supported"); - } - - if (!tex->AreAllLevel0ImageInfosEqual()) - return ErrorInvalidOperation("generateMipmap: The six faces of this cube map have different dimensions, format, or type."); - - tex->SetGeneratedMipmap(); - - MakeContextCurrent(); - - if (gl->WorkAroundDriverBugs()) { - // bug 696495 - to work around failures in the texture-mips.html test on various drivers, we - // set the minification filter before calling glGenerateMipmap. This should not carry a significant performance - // overhead so we do it unconditionally. - // - // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See Chromium bug 101105. - gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST_MIPMAP_NEAREST); - gl->fGenerateMipmap(target.get()); - gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, tex->MinFilter().get()); - } else { - gl->fGenerateMipmap(target.get()); - } -} - already_AddRefed WebGLContext::GetActiveAttrib(WebGLProgram* prog, GLuint index) { @@ -1433,210 +1055,6 @@ WebGLContext::GetProgramInfoLog(WebGLProgram* prog, nsAString& retval) retval.SetIsVoid(false); } -// here we have to support all pnames with both int and float params. -// See this discussion: -// https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html -void WebGLContext::TexParameter_base(GLenum rawTarget, GLenum pname, - GLint* intParamPtr, - GLfloat* floatParamPtr) -{ - MOZ_ASSERT(intParamPtr || floatParamPtr); - - if (IsContextLost()) - return; - - GLint intParam = intParamPtr ? *intParamPtr : GLint(*floatParamPtr); - GLfloat floatParam = floatParamPtr ? *floatParamPtr : GLfloat(*intParamPtr); - - if (!ValidateTextureTargetEnum(rawTarget, "texParameter: target")) - return; - - const TexTarget texTarget = TexTarget(rawTarget); - - WebGLTexture* tex = ActiveBoundTextureForTarget(texTarget); - if (!tex) - return ErrorInvalidOperation("texParameter: no texture is bound to this target"); - - bool pnameAndParamAreIncompatible = false; - bool paramValueInvalid = false; - - switch (pname) { - case LOCAL_GL_TEXTURE_BASE_LEVEL: - case LOCAL_GL_TEXTURE_MAX_LEVEL: - if (!IsWebGL2()) - return ErrorInvalidEnumInfo("texParameter: pname", pname); - if (intParam < 0) { - paramValueInvalid = true; - break; - } - if (pname == LOCAL_GL_TEXTURE_BASE_LEVEL) - tex->SetBaseMipmapLevel(intParam); - else - tex->SetMaxMipmapLevel(intParam); - break; - - case LOCAL_GL_TEXTURE_COMPARE_MODE: - if (!IsWebGL2()) - return ErrorInvalidEnumInfo("texParameter: pname", pname); - - paramValueInvalid = (intParam != LOCAL_GL_NONE && - intParam != LOCAL_GL_COMPARE_REF_TO_TEXTURE); - break; - - case LOCAL_GL_TEXTURE_COMPARE_FUNC: - if (!IsWebGL2()) - return ErrorInvalidEnumInfo("texParameter: pname", pname); - - switch (intParam) { - case LOCAL_GL_LEQUAL: - case LOCAL_GL_GEQUAL: - case LOCAL_GL_LESS: - case LOCAL_GL_GREATER: - case LOCAL_GL_EQUAL: - case LOCAL_GL_NOTEQUAL: - case LOCAL_GL_ALWAYS: - case LOCAL_GL_NEVER: - break; - - default: - pnameAndParamAreIncompatible = true; - } - break; - - case LOCAL_GL_TEXTURE_MIN_FILTER: - switch (intParam) { - case LOCAL_GL_NEAREST: - case LOCAL_GL_LINEAR: - case LOCAL_GL_NEAREST_MIPMAP_NEAREST: - case LOCAL_GL_LINEAR_MIPMAP_NEAREST: - case LOCAL_GL_NEAREST_MIPMAP_LINEAR: - case LOCAL_GL_LINEAR_MIPMAP_LINEAR: - tex->SetMinFilter(intParam); - break; - default: - pnameAndParamAreIncompatible = true; - } - break; - case LOCAL_GL_TEXTURE_MAG_FILTER: - switch (intParam) { - case LOCAL_GL_NEAREST: - case LOCAL_GL_LINEAR: - tex->SetMagFilter(intParam); - break; - default: - pnameAndParamAreIncompatible = true; - } - break; - case LOCAL_GL_TEXTURE_WRAP_S: - switch (intParam) { - case LOCAL_GL_CLAMP_TO_EDGE: - case LOCAL_GL_MIRRORED_REPEAT: - case LOCAL_GL_REPEAT: - tex->SetWrapS(intParam); - break; - default: - pnameAndParamAreIncompatible = true; - } - break; - case LOCAL_GL_TEXTURE_WRAP_T: - switch (intParam) { - case LOCAL_GL_CLAMP_TO_EDGE: - case LOCAL_GL_MIRRORED_REPEAT: - case LOCAL_GL_REPEAT: - tex->SetWrapT(intParam); - break; - default: - pnameAndParamAreIncompatible = true; - } - break; - case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: - if (IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic)) { - if (floatParamPtr && floatParam < 1.f) - paramValueInvalid = true; - else if (intParamPtr && intParam < 1) - paramValueInvalid = true; - } - else - pnameAndParamAreIncompatible = true; - break; - default: - return ErrorInvalidEnumInfo("texParameter: pname", pname); - } - - if (pnameAndParamAreIncompatible) { - if (intParamPtr) - return ErrorInvalidEnum("texParameteri: pname %x and param %x (decimal %d) are mutually incompatible", - pname, intParam, intParam); - else - return ErrorInvalidEnum("texParameterf: pname %x and param %g are mutually incompatible", - pname, floatParam); - } else if (paramValueInvalid) { - if (intParamPtr) - return ErrorInvalidValue("texParameteri: pname %x and param %x (decimal %d) is invalid", - pname, intParam, intParam); - else - return ErrorInvalidValue("texParameterf: pname %x and param %g is invalid", - pname, floatParam); - } - - MakeContextCurrent(); - if (intParamPtr) - gl->fTexParameteri(texTarget.get(), pname, intParam); - else - gl->fTexParameterf(texTarget.get(), pname, floatParam); -} - -JS::Value -WebGLContext::GetTexParameter(GLenum rawTarget, GLenum pname) -{ - if (IsContextLost()) - return JS::NullValue(); - - MakeContextCurrent(); - - if (!ValidateTextureTargetEnum(rawTarget, "getTexParameter: target")) - return JS::NullValue(); - - const TexTarget target(rawTarget); - - if (!ActiveBoundTextureForTarget(target)) { - ErrorInvalidOperation("getTexParameter: no texture bound"); - return JS::NullValue(); - } - - return GetTexParameterInternal(target, pname); -} - -JS::Value -WebGLContext::GetTexParameterInternal(const TexTarget& target, GLenum pname) -{ - switch (pname) { - case LOCAL_GL_TEXTURE_MIN_FILTER: - case LOCAL_GL_TEXTURE_MAG_FILTER: - case LOCAL_GL_TEXTURE_WRAP_S: - case LOCAL_GL_TEXTURE_WRAP_T: - { - GLint i = 0; - gl->fGetTexParameteriv(target.get(), pname, &i); - return JS::NumberValue(uint32_t(i)); - } - case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: - if (IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic)) { - GLfloat f = 0.f; - gl->fGetTexParameterfv(target.get(), pname, &f); - return JS::DoubleValue(f); - } - - ErrorInvalidEnumInfo("getTexParameter: parameter", pname); - break; - - default: - ErrorInvalidEnumInfo("getTexParameter: parameter", pname); - } - - return JS::NullValue(); -} - JS::Value WebGLContext::GetUniform(JSContext* js, WebGLProgram* prog, WebGLUniformLocation* loc) @@ -1760,17 +1178,6 @@ WebGLContext::IsShader(WebGLShader* shader) !shader->IsDeleted(); } -bool -WebGLContext::IsTexture(WebGLTexture* tex) -{ - if (IsContextLost()) - return false; - - return ValidateObjectAllowDeleted("isTexture", tex) && - !tex->IsDeleted() && - tex->HasEverBeenBound(); -} - void WebGLContext::LinkProgram(WebGLProgram* prog) { @@ -1965,7 +1372,7 @@ IsFormatAndTypeUnpackable(GLenum format, GLenum type) void WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, - GLenum type, const dom::Nullable& pixels, + GLenum type, const dom::Nullable& pixels, ErrorResult& rv) { if (IsContextLost()) @@ -2038,7 +1445,7 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, MOZ_CRASH("bad `type`"); } - const ArrayBufferView& pixbuf = pixels.Value(); + const dom::ArrayBufferView& pixbuf = pixels.Value(); int dataType = pixbuf.Type(); // Check the pixels param type @@ -2942,127 +2349,6 @@ WebGLContext::CompileShader(WebGLShader* shader) shader->CompileShader(); } -void -WebGLContext::CompressedTexImage2D(GLenum rawTexImgTarget, - GLint level, - GLenum internalformat, - GLsizei width, GLsizei height, GLint border, - const ArrayBufferView& view) -{ - if (IsContextLost()) - return; - - const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; - - if (!ValidateTexImageTarget(rawTexImgTarget, func, dims)) - return; - - if (!ValidateTexImage(rawTexImgTarget, level, internalformat, - 0, 0, 0, width, height, 0, - border, LOCAL_GL_NONE, - LOCAL_GL_NONE, - func, dims)) - { - return; - } - - view.ComputeLengthAndData(); - - uint32_t byteLength = view.Length(); - if (!ValidateCompTexImageDataSize(level, internalformat, width, height, byteLength, func, dims)) { - return; - } - - if (!ValidateCompTexImageSize(level, internalformat, 0, 0, width, height, width, height, func, dims)) - { - return; - } - - const TexImageTarget texImageTarget(rawTexImgTarget); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - MOZ_ASSERT(tex); - if (tex->IsImmutable()) { - return ErrorInvalidOperation( - "compressedTexImage2D: disallowed because the texture bound to " - "this target has already been made immutable by texStorage2D"); - } - - MakeContextCurrent(); - gl->fCompressedTexImage2D(texImageTarget.get(), level, internalformat, width, height, border, byteLength, view.Data()); - - tex->SetImageInfo(texImageTarget, level, width, height, 1, internalformat, - WebGLImageDataStatus::InitializedImageData); -} - -void -WebGLContext::CompressedTexSubImage2D(GLenum rawTexImgTarget, GLint level, GLint xoffset, - GLint yoffset, GLsizei width, GLsizei height, - GLenum internalformat, - const ArrayBufferView& view) -{ - if (IsContextLost()) - return; - - const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexSubImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; - - if (!ValidateTexImageTarget(rawTexImgTarget, func, dims)) - return; - - if (!ValidateTexImage(rawTexImgTarget, - level, internalformat, - xoffset, yoffset, 0, - width, height, 0, - 0, LOCAL_GL_NONE, LOCAL_GL_NONE, - func, dims)) - { - return; - } - - const TexImageTarget texImageTarget(rawTexImgTarget); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - MOZ_ASSERT(tex); - WebGLTexture::ImageInfo& levelInfo = tex->ImageInfoAt(texImageTarget, level); - - if (internalformat != levelInfo.EffectiveInternalFormat()) { - return ErrorInvalidOperation("compressedTexImage2D: internalformat does not match the existing image"); - } - - view.ComputeLengthAndData(); - - uint32_t byteLength = view.Length(); - if (!ValidateCompTexImageDataSize(level, internalformat, width, height, byteLength, func, dims)) - return; - - if (!ValidateCompTexImageSize(level, internalformat, - xoffset, yoffset, - width, height, - levelInfo.Width(), levelInfo.Height(), - func, dims)) - { - return; - } - - if (levelInfo.HasUninitializedImageData()) { - bool coversWholeImage = xoffset == 0 && - yoffset == 0 && - width == levelInfo.Width() && - height == levelInfo.Height(); - if (coversWholeImage) { - tex->SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); - } else { - if (!tex->EnsureInitializedImageData(texImageTarget, level)) - return; - } - } - - MakeContextCurrent(); - gl->fCompressedTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, width, height, internalformat, byteLength, view.Data()); -} - JS::Value WebGLContext::GetShaderParameter(WebGLShader* shader, GLenum pname) { @@ -3179,461 +2465,6 @@ WebGLContext::GetShaderTranslatedSource(WebGLShader* shader, nsAString& retval) shader->GetShaderTranslatedSource(&retval); } -GLenum WebGLContext::CheckedTexImage2D(TexImageTarget texImageTarget, - GLint level, - TexInternalFormat internalformat, - GLsizei width, - GLsizei height, - GLint border, - TexFormat format, - TexType type, - const GLvoid* data) -{ - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - MOZ_ASSERT(tex, "no texture bound"); - - TexInternalFormat effectiveInternalFormat = - EffectiveInternalFormatFromInternalFormatAndType(internalformat, type); - bool sizeMayChange = true; - - if (tex->HasImageInfoAt(texImageTarget, level)) { - const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level); - sizeMayChange = width != imageInfo.Width() || - height != imageInfo.Height() || - effectiveInternalFormat != imageInfo.EffectiveInternalFormat(); - } - - // Convert to format and type required by OpenGL 'driver'. - GLenum driverType = LOCAL_GL_NONE; - GLenum driverInternalFormat = LOCAL_GL_NONE; - GLenum driverFormat = LOCAL_GL_NONE; - DriverFormatsFromEffectiveInternalFormat(gl, - effectiveInternalFormat, - &driverInternalFormat, - &driverFormat, - &driverType); - - if (sizeMayChange) { - GetAndFlushUnderlyingGLErrors(); - } - - gl->fTexImage2D(texImageTarget.get(), level, driverInternalFormat, width, height, border, driverFormat, driverType, data); - - if (effectiveInternalFormat != driverInternalFormat) - SetLegacyTextureSwizzle(gl, texImageTarget.get(), internalformat.get()); - - GLenum error = LOCAL_GL_NO_ERROR; - if (sizeMayChange) { - error = GetAndFlushUnderlyingGLErrors(); - } - - return error; -} - -void -WebGLContext::TexImage2D_base(TexImageTarget texImageTarget, GLint level, - GLenum internalformat, - GLsizei width, GLsizei height, GLsizei srcStrideOrZero, - GLint border, - GLenum format, - GLenum type, - void* data, uint32_t byteLength, - js::Scalar::Type jsArrayType, - WebGLTexelFormat srcFormat, bool srcPremultiplied) -{ - const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; - - if (type == LOCAL_GL_HALF_FLOAT_OES) { - type = LOCAL_GL_HALF_FLOAT; - } - - if (!ValidateTexImage(texImageTarget, level, internalformat, - 0, 0, 0, - width, height, 0, - border, format, type, func, dims)) - { - return; - } - - const bool isDepthTexture = format == LOCAL_GL_DEPTH_COMPONENT || - format == LOCAL_GL_DEPTH_STENCIL; - - if (isDepthTexture && !IsWebGL2()) { - if (data != nullptr || level != 0) - return ErrorInvalidOperation("texImage2D: " - "with format of DEPTH_COMPONENT or DEPTH_STENCIL, " - "data must be nullptr, " - "level must be zero"); - } - - if (!ValidateTexInputData(type, jsArrayType, func, dims)) - return; - - TexInternalFormat effectiveInternalFormat = - EffectiveInternalFormatFromInternalFormatAndType(internalformat, type); - - if (effectiveInternalFormat == LOCAL_GL_NONE) { - return ErrorInvalidOperation("texImage2D: bad combination of internalformat and type"); - } - - size_t srcTexelSize = size_t(-1); - if (srcFormat == WebGLTexelFormat::Auto) { - // we need to find the exact sized format of the source data. Slightly abusing - // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format - // is the same thing as an unsized internalformat. - TexInternalFormat effectiveSourceFormat = - EffectiveInternalFormatFromInternalFormatAndType(format, type); - MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated format/type combo earlier - const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat); - MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here. - srcTexelSize = srcbitsPerTexel / 8; - } else { - srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(srcFormat); - } - - CheckedUint32 checked_neededByteLength = - GetImageSize(height, width, 1, srcTexelSize, mPixelStoreUnpackAlignment); - - CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize; - CheckedUint32 checked_alignedRowSize = - RoundedToNextMultipleOf(checked_plainRowSize.value(), mPixelStoreUnpackAlignment); - - if (!checked_neededByteLength.isValid()) - return ErrorInvalidOperation("texImage2D: integer overflow computing the needed buffer size"); - - uint32_t bytesNeeded = checked_neededByteLength.value(); - - if (byteLength && byteLength < bytesNeeded) - return ErrorInvalidOperation("texImage2D: not enough data for operation (need %d, have %d)", - bytesNeeded, byteLength); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - - if (!tex) - return ErrorInvalidOperation("texImage2D: no texture is bound to this target"); - - if (tex->IsImmutable()) { - return ErrorInvalidOperation( - "texImage2D: disallowed because the texture " - "bound to this target has already been made immutable by texStorage2D"); - } - MakeContextCurrent(); - - nsAutoArrayPtr convertedData; - void* pixels = nullptr; - WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData; - - WebGLTexelFormat dstFormat = GetWebGLTexelFormat(effectiveInternalFormat); - WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat; - - if (byteLength) { - size_t bitsPerTexel = GetBitsPerTexel(effectiveInternalFormat); - MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here. - size_t dstTexelSize = bitsPerTexel / 8; - size_t srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value(); - size_t dstPlainRowSize = dstTexelSize * width; - size_t unpackAlignment = mPixelStoreUnpackAlignment; - size_t dstStride = ((dstPlainRowSize + unpackAlignment-1) / unpackAlignment) * unpackAlignment; - - if (actualSrcFormat == dstFormat && - srcPremultiplied == mPixelStorePremultiplyAlpha && - srcStride == dstStride && - !mPixelStoreFlipY) - { - // no conversion, no flipping, so we avoid copying anything and just pass the source pointer - pixels = data; - } - else - { - size_t convertedDataSize = height * dstStride; - convertedData = new (fallible) uint8_t[convertedDataSize]; - if (!convertedData) { - ErrorOutOfMemory("texImage2D: Ran out of memory when allocating" - " a buffer for doing format conversion."); - return; - } - if (!ConvertImage(width, height, srcStride, dstStride, - static_cast(data), convertedData, - actualSrcFormat, srcPremultiplied, - dstFormat, mPixelStorePremultiplyAlpha, dstTexelSize)) - { - return ErrorInvalidOperation("texImage2D: Unsupported texture format conversion"); - } - pixels = reinterpret_cast(convertedData.get()); - } - imageInfoStatusIfSuccess = WebGLImageDataStatus::InitializedImageData; - } - - GLenum error = CheckedTexImage2D(texImageTarget, level, internalformat, width, - height, border, format, type, pixels); - - if (error) { - GenerateWarning("texImage2D generated error %s", ErrorName(error)); - return; - } - - // in all of the code paths above, we should have either initialized data, - // or allocated data and left it uninitialized, but in any case we shouldn't - // have NoImageData at this point. - MOZ_ASSERT(imageInfoStatusIfSuccess != WebGLImageDataStatus::NoImageData); - - tex->SetImageInfo(texImageTarget, level, width, height, 1, - effectiveInternalFormat, imageInfoStatusIfSuccess); -} - -void -WebGLContext::TexImage2D(GLenum rawTarget, GLint level, - GLenum internalformat, GLsizei width, - GLsizei height, GLint border, GLenum format, - GLenum type, const dom::Nullable& pixels, - ErrorResult& rv) -{ - if (IsContextLost()) - return; - - void* data; - uint32_t length; - js::Scalar::Type jsArrayType; - if (pixels.IsNull()) { - data = nullptr; - length = 0; - jsArrayType = js::Scalar::MaxTypedArrayViewType; - } else { - const ArrayBufferView& view = pixels.Value(); - view.ComputeLengthAndData(); - - data = view.Data(); - length = view.Length(); - jsArrayType = view.Type(); - } - - if (!ValidateTexImageTarget(rawTarget, WebGLTexImageFunc::TexImage, WebGLTexDimensions::Tex2D)) - return; - - return TexImage2D_base(rawTarget, level, internalformat, width, height, 0, border, format, type, - data, length, jsArrayType, - WebGLTexelFormat::Auto, false); -} - -void -WebGLContext::TexImage2D(GLenum rawTarget, GLint level, - GLenum internalformat, GLenum format, - GLenum type, ImageData* pixels, ErrorResult& rv) -{ - if (IsContextLost()) - return; - - if (!pixels) { - // Spec says to generate an INVALID_VALUE error - return ErrorInvalidValue("texImage2D: null ImageData"); - } - - Uint8ClampedArray arr; - DebugOnly inited = arr.Init(pixels->GetDataObject()); - MOZ_ASSERT(inited); - arr.ComputeLengthAndData(); - - void* pixelData = arr.Data(); - const uint32_t pixelDataLength = arr.Length(); - - if (!ValidateTexImageTarget(rawTarget, WebGLTexImageFunc::TexImage, WebGLTexDimensions::Tex2D)) - return; - - return TexImage2D_base(rawTarget, level, internalformat, pixels->Width(), - pixels->Height(), 4*pixels->Width(), 0, - format, type, pixelData, pixelDataLength, js::Scalar::MaxTypedArrayViewType, - WebGLTexelFormat::RGBA8, false); -} - - -void -WebGLContext::TexSubImage2D_base(GLenum rawImageTarget, GLint level, - GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLsizei srcStrideOrZero, - GLenum format, GLenum type, - void* data, uint32_t byteLength, - js::Scalar::Type jsArrayType, - WebGLTexelFormat srcFormat, bool srcPremultiplied) -{ - const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage; - const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; - - if (type == LOCAL_GL_HALF_FLOAT_OES) - type = LOCAL_GL_HALF_FLOAT; - - if (!ValidateTexImageTarget(rawImageTarget, func, dims)) - return; - - TexImageTarget texImageTarget(rawImageTarget); - - WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget); - if (!tex) - return ErrorInvalidOperation("texSubImage2D: no texture bound on active texture unit"); - - if (!tex->HasImageInfoAt(texImageTarget, level)) - return ErrorInvalidOperation("texSubImage2D: no previously defined texture image"); - - const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level); - const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat(); - - if (!ValidateTexImage(texImageTarget, level, - existingEffectiveInternalFormat.get(), - xoffset, yoffset, 0, - width, height, 0, - 0, format, type, func, dims)) - { - return; - } - - if (!ValidateTexInputData(type, jsArrayType, func, dims)) - return; - - if (type != TypeFromInternalFormat(existingEffectiveInternalFormat)) { - return ErrorInvalidOperation("texSubImage2D: type differs from that of the existing image"); - } - - size_t srcTexelSize = size_t(-1); - if (srcFormat == WebGLTexelFormat::Auto) { - const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat); - MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here. - srcTexelSize = bitsPerTexel / 8; - } else { - srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(srcFormat); - } - - if (width == 0 || height == 0) - return; // ES 2.0 says it has no effect, we better return right now - - CheckedUint32 checked_neededByteLength = - GetImageSize(height, width, 1, srcTexelSize, mPixelStoreUnpackAlignment); - - CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize; - - CheckedUint32 checked_alignedRowSize = - RoundedToNextMultipleOf(checked_plainRowSize.value(), mPixelStoreUnpackAlignment); - - if (!checked_neededByteLength.isValid()) - return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); - - uint32_t bytesNeeded = checked_neededByteLength.value(); - - if (byteLength < bytesNeeded) - return ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength); - - if (imageInfo.HasUninitializedImageData()) { - bool coversWholeImage = xoffset == 0 && - yoffset == 0 && - width == imageInfo.Width() && - height == imageInfo.Height(); - if (coversWholeImage) { - tex->SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); - } else { - if (!tex->EnsureInitializedImageData(texImageTarget, level)) - return; - } - } - MakeContextCurrent(); - - size_t srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value(); - uint32_t dstTexelSize = GetBitsPerTexel(existingEffectiveInternalFormat) / 8; - size_t dstPlainRowSize = dstTexelSize * width; - // There are checks above to ensure that this won't overflow. - size_t dstStride = RoundedToNextMultipleOf(dstPlainRowSize, mPixelStoreUnpackAlignment).value(); - - void* pixels = data; - nsAutoArrayPtr convertedData; - - WebGLTexelFormat dstFormat = GetWebGLTexelFormat(existingEffectiveInternalFormat); - WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat; - - // no conversion, no flipping, so we avoid copying anything and just pass the source pointer - bool noConversion = (actualSrcFormat == dstFormat && - srcPremultiplied == mPixelStorePremultiplyAlpha && - srcStride == dstStride && - !mPixelStoreFlipY); - - if (!noConversion) { - size_t convertedDataSize = height * dstStride; - convertedData = new (fallible) uint8_t[convertedDataSize]; - if (!convertedData) { - ErrorOutOfMemory("texImage2D: Ran out of memory when allocating" - " a buffer for doing format conversion."); - return; - } - if (!ConvertImage(width, height, srcStride, dstStride, - static_cast(data), convertedData, - actualSrcFormat, srcPremultiplied, - dstFormat, mPixelStorePremultiplyAlpha, dstTexelSize)) - { - return ErrorInvalidOperation("texSubImage2D: Unsupported texture format conversion"); - } - pixels = reinterpret_cast(convertedData.get()); - } - - GLenum driverType = LOCAL_GL_NONE; - GLenum driverInternalFormat = LOCAL_GL_NONE; - GLenum driverFormat = LOCAL_GL_NONE; - DriverFormatsFromEffectiveInternalFormat(gl, - existingEffectiveInternalFormat, - &driverInternalFormat, - &driverFormat, - &driverType); - - gl->fTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, width, height, driverFormat, driverType, pixels); -} - -void -WebGLContext::TexSubImage2D(GLenum rawTarget, GLint level, - GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, - GLenum format, GLenum type, - const dom::Nullable& pixels, - ErrorResult& rv) -{ - if (IsContextLost()) - return; - - if (pixels.IsNull()) - return ErrorInvalidValue("texSubImage2D: pixels must not be null!"); - - const ArrayBufferView& view = pixels.Value(); - view.ComputeLengthAndData(); - - if (!ValidateTexImageTarget(rawTarget, WebGLTexImageFunc::TexSubImage, WebGLTexDimensions::Tex2D)) - return; - - return TexSubImage2D_base(rawTarget, level, xoffset, yoffset, - width, height, 0, format, type, - view.Data(), view.Length(), view.Type(), - WebGLTexelFormat::Auto, false); -} - -void -WebGLContext::TexSubImage2D(GLenum target, GLint level, - GLint xoffset, GLint yoffset, - GLenum format, GLenum type, ImageData* pixels, - ErrorResult& rv) -{ - if (IsContextLost()) - return; - - if (!pixels) - return ErrorInvalidValue("texSubImage2D: pixels must not be null!"); - - Uint8ClampedArray arr; - DebugOnly inited = arr.Init(pixels->GetDataObject()); - MOZ_ASSERT(inited); - arr.ComputeLengthAndData(); - - return TexSubImage2D_base(target, level, xoffset, yoffset, - pixels->Width(), pixels->Height(), - 4*pixels->Width(), format, type, - arr.Data(), arr.Length(), - js::Scalar::MaxTypedArrayViewType, - WebGLTexelFormat::RGBA8, false); -} - void WebGLContext::LoseContext() { diff --git a/dom/canvas/WebGLContextTextures.cpp b/dom/canvas/WebGLContextTextures.cpp new file mode 100644 index 0000000000..b7f8b81b25 --- /dev/null +++ b/dom/canvas/WebGLContextTextures.cpp @@ -0,0 +1,469 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "WebGLContextUtils.h" +#include "WebGLBuffer.h" +#include "WebGLVertexAttribData.h" +#include "WebGLShader.h" +#include "WebGLProgram.h" +#include "WebGLUniformLocation.h" +#include "WebGLFramebuffer.h" +#include "WebGLRenderbuffer.h" +#include "WebGLShaderPrecisionFormat.h" +#include "WebGLTexture.h" +#include "WebGLExtensions.h" +#include "WebGLVertexArray.h" + +#include "nsString.h" +#include "nsDebug.h" +#include "nsReadableUtils.h" + +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "GLContext.h" + +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsLayoutUtils.h" + +#include "CanvasUtils.h" +#include "gfxUtils.h" + +#include "jsfriendapi.h" + +#include "WebGLTexelConversions.h" +#include "WebGLValidateStrings.h" +#include + +// needed to check if current OS is lower than 10.7 +#if defined(MOZ_WIDGET_COCOA) +#include "nsCocoaFeatures.h" +#endif + +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/Endian.h" + +namespace mozilla { + +static bool +IsValidTexTarget(WebGLContext* webgl, GLenum rawTexTarget, TexTarget* const out) +{ + switch (rawTexTarget) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP: + break; + + case LOCAL_GL_TEXTURE_3D: + if (!webgl->IsWebGL2()) + return false; + + break; + + default: + return false; + } + + *out = rawTexTarget; + return true; +} + +static bool +IsValidTexImageTarget(WebGLContext* webgl, GLenum rawTexImageTarget, + TexImageTarget* const out) +{ + switch (rawTexImageTarget) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + break; + + case LOCAL_GL_TEXTURE_3D: + if (!webgl->IsWebGL2()) + return false; + + break; + + default: + return false; + } + + *out = rawTexImageTarget; + return true; +} + +bool +ValidateTexTarget(WebGLContext* webgl, GLenum rawTexTarget, const char* funcName, + TexTarget* const out_texTarget, WebGLTexture** const out_tex) +{ + if (webgl->IsContextLost()) + return false; + + TexTarget texTarget; + if (!IsValidTexTarget(webgl, rawTexTarget, &texTarget)) { + webgl->ErrorInvalidEnum("%s: Invalid texTarget.", funcName); + return false; + } + + WebGLTexture* tex = webgl->ActiveBoundTextureForTarget(texTarget); + if (!tex) { + webgl->ErrorInvalidOperation("%s: No texture is bound to this target.", funcName); + return false; + } + + *out_texTarget = texTarget; + *out_tex = tex; + return true; +} + +bool +ValidateTexImageTarget(WebGLContext* webgl, GLenum rawTexImageTarget, + const char* funcName, TexImageTarget* const out_texImageTarget, + WebGLTexture** const out_tex) +{ + if (webgl->IsContextLost()) + return false; + + TexImageTarget texImageTarget; + if (!IsValidTexImageTarget(webgl, rawTexImageTarget, &texImageTarget)) { + webgl->ErrorInvalidEnum("%s: Invalid texImageTarget.", funcName); + return false; + } + + WebGLTexture* tex = webgl->ActiveBoundTextureForTexImageTarget(texImageTarget); + if (!tex) { + webgl->ErrorInvalidOperation("%s: No texture is bound to this target.", funcName); + return false; + } + + *out_texImageTarget = texImageTarget; + *out_tex = tex; + return true; +} + +bool +WebGLContext::IsTexParamValid(GLenum pname) const +{ + switch (pname) { + case LOCAL_GL_TEXTURE_MIN_FILTER: + case LOCAL_GL_TEXTURE_MAG_FILTER: + case LOCAL_GL_TEXTURE_WRAP_S: + case LOCAL_GL_TEXTURE_WRAP_T: + return true; + + case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: + return IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic); + + default: + return false; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// GL calls + +void +WebGLContext::BindTexture(GLenum rawTarget, WebGLTexture* newTex) +{ + if (IsContextLost()) + return; + + if (!ValidateObjectAllowDeletedOrNull("bindTexture", newTex)) + return; + + // Need to check rawTarget first before comparing against newTex->Target() as + // newTex->Target() returns a TexTarget, which will assert on invalid value. + WebGLRefPtr* currentTexPtr = nullptr; + switch (rawTarget) { + case LOCAL_GL_TEXTURE_2D: + currentTexPtr = &mBound2DTextures[mActiveTexture]; + break; + + case LOCAL_GL_TEXTURE_CUBE_MAP: + currentTexPtr = &mBoundCubeMapTextures[mActiveTexture]; + break; + + case LOCAL_GL_TEXTURE_3D: + if (!IsWebGL2()) + return ErrorInvalidEnum("bindTexture: target TEXTURE_3D is only available in WebGL version 2.0 or newer"); + + currentTexPtr = &mBound3DTextures[mActiveTexture]; + break; + + default: + return ErrorInvalidEnumInfo("bindTexture: target", rawTarget); + } + const TexTarget texTarget(rawTarget); + + MakeContextCurrent(); + + if (newTex && !newTex->BindTexture(texTarget)) + return; + + if (!newTex) { + gl->fBindTexture(texTarget.get(), 0); + } + + *currentTexPtr = newTex; +} + +void +WebGLContext::GenerateMipmap(GLenum rawTexTarget) +{ + TexTarget texTarget; + WebGLTexture* tex; + if (!ValidateTexTarget(this, rawTexTarget, "texParameter", &texTarget, &tex)) + return; + + tex->GenerateMipmap(texTarget); +} + +JS::Value +WebGLContext::GetTexParameter(GLenum rawTexTarget, GLenum pname) +{ + TexTarget texTarget; + WebGLTexture* tex; + if (!ValidateTexTarget(this, rawTexTarget, "texParameter", &texTarget, &tex)) + return JS::NullValue(); + + if (!IsTexParamValid(pname)) { + ErrorInvalidEnumInfo("getTexParameter: pname", pname); + return JS::NullValue(); + } + + return tex->GetTexParameter(texTarget, pname); +} + +bool +WebGLContext::IsTexture(WebGLTexture* tex) +{ + if (IsContextLost()) + return false; + + if (!ValidateObjectAllowDeleted("isTexture", tex)) + return false; + + return tex->IsTexture(); +} + +void +WebGLContext::TexParameter_base(GLenum rawTexTarget, GLenum pname, GLint* maybeIntParam, + GLfloat* maybeFloatParam) +{ + MOZ_ASSERT(maybeIntParam || maybeFloatParam); + + TexTarget texTarget; + WebGLTexture* tex; + if (!ValidateTexTarget(this, rawTexTarget, "texParameter", &texTarget, &tex)) + return; + + tex->TexParameter(texTarget, pname, maybeIntParam, maybeFloatParam); +} + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +// Uploads + + +////////////////////////////////////////////////////////////////////////////////////////// +// TexImage + +void +WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, dom::Element* elem, + ErrorResult* const out_rv) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "texImage2D", &texImageTarget, + &tex)) + { + return; + } + + tex->TexImage2D(texImageTarget, level, internalFormat, unpackFormat, unpackType, elem, + out_rv); +} + +void +WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat, + GLsizei width, GLsizei height, GLint border, GLenum unpackFormat, + GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult& out_rv) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "texImage2D", &texImageTarget, + &tex)) + { + return; + } + + tex->TexImage2D(texImageTarget, level, internalFormat, width, height, border, + unpackFormat, unpackType, maybeView, &out_rv); +} + +void +WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, + dom::ImageData* imageData, ErrorResult& out_rv) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "texImage2D", &texImageTarget, + &tex)) + { + return; + } + + tex->TexImage2D(texImageTarget, level, internalFormat, unpackFormat, unpackType, + imageData, &out_rv); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// TexSubImage + + +void +WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLenum unpackFormat, GLenum unpackType, + dom::Element* elem, ErrorResult* const out_rv) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "texSubImage2D", &texImageTarget, + &tex)) + { + return; + } + + tex->TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType, + elem, out_rv); +} + +void +WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLsizei width, GLsizei height, + GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult& out_rv) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "texSubImage2D", &texImageTarget, + &tex)) + { + return; + } + + tex->TexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height, + unpackFormat, unpackType, maybeView, &out_rv); +} + +void +WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset, GLint yOffset, + GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData, + ErrorResult& out_rv) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "texSubImage2D", &texImageTarget, + &tex)) + { + return; + } + + tex->TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType, + imageData, &out_rv); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CopyTex(Sub)Image + + +void +WebGLContext::CopyTexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat, + GLint x, GLint y, GLsizei width, GLsizei height, + GLint border) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "copyTexImage2D", + &texImageTarget, &tex)) + { + return; + } + + tex->CopyTexImage2D(texImageTarget, level, internalFormat, x, y, width, height, + border); +} + +void +WebGLContext::CopyTexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint x, GLint y, GLsizei width, + GLsizei height) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "copyTexSubImage2D", + &texImageTarget, &tex)) + { + return; + } + + tex->CopyTexSubImage2D(texImageTarget, level, xOffset, yOffset, x, y, width, height); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// CompressedTex(Sub)Image + + +void +WebGLContext::CompressedTexImage2D(GLenum rawTexImageTarget, GLint level, + GLenum internalFormat, GLsizei width, GLsizei height, + GLint border, const dom::ArrayBufferView& view) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "compressedTexImage2D", + &texImageTarget, &tex)) + { + return; + } + + tex->CompressedTexImage2D(texImageTarget, level, internalFormat, width, height, + border, view); +} + +void +WebGLContext::CompressedTexSubImage2D(GLenum rawTexImageTarget, GLint level, + GLint xOffset, GLint yOffset, GLsizei width, + GLsizei height, GLenum unpackFormat, + const dom::ArrayBufferView& view) +{ + TexImageTarget texImageTarget; + WebGLTexture* tex; + if (!ValidateTexImageTarget(this, rawTexImageTarget, "compressedTexSubImage2D", + &texImageTarget, &tex)) + { + return; + } + + tex->CompressedTexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height, + unpackFormat, view); +} + +} // namespace mozilla diff --git a/dom/canvas/WebGLContextUtils.cpp b/dom/canvas/WebGLContextUtils.cpp index 1de6f9b925..9b7ca8e233 100644 --- a/dom/canvas/WebGLContextUtils.cpp +++ b/dom/canvas/WebGLContextUtils.cpp @@ -1001,7 +1001,7 @@ WebGLContext::EnumName(GLenum glenum, nsACString* out_name) } bool -WebGLContext::IsCompressedTextureFormat(GLenum format) +IsCompressedTextureFormat(GLenum format) { switch (format) { case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: @@ -1034,7 +1034,7 @@ WebGLContext::IsCompressedTextureFormat(GLenum format) bool -WebGLContext::IsTextureFormatCompressed(TexInternalFormat format) +IsTextureFormatCompressed(TexInternalFormat format) { return IsCompressedTextureFormat(format.get()); } diff --git a/dom/canvas/WebGLContextUtils.h b/dom/canvas/WebGLContextUtils.h index 62fc6390ee..6f6c7309d9 100644 --- a/dom/canvas/WebGLContextUtils.h +++ b/dom/canvas/WebGLContextUtils.h @@ -61,6 +61,9 @@ TexTarget TexImageTargetToTexTarget(TexImageTarget texImageTarget); // Helper function to create a JS::Value from a C string JS::Value StringValue(JSContext* cx, const char* str, ErrorResult& rv); +bool IsCompressedTextureFormat(GLenum format); +bool IsTextureFormatCompressed(TexInternalFormat format); + struct GLComponents { unsigned char mComponents; diff --git a/dom/canvas/WebGLContextValidate.cpp b/dom/canvas/WebGLContextValidate.cpp index 82e8fefa8e..bc18b8f70e 100644 --- a/dom/canvas/WebGLContextValidate.cpp +++ b/dom/canvas/WebGLContextValidate.cpp @@ -607,40 +607,6 @@ WebGLContext::ValidateTexImageFormat(GLenum format, WebGLTexImageFunc func, return false; } -/** - * Check if the given texture target is valid for TexImage. - */ -bool -WebGLContext::ValidateTexImageTarget(GLenum target, WebGLTexImageFunc func, - WebGLTexDimensions dims) -{ - switch (dims) { - case WebGLTexDimensions::Tex2D: - if (target == LOCAL_GL_TEXTURE_2D || - IsTexImageCubemapTarget(target)) - { - return true; - } - - ErrorInvalidEnumWithName(this, "invalid target", target, func, dims); - return false; - - case WebGLTexDimensions::Tex3D: - if (target == LOCAL_GL_TEXTURE_3D) - { - return true; - } - - ErrorInvalidEnumWithName(this, "invalid target", target, func, dims); - return false; - - default: - MOZ_ASSERT(false, "ValidateTexImageTarget: Invalid dims"); - } - - return false; -} - /** * Return true if type is a valid texture image type for source, * taking into account enabled WebGL extensions. diff --git a/dom/canvas/WebGLFramebuffer.cpp b/dom/canvas/WebGLFramebuffer.cpp index 872e9237e5..34363d1b0a 100644 --- a/dom/canvas/WebGLFramebuffer.cpp +++ b/dom/canvas/WebGLFramebuffer.cpp @@ -121,8 +121,14 @@ UnmarkAttachment(WebGLFBAttachPoint& attachment) } void -WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, - GLint level) +WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level) +{ + SetTexImageLayer(tex, target, level, 0); +} + +void +WebGLFBAttachPoint::SetTexImageLayer(WebGLTexture* tex, TexImageTarget target, + GLint level, GLint layer) { mFB->InvalidateFramebufferStatus(); @@ -132,6 +138,7 @@ WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, mRenderbufferPtr = nullptr; mTexImageTarget = target; mTexImageLevel = level; + mTexImageLayer = layer; if (tex) tex->MarkAttachment(*this); @@ -378,18 +385,44 @@ WebGLFBAttachPoint::FinalizeAttachment(gl::GLContext* gl, const GLenum imageTarget = ImageTarget().get(); const GLint mipLevel = MipLevel(); + const GLint layer = Layer(); const GLuint glName = Texture()->mGLName; - if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { - gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, - imageTarget, glName, mipLevel); - gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, - imageTarget, glName, mipLevel); - } else { - gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(), - imageTarget, glName, mipLevel); + switch (imageTarget) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { + gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, + imageTarget, glName, mipLevel); + gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, + imageTarget, glName, mipLevel); + } else { + gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(), + imageTarget, glName, mipLevel); + } + break; + + case LOCAL_GL_TEXTURE_2D_ARRAY: + case LOCAL_GL_TEXTURE_3D: + if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { + gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, + LOCAL_GL_DEPTH_ATTACHMENT, + glName, mipLevel, layer); + gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, + LOCAL_GL_STENCIL_ATTACHMENT, + glName, mipLevel, layer); + } else { + gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(), + glName, mipLevel, layer); + } + break; } - return; + return ; } if (Renderbuffer()) { @@ -487,6 +520,21 @@ WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPointEnum, InvalidateFramebufferStatus(); } +void +WebGLFramebuffer::FramebufferTextureLayer(FBAttachment attachment, WebGLTexture* tex, + GLint level, GLint layer) +{ + MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || + mContext->mBoundReadFramebuffer == this); + MOZ_ASSERT(tex); + + WebGLFBAttachPoint& attachPoint = GetAttachPoint(attachment); + TexImageTarget texImageTarget = tex->Target(); + attachPoint.SetTexImageLayer(tex, texImageTarget, level, layer); + + InvalidateFramebufferStatus(); +} + WebGLFBAttachPoint& WebGLFramebuffer::GetAttachPoint(FBAttachment attachPoint) { @@ -509,7 +557,7 @@ WebGLFramebuffer::GetAttachPoint(FBAttachment attachPoint) if (attachPoint >= LOCAL_GL_COLOR_ATTACHMENT1) { size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0; - if (colorAttachmentId < WebGLContext::kMaxColorAttachments) { + if (colorAttachmentId < (size_t)mContext->mGLMaxColorAttachments) { EnsureColorAttachPoints(colorAttachmentId); return mMoreColorAttachments[colorAttachmentId - 1]; } @@ -832,17 +880,19 @@ WebGLFramebuffer::CheckAndInitializeAttachments() void WebGLFramebuffer::EnsureColorAttachPoints(size_t colorAttachmentId) { - MOZ_ASSERT(colorAttachmentId < WebGLContext::kMaxColorAttachments); + size_t maxColorAttachments = mContext->mGLMaxColorAttachments; + + MOZ_ASSERT(colorAttachmentId < maxColorAttachments); if (colorAttachmentId < ColorAttachmentCount()) return; - while (ColorAttachmentCount() < WebGLContext::kMaxColorAttachments) { + while (ColorAttachmentCount() < maxColorAttachments) { GLenum nextAttachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + ColorAttachmentCount(); mMoreColorAttachments.AppendElement(WebGLFBAttachPoint(this, nextAttachPoint)); } - MOZ_ASSERT(ColorAttachmentCount() == WebGLContext::kMaxColorAttachments); + MOZ_ASSERT(ColorAttachmentCount() == maxColorAttachments); } static void diff --git a/dom/canvas/WebGLFramebuffer.h b/dom/canvas/WebGLFramebuffer.h index eeae53c1c8..7e12b8f471 100644 --- a/dom/canvas/WebGLFramebuffer.h +++ b/dom/canvas/WebGLFramebuffer.h @@ -34,6 +34,7 @@ private: WebGLRefPtr mRenderbufferPtr; FBAttachment mAttachmentPoint; TexImageTarget mTexImageTarget; + GLint mTexImageLayer; GLint mTexImageLevel; public: @@ -58,8 +59,10 @@ public: } void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level); + void SetTexImageLayer(WebGLTexture* tex, TexImageTarget target, GLint level, + GLint layer); void SetRenderbuffer(WebGLRenderbuffer* rb); - + const WebGLTexture* Texture() const { return mTexturePtr; } @@ -75,6 +78,9 @@ public: TexImageTarget ImageTarget() const { return mTexImageTarget; } + GLint Layer() const { + return mTexImageLayer; + } GLint MipLevel() const { return mTexImageLevel; } @@ -146,6 +152,9 @@ public: TexImageTarget texImageTarget, WebGLTexture* tex, GLint level); + void FramebufferTextureLayer(FBAttachment attachment, WebGLTexture* tex, GLint level, + GLint layer); + bool HasDefinedAttachments() const; bool HasIncompleteAttachments() const; bool AllImageRectsMatch() const; diff --git a/dom/canvas/WebGLTexture.cpp b/dom/canvas/WebGLTexture.cpp index 976c2a2cd8..3312952c12 100644 --- a/dom/canvas/WebGLTexture.cpp +++ b/dom/canvas/WebGLTexture.cpp @@ -8,13 +8,13 @@ #include #include "GLContext.h" #include "mozilla/dom/WebGLRenderingContextBinding.h" +#include "mozilla/gfx/Logging.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Scoped.h" #include "ScopedGLHelpers.h" #include "WebGLContext.h" #include "WebGLContextUtils.h" #include "WebGLTexelConversions.h" -#include "mozilla/gfx/Logging.h" namespace mozilla { @@ -729,6 +729,289 @@ WebGLTexture::SetFakeBlackStatus(WebGLTextureFakeBlackStatus x) mContext->SetFakeBlackStatus(WebGLContextFakeBlackStatus::Unknown); } +////////////////////////////////////////////////////////////////////////////////////////// +// GL calls + +bool +WebGLTexture::BindTexture(TexTarget texTarget) +{ + // silently ignore a deleted texture + if (IsDeleted()) + return false; + + if (HasEverBeenBound() && mTarget != texTarget) { + mContext->ErrorInvalidOperation("bindTexture: this texture has already been bound to a different target"); + return false; + } + + mContext->SetFakeBlackStatus(WebGLContextFakeBlackStatus::Unknown); + Bind(texTarget); + return true; +} + +void +WebGLTexture::GenerateMipmap(TexTarget texTarget) +{ + const TexImageTarget imageTarget = (texTarget == LOCAL_GL_TEXTURE_2D) + ? LOCAL_GL_TEXTURE_2D + : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X; + if (!IsMipmapRangeValid()) + { + return mContext->ErrorInvalidOperation("generateMipmap: Texture does not have a valid mipmap range."); + } + if (!HasImageInfoAt(imageTarget, EffectiveBaseMipmapLevel())) + { + return mContext->ErrorInvalidOperation("generateMipmap: Level zero of texture is not defined."); + } + + if (!mContext->IsWebGL2() && !IsFirstImagePowerOfTwo()) + return mContext->ErrorInvalidOperation("generateMipmap: Level zero of texture does not have power-of-two width and height."); + + TexInternalFormat internalformat = ImageInfoAt(imageTarget, 0).EffectiveInternalFormat(); + if (IsTextureFormatCompressed(internalformat)) + return mContext->ErrorInvalidOperation("generateMipmap: Texture data at level zero is compressed."); + + if (mContext->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) && + (IsGLDepthFormat(internalformat) || IsGLDepthStencilFormat(internalformat))) + { + return mContext->ErrorInvalidOperation("generateMipmap: " + "A texture that has a base internal format of " + "DEPTH_COMPONENT or DEPTH_STENCIL isn't supported"); + } + + if (!AreAllLevel0ImageInfosEqual()) + return mContext->ErrorInvalidOperation("generateMipmap: The six faces of this cube map have different dimensions, format, or type."); + + SetGeneratedMipmap(); + + mContext->MakeContextCurrent(); + gl::GLContext* gl = mContext->gl; + + if (gl->WorkAroundDriverBugs()) { + // bug 696495 - to work around failures in the texture-mips.html test on various drivers, we + // set the minification filter before calling glGenerateMipmap. This should not carry a significant performance + // overhead so we do it unconditionally. + // + // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See Chromium bug 101105. + gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST_MIPMAP_NEAREST); + gl->fGenerateMipmap(texTarget.get()); + gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER, MinFilter().get()); + } else { + gl->fGenerateMipmap(texTarget.get()); + } +} + +JS::Value +WebGLTexture::GetTexParameter(TexTarget texTarget, GLenum pname) +{ + mContext->MakeContextCurrent(); + + GLint i = 0; + GLfloat f = 0.0f; + + switch (pname) { + case LOCAL_GL_TEXTURE_MIN_FILTER: + case LOCAL_GL_TEXTURE_MAG_FILTER: + case LOCAL_GL_TEXTURE_WRAP_S: + case LOCAL_GL_TEXTURE_WRAP_T: + case LOCAL_GL_TEXTURE_BASE_LEVEL: + case LOCAL_GL_TEXTURE_COMPARE_FUNC: + case LOCAL_GL_TEXTURE_COMPARE_MODE: + case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT: + case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS: + case LOCAL_GL_TEXTURE_MAX_LEVEL: + case LOCAL_GL_TEXTURE_SWIZZLE_A: + case LOCAL_GL_TEXTURE_SWIZZLE_B: + case LOCAL_GL_TEXTURE_SWIZZLE_G: + case LOCAL_GL_TEXTURE_SWIZZLE_R: + case LOCAL_GL_TEXTURE_WRAP_R: + mContext->gl->fGetTexParameteriv(texTarget.get(), pname, &i); + return JS::NumberValue(uint32_t(i)); + + case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: + case LOCAL_GL_TEXTURE_MAX_LOD: + case LOCAL_GL_TEXTURE_MIN_LOD: + mContext->gl->fGetTexParameterfv(texTarget.get(), pname, &f); + return JS::NumberValue(float(f)); + + default: + MOZ_CRASH("Unhandled pname."); + } +} + +bool +WebGLTexture::IsTexture() const +{ + return HasEverBeenBound() && !IsDeleted(); +} + +// Here we have to support all pnames with both int and float params. +// See this discussion: +// https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html +void +WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname, GLint* maybeIntParam, + GLfloat* maybeFloatParam) +{ + MOZ_ASSERT(maybeIntParam || maybeFloatParam); + + GLint intParam = maybeIntParam ? *maybeIntParam : GLint(*maybeFloatParam); + GLfloat floatParam = maybeFloatParam ? *maybeFloatParam : GLfloat(*maybeIntParam); + + bool paramBadEnum = false; + bool paramBadValue = false; + + switch (pname) { + case LOCAL_GL_TEXTURE_BASE_LEVEL: + case LOCAL_GL_TEXTURE_MAX_LEVEL: + if (!mContext->IsWebGL2()) + return mContext->ErrorInvalidEnumInfo("texParameter: pname", pname); + + if (intParam < 0) { + paramBadValue = true; + break; + } + + SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown); + + if (pname == LOCAL_GL_TEXTURE_BASE_LEVEL) + mBaseMipmapLevel = intParam; + else + mMaxMipmapLevel = intParam; + + break; + + case LOCAL_GL_TEXTURE_COMPARE_MODE: + if (!mContext->IsWebGL2()) + return mContext->ErrorInvalidEnumInfo("texParameter: pname", pname); + + paramBadValue = (intParam != LOCAL_GL_NONE && + intParam != LOCAL_GL_COMPARE_REF_TO_TEXTURE); + break; + + case LOCAL_GL_TEXTURE_COMPARE_FUNC: + if (!mContext->IsWebGL2()) + return mContext->ErrorInvalidEnumInfo("texParameter: pname", pname); + + switch (intParam) { + case LOCAL_GL_LEQUAL: + case LOCAL_GL_GEQUAL: + case LOCAL_GL_LESS: + case LOCAL_GL_GREATER: + case LOCAL_GL_EQUAL: + case LOCAL_GL_NOTEQUAL: + case LOCAL_GL_ALWAYS: + case LOCAL_GL_NEVER: + break; + + default: + paramBadValue = true; + } + break; + + case LOCAL_GL_TEXTURE_MIN_FILTER: + switch (intParam) { + case LOCAL_GL_NEAREST: + case LOCAL_GL_LINEAR: + case LOCAL_GL_NEAREST_MIPMAP_NEAREST: + case LOCAL_GL_LINEAR_MIPMAP_NEAREST: + case LOCAL_GL_NEAREST_MIPMAP_LINEAR: + case LOCAL_GL_LINEAR_MIPMAP_LINEAR: + SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown); + mMinFilter = intParam; + break; + + default: + paramBadEnum = true; + } + break; + + case LOCAL_GL_TEXTURE_MAG_FILTER: + switch (intParam) { + case LOCAL_GL_NEAREST: + case LOCAL_GL_LINEAR: + SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown); + mMagFilter = intParam; + break; + + default: + paramBadEnum = true; + } + break; + + case LOCAL_GL_TEXTURE_WRAP_S: + switch (intParam) { + case LOCAL_GL_CLAMP_TO_EDGE: + case LOCAL_GL_MIRRORED_REPEAT: + case LOCAL_GL_REPEAT: + SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown); + mWrapS = intParam; + break; + + default: + paramBadEnum = true; + } + break; + + case LOCAL_GL_TEXTURE_WRAP_T: + switch (intParam) { + case LOCAL_GL_CLAMP_TO_EDGE: + case LOCAL_GL_MIRRORED_REPEAT: + case LOCAL_GL_REPEAT: + SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown); + mWrapT = intParam; + break; + + default: + paramBadEnum = true; + } + break; + + case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: + if (!mContext->IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic)) + return mContext->ErrorInvalidEnumInfo("texParameter: pname", pname); + + if (maybeFloatParam && floatParam < 1.0f) + paramBadValue = true; + else if (maybeIntParam && intParam < 1) + paramBadValue = true; + + break; + + default: + return mContext->ErrorInvalidEnumInfo("texParameter: pname", pname); + } + + if (paramBadEnum) { + if (maybeIntParam) { + mContext->ErrorInvalidEnum("texParameteri: pname 0x%04x: Invalid param" + " 0x%04x.", + pname, intParam); + } else { + mContext->ErrorInvalidEnum("texParameterf: pname 0x%04x: Invalid param %g.", + pname, floatParam); + } + return; + } + + if (paramBadValue) { + if (maybeIntParam) { + mContext->ErrorInvalidValue("texParameteri: pname 0x%04x: Invalid param %i" + " (0x%x).", + pname, intParam, intParam); + } else { + mContext->ErrorInvalidValue("texParameterf: pname 0x%04x: Invalid param %g.", + pname, floatParam); + } + return; + } + + mContext->MakeContextCurrent(); + if (maybeIntParam) + mContext->gl->fTexParameteri(texTarget.get(), pname, intParam); + else + mContext->gl->fTexParameterf(texTarget.get(), pname, floatParam); +} + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTexture) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTexture, AddRef) diff --git a/dom/canvas/WebGLTexture.h b/dom/canvas/WebGLTexture.h index 344d1d5183..d35effda70 100644 --- a/dom/canvas/WebGLTexture.h +++ b/dom/canvas/WebGLTexture.h @@ -10,6 +10,7 @@ #include "mozilla/Assertions.h" #include "mozilla/CheckedInt.h" +#include "mozilla/dom/TypedArray.h" #include "mozilla/LinkedList.h" #include "nsWrapperCache.h" @@ -18,6 +19,12 @@ #include "WebGLStrongTypes.h" namespace mozilla { +class ErrorResult; + +namespace dom { +class Element; +class ImageData; +} // namespace dom // Zero is not an integer power of two. inline bool @@ -27,6 +34,10 @@ IsPOTAssumingNonnegative(GLsizei x) return x && (x & (x-1)) == 0; } +bool +DoesTargetMatchDimensions(WebGLContext* webgl, TexImageTarget target, uint8_t dims, + const char* funcName); + // NOTE: When this class is switched to new DOM bindings, update the (then-slow) // WrapObject calls in GetParameter and GetFramebufferAttachmentParameter. class WebGLTexture final @@ -36,7 +47,35 @@ class WebGLTexture final , public WebGLContextBoundObject , public WebGLFramebufferAttachable { + friend class WebGLContext; + friend class WebGLFramebuffer; + +public: + class ImageInfo; + + const GLuint mGLName; + +protected: + GLenum mTarget; + TexMinFilter mMinFilter; + TexMagFilter mMagFilter; + TexWrap mWrapS, mWrapT; + + size_t mFacesCount, mMaxLevelWithCustomImages; + nsTArray mImageInfos; + + bool mHaveGeneratedMipmap; // Set by generateMipmap + bool mImmutable; // Set by texStorage* + + size_t mBaseMipmapLevel; // Set by texParameter (defaults to 0) + size_t mMaxMipmapLevel; // Set by texParameter (defaults to 1000) + + WebGLTextureFakeBlackStatus mFakeBlackStatus; + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTexture) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTexture) + explicit WebGLTexture(WebGLContext* webgl, GLuint tex); void Delete(); @@ -50,27 +89,156 @@ public: virtual JSObject* WrapObject(JSContext* cx, JS::Handle givenProto) override; - NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTexture) - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTexture) - protected: ~WebGLTexture() { DeleteOnce(); } - - friend class WebGLContext; - friend class WebGLFramebuffer; - - // We store information about the various images that are part of this - // texture. (cubemap faces, mipmap levels) - public: - const GLuint mGLName; + //////////////////////////////////// + // GL calls + bool BindTexture(TexTarget texTarget); + void GenerateMipmap(TexTarget texTarget); + JS::Value GetTexParameter(TexTarget texTarget, GLenum pname); + bool IsTexture() const; + void TexParameter(TexTarget texTarget, GLenum pname, GLint* maybeIntParam, + GLfloat* maybeFloatParam); + + //////////////////////////////////// + // WebGLTextureUpload.cpp + + void CompressedTexImage2D(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLsizei width, GLsizei height, + GLint border, const dom::ArrayBufferView& view); + + void CompressedTexImage3D(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLsizei imageSize, + const dom::ArrayBufferView& view); + + + void CompressedTexSubImage2D(TexImageTarget texImageTarget, GLint level, + GLint xOffset, GLint yOffset, GLsizei width, + GLsizei height, GLenum unpackFormat, + const dom::ArrayBufferView& view); + + void CompressedTexSubImage3D(TexImageTarget texImageTarget, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, + GLsizei width, GLsizei height, GLsizei depth, + GLenum unpackFormat, GLsizei imageSize, + const dom::ArrayBufferView& view); + + + void CopyTexImage2D(TexImageTarget texImageTarget, GLint level, GLenum internalFormat, + GLint x, GLint y, GLsizei width, GLsizei height, GLint border); + + + void CopyTexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint x, GLint y, GLsizei width, + GLsizei height); + + void CopyTexSubImage3D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, GLint x, GLint y, GLsizei width, + GLsizei height); + + + void TexImage2D(TexImageTarget texImageTarget, GLint level, GLenum internalFormat, + GLsizei width, GLsizei height, GLint border, GLenum unpackFormat, + GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv); + void TexImage2D(TexImageTarget texImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData, + ErrorResult* const out_rv); + void TexImage2D(TexImageTarget texImageTarget, GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, dom::Element* elem, + ErrorResult* const out_rv); + + void TexImage3D(TexImageTarget target, GLint level, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth, GLint border, + GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv); + + + void TexStorage2D(TexTarget texTarget, GLsizei levels, GLenum internalFormat, + GLsizei width, GLsizei height); + void TexStorage3D(TexTarget texTarget, GLsizei levels, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth); + + + void TexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLsizei width, GLsizei height, GLenum unpackFormat, + GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv); + void TexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLenum unpackFormat, GLenum unpackType, + dom::ImageData* imageData, ErrorResult* const out_rv); + void TexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLenum unpackFormat, GLenum unpackType, + dom::Element* elem, ErrorResult* const out_rv); + + void TexSubImage3D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, + GLsizei depth, GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv); + void TexSubImage3D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, GLenum unpackFormat, + GLenum unpackType, dom::ImageData* imageData, + ErrorResult* const out_rv); + void TexSubImage3D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, GLenum unpackFormat, + GLenum unpackType, dom::Element* elem, ErrorResult* const out_rv); protected: - GLenum mTarget; + + /** Like glTexImage2D, but if the call may change the texture size, checks + * any GL error generated by this glTexImage2D call and returns it. + */ + GLenum CheckedTexImage2D(TexImageTarget texImageTarget, GLint level, + TexInternalFormat internalFormat, GLsizei width, + GLsizei height, GLint border, TexFormat format, + TexType type, const GLvoid* data); + + bool ValidateTexStorage(TexImageTarget texImageTarget, GLsizei levels, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth, + const char* funcName); + void SpecifyTexStorage(GLsizei levels, TexInternalFormat internalFormat, + GLsizei width, GLsizei height, GLsizei depth); + + void CopyTexSubImage2D_base(TexImageTarget texImageTarget, + GLint level, TexInternalFormat internalFormat, + GLint xoffset, GLint yoffset, GLint x, GLint y, + GLsizei width, GLsizei height, bool isSub); + + bool TexImageFromVideoElement(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLenum unpackFormat, + GLenum unpackType, dom::Element* elem); + + // If jsArrayType is MaxTypedArrayViewType, it means no array. + void TexImage2D_base(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLsizei width, GLsizei height, + GLsizei srcStrideOrZero, GLint border, GLenum unpackFormat, + GLenum unpackType, void* data, uint32_t byteLength, + js::Scalar::Type jsArrayType, WebGLTexelFormat srcFormat, + bool srcPremultiplied); + void TexSubImage2D_base(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLsizei width, GLsizei height, + GLsizei srcStrideOrZero, GLenum unpackFormat, + GLenum unpackType, void* pixels, uint32_t byteLength, + js::Scalar::Type jsArrayType, WebGLTexelFormat srcFormat, + bool srcPremultiplied); + + bool ValidateTexStorage(TexTarget texTarget, GLsizei levels, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth, + const char* info); + bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info); + public: + // We store information about the various images that are part of this + // texture. (cubemap faces, mipmap levels) class ImageInfo : public WebGLRectangleObject { @@ -216,20 +384,6 @@ public: bool EnsureInitializedImageData(TexImageTarget imageTarget, GLint level); protected: - TexMinFilter mMinFilter; - TexMagFilter mMagFilter; - TexWrap mWrapS, mWrapT; - - size_t mFacesCount, mMaxLevelWithCustomImages; - nsTArray mImageInfos; - - bool mHaveGeneratedMipmap; // Set by generateMipmap - bool mImmutable; // Set by texStorage* - - size_t mBaseMipmapLevel; // Set by texParameter (defaults to 0) - size_t mMaxMipmapLevel; // Set by texParameter (defaults to 1000) - - WebGLTextureFakeBlackStatus mFakeBlackStatus; void EnsureMaxLevelWithCustomImagesAtLeast(size_t maxLevelWithCustomImages) { mMaxLevelWithCustomImages = std::max(mMaxLevelWithCustomImages, diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp new file mode 100644 index 0000000000..ffb2258a39 --- /dev/null +++ b/dom/canvas/WebGLTextureUpload.cpp @@ -0,0 +1,1419 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "WebGLTexture.h" + +#include +#include "CanvasUtils.h" +#include "GLBlitHelper.h" +#include "GLContext.h" +#include "mozilla/dom/HTMLVideoElement.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Scoped.h" +#include "ScopedGLHelpers.h" +#include "WebGLContext.h" +#include "WebGLContextUtils.h" +#include "WebGLFramebuffer.h" +#include "WebGLTexelConversions.h" + +namespace mozilla { + +bool +DoesTargetMatchDimensions(WebGLContext* webgl, TexImageTarget target, uint8_t funcDims, + const char* funcName) +{ + uint8_t targetDims; + switch (target.get()) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + targetDims = 2; + break; + + case LOCAL_GL_TEXTURE_3D: + targetDims = 3; + break; + + default: + MOZ_CRASH("Unhandled texImageTarget."); + } + + if (targetDims != funcDims) { + webgl->ErrorInvalidEnum("%s: `target` must match function dimensions.", funcName); + return false; + } + + return true; +} + +void +WebGLTexture::CompressedTexImage2D(TexImageTarget texImageTarget, + GLint level, + GLenum internalFormat, + GLsizei width, GLsizei height, GLint border, + const dom::ArrayBufferView& view) +{ + const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; + + const char funcName[] = "compressedTexImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + if (!mContext->ValidateTexImage(texImageTarget.get(), level, internalFormat, + 0, 0, 0, width, height, 0, + border, LOCAL_GL_NONE, + LOCAL_GL_NONE, + func, dims)) + { + return; + } + + view.ComputeLengthAndData(); + + uint32_t byteLength = view.Length(); + if (!mContext->ValidateCompTexImageDataSize(level, internalFormat, width, height, byteLength, func, dims)) { + return; + } + + if (!mContext->ValidateCompTexImageSize(level, internalFormat, 0, 0, width, height, width, height, func, dims)) + { + return; + } + + if (mImmutable) { + return mContext->ErrorInvalidOperation( + "compressedTexImage2D: disallowed because the texture bound to " + "this target has already been made immutable by texStorage2D"); + } + + mContext->MakeContextCurrent(); + gl::GLContext* gl = mContext->gl; + gl->fCompressedTexImage2D(texImageTarget.get(), level, internalFormat, width, height, border, byteLength, view.Data()); + + SetImageInfo(texImageTarget, level, width, height, 1, internalFormat, + WebGLImageDataStatus::InitializedImageData); +} + +void +WebGLTexture::CompressedTexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLsizei width, GLsizei height, + GLenum internalFormat, + const dom::ArrayBufferView& view) +{ + const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexSubImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; + + const char funcName[] = "compressedTexSubImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + if (!mContext->ValidateTexImage(texImageTarget.get(), + level, internalFormat, + xOffset, yOffset, 0, + width, height, 0, + 0, LOCAL_GL_NONE, LOCAL_GL_NONE, + func, dims)) + { + return; + } + + WebGLTexture::ImageInfo& levelInfo = ImageInfoAt(texImageTarget, level); + + if (internalFormat != levelInfo.EffectiveInternalFormat()) { + return mContext->ErrorInvalidOperation("compressedTexImage2D: internalFormat does not match the existing image"); + } + + view.ComputeLengthAndData(); + + uint32_t byteLength = view.Length(); + if (!mContext->ValidateCompTexImageDataSize(level, internalFormat, width, height, byteLength, func, dims)) + return; + + if (!mContext->ValidateCompTexImageSize(level, internalFormat, + xOffset, yOffset, + width, height, + levelInfo.Width(), levelInfo.Height(), + func, dims)) + { + return; + } + + if (levelInfo.HasUninitializedImageData()) { + bool coversWholeImage = xOffset == 0 && + yOffset == 0 && + width == levelInfo.Width() && + height == levelInfo.Height(); + if (coversWholeImage) { + SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); + } else { + if (!EnsureInitializedImageData(texImageTarget, level)) + return; + } + } + + mContext->MakeContextCurrent(); + gl::GLContext* gl = mContext->gl; + gl->fCompressedTexSubImage2D(texImageTarget.get(), level, xOffset, yOffset, width, height, internalFormat, byteLength, view.Data()); +} + +void +WebGLTexture::CopyTexSubImage2D_base(TexImageTarget texImageTarget, GLint level, + TexInternalFormat internalFormat, + GLint xOffset, GLint yOffset, GLint x, + GLint y, GLsizei width, GLsizei height, + bool sub) +{ + const WebGLRectangleObject* framebufferRect = mContext->CurValidReadFBRectObject(); + GLsizei framebufferWidth = framebufferRect ? framebufferRect->Width() : 0; + GLsizei framebufferHeight = framebufferRect ? framebufferRect->Height() : 0; + + WebGLTexImageFunc func = sub + ? WebGLTexImageFunc::CopyTexSubImage + : WebGLTexImageFunc::CopyTexImage; + WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; + const char* info = InfoFrom(func, dims); + + // TODO: This changes with color_buffer_float. Reassess when the + // patch lands. + if (!mContext->ValidateTexImage(texImageTarget, level, internalFormat.get(), + xOffset, yOffset, 0, + width, height, 0, + 0, + LOCAL_GL_NONE, LOCAL_GL_NONE, + func, dims)) + { + return; + } + + if (!mContext->ValidateCopyTexImage(internalFormat.get(), func, dims)) + return; + + if (!mContext->mBoundReadFramebuffer) + mContext->ClearBackbufferIfNeeded(); + + mContext->MakeContextCurrent(); + gl::GLContext* gl = mContext->gl; + + if (mImmutable) { + if (!sub) { + return mContext->ErrorInvalidOperation("copyTexImage2D: disallowed because the texture bound to this target has already been made immutable by texStorage2D"); + } + } + + TexType framebuffertype = LOCAL_GL_NONE; + if (mContext->mBoundReadFramebuffer) { + TexInternalFormat framebuffereffectiveformat = mContext->mBoundReadFramebuffer->ColorAttachment(0).EffectiveInternalFormat(); + framebuffertype = TypeFromInternalFormat(framebuffereffectiveformat); + } else { + // FIXME - here we're assuming that the default framebuffer is backed by UNSIGNED_BYTE + // that might not always be true, say if we had a 16bpp default framebuffer. + framebuffertype = LOCAL_GL_UNSIGNED_BYTE; + } + + TexInternalFormat effectiveInternalFormat = + EffectiveInternalFormatFromUnsizedInternalFormatAndType(internalFormat, framebuffertype); + + // this should never fail, validation happened earlier. + MOZ_ASSERT(effectiveInternalFormat != LOCAL_GL_NONE); + + const bool widthOrHeightIsZero = (width == 0 || height == 0); + if (gl->WorkAroundDriverBugs() && + sub && widthOrHeightIsZero) + { + // NV driver on Linux complains that CopyTexSubImage2D(level=0, + // xOffset=0, yOffset=2, x=0, y=0, width=0, height=0) from a 300x150 FB + // to a 0x2 texture. This a useless thing to do, but technically legal. + // NV331.38 generates INVALID_VALUE. + return mContext->DummyFramebufferOperation(info); + } + + // check if the memory size of this texture may change with this call + bool sizeMayChange = !sub; + if (!sub && HasImageInfoAt(texImageTarget, level)) { + const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level); + sizeMayChange = width != imageInfo.Width() || + height != imageInfo.Height() || + effectiveInternalFormat != imageInfo.EffectiveInternalFormat(); + } + + if (sizeMayChange) + mContext->GetAndFlushUnderlyingGLErrors(); + + if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) { + if (sub) + gl->fCopyTexSubImage2D(texImageTarget.get(), level, xOffset, yOffset, x, y, width, height); + else + gl->fCopyTexImage2D(texImageTarget.get(), level, internalFormat.get(), x, y, width, height, 0); + } else { + + // the rect doesn't fit in the framebuffer + + // first, we initialize the texture as black + if (!sub) { + SetImageInfo(texImageTarget, level, width, height, 1, + effectiveInternalFormat, + WebGLImageDataStatus::UninitializedImageData); + if (!EnsureInitializedImageData(texImageTarget, level)) + return; + } + + // if we are completely outside of the framebuffer, we can exit now with our black texture + if ( x >= framebufferWidth + || x+width <= 0 + || y >= framebufferHeight + || y+height <= 0) + { + // we are completely outside of range, can exit now with buffer filled with zeros + return mContext->DummyFramebufferOperation(info); + } + + GLint actual_x = clamped(x, 0, framebufferWidth); + GLint actual_x_plus_width = clamped(x + width, 0, framebufferWidth); + GLsizei actual_width = actual_x_plus_width - actual_x; + GLint actual_xOffset = xOffset + actual_x - x; + + GLint actual_y = clamped(y, 0, framebufferHeight); + GLint actual_y_plus_height = clamped(y + height, 0, framebufferHeight); + GLsizei actual_height = actual_y_plus_height - actual_y; + GLint actual_yOffset = yOffset + actual_y - y; + + gl->fCopyTexSubImage2D(texImageTarget.get(), level, actual_xOffset, actual_yOffset, actual_x, actual_y, actual_width, actual_height); + } + + if (sizeMayChange) { + GLenum error = mContext->GetAndFlushUnderlyingGLErrors(); + if (error) { + mContext->GenerateWarning("copyTexImage2D generated error %s", mContext->ErrorName(error)); + return; + } + } + + if (!sub) { + SetImageInfo(texImageTarget, level, width, height, 1, + effectiveInternalFormat, + WebGLImageDataStatus::InitializedImageData); + } +} + +void +WebGLTexture::CopyTexImage2D(TexImageTarget texImageTarget, + GLint level, + GLenum internalFormat, + GLint x, + GLint y, + GLsizei width, + GLsizei height, + GLint border) +{ + // copyTexImage2D only generates textures with type = UNSIGNED_BYTE + const WebGLTexImageFunc func = WebGLTexImageFunc::CopyTexImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; + + const char funcName[] = "copyTexImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + if (!mContext->ValidateTexImage(texImageTarget.get(), level, internalFormat, + 0, 0, 0, + width, height, 0, + border, LOCAL_GL_NONE, LOCAL_GL_NONE, + func, dims)) + { + return; + } + + if (!mContext->ValidateCopyTexImage(internalFormat, func, dims)) + return; + + if (!mContext->mBoundReadFramebuffer) + mContext->ClearBackbufferIfNeeded(); + + CopyTexSubImage2D_base(texImageTarget, level, internalFormat, 0, 0, x, y, width, height, false); +} + +void +WebGLTexture::CopyTexSubImage2D(TexImageTarget texImageTarget, + GLint level, + GLint xOffset, + GLint yOffset, + GLint x, + GLint y, + GLsizei width, + GLsizei height) +{ + switch (texImageTarget.get()) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + break; + default: + return mContext->ErrorInvalidEnumInfo("copyTexSubImage2D: target", texImageTarget.get()); + } + + if (level < 0) + return mContext->ErrorInvalidValue("copyTexSubImage2D: level may not be negative"); + + GLsizei maxTextureSize = mContext->MaxTextureSizeForTarget(TexImageTargetToTexTarget(texImageTarget)); + if (!(maxTextureSize >> level)) + return mContext->ErrorInvalidValue("copyTexSubImage2D: 2^level exceeds maximum texture size"); + + if (width < 0 || height < 0) + return mContext->ErrorInvalidValue("copyTexSubImage2D: width and height may not be negative"); + + if (xOffset < 0 || yOffset < 0) + return mContext->ErrorInvalidValue("copyTexSubImage2D: xOffset and yOffset may not be negative"); + + if (!HasImageInfoAt(texImageTarget, level)) + return mContext->ErrorInvalidOperation("copyTexSubImage2D: no texture image previously defined for this level and face"); + + const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level); + GLsizei texWidth = imageInfo.Width(); + GLsizei texHeight = imageInfo.Height(); + + if (xOffset + width > texWidth || xOffset + width < 0) + return mContext->ErrorInvalidValue("copyTexSubImage2D: xOffset+width is too large"); + + if (yOffset + height > texHeight || yOffset + height < 0) + return mContext->ErrorInvalidValue("copyTexSubImage2D: yOffset+height is too large"); + + if (!mContext->mBoundReadFramebuffer) + mContext->ClearBackbufferIfNeeded(); + + if (imageInfo.HasUninitializedImageData()) { + bool coversWholeImage = xOffset == 0 && + yOffset == 0 && + width == texWidth && + height == texHeight; + if (coversWholeImage) { + SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); + } else { + if (!EnsureInitializedImageData(texImageTarget, level)) + return; + } + } + + TexInternalFormat internalFormat; + TexType type; + UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(imageInfo.EffectiveInternalFormat(), + &internalFormat, &type); + return CopyTexSubImage2D_base(texImageTarget, level, internalFormat, xOffset, yOffset, x, y, width, height, true); +} + + +GLenum +WebGLTexture::CheckedTexImage2D(TexImageTarget texImageTarget, + GLint level, + TexInternalFormat internalFormat, + GLsizei width, + GLsizei height, + GLint border, + TexFormat unpackFormat, + TexType unpackType, + const GLvoid* data) +{ + TexInternalFormat effectiveInternalFormat = + EffectiveInternalFormatFromInternalFormatAndType(internalFormat, unpackType); + bool sizeMayChange = true; + + if (HasImageInfoAt(texImageTarget, level)) { + const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level); + sizeMayChange = width != imageInfo.Width() || + height != imageInfo.Height() || + effectiveInternalFormat != imageInfo.EffectiveInternalFormat(); + } + + gl::GLContext* gl = mContext->gl; + + // Convert to format and type required by OpenGL 'driver'. + GLenum driverType = LOCAL_GL_NONE; + GLenum driverInternalFormat = LOCAL_GL_NONE; + GLenum driverFormat = LOCAL_GL_NONE; + DriverFormatsFromEffectiveInternalFormat(gl, + effectiveInternalFormat, + &driverInternalFormat, + &driverFormat, + &driverType); + + if (sizeMayChange) { + mContext->GetAndFlushUnderlyingGLErrors(); + } + + gl->fTexImage2D(texImageTarget.get(), level, driverInternalFormat, width, height, border, driverFormat, driverType, data); + + if (effectiveInternalFormat != driverInternalFormat) + SetLegacyTextureSwizzle(gl, texImageTarget.get(), internalFormat.get()); + + GLenum error = LOCAL_GL_NO_ERROR; + if (sizeMayChange) { + error = mContext->GetAndFlushUnderlyingGLErrors(); + } + + return error; +} + +void +WebGLTexture::TexImage2D_base(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei srcStrideOrZero, + GLint border, + GLenum unpackFormat, + GLenum unpackType, + void* data, uint32_t byteLength, + js::Scalar::Type jsArrayType, + WebGLTexelFormat srcFormat, bool srcPremultiplied) +{ + const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; + + if (unpackType == LOCAL_GL_HALF_FLOAT_OES) { + unpackType = LOCAL_GL_HALF_FLOAT; + } + + if (!mContext->ValidateTexImage(texImageTarget, level, internalFormat, + 0, 0, 0, + width, height, 0, + border, unpackFormat, unpackType, func, dims)) + { + return; + } + + const bool isDepthTexture = unpackFormat == LOCAL_GL_DEPTH_COMPONENT || + unpackFormat == LOCAL_GL_DEPTH_STENCIL; + + if (isDepthTexture && !mContext->IsWebGL2()) { + if (data != nullptr || level != 0) + return mContext->ErrorInvalidOperation("texImage2D: " + "with format of DEPTH_COMPONENT or DEPTH_STENCIL, " + "data must be nullptr, " + "level must be zero"); + } + + if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims)) + return; + + TexInternalFormat effectiveInternalFormat = + EffectiveInternalFormatFromInternalFormatAndType(internalFormat, unpackType); + + if (effectiveInternalFormat == LOCAL_GL_NONE) { + return mContext->ErrorInvalidOperation("texImage2D: bad combination of internalFormat and type"); + } + + size_t srcTexelSize = size_t(-1); + if (srcFormat == WebGLTexelFormat::Auto) { + // we need to find the exact sized format of the source data. Slightly abusing + // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format + // is the same thing as an unsized internalFormat. + TexInternalFormat effectiveSourceFormat = + EffectiveInternalFormatFromInternalFormatAndType(unpackFormat, unpackType); + MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated format/type combo earlier + const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat); + MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here. + srcTexelSize = srcbitsPerTexel / 8; + } else { + srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(srcFormat); + } + + CheckedUint32 checked_neededByteLength = + mContext->GetImageSize(height, width, 1, srcTexelSize, mContext->mPixelStoreUnpackAlignment); + + CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize; + CheckedUint32 checked_alignedRowSize = + RoundedToNextMultipleOf(checked_plainRowSize.value(), mContext->mPixelStoreUnpackAlignment); + + if (!checked_neededByteLength.isValid()) + return mContext->ErrorInvalidOperation("texImage2D: integer overflow computing the needed buffer size"); + + uint32_t bytesNeeded = checked_neededByteLength.value(); + + if (byteLength && byteLength < bytesNeeded) + return mContext->ErrorInvalidOperation("texImage2D: not enough data for operation (need %d, have %d)", + bytesNeeded, byteLength); + + if (mImmutable) { + return mContext->ErrorInvalidOperation( + "texImage2D: disallowed because the texture " + "bound to this target has already been made immutable by texStorage2D"); + } + mContext->MakeContextCurrent(); + + nsAutoArrayPtr convertedData; + void* pixels = nullptr; + WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData; + + WebGLTexelFormat dstFormat = GetWebGLTexelFormat(effectiveInternalFormat); + WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat; + + if (byteLength) { + size_t bitsPerTexel = GetBitsPerTexel(effectiveInternalFormat); + MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here. + size_t dstTexelSize = bitsPerTexel / 8; + size_t srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value(); + size_t dstPlainRowSize = dstTexelSize * width; + size_t unpackAlignment = mContext->mPixelStoreUnpackAlignment; + size_t dstStride = ((dstPlainRowSize + unpackAlignment-1) / unpackAlignment) * unpackAlignment; + + if (actualSrcFormat == dstFormat && + srcPremultiplied == mContext->mPixelStorePremultiplyAlpha && + srcStride == dstStride && + !mContext->mPixelStoreFlipY) + { + // no conversion, no flipping, so we avoid copying anything and just pass the source pointer + pixels = data; + } + else + { + size_t convertedDataSize = height * dstStride; + convertedData = new (fallible) uint8_t[convertedDataSize]; + if (!convertedData) { + mContext->ErrorOutOfMemory("texImage2D: Ran out of memory when allocating" + " a buffer for doing format conversion."); + return; + } + if (!mContext->ConvertImage(width, height, srcStride, dstStride, + static_cast(data), convertedData, + actualSrcFormat, srcPremultiplied, + dstFormat, mContext->mPixelStorePremultiplyAlpha, dstTexelSize)) + { + return mContext->ErrorInvalidOperation("texImage2D: Unsupported texture format conversion"); + } + pixels = reinterpret_cast(convertedData.get()); + } + imageInfoStatusIfSuccess = WebGLImageDataStatus::InitializedImageData; + } + + GLenum error = CheckedTexImage2D(texImageTarget, level, internalFormat, width, + height, border, unpackFormat, unpackType, pixels); + + if (error) { + mContext->GenerateWarning("texImage2D generated error %s", mContext->ErrorName(error)); + return; + } + + // in all of the code paths above, we should have either initialized data, + // or allocated data and left it uninitialized, but in any case we shouldn't + // have NoImageData at this point. + MOZ_ASSERT(imageInfoStatusIfSuccess != WebGLImageDataStatus::NoImageData); + + SetImageInfo(texImageTarget, level, width, height, 1, + effectiveInternalFormat, imageInfoStatusIfSuccess); +} + +void +WebGLTexture::TexImage2D(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLsizei width, + GLsizei height, GLint border, GLenum unpackFormat, + GLenum unpackType, const dom::Nullable& maybeView, + ErrorResult* const out_rv) +{ + void* data; + uint32_t length; + js::Scalar::Type jsArrayType; + if (maybeView.IsNull()) { + data = nullptr; + length = 0; + jsArrayType = js::Scalar::MaxTypedArrayViewType; + } else { + const dom::ArrayBufferView& view = maybeView.Value(); + view.ComputeLengthAndData(); + + data = view.Data(); + length = view.Length(); + jsArrayType = view.Type(); + } + + const char funcName[] = "texImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + return TexImage2D_base(texImageTarget, level, internalFormat, width, height, 0, border, unpackFormat, unpackType, + data, length, jsArrayType, + WebGLTexelFormat::Auto, false); +} + +void +WebGLTexture::TexImage2D(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLenum unpackFormat, + GLenum unpackType, dom::ImageData* imageData, ErrorResult* const out_rv) +{ + if (!imageData) { + // Spec says to generate an INVALID_VALUE error + return mContext->ErrorInvalidValue("texImage2D: null ImageData"); + } + + dom::Uint8ClampedArray arr; + DebugOnly inited = arr.Init(imageData->GetDataObject()); + MOZ_ASSERT(inited); + arr.ComputeLengthAndData(); + + void* pixelData = arr.Data(); + const uint32_t pixelDataLength = arr.Length(); + + const char funcName[] = "texImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + return TexImage2D_base(texImageTarget, level, internalFormat, imageData->Width(), + imageData->Height(), 4*imageData->Width(), 0, + unpackFormat, unpackType, pixelData, pixelDataLength, js::Scalar::MaxTypedArrayViewType, + WebGLTexelFormat::RGBA8, false); +} + +void +WebGLTexture::TexImage2D(TexImageTarget texImageTarget, GLint level, + GLenum internalFormat, GLenum unpackFormat, GLenum unpackType, + dom::Element* elem, ErrorResult* out_rv) +{ + const char funcName[] = "texImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + if (level < 0) + return mContext->ErrorInvalidValue("texImage2D: level is negative"); + + const int32_t maxLevel = mContext->MaxTextureLevelForTexImageTarget(texImageTarget); + if (level > maxLevel) { + mContext->ErrorInvalidValue("texImage2D: level %d is too large, max is %d", + level, maxLevel); + return; + } + + // Trying to handle the video by GPU directly first + if (TexImageFromVideoElement(texImageTarget, level, internalFormat, + unpackFormat, unpackType, elem)) + { + return; + } + + RefPtr data; + WebGLTexelFormat srcFormat; + nsLayoutUtils::SurfaceFromElementResult res = mContext->SurfaceFromElement(elem); + *out_rv = mContext->SurfaceFromElementResultToImageSurface(res, data, &srcFormat); + if (out_rv->Failed() || !data) + return; + + gfx::IntSize size = data->GetSize(); + uint32_t byteLength = data->Stride() * size.height; + return TexImage2D_base(texImageTarget, level, internalFormat, + size.width, size.height, data->Stride(), 0, + unpackFormat, unpackType, data->GetData(), byteLength, + js::Scalar::MaxTypedArrayViewType, srcFormat, + res.mIsPremultiplied); +} + + +void +WebGLTexture::TexSubImage2D_base(TexImageTarget texImageTarget, GLint level, + GLint xOffset, GLint yOffset, + GLsizei width, GLsizei height, GLsizei srcStrideOrZero, + GLenum unpackFormat, GLenum unpackType, + void* data, uint32_t byteLength, + js::Scalar::Type jsArrayType, + WebGLTexelFormat srcFormat, bool srcPremultiplied) +{ + const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D; + + if (unpackType == LOCAL_GL_HALF_FLOAT_OES) + unpackType = LOCAL_GL_HALF_FLOAT; + + const char funcName[] = "texSubImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + if (!HasImageInfoAt(texImageTarget, level)) + return mContext->ErrorInvalidOperation("texSubImage2D: no previously defined texture image"); + + const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level); + const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat(); + + if (!mContext->ValidateTexImage(texImageTarget, level, + existingEffectiveInternalFormat.get(), + xOffset, yOffset, 0, + width, height, 0, + 0, unpackFormat, unpackType, func, dims)) + { + return; + } + + if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims)) + return; + + if (unpackType != TypeFromInternalFormat(existingEffectiveInternalFormat)) { + return mContext->ErrorInvalidOperation("texSubImage2D: type differs from that of the existing image"); + } + + size_t srcTexelSize = size_t(-1); + if (srcFormat == WebGLTexelFormat::Auto) { + const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat); + MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here. + srcTexelSize = bitsPerTexel / 8; + } else { + srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(srcFormat); + } + + if (width == 0 || height == 0) + return; // ES 2.0 says it has no effect, we better return right now + + CheckedUint32 checked_neededByteLength = + mContext->GetImageSize(height, width, 1, srcTexelSize, mContext->mPixelStoreUnpackAlignment); + + CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize; + + CheckedUint32 checked_alignedRowSize = + RoundedToNextMultipleOf(checked_plainRowSize.value(), mContext->mPixelStoreUnpackAlignment); + + if (!checked_neededByteLength.isValid()) + return mContext->ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); + + uint32_t bytesNeeded = checked_neededByteLength.value(); + + if (byteLength < bytesNeeded) + return mContext->ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength); + + if (imageInfo.HasUninitializedImageData()) { + bool coversWholeImage = xOffset == 0 && + yOffset == 0 && + width == imageInfo.Width() && + height == imageInfo.Height(); + if (coversWholeImage) { + SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); + } else { + if (!EnsureInitializedImageData(texImageTarget, level)) + return; + } + } + mContext->MakeContextCurrent(); + gl::GLContext* gl = mContext->gl; + + size_t srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value(); + uint32_t dstTexelSize = GetBitsPerTexel(existingEffectiveInternalFormat) / 8; + size_t dstPlainRowSize = dstTexelSize * width; + // There are checks above to ensure that this won't overflow. + size_t dstStride = RoundedToNextMultipleOf(dstPlainRowSize, mContext->mPixelStoreUnpackAlignment).value(); + + void* pixels = data; + nsAutoArrayPtr convertedData; + + WebGLTexelFormat dstFormat = GetWebGLTexelFormat(existingEffectiveInternalFormat); + WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat; + + // no conversion, no flipping, so we avoid copying anything and just pass the source pointer + bool noConversion = (actualSrcFormat == dstFormat && + srcPremultiplied == mContext->mPixelStorePremultiplyAlpha && + srcStride == dstStride && + !mContext->mPixelStoreFlipY); + + if (!noConversion) { + size_t convertedDataSize = height * dstStride; + convertedData = new (fallible) uint8_t[convertedDataSize]; + if (!convertedData) { + mContext->ErrorOutOfMemory("texImage2D: Ran out of memory when allocating" + " a buffer for doing format conversion."); + return; + } + if (!mContext->ConvertImage(width, height, srcStride, dstStride, + static_cast(data), convertedData, + actualSrcFormat, srcPremultiplied, + dstFormat, mContext->mPixelStorePremultiplyAlpha, dstTexelSize)) + { + return mContext->ErrorInvalidOperation("texSubImage2D: Unsupported texture format conversion"); + } + pixels = reinterpret_cast(convertedData.get()); + } + + GLenum driverType = LOCAL_GL_NONE; + GLenum driverInternalFormat = LOCAL_GL_NONE; + GLenum driverFormat = LOCAL_GL_NONE; + DriverFormatsFromEffectiveInternalFormat(gl, + existingEffectiveInternalFormat, + &driverInternalFormat, + &driverFormat, + &driverType); + + gl->fTexSubImage2D(texImageTarget.get(), level, xOffset, yOffset, width, height, driverFormat, driverType, pixels); +} + +void +WebGLTexture::TexSubImage2D(TexImageTarget texImageTarget, GLint level, + GLint xOffset, GLint yOffset, + GLsizei width, GLsizei height, + GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv) +{ + if (maybeView.IsNull()) + return mContext->ErrorInvalidValue("texSubImage2D: pixels must not be null!"); + + const dom::ArrayBufferView& view = maybeView.Value(); + view.ComputeLengthAndData(); + + const char funcName[] = "texSubImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + return TexSubImage2D_base(texImageTarget, level, xOffset, yOffset, + width, height, 0, unpackFormat, unpackType, + view.Data(), view.Length(), view.Type(), + WebGLTexelFormat::Auto, false); +} + +void +WebGLTexture::TexSubImage2D(TexImageTarget texImageTarget, GLint level, + GLint xOffset, GLint yOffset, + GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData, + ErrorResult* const out_rv) +{ + if (!imageData) + return mContext->ErrorInvalidValue("texSubImage2D: pixels must not be null!"); + + dom::Uint8ClampedArray arr; + DebugOnly inited = arr.Init(imageData->GetDataObject()); + MOZ_ASSERT(inited); + arr.ComputeLengthAndData(); + + return TexSubImage2D_base(texImageTarget, level, xOffset, yOffset, + imageData->Width(), imageData->Height(), + 4*imageData->Width(), unpackFormat, unpackType, + arr.Data(), arr.Length(), + js::Scalar::MaxTypedArrayViewType, + WebGLTexelFormat::RGBA8, false); +} + + + +bool +WebGLTexture::TexImageFromVideoElement(TexImageTarget texImageTarget, + GLint level, GLenum internalFormat, + GLenum unpackFormat, GLenum unpackType, + mozilla::dom::Element* elem) +{ + if (unpackType == LOCAL_GL_HALF_FLOAT_OES && + !mContext->gl->IsExtensionSupported(gl::GLContext::OES_texture_half_float)) + { + unpackType = LOCAL_GL_HALF_FLOAT; + } + + if (!mContext->ValidateTexImageFormatAndType(unpackFormat, unpackType, + WebGLTexImageFunc::TexImage, + WebGLTexDimensions::Tex2D)) + { + return false; + } + + dom::HTMLVideoElement* video = dom::HTMLVideoElement::FromContentOrNull(elem); + if (!video) + return false; + + uint16_t readyState; + if (NS_SUCCEEDED(video->GetReadyState(&readyState)) && + readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) + { + //No frame inside, just return + return false; + } + + // If it doesn't have a principal, just bail + nsCOMPtr principal = video->GetCurrentPrincipal(); + if (!principal) + return false; + + mozilla::layers::ImageContainer* container = video->GetImageContainer(); + if (!container) + return false; + + if (video->GetCORSMode() == CORS_NONE) { + bool subsumes; + nsresult rv = mContext->mCanvasElement->NodePrincipal()->Subsumes(principal, &subsumes); + if (NS_FAILED(rv) || !subsumes) { + mContext->GenerateWarning("It is forbidden to load a WebGL texture from a cross-domain element that has not been validated with CORS. " + "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures"); + return false; + } + } + + layers::AutoLockImage lockedImage(container); + layers::Image* srcImage = lockedImage.GetImage(); + if (!srcImage) { + return false; + } + + gl::GLContext* gl = mContext->gl; + gl->MakeCurrent(); + + const WebGLTexture::ImageInfo& info = ImageInfoAt(texImageTarget, 0); + bool dimensionsMatch = info.Width() == srcImage->GetSize().width && + info.Height() == srcImage->GetSize().height; + if (!dimensionsMatch) { + // we need to allocation + gl->fTexImage2D(texImageTarget.get(), level, internalFormat, + srcImage->GetSize().width, srcImage->GetSize().height, + 0, unpackFormat, unpackType, nullptr); + } + + const gl::OriginPos destOrigin = mContext->mPixelStoreFlipY ? gl::OriginPos::BottomLeft + : gl::OriginPos::TopLeft; + bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, + srcImage->GetSize(), + mGLName, + texImageTarget.get(), + destOrigin); + if (ok) { + TexInternalFormat effectiveInternalFormat = + EffectiveInternalFormatFromInternalFormatAndType(internalFormat, + unpackType); + MOZ_ASSERT(effectiveInternalFormat != LOCAL_GL_NONE); + SetImageInfo(texImageTarget, level, srcImage->GetSize().width, + srcImage->GetSize().height, 1, + effectiveInternalFormat, + WebGLImageDataStatus::InitializedImageData); + Bind(TexImageTargetToTexTarget(texImageTarget)); + } + return ok; +} + +void +WebGLTexture::TexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset, + GLint yOffset, GLenum unpackFormat, GLenum unpackType, + dom::Element* elem, ErrorResult* const out_rv) +{ + const char funcName[] = "texSubImage2D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName)) + return; + + if (level < 0) + return mContext->ErrorInvalidValue("texSubImage2D: level is negative"); + + const int32_t maxLevel = mContext->MaxTextureLevelForTexImageTarget(texImageTarget); + if (level > maxLevel) { + mContext->ErrorInvalidValue("texSubImage2D: level %d is too large, max is %d", + level, maxLevel); + return; + } + + const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level); + const TexInternalFormat internalFormat = imageInfo.EffectiveInternalFormat(); + + // Trying to handle the video by GPU directly first + if (TexImageFromVideoElement(texImageTarget, level, internalFormat.get(), unpackFormat, unpackType, elem)) + { + return; + } + + RefPtr data; + WebGLTexelFormat srcFormat; + nsLayoutUtils::SurfaceFromElementResult res = mContext->SurfaceFromElement(elem); + *out_rv = mContext->SurfaceFromElementResultToImageSurface(res, data, &srcFormat); + if (out_rv->Failed() || !data) + return; + + gfx::IntSize size = data->GetSize(); + uint32_t byteLength = data->Stride() * size.height; + TexSubImage2D_base(texImageTarget, level, xOffset, yOffset, size.width, + size.height, data->Stride(), unpackFormat, unpackType, data->GetData(), + byteLength, js::Scalar::MaxTypedArrayViewType, srcFormat, + res.mIsPremultiplied); +} + +bool +WebGLTexture::ValidateSizedInternalFormat(GLenum internalFormat, const char* info) +{ + switch (internalFormat) { + // Sized Internal Formats + // https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml + case LOCAL_GL_R8: + case LOCAL_GL_R8_SNORM: + case LOCAL_GL_R16F: + case LOCAL_GL_R32F: + case LOCAL_GL_R8UI: + case LOCAL_GL_R8I: + case LOCAL_GL_R16UI: + case LOCAL_GL_R16I: + case LOCAL_GL_R32UI: + case LOCAL_GL_R32I: + case LOCAL_GL_RG8: + case LOCAL_GL_RG8_SNORM: + case LOCAL_GL_RG16F: + case LOCAL_GL_RG32F: + case LOCAL_GL_RG8UI: + case LOCAL_GL_RG8I: + case LOCAL_GL_RG16UI: + case LOCAL_GL_RG16I: + case LOCAL_GL_RG32UI: + case LOCAL_GL_RG32I: + case LOCAL_GL_RGB8: + case LOCAL_GL_SRGB8: + case LOCAL_GL_RGB565: + case LOCAL_GL_RGB8_SNORM: + case LOCAL_GL_R11F_G11F_B10F: + case LOCAL_GL_RGB9_E5: + case LOCAL_GL_RGB16F: + case LOCAL_GL_RGB32F: + case LOCAL_GL_RGB8UI: + case LOCAL_GL_RGB8I: + case LOCAL_GL_RGB16UI: + case LOCAL_GL_RGB16I: + case LOCAL_GL_RGB32UI: + case LOCAL_GL_RGB32I: + case LOCAL_GL_RGBA8: + case LOCAL_GL_SRGB8_ALPHA8: + case LOCAL_GL_RGBA8_SNORM: + case LOCAL_GL_RGB5_A1: + case LOCAL_GL_RGBA4: + case LOCAL_GL_RGB10_A2: + case LOCAL_GL_RGBA16F: + case LOCAL_GL_RGBA32F: + case LOCAL_GL_RGBA8UI: + case LOCAL_GL_RGBA8I: + case LOCAL_GL_RGB10_A2UI: + case LOCAL_GL_RGBA16UI: + case LOCAL_GL_RGBA16I: + case LOCAL_GL_RGBA32I: + case LOCAL_GL_RGBA32UI: + case LOCAL_GL_DEPTH_COMPONENT16: + case LOCAL_GL_DEPTH_COMPONENT24: + case LOCAL_GL_DEPTH_COMPONENT32F: + case LOCAL_GL_DEPTH24_STENCIL8: + case LOCAL_GL_DEPTH32F_STENCIL8: + return true; + } + + if (IsCompressedTextureFormat(internalFormat)) + return true; + + nsCString name; + mContext->EnumName(internalFormat, &name); + mContext->ErrorInvalidEnum("%s: invalid internal format %s", info, name.get()); + + return false; +} + + +/** Validates parameters to texStorage{2D,3D} */ +bool +WebGLTexture::ValidateTexStorage(TexTarget texTarget, GLsizei levels, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth, + const char* info) +{ + // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has + // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE. + if (mImmutable) { + mContext->ErrorInvalidOperation("%s: texture bound to target %s is already immutable", + info, mContext->EnumName(texTarget.get())); + return false; + } + + // GL_INVALID_ENUM is generated if internalFormat is not a valid sized internal format. + if (!ValidateSizedInternalFormat(internalFormat, info)) + return false; + + // GL_INVALID_VALUE is generated if width, height or levels are less than 1. + if (width < 1) { mContext->ErrorInvalidValue("%s: width is < 1", info); return false; } + if (height < 1) { mContext->ErrorInvalidValue("%s: height is < 1", info); return false; } + if (depth < 1) { mContext->ErrorInvalidValue("%s: depth is < 1", info); return false; } + if (levels < 1) { mContext->ErrorInvalidValue("%s: levels is < 1", info); return false; } + + // GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1. + if (FloorLog2(std::max(std::max(width, height), depth)) + 1 < levels) { + mContext->ErrorInvalidOperation("%s: too many levels for given texture dimensions", info); + return false; + } + + return true; +} + +void +WebGLTexture::TexStorage2D(TexTarget texTarget, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height) +{ + // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants. + if (texTarget != LOCAL_GL_TEXTURE_2D && texTarget != LOCAL_GL_TEXTURE_CUBE_MAP) + return mContext->ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP"); + + if (!ValidateTexStorage(texTarget, levels, internalFormat, width, height, 1, "texStorage2D")) + return; + + gl::GLContext* gl = mContext->gl; + gl->MakeCurrent(); + + mContext->GetAndFlushUnderlyingGLErrors(); + gl->fTexStorage2D(texTarget.get(), levels, internalFormat, width, height); + GLenum error = mContext->GetAndFlushUnderlyingGLErrors(); + if (error) { + return mContext->GenerateWarning("texStorage2D generated error %s", mContext->ErrorName(error)); + } + + SetImmutable(); + + const size_t facesCount = (texTarget == LOCAL_GL_TEXTURE_2D) ? 1 : 6; + GLsizei w = width; + GLsizei h = height; + for (size_t l = 0; l < size_t(levels); l++) { + for (size_t f = 0; f < facesCount; f++) { + SetImageInfo(TexImageTargetForTargetAndFace(texTarget, f), + l, w, h, 1, + internalFormat, + WebGLImageDataStatus::UninitializedImageData); + } + w = std::max(1, w / 2); + h = std::max(1, h / 2); + } +} + +void +WebGLTexture::TexStorage3D(TexTarget texTarget, GLsizei levels, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth) +{ + // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants. + if (texTarget != LOCAL_GL_TEXTURE_3D) + return mContext->ErrorInvalidEnum("texStorage3D: target is not TEXTURE_3D"); + + if (!ValidateTexStorage(texTarget, levels, internalFormat, width, height, depth, "texStorage3D")) + return; + + gl::GLContext* gl = mContext->gl; + gl->MakeCurrent(); + + mContext->GetAndFlushUnderlyingGLErrors(); + gl->fTexStorage3D(texTarget.get(), levels, internalFormat, width, height, depth); + GLenum error = mContext->GetAndFlushUnderlyingGLErrors(); + if (error) { + return mContext->GenerateWarning("texStorage3D generated error %s", mContext->ErrorName(error)); + } + + SetImmutable(); + + GLsizei w = width; + GLsizei h = height; + GLsizei d = depth; + for (size_t l = 0; l < size_t(levels); l++) { + SetImageInfo(TexImageTargetForTargetAndFace(texTarget, 0), + l, w, h, d, + internalFormat, + WebGLImageDataStatus::UninitializedImageData); + w = std::max(1, w >> 1); + h = std::max(1, h >> 1); + d = std::max(1, d >> 1); + } +} + +void +WebGLTexture::TexImage3D(TexImageTarget texImageTarget, GLint level, GLenum internalFormat, + GLsizei width, GLsizei height, GLsizei depth, + GLint border, GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv) +{ + void* data; + size_t dataLength; + js::Scalar::Type jsArrayType; + if (maybeView.IsNull()) { + data = nullptr; + dataLength = 0; + jsArrayType = js::Scalar::MaxTypedArrayViewType; + } else { + const dom::ArrayBufferView& view = maybeView.Value(); + view.ComputeLengthAndData(); + + data = view.Data(); + dataLength = view.Length(); + jsArrayType = view.Type(); + } + + const char funcName[] = "texImage3D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 3, funcName)) + return; + + const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D; + + if (!mContext->ValidateTexImage(texImageTarget, level, internalFormat, + 0, 0, 0, + width, height, depth, + border, unpackFormat, unpackType, func, dims)) + { + return; + } + + if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims)) + return; + + TexInternalFormat effectiveInternalFormat = + EffectiveInternalFormatFromInternalFormatAndType(internalFormat, unpackType); + + if (effectiveInternalFormat == LOCAL_GL_NONE) { + return mContext->ErrorInvalidOperation("texImage3D: bad combination of internalFormat and unpackType"); + } + + // we need to find the exact sized format of the source data. Slightly abusing + // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format + // is the same thing as an unsized internalFormat. + TexInternalFormat effectiveSourceFormat = + EffectiveInternalFormatFromInternalFormatAndType(unpackFormat, unpackType); + MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated unpack format/type combo earlier + const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat); + MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here. + size_t srcTexelSize = srcbitsPerTexel / 8; + + CheckedUint32 checked_neededByteLength = + mContext->GetImageSize(height, width, depth, srcTexelSize, mContext->mPixelStoreUnpackAlignment); + + if (!checked_neededByteLength.isValid()) + return mContext->ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); + + uint32_t bytesNeeded = checked_neededByteLength.value(); + + if (dataLength && dataLength < bytesNeeded) + return mContext->ErrorInvalidOperation("texImage3D: not enough data for operation (need %d, have %d)", + bytesNeeded, dataLength); + + if (IsImmutable()) { + return mContext->ErrorInvalidOperation( + "texImage3D: disallowed because the texture " + "bound to this target has already been made immutable by texStorage3D"); + } + + gl::GLContext* gl = mContext->gl; + gl->MakeCurrent(); + + GLenum driverUnpackType = LOCAL_GL_NONE; + GLenum driverInternalFormat = LOCAL_GL_NONE; + GLenum driverUnpackFormat = LOCAL_GL_NONE; + DriverFormatsFromEffectiveInternalFormat(gl, + effectiveInternalFormat, + &driverInternalFormat, + &driverUnpackFormat, + &driverUnpackType); + + mContext->GetAndFlushUnderlyingGLErrors(); + gl->fTexImage3D(texImageTarget.get(), level, + driverInternalFormat, + width, height, depth, + 0, driverUnpackFormat, driverUnpackType, + data); + GLenum error = mContext->GetAndFlushUnderlyingGLErrors(); + if (error) { + return mContext->GenerateWarning("texImage3D generated error %s", mContext->ErrorName(error)); + } + + SetImageInfo(texImageTarget, level, + width, height, depth, + effectiveInternalFormat, + data ? WebGLImageDataStatus::InitializedImageData + : WebGLImageDataStatus::UninitializedImageData); +} + +void +WebGLTexture::TexSubImage3D(TexImageTarget texImageTarget, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, + GLsizei width, GLsizei height, GLsizei depth, + GLenum unpackFormat, GLenum unpackType, + const dom::Nullable& maybeView, + ErrorResult* const out_rv) +{ + if (maybeView.IsNull()) + return mContext->ErrorInvalidValue("texSubImage3D: pixels must not be null!"); + + const dom::ArrayBufferView& view = maybeView.Value(); + view.ComputeLengthAndData(); + + const char funcName[] = "texSubImage3D"; + if (!DoesTargetMatchDimensions(mContext, texImageTarget, 3, funcName)) + return; + + const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage; + const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D; + + if (!HasImageInfoAt(texImageTarget, level)) { + return mContext->ErrorInvalidOperation("texSubImage3D: no previously defined texture image"); + } + + const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level); + const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat(); + TexInternalFormat existingUnsizedInternalFormat = LOCAL_GL_NONE; + TexType existingType = LOCAL_GL_NONE; + UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(existingEffectiveInternalFormat, + &existingUnsizedInternalFormat, + &existingType); + + if (!mContext->ValidateTexImage(texImageTarget, level, existingEffectiveInternalFormat.get(), + xOffset, yOffset, zOffset, + width, height, depth, + 0, unpackFormat, unpackType, func, dims)) + { + return; + } + + if (unpackType != existingType) { + return mContext->ErrorInvalidOperation("texSubImage3D: type differs from that of the existing image"); + } + + js::Scalar::Type jsArrayType = view.Type(); + void* data = view.Data(); + size_t dataLength = view.Length(); + + if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims)) + return; + + const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat); + MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here. + size_t srcTexelSize = bitsPerTexel / 8; + + if (width == 0 || height == 0 || depth == 0) + return; // no effect, we better return right now + + CheckedUint32 checked_neededByteLength = + mContext->GetImageSize(height, width, depth, srcTexelSize, mContext->mPixelStoreUnpackAlignment); + + if (!checked_neededByteLength.isValid()) + return mContext->ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); + + uint32_t bytesNeeded = checked_neededByteLength.value(); + + if (dataLength < bytesNeeded) + return mContext->ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, dataLength); + + if (imageInfo.HasUninitializedImageData()) { + bool coversWholeImage = xOffset == 0 && + yOffset == 0 && + zOffset == 0 && + width == imageInfo.Width() && + height == imageInfo.Height() && + depth == imageInfo.Depth(); + if (coversWholeImage) { + SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData); + } else { + if (!EnsureInitializedImageData(texImageTarget, level)) + return; + } + } + + GLenum driverUnpackType = LOCAL_GL_NONE; + GLenum driverInternalFormat = LOCAL_GL_NONE; + GLenum driverUnpackFormat = LOCAL_GL_NONE; + DriverFormatsFromEffectiveInternalFormat(mContext->gl, + existingEffectiveInternalFormat, + &driverInternalFormat, + &driverUnpackFormat, + &driverUnpackType); + + mContext->MakeContextCurrent(); + mContext->gl->fTexSubImage3D(texImageTarget.get(), level, + xOffset, yOffset, zOffset, + width, height, depth, + driverUnpackFormat, driverUnpackType, data); + +} + + +} // namespace mozilla diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 331a65ee5c..117eb3c795 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -65,6 +65,7 @@ UNIFIED_SOURCES += [ 'WebGL2ContextMRTs.cpp', 'WebGL2ContextPrograms.cpp', 'WebGL2ContextQueries.cpp', + 'WebGL2ContextRenderbuffers.cpp', 'WebGL2ContextSamplers.cpp', 'WebGL2ContextState.cpp', 'WebGL2ContextSync.cpp', @@ -83,6 +84,7 @@ UNIFIED_SOURCES += [ 'WebGLContextGL.cpp', 'WebGLContextLossHandler.cpp', 'WebGLContextState.cpp', + 'WebGLContextTextures.cpp', 'WebGLContextUnchecked.cpp', 'WebGLContextUtils.cpp', 'WebGLContextValidate.cpp', @@ -130,6 +132,7 @@ UNIFIED_SOURCES += [ 'WebGLSync.cpp', 'WebGLTexelConversions.cpp', 'WebGLTexture.cpp', + 'WebGLTextureUpload.cpp', 'WebGLTimerQuery.cpp', 'WebGLTransformFeedback.cpp', 'WebGLUniformLocation.cpp', diff --git a/dom/webidl/WebGL2RenderingContext.webidl b/dom/webidl/WebGL2RenderingContext.webidl index 69c8de3bb9..58d85761bc 100644 --- a/dom/webidl/WebGL2RenderingContext.webidl +++ b/dom/webidl/WebGL2RenderingContext.webidl @@ -328,8 +328,7 @@ interface WebGL2RenderingContext : WebGLRenderingContext /* Framebuffer objects */ void blitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void framebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); - any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); + void framebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture? texture, GLint level, GLint layer); [Throws] void invalidateFramebuffer(GLenum target, sequence attachments); @@ -341,6 +340,8 @@ interface WebGL2RenderingContext : WebGLRenderingContext void readBuffer(GLenum src); /* Renderbuffer objects */ + [Throws] + any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); void renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); /* Texture objects */ diff --git a/embedding/test/browser.ini b/embedding/test/browser.ini new file mode 100644 index 0000000000..936b071049 --- /dev/null +++ b/embedding/test/browser.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + bug1204626_doc0.html + bug1204626_doc1.html + +[browser_bug1204626.js] diff --git a/embedding/test/browser_bug1204626.js b/embedding/test/browser_bug1204626.js new file mode 100644 index 0000000000..80494290b2 --- /dev/null +++ b/embedding/test/browser_bug1204626.js @@ -0,0 +1,87 @@ +"use strict"; // -*- js-indent-level: 2; indent-tabs-mode: nil -*- +const Cc = Components.classes; +const Ci = Components.interfaces; +const contentBase = "https://example.com/browser/embedding/test/"; +const chromeBase = "chrome://mochitests/content/browser/embedding/test/"; +const testPageURL = contentBase + "bug1204626_doc0.html"; + +function one_test(delay, continuation) { + let delayStr = delay === null ? "no delay" : "delay = " + delay + "ms"; + let browser; + + BrowserTestUtils.openNewForegroundTab(gBrowser, testPageURL).then((tab) => { + browser = tab.linkedBrowser; + let persistable = browser.QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader + .QueryInterface(Ci.nsIWebBrowserPersistable); + persistable.startPersistence(/* outer window ID: */ 0, { + onDocumentReady, + onError: function(status) { + ok(false, new Components.Exception("startPersistence failed", status)); + continuation(); + } + }); + }); + + function onDocumentReady(doc) { + const nameStem="test_bug1204626_" + Date.now(); + let wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + let tmp = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + let tmpFile = tmp.clone(); + tmpFile.append(nameStem + "_saved.html"); + let tmpDir = tmp.clone(); + tmpDir.append(nameStem + "_files"); + + registerCleanupFunction(function cleanUp() { + if (tmpFile.exists()) { + tmpFile.remove(/* recursive: */ false); + } + if (tmpDir.exists()) { + tmpDir.remove(/* recursive: */ true); + } + }); + + wbp.progressListener = { + onProgressChange: function(){}, + onLocationChange: function(){}, + onStatusChange: function(){}, + onSecurityChange: function(){}, + onStateChange: function wbp_stateChange(_wbp, _req, state, _status) { + if ((state & Ci.nsIWebProgressListener.STATE_STOP) == 0) { + return; + } + ok(true, "Finished save (" + delayStr + ") but might have crashed."); + continuation(); + } + } + + function doSave() { + wbp.saveDocument(doc, tmpFile, tmpDir, null, 0, 0); + } + if (delay === null) { + doSave(); + } else { + setTimeout(doSave, delay); + } + browser.messageManager.loadFrameScript("data:,content.window.close()", true); + } +} + +function test() { + waitForExplicitFinish(); + // 0ms breaks having the actor under PBrowser, but not 10ms. + // 10ms provokes the double-__delete__, but not 0ms. + // And a few others, just in case. + const testRuns = [null, 0, 10, 0, 10, 20, 50, 100]; + let i = 0; + (function next_test() { + if (i < testRuns.length) { + one_test(testRuns[i++], next_test); + } else { + finish(); + } + })(); +} diff --git a/embedding/test/bug1204626_doc0.html b/embedding/test/bug1204626_doc0.html new file mode 100644 index 0000000000..cbced762c3 --- /dev/null +++ b/embedding/test/bug1204626_doc0.html @@ -0,0 +1,3 @@ + +

This is a document, and it contains an iframe:

+ diff --git a/embedding/test/bug1204626_doc1.html b/embedding/test/bug1204626_doc1.html new file mode 100644 index 0000000000..cffc283d2c --- /dev/null +++ b/embedding/test/bug1204626_doc1.html @@ -0,0 +1,5 @@ + +

This is the document inside the iframe. (Currently this +document doesn't even need to exist in order to reproduce the bug in +question, as long as the parent contains a frame, but it's probably +best not to depend on that.)

diff --git a/embedding/test/moz.build b/embedding/test/moz.build index 846268289f..2171b19219 100644 --- a/embedding/test/moz.build +++ b/embedding/test/moz.build @@ -6,3 +6,4 @@ MOCHITEST_MANIFESTS += ['mochitest.ini'] MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] +BROWSER_CHROME_MANIFESTS += ['browser.ini'] diff --git a/gfx/gl/GLScreenBuffer.cpp b/gfx/gl/GLScreenBuffer.cpp index 8d02ca3680..24dd286752 100755 --- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -59,6 +59,7 @@ GLScreenBuffer::GLScreenBuffer(GLContext* gl, , mFactory(Move(factory)) , mNeedsBlit(true) , mUserReadBufferMode(LOCAL_GL_BACK) + , mUserDrawBufferMode(LOCAL_GL_BACK) , mUserDrawFB(0) , mUserReadFB(0) , mInternalDrawFB(0) @@ -445,6 +446,12 @@ GLScreenBuffer::Attach(SharedSurface* surf, const gfx::IntSize& size) mRead->SetReadBuffer(mUserReadBufferMode); } + // Update the DrawBuffer mode. + if (mGL->IsSupported(gl::GLFeature::draw_buffers)) { + BindFB(0); + SetDrawBuffer(mUserDrawBufferMode); + } + RequireBlit(); return true; @@ -548,6 +555,38 @@ GLScreenBuffer::CreateRead(SharedSurface* surf) return ReadBuffer::Create(gl, caps, formats, surf); } +void +GLScreenBuffer::SetDrawBuffer(GLenum mode) +{ + MOZ_ASSERT(mGL->IsSupported(gl::GLFeature::draw_buffers)); + MOZ_ASSERT(GetDrawFB() == 0); + + if (!mGL->IsSupported(GLFeature::draw_buffers)) + return; + + mUserDrawBufferMode = mode; + + GLuint fb = mDraw ? mDraw->mFB : mRead->mFB; + GLenum internalMode; + + switch (mode) { + case LOCAL_GL_BACK: + internalMode = (fb == 0) ? LOCAL_GL_BACK + : LOCAL_GL_COLOR_ATTACHMENT0; + break; + + case LOCAL_GL_NONE: + internalMode = LOCAL_GL_NONE; + break; + + default: + MOZ_CRASH("Bad value."); + } + + mGL->MakeCurrent(); + mGL->fDrawBuffers(1, &internalMode); +} + void GLScreenBuffer::SetReadBuffer(GLenum mode) { diff --git a/gfx/gl/GLScreenBuffer.h b/gfx/gl/GLScreenBuffer.h index e835faff63..760224ab5e 100644 --- a/gfx/gl/GLScreenBuffer.h +++ b/gfx/gl/GLScreenBuffer.h @@ -149,6 +149,7 @@ protected: bool mNeedsBlit; GLenum mUserReadBufferMode; + GLenum mUserDrawBufferMode; // Below are the parts that help us pretend to be framebuffer 0: GLuint mUserDrawFB; @@ -222,6 +223,7 @@ public: GLint y, GLsizei width, GLsizei height, GLint border); void SetReadBuffer(GLenum userMode); + void SetDrawBuffer(GLenum userMode); /** * Attempts to read pixels from the current bound framebuffer, if diff --git a/gfx/gl/GLTextureImage.h b/gfx/gl/GLTextureImage.h index 6d1de5750d..5c0203e833 100644 --- a/gfx/gl/GLTextureImage.h +++ b/gfx/gl/GLTextureImage.h @@ -196,7 +196,6 @@ public: gfx::IntSize GetSize() const; ContentType GetContentType() const { return mContentType; } - ImageFormat GetImageFormat() const { return mImageFormat; } virtual bool InUpdate() const = 0; GLenum GetWrapMode() const { return mWrapMode; } @@ -224,7 +223,6 @@ protected: gfx::IntSize mSize; GLenum mWrapMode; ContentType mContentType; - ImageFormat mImageFormat; gfx::SurfaceFormat mTextureFormat; GraphicsFilter mFilter; Flags mFlags; @@ -329,7 +327,6 @@ protected: void* mIterationCallbackData; nsTArray< nsRefPtr > mImages; bool mInUpdate; - gfx::IntSize mSize; unsigned int mTileSize; unsigned int mRows, mColumns; GLContext* mGL; diff --git a/gfx/gl/SharedSurface.h b/gfx/gl/SharedSurface.h index 75cbf60bd6..8c25ac7063 100644 --- a/gfx/gl/SharedSurface.h +++ b/gfx/gl/SharedSurface.h @@ -34,7 +34,7 @@ class nsIThread; namespace mozilla { namespace gfx { class DrawTarget; -} +} // namespace gfx namespace layers { class ISurfaceAllocator; @@ -42,7 +42,7 @@ class SharedSurfaceTextureClient; enum class TextureFlags : uint32_t; class SurfaceDescriptor; class TextureClient; -} +} // namespace layers namespace gl { diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp index efe2e02ad0..132feb9a31 100644 --- a/image/Downscaler.cpp +++ b/image/Downscaler.cpp @@ -27,6 +27,7 @@ Downscaler::Downscaler(const nsIntSize& aTargetSize) , mYFilter(MakeUnique()) , mWindowCapacity(0) , mHasAlpha(true) + , mFlipVertically(false) { MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(), "Downscaling even though downscale-during-decode is disabled?"); @@ -57,7 +58,8 @@ Downscaler::ReleaseWindow() nsresult Downscaler::BeginFrame(const nsIntSize& aOriginalSize, uint8_t* aOutputBuffer, - bool aHasAlpha) + bool aHasAlpha, + bool aFlipVertically /* = false */) { MOZ_ASSERT(aOutputBuffer); MOZ_ASSERT(mTargetSize != aOriginalSize, @@ -74,6 +76,7 @@ Downscaler::BeginFrame(const nsIntSize& aOriginalSize, double(mOriginalSize.height) / mTargetSize.height); mOutputBuffer = aOutputBuffer; mHasAlpha = aHasAlpha; + mFlipVertically = aFlipVertically; ResetForNextProgressivePass(); ReleaseWindow(); @@ -150,6 +153,16 @@ GetFilterOffsetAndLength(UniquePtr& aFilter, aFilterLengthOut); } +void +Downscaler::ClearRow(uint32_t aStartingAtCol) +{ + MOZ_ASSERT(int64_t(mOriginalSize.width) > int64_t(aStartingAtCol)); + uint32_t bytesToClear = (mOriginalSize.width - aStartingAtCol) + * sizeof(uint32_t); + memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)), + 0, bytesToClear); +} + void Downscaler::CommitRow() { @@ -204,9 +217,18 @@ Downscaler::TakeInvalidRect() DownscalerInvalidRect invalidRect; // Compute the target size invalid rect. - invalidRect.mTargetSizeRect = - nsIntRect(0, mPrevInvalidatedLine, + if (mFlipVertically) { + // We need to flip it. This will implicitly flip the original size invalid + // rect, since we compute it by scaling this rect. + invalidRect.mTargetSizeRect = + IntRect(0, mTargetSize.height - mCurrentOutLine, mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine); + } else { + invalidRect.mTargetSizeRect = + IntRect(0, mPrevInvalidatedLine, + mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine); + } + mPrevInvalidatedLine = mCurrentOutLine; // Compute the original size invalid rect. @@ -231,8 +253,13 @@ Downscaler::DownscaleInputLine() auto filterValues = mYFilter->FilterForValue(mCurrentOutLine, &filterOffset, &filterLength); + int32_t currentOutLine = mFlipVertically + ? mTargetSize.height - (mCurrentOutLine + 1) + : mCurrentOutLine; + MOZ_ASSERT(currentOutLine >= 0); + uint8_t* outputLine = - &mOutputBuffer[mCurrentOutLine * mTargetSize.width * sizeof(uint32_t)]; + &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)]; skia::ConvolveVertically(static_cast(filterValues), filterLength, mWindow.get(), mXFilter->num_values(), outputLine, mHasAlpha, supports_sse2()); diff --git a/image/Downscaler.h b/image/Downscaler.h index d676717912..3f1365bf18 100644 --- a/image/Downscaler.h +++ b/image/Downscaler.h @@ -70,14 +70,21 @@ public: * decode. * @param aHasAlpha Whether or not this frame has an alpha channel. * Performance is a little better if it doesn't have one. + * @param aFlipVertically If true, output rows will be written to the output + * buffer in reverse order vertically, which matches + * the way they are stored in some image formats. */ nsresult BeginFrame(const nsIntSize& aOriginalSize, uint8_t* aOutputBuffer, - bool aHasAlpha); + bool aHasAlpha, + bool aFlipVertically = false); /// Retrieves the buffer into which the Decoder should write each row. uint8_t* RowBuffer() { return mRowBuffer.get(); } + /// Clears the current row buffer (optionally starting at @aStartingAtCol). + void ClearRow(uint32_t aStartingAtCol = 0); + /// Signals that the decoder has finished writing a row into the row buffer. void CommitRow(); @@ -117,7 +124,8 @@ private: int32_t mCurrentOutLine; int32_t mCurrentInLine; - bool mHasAlpha; + bool mHasAlpha : 1; + bool mFlipVertically : 1; }; #else @@ -139,12 +147,13 @@ public: const nsIntSize& TargetSize() const { return nsIntSize(); } const gfxSize& Scale() const { return gfxSize(1.0, 1.0); } - nsresult BeginFrame(const nsIntSize&, uint8_t*, bool) + nsresult BeginFrame(const nsIntSize&, uint8_t*, bool, bool = false) { return NS_ERROR_FAILURE; } uint8_t* RowBuffer() { return nullptr; } + void ClearRow(uint32_t = 0) { } void CommitRow() { } bool HasInvalidation() const { return false; } DownscalerInvalidRect TakeInvalidRect() { return DownscalerInvalidRect(); } diff --git a/image/ImageFactory.cpp b/image/ImageFactory.cpp index 3f534cb0ef..eabeaf29f3 100644 --- a/image/ImageFactory.cpp +++ b/image/ImageFactory.cpp @@ -35,9 +35,10 @@ ImageFactory::Initialize() static bool ShouldDownscaleDuringDecode(const nsCString& aMimeType) { - return aMimeType.EqualsLiteral(IMAGE_JPEG) || - aMimeType.EqualsLiteral(IMAGE_JPG) || - aMimeType.EqualsLiteral(IMAGE_PJPEG); + DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get()); + return type == DecoderType::JPEG || + type == DecoderType::PNG || + type == DecoderType::BMP; } static uint32_t diff --git a/image/RasterImage.h b/image/RasterImage.h index 36fefa6d6d..b2aa41d17d 100644 --- a/image/RasterImage.h +++ b/image/RasterImage.h @@ -147,10 +147,10 @@ DecodeFlags(uint32_t aFlags) } class RasterImage final : public ImageResource - , public nsIProperties - , public SupportsWeakPtr + , public nsIProperties + , public SupportsWeakPtr #ifdef DEBUG - , public imgIContainerDebug + , public imgIContainerDebug #endif { // (no public constructor - use ImageFactory) diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp index d049b3c2fe..c136b246eb 100644 --- a/image/decoders/nsBMPDecoder.cpp +++ b/image/decoders/nsBMPDecoder.cpp @@ -19,6 +19,8 @@ #include "RasterImage.h" #include +using namespace mozilla::gfx; + namespace mozilla { namespace image { @@ -62,6 +64,20 @@ nsBMPDecoder::~nsBMPDecoder() } } +nsresult +nsBMPDecoder::SetTargetSize(const nsIntSize& aSize) +{ + // Make sure the size is reasonable. + if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) { + return NS_ERROR_FAILURE; + } + + // Create a downscaler that we'll filter our output through. + mDownscaler.emplace(aSize); + + return NS_OK; +} + // Sets whether or not the BMP will use alpha data void nsBMPDecoder::SetUseAlphaData(bool useAlphaData) @@ -122,15 +138,6 @@ nsBMPDecoder::GetCompressedImageSize() const return pixelArraySize; } -// Obtains whether or not a BMP file had alpha data in its 4th byte -// for 32BPP bitmaps. Only use after the bitmap has been processed. -bool -nsBMPDecoder::HasAlphaData() const -{ - return mHaveAlphaData; -} - - void nsBMPDecoder::FinishInternal() { @@ -147,7 +154,7 @@ nsBMPDecoder::FinishInternal() nsIntRect r(0, 0, mBIH.width, GetHeight()); PostInvalidation(r); - if (mUseAlphaData) { + if (mUseAlphaData && mHaveAlphaData) { PostFrameStop(Opacity::SOME_TRANSPARENCY); } else { PostFrameStop(Opacity::OPAQUE); @@ -372,9 +379,10 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) // We treat BMPs as transparent if they're 32bpp and alpha is enabled, but // also if they use RLE encoding, because the 'delta' mode can skip pixels // and cause implicit transparency. - if ((mBIH.compression == BMPINFOHEADER::RLE8) || - (mBIH.compression == BMPINFOHEADER::RLE4) || - (mBIH.bpp == 32 && mUseAlphaData)) { + bool hasTransparency = (mBIH.compression == BMPINFOHEADER::RLE8) || + (mBIH.compression == BMPINFOHEADER::RLE4) || + (mBIH.bpp == 32 && mUseAlphaData); + if (hasTransparency) { PostHasTransparency(); } @@ -453,18 +461,25 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) } MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); - nsresult rv = AllocateBasicFrame(); + IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() + : GetSize(); + nsresult rv = AllocateFrame(/* aFrameNum = */ 0, targetSize, + IntRect(IntPoint(), targetSize), + SurfaceFormat::B8G8R8A8); if (NS_FAILED(rv)) { return; } MOZ_ASSERT(mImageData, "Should have a buffer now"); - // Prepare for transparency - if ((mBIH.compression == BMPINFOHEADER::RLE8) || - (mBIH.compression == BMPINFOHEADER::RLE4)) { - // Clear the image, as the RLE may jump over areas - memset(mImageData, 0, mImageDataLength); + if (mDownscaler) { + // BMPs store their rows in reverse order, so the downscaler needs to + // reverse them again when writing its output. + rv = mDownscaler->BeginFrame(GetSize(), mImageData, hasTransparency, + /* aFlipVertically = */ true); + if (NS_FAILED(rv)) { + return; + } } } @@ -606,8 +621,10 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) if (rowSize == mRowBytes) { // Collected a whole row into mRow, process it uint8_t* p = mRow; - uint32_t* d = reinterpret_cast(mImageData) + - PIXEL_OFFSET(mCurLine, 0); + uint32_t* d = mDownscaler + ? reinterpret_cast(mDownscaler->RowBuffer()) + : reinterpret_cast(mImageData) + + PIXEL_OFFSET(mCurLine, 0); uint32_t lpos = mBIH.width; switch (mBIH.bpp) { case 1: @@ -664,28 +681,11 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) case 32: while (lpos > 0) { if (mUseAlphaData) { - if (!mHaveAlphaData && p[3]) { - // Non-zero alpha byte detected! Clear previous - // pixels that we have already processed. - // This works because we know that if we - // are reaching here then the alpha data in byte - // 4 has been right all along. And we know it - // has been set to 0 the whole time, so that - // means that everything is transparent so far. - uint32_t* start = reinterpret_cast - (mImageData) + GetWidth() * - (mCurLine - 1); - uint32_t heightDifference = GetHeight() - - mCurLine + 1; - uint32_t pixelCount = GetWidth() * - heightDifference; - - memset(start, 0, pixelCount * sizeof(uint32_t)); - + if (MOZ_UNLIKELY(!mHaveAlphaData && p[3])) { PostHasTransparency(); mHaveAlphaData = true; } - SetPixel(d, p[2], p[1], p[0], mHaveAlphaData ? p[3] : 0xFF); + SetPixel(d, p[2], p[1], p[0], p[3]); } else { SetPixel(d, p[2], p[1], p[0]); } @@ -697,6 +697,11 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) NS_NOTREACHED("Unsupported color depth," " but earlier check didn't catch it"); } + + if (mDownscaler) { + mDownscaler->CommitRow(); + } + mCurLine --; if (mCurLine == 0) { // Finished last line break; @@ -741,8 +746,12 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) uint32_t pixelsNeeded = std::min(mBIH.width - mCurPos, mStateData); if (pixelsNeeded) { - uint32_t* d = reinterpret_cast - (mImageData) + PIXEL_OFFSET(mCurLine, mCurPos); + uint32_t* d = mDownscaler + ? reinterpret_cast(mDownscaler->RowBuffer()) + + mCurPos + : reinterpret_cast(mImageData) + + PIXEL_OFFSET(mCurLine, mCurPos); + mCurPos += pixelsNeeded; if (mBIH.compression == BMPINFOHEADER::RLE8) { do { @@ -761,6 +770,10 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) switch(byte) { case RLE::ESCAPE_EOL: // End of Line: Go to next row + if (mDownscaler) { + mDownscaler->CommitRow(); + } + mCurLine --; mCurPos = 0; mState = eRLEStateInitial; @@ -810,11 +823,20 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) // Handle the XDelta and proceed to get Y Delta byte = *aBuffer++; aCount--; + + if (mDownscaler) { + // Clear the skipped pixels. (This clears to the end of the row, + // which is perfect if there's a Y delta and harmless if not). + mDownscaler->ClearRow(/* aStartingAtCol = */ mCurPos); + } + mCurPos += byte; + // Delta encoding makes it possible to skip pixels // making the image transparent. if (MOZ_UNLIKELY(!mHaveAlphaData)) { PostHasTransparency(); + mHaveAlphaData = true; } mUseAlphaData = mHaveAlphaData = true; if (mCurPos > mBIH.width) { @@ -824,7 +846,7 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) mState = eRLEStateNeedYDelta; continue; - case eRLEStateNeedYDelta: + case eRLEStateNeedYDelta: { // Get the Y Delta and then "handle" the move byte = *aBuffer++; aCount--; @@ -833,10 +855,26 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) // making the image transparent. if (MOZ_UNLIKELY(!mHaveAlphaData)) { PostHasTransparency(); + mHaveAlphaData = true; } mUseAlphaData = mHaveAlphaData = true; - mCurLine -= std::min(byte, mCurLine); + + int32_t yDelta = std::min(byte, mCurLine); + mCurLine -= yDelta; + + if (mDownscaler && yDelta > 0) { + // Commit the current row (the first of the skipped rows). + mDownscaler->CommitRow(); + + // Clear and commit the remaining skipped rows. + for (int32_t line = 1 ; line < yDelta ; ++line) { + mDownscaler->ClearRow(); + mDownscaler->CommitRow(); + } + } + break; + } case eRLEStateAbsoluteMode: // Absolute Mode case eRLEStateAbsoluteModePadded: @@ -845,9 +883,12 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) // represents the number of pixels // that follow, each of which contains // the color index of a single pixel. - uint32_t* d = reinterpret_cast - (mImageData) + - PIXEL_OFFSET(mCurLine, mCurPos); + uint32_t* d = mDownscaler + ? reinterpret_cast(mDownscaler->RowBuffer()) + + mCurPos + : reinterpret_cast(mImageData) + + PIXEL_OFFSET(mCurLine, mCurPos); + uint32_t* oldPos = d; if (mBIH.compression == BMPINFOHEADER::RLE8) { while (aCount > 0 && mStateData > 0) { @@ -903,9 +944,15 @@ nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) const uint32_t rows = mOldLine - mCurLine; if (rows) { // Invalidate - nsIntRect r(0, mBIH.height < 0 ? -mBIH.height - mOldLine : mCurLine, - mBIH.width, rows); - PostInvalidation(r); + if (!mDownscaler) { + nsIntRect r(0, mBIH.height < 0 ? -mBIH.height - mOldLine : mCurLine, + mBIH.width, rows); + PostInvalidation(r); + } else if (mDownscaler->HasInvalidation()) { + DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); + PostInvalidation(invalidRect.mOriginalSizeRect, + Some(invalidRect.mTargetSizeRect)); + } mOldLine = mCurLine; } diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h index 81f8d66d69..ad02e73123 100644 --- a/image/decoders/nsBMPDecoder.h +++ b/image/decoders/nsBMPDecoder.h @@ -9,6 +9,7 @@ #include "BMPFileHeaders.h" #include "Decoder.h" +#include "Downscaler.h" #include "gfxColor.h" #include "nsAutoPtr.h" @@ -24,6 +25,8 @@ class nsBMPDecoder : public Decoder public: ~nsBMPDecoder(); + nsresult SetTargetSize(const nsIntSize& aSize) override; + // Specifies whether or not the BMP file will contain alpha data // If set to true and the BMP is 32BPP, the alpha data will be // retrieved from the 4th byte of image data per pixel @@ -46,7 +49,10 @@ public: // Obtains whether or not a BMP file had alpha data in its 4th byte // for 32BPP bitmaps. Only use after the bitmap has been processed. - bool HasAlphaData() const; + bool HasAlphaData() const { return mHaveAlphaData; } + + /// Marks this BMP as having alpha data (due to e.g. an ICO alpha mask). + void SetHasAlphaData() { mHaveAlphaData = true; } virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override; @@ -71,6 +77,8 @@ private: char mRawBuf[BIH_INTERNAL_LENGTH::WIN_V3]; //< If this is changed, // WriteInternal() MUST be updated + Maybe mDownscaler; + uint32_t mLOH; //< Length of the header uint32_t mNumColors; //< The number of used colors, i.e. the number of diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index 6bab3e5d24..0531395b59 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -531,14 +531,14 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) // If the bitmap is fully processed, treat any left over data as the ICO's // 'AND buffer mask' which appears after the bitmap resource. if (!mIsPNG && mPos >= bmpDataEnd) { + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + // There may be an optional AND bit mask after the data. This is // only used if the alpha data is not already set. The alpha data // is used for 32bpp bitmaps as per the comment in ICODecoder.h // The alpha mask should be checked in all other cases. - if (static_cast(mContainedDecoder.get())-> - GetBitsPerPixel() != 32 || - !static_cast(mContainedDecoder.get())-> - HasAlphaData()) { + if (bmpDecoder->GetBitsPerPixel() != 32 || !bmpDecoder->HasAlphaData()) { uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up if (mPos == bmpDataEnd) { mPos++; @@ -572,9 +572,7 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) mCurLine--; mRowBytes = 0; - uint32_t* imageData = - static_cast(mContainedDecoder.get())-> - GetImageData(); + uint32_t* imageData = bmpDecoder->GetImageData(); if (!imageData) { PostDataError(); return; @@ -600,7 +598,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) // If any bits are set in sawTransparency, then we know at least one // pixel was transparent. if (sawTransparency) { - PostHasTransparency(); + PostHasTransparency(); + bmpDecoder->SetHasAlphaData(); } } } diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp index 6763ae56e8..445734ce9b 100644 --- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -148,6 +148,20 @@ nsPNGDecoder::~nsPNGDecoder() } } +nsresult +nsPNGDecoder::SetTargetSize(const nsIntSize& aSize) +{ + // Make sure the size is reasonable. + if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) { + return NS_ERROR_FAILURE; + } + + // Create a downscaler that we'll filter our output through. + mDownscaler.emplace(aSize); + + return NS_OK; +} + void nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat, const IntRect& aFrameRect) @@ -181,7 +195,16 @@ nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset, format = gfx::SurfaceFormat::B8G8R8A8; } - nsresult rv = AllocateFrame(mNumFrames, GetSize(), frameRect, format); + // Make sure there's no animation or padding if we're downscaling. + MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation()); + MOZ_ASSERT_IF(mDownscaler, + IntRect(IntPoint(), GetSize()).IsEqualEdges(frameRect)); + + IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() + : GetSize(); + IntRect targetFrameRect = mDownscaler ? IntRect(IntPoint(), targetSize) + : frameRect; + nsresult rv = AllocateFrame(mNumFrames, targetSize, targetFrameRect, format); if (NS_FAILED(rv)) { return rv; } @@ -205,6 +228,14 @@ nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset, } #endif + if (mDownscaler) { + bool hasAlpha = aFormat != SurfaceFormat::B8G8R8X8; + rv = mDownscaler->BeginFrame(frameRect.Size(), mImageData, hasAlpha); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; } @@ -611,6 +642,12 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL); if (isAnimated) { decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr)); + + if (decoder->mDownscaler && !decoder->IsFirstFrameDecode()) { + MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode " + "for an animated image?"); + decoder->mDownscaler.reset(); + } } #endif @@ -663,6 +700,33 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) } } +void +nsPNGDecoder::PostPartialInvalidation(const IntRect& aInvalidRegion) +{ + if (!mDownscaler) { + PostInvalidation(aInvalidRegion); + return; + } + + if (!mDownscaler->HasInvalidation()) { + return; + } + + DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); + PostInvalidation(invalidRect.mOriginalSizeRect, + Some(invalidRect.mTargetSizeRect)); +} + +void +nsPNGDecoder::PostFullInvalidation() +{ + PostInvalidation(mFrameRect); + + if (mDownscaler) { + mDownscaler->ResetForNextProgressivePass(); + } +} + void nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) @@ -706,7 +770,12 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, return; } - if (new_row) { + // If |new_row| is null, that indicates that this is an interlaced image and + // |row_callback| is being called for a row that hasn't changed. Ordinarily + // we don't need to do anything in this case, but if we're downscaling, the + // downscaler doesn't store the rows from previous passes, so we still need to + // process the row. + if (new_row || decoder->mDownscaler) { int32_t width = decoder->mFrameRect.width; uint32_t iwidth = decoder->mFrameRect.width; @@ -717,7 +786,9 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, } uint32_t bpr = width * sizeof(uint32_t); - uint32_t* cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr)); + uint32_t* cptr32 = decoder->mDownscaler + ? reinterpret_cast(decoder->mDownscaler->RowBuffer()) + : reinterpret_cast(decoder->mImageData + (row_num*bpr)); if (decoder->mTransform) { if (decoder->mCMSLine) { @@ -781,14 +852,17 @@ nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, png_longjmp(decoder->mPNG, 1); } + if (decoder->mDownscaler) { + decoder->mDownscaler->CommitRow(); + } + if (!decoder->interlacebuf) { - // Do line-by-line partial invalidations for non-interlaced images - decoder->PostInvalidation(IntRect(0, row_num, width, 1)); + // Do line-by-line partial invalidations for non-interlaced images. + decoder->PostPartialInvalidation(IntRect(0, row_num, width, 1)); } else if (row_num == static_cast(decoder->mFrameRect.height - 1)) { - // Do only one full image invalidation for each pass (Bug 1187569) - decoder->PostInvalidation(IntRect(0, 0, width, - decoder->mFrameRect.height)); + // Do only one full image invalidation for each pass. (Bug 1187569) + decoder->PostFullInvalidation(); } } } diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h index fce64d1f1e..edd2ef6cef 100644 --- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -8,6 +8,7 @@ #define mozilla_image_decoders_nsPNGDecoder_h #include "Decoder.h" +#include "Downscaler.h" #include "gfxTypes.h" @@ -26,6 +27,8 @@ class nsPNGDecoder : public Decoder public: virtual ~nsPNGDecoder(); + virtual nsresult SetTargetSize(const nsIntSize& aSize) override; + virtual void InitInternal() override; virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override; virtual Telemetry::ID SpeedHistogram() override; @@ -77,8 +80,12 @@ private: // XXX(seth): nsICODecoder is temporarily an exception to this rule. explicit nsPNGDecoder(RasterImage* aImage); + void PostPartialInvalidation(const gfx::IntRect& aInvalidRegion); + void PostFullInvalidation(); + public: png_structp mPNG; + Maybe mDownscaler; png_infop mInfo; nsIntRect mFrameRect; uint8_t* mCMSLine; diff --git a/image/test/reftest/downscaling/downscale-png.html b/image/test/reftest/downscaling/downscale-png.html new file mode 100644 index 0000000000..4752b2155a --- /dev/null +++ b/image/test/reftest/downscaling/downscale-png.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/png-interlaced.png b/image/test/reftest/downscaling/png-interlaced.png new file mode 100644 index 0000000000000000000000000000000000000000..a938d0bd6a93e72d5c381e2815123c720db31bcf GIT binary patch literal 806 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE#K6GVEq~V($YDu$^mSxlY+GRXVp=>%GT9@@ zmw};5je((|g@NH0P^jSr14F3+1H-EX1_rAc3=HB0b9M#V043OxyxmmfCPS#u0`IqM*LsgcCLRoI6B2G=f0T8 zFXeT?Q{;H4Aejc910=ju(uZFYd#^U`+JvL8_!|6^Syqec9AGcG5tsI?QjYWUjB9&Y zHTrX<=5$Z{^=?)`ciV=Z3q_L@>b5giFdeWwV9T(7>(bpBo+=MG8RQP-2kl>*wzMF4 zw*PUq>06t*t$o*V7Or_b`JTE-v}mK}q3eq|Hg}v|#yNM{<{4%aziwjme!1n4jgL}q zu-D_i<`a$_l-#tB|7qTR@wPqpSH7yhAMSl4_{aPfcC)`9{xAG2a4xh??6N)AzU#lg zKK&=*a(nv1yX_H2zLbSHY_#WFB+RtxgZIJv{)z>MBc8BdT+Cu;@3!D`%$Jf)i94ik zI(}Kf7j*2Ad@N8R#Z21Hv+d!#(vt~}3*YXy&n#WCF?rTY&K{rQvLADlS^e%E^kmtT z^J9r%0YwSWG*Z&Ke}9oWz3dA!IV&GgJlKW_g3k5JYxO%->Y z*F2OC7#SFv>Ka(+8d-!G7+4uuS{az@8kk!d7;M!$tBj%{H$Npa YtrE9}BgLijff^V*UHx3vIVCg!04+q4!T=1008) != downscale-2e.html?205,53,bottom about:blank +== downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal +== downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal + # RUN TESTS WITH HIGH QUALITY DOWNSCALING ENABLED: # ================================================ # High-quality downscaling enabled: @@ -149,3 +152,6 @@ fuzzy(20,999) != downscale-2c.html?205,53,bottom about:blank fuzzy(20,999) != downscale-2d.html?205,53,bottom about:blank fuzzy(20,999) != downscale-2e.html?205,53,bottom about:blank fuzzy(20,999) != downscale-2f.html?205,53,bottom about:blank + +== downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal +== downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal diff --git a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp index 985068b0f9..b8c4b9995c 100644 --- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp @@ -736,7 +736,7 @@ nsUrlClassifierDBServiceWorker::OpenDb() // the client callback. class nsUrlClassifierLookupCallback final : public nsIUrlClassifierLookupCallback - , public nsIUrlClassifierHashCompleterCallback + , public nsIUrlClassifierHashCompleterCallback { public: NS_DECL_THREADSAFE_ISUPPORTS From f1d1e166698f31013fbe05ca1e3570d0fa9287c9 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 18 May 2022 11:52:08 +0800 Subject: [PATCH 3/4] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1188569: Drop unneeded MOZ_WARN_UNUSED_RESULT from from LookupBestMatch in SurfaceCache.cpp. r=seth (5e74e0028c) - Bug 1192356 (Part 1) - Take advantage of mozilla::Tie() in SurfaceCache.cpp. r=dholbert (e4908c725d) - Bug 1192356 (Part 2) - Take advantage of mozilla::Tie() in RasterImage.cpp. r=tn (1204189b73) - Bug 1185800 - Add DecoderFlags and SurfaceFlags enum classes and use them instead of imgIContainer flags in all decoder-related code. r=tn (3abdab11f6) - Bug 1196066 (Part 3) - Rewrite nsICODecoder to use StreamingLexer. r=tn (e2ba590c9d) - Bug 1196066 (Part 4) - Enable the ICOMultiChunk test, which now passes. r=tn (9e02611959) - Bug 1124084 - Flip on downscale-during-decode everywhere. r=tn (bd9deff966) - Bug 1160801 - Treat invalid GIF disposal methods as DisposalMethod::NOT_SPECIFIED. r=jrmuizel (e26feaf8fb) - Bug 1201796 (Part 1) - Treat ICOs with wrong widths and heights as corrupt. r=tn (322ba20808) - Bug 1201796 (Part 2) - Add GetFrameAtSize() to support downscale-during-decode for GetFrame() use cases. r=tn (92f5d3a0a7) - Bug 1194906 - Replace 'NS_ENSURE_TRUE(BadImage(..))' warnings with more useful messages. r=tn (cc3b368673) - Bug 1201796 (Part 3) - Enable downscale-during-decode for imgITools::EncodeScaledImage(). r=tn (e2cdb5b520) - Bug 1194472 - Correctly fetch compositor backend in WebGLContext. r=jgilbert (0092052dfc) - Bug 1161913 - Part 1 - Add invalidation state for CaptureStream to Canvas and Contexts. r=mt (0377d6bbe7) - Bug 1168075 - Fix CanvasCaptureMediaStream build fail for bluetooth2. r=pehrsons (53c67c0056) - Bug 1176363 - Part 1: Make a raw copy of each Canvas::CaptureStream frame. r=mattwoodrow (a5df5793d6) - Bug 1194575 - Rename RecoverFromLossOfFrames() to RecoverFromInvalidFrames() to better reflect its role. r=tn (baa6455e79) - Bug 1146663 (Part 1) - Remove HQ scaling, which is now dead code. r=tn (efaddadea0) - Bug 1146663 (Part 2) - Remove the concept of lifetimes from the SurfaceCache. r=dholbert (ab9862d7ee) - Bug 1146663 (Part 3) - Make it impossible to deoptimize imgFrames. r=tn (19e2f1b370) - Bug 1201763 - Add downscale-during-decode support for the ICON decoder. r=tn (33a2b95e5c) - Bug 1194058 (Part 1) - Add Deinterlacer to allow Downscaler to work with interlaced images. r=tn (f7c57b7a8e) - Bug 1194058 (Part 2) - Add downscale-during-decode support for the GIF decoder. r=tn (85622f9d55) - Bug 1201796 (Part 4) - Add downscale-during-decode support for the ICO decoder. r=tn (d09d18b0d9) - Bug 1146663 (Part 4) - Make all RasterImages support downscale-during-decode. r=tn (264642a895) - Bug 1146663 (Part 5) - Require that all image decoders support downscale-during-decode. r=tn (79ad99885d) - Bug 1206836 - When downscaling ICOs, downscale the AND mask as well. r=tn a=KWierso (08ec3d092b) - missing bit of Bug 1138293 - Use malloc/free/realloc/calloc (eb8e5e1b9c) - missing bit of Bug 1146663 (Part 3) - Make it impossible to deoptimize imgFrames. (233befe48f) - Bug 1208935 - Move Deinterlacer to a standalone file. r=seth (b50322abc286) --- b2g/app/b2g.js | 2 +- dom/canvas/CanvasRenderingContext2D.cpp | 22 +- dom/canvas/CanvasRenderingContext2D.h | 9 + dom/canvas/WebGLContext.cpp | 24 +- dom/canvas/WebGLContext.h | 7 + .../nsICanvasRenderingContextInternal.h | 7 + dom/html/HTMLCanvasElement.cpp | 15 + dom/html/HTMLCanvasElement.h | 7 + dom/media/CanvasCaptureMediaStream.cpp | 40 +- dom/media/CanvasCaptureMediaStream.h | 5 +- gfx/thebes/gfxPrefs.h | 5 +- image/ClippedImage.cpp | 10 + image/ClippedImage.h | 4 + image/DecodePool.cpp | 24 +- image/Decoder.cpp | 41 +- image/Decoder.h | 80 +- image/DecoderFactory.cpp | 40 +- image/DecoderFactory.h | 41 +- image/DecoderFlags.h | 42 + image/Deinterlacer.cpp | 45 + image/Deinterlacer.h | 50 ++ image/Downscaler.cpp | 2 + image/Downscaler.h | 1 + image/DynamicImage.cpp | 14 +- image/FrameAnimator.cpp | 6 +- image/FrozenImage.cpp | 8 + image/FrozenImage.h | 4 + image/Image.h | 11 +- image/ImageFactory.cpp | 47 +- image/ImageOps.cpp | 4 +- image/ImageWrapper.cpp | 8 + image/OrientedImage.cpp | 10 + image/OrientedImage.h | 4 + image/RasterImage.cpp | 486 +++------- image/RasterImage.h | 64 +- image/SurfaceCache.cpp | 80 +- image/SurfaceCache.h | 111 ++- image/SurfaceFlags.h | 56 ++ image/VectorImage.cpp | 46 +- image/decoders/nsBMPDecoder.cpp | 14 - image/decoders/nsBMPDecoder.h | 6 +- image/decoders/nsGIFDecoder2.cpp | 165 +++- image/decoders/nsGIFDecoder2.h | 6 +- image/decoders/nsICODecoder.cpp | 832 ++++++++++-------- image/decoders/nsICODecoder.h | 106 ++- image/decoders/nsIconDecoder.cpp | 101 ++- image/decoders/nsIconDecoder.h | 6 +- image/decoders/nsJPEGDecoder.cpp | 16 +- image/decoders/nsJPEGDecoder.h | 5 - image/decoders/nsPNGDecoder.cpp | 18 +- image/decoders/nsPNGDecoder.h | 4 - image/imgFrame.cpp | 90 +- image/imgFrame.h | 4 +- image/imgIContainer.idl | 17 +- image/imgLoader.cpp | 5 +- image/imgTools.cpp | 36 +- image/moz.build | 2 + image/test/crashtests/crashtests.list | 12 +- .../crashtests/invalid-disposal-method-1.gif | Bin 0 -> 167 bytes .../crashtests/invalid-disposal-method-2.gif | Bin 0 -> 167 bytes .../crashtests/invalid-disposal-method-3.gif | Bin 0 -> 167 bytes .../invalid_ico_height.ico | Bin .../invalid_ico_width.ico | Bin image/test/gtest/TestDecoders.cpp | 3 +- image/test/gtest/TestMetadata.cpp | 6 +- .../test/mochitest/test_has_transparency.html | 5 +- image/test/reftest/downscaling/reftest.list | 17 +- image/test/reftest/ico/cur/pointer.cur | Bin 13942 -> 4286 bytes .../ico/ico-bmp-corrupted/reftest.list | 5 - image/test/reftest/ico/ico-mixed/reftest.list | 13 +- .../reftest/ico/ico-png/transparent-png.ico | Bin 3084 -> 1150 bytes image/test/unit/image1png16x16.jpg | Bin 1078 -> 1051 bytes image/test/unit/image2jpg16x16.png | Bin 948 -> 950 bytes image/test/unit/test_imgtools.js | 12 +- layout/tools/reftest/reftest-preferences.js | 4 +- modules/libpref/init/all.js | 12 +- .../favicons/expected-favicon-big32.jpg.png | Bin 948 -> 950 bytes .../favicons/expected-favicon-big64.png.png | Bin 868 -> 979 bytes 78 files changed, 1620 insertions(+), 1384 deletions(-) create mode 100644 image/DecoderFlags.h create mode 100644 image/Deinterlacer.cpp create mode 100644 image/Deinterlacer.h create mode 100644 image/SurfaceFlags.h create mode 100644 image/test/crashtests/invalid-disposal-method-1.gif create mode 100644 image/test/crashtests/invalid-disposal-method-2.gif create mode 100644 image/test/crashtests/invalid-disposal-method-3.gif rename image/test/{reftest/ico/ico-bmp-corrupted => crashtests}/invalid_ico_height.ico (100%) rename image/test/{reftest/ico/ico-bmp-corrupted => crashtests}/invalid_ico_width.ico (100%) diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 5cad275a66..909d5a43f2 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -62,7 +62,6 @@ pref("browser.cache.memory_limit", 2048); // 2 MB /* image cache prefs */ pref("image.cache.size", 1048576); // bytes -pref("image.high_quality_downscaling.enabled", false); pref("canvas.image.cache.limit", 20971520); // 20 MB /* offline cache prefs */ @@ -326,6 +325,7 @@ pref("media.gonk.enabled", true); pref("media.video-queue.default-size", 3); // optimize images' memory usage +pref("image.downscale-during-decode.enabled", true); pref("image.decode-only-on-draw.enabled", true); pref("image.mem.allow_locking_in_content_processes", false); /* don't allow image locking */ // Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller. diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 712a1c2f23..e1cc382ba5 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -949,7 +949,9 @@ CanvasRenderingContext2D::CanvasRenderingContext2D() , mIPC(false) , mDrawObserver(nullptr) , mIsEntireFrameInvalid(false) - , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) + , mPredictManyRedrawCalls(false) + , mIsCapturedFrameInvalid(false) + , mPathTransformWillUpdate(false) , mInvalidateCount(0) { sNumLivingContexts++; @@ -1054,6 +1056,7 @@ CanvasRenderingContext2D::Reset() // no longer be valid. mIsEntireFrameInvalid = false; mPredictManyRedrawCalls = false; + mIsCapturedFrameInvalid = false; return NS_OK; } @@ -1112,6 +1115,8 @@ CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& a nsresult CanvasRenderingContext2D::Redraw() { + mIsCapturedFrameInvalid = true; + if (mIsEntireFrameInvalid) { return NS_OK; } @@ -1133,6 +1138,8 @@ CanvasRenderingContext2D::Redraw() void CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) { + mIsCapturedFrameInvalid = true; + ++mInvalidateCount; if (mIsEntireFrameInvalid) { @@ -1170,6 +1177,8 @@ CanvasRenderingContext2D::DidRefresh() void CanvasRenderingContext2D::RedrawUser(const gfxRect& r) { + mIsCapturedFrameInvalid = true; + if (mIsEntireFrameInvalid) { ++mInvalidateCount; return; @@ -5710,6 +5719,17 @@ CanvasRenderingContext2D::MarkContextClean() mInvalidateCount = 0; } +void +CanvasRenderingContext2D::MarkContextCleanForFrameCapture() +{ + mIsCapturedFrameInvalid = false; +} + +bool +CanvasRenderingContext2D::IsContextCleanForFrameCapture() +{ + return !mIsCapturedFrameInvalid; +} bool CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 5aa11746c2..dee05fd671 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -527,6 +527,8 @@ public: LayerManager *aManager) override; virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override; void MarkContextClean() override; + void MarkContextCleanForFrameCapture() override; + bool IsContextCleanForFrameCapture() override; NS_IMETHOD SetIsIPC(bool isIPC) override; // this rect is in canvas device space void Redraw(const mozilla::gfx::Rect &r); @@ -819,6 +821,13 @@ protected: */ bool mPredictManyRedrawCalls; + /** + * Flag to avoid unnecessary surface copies to FrameCaptureListeners in the + * case when the canvas is not currently being drawn into and not rendered + * but canvas capturing is still ongoing. + */ + bool mIsCapturedFrameInvalid; + // This is stored after GetThebesSurface has been called once to avoid // excessive ThebesSurface initialization overhead. nsRefPtr mThebesSurface; diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index 2335603ce6..4e3ad24f86 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -220,6 +220,7 @@ WebGLContext::WebGLContext() { mGeneration = 0; mInvalidated = false; + mCapturedFrameInvalidated = false; mShouldPresent = true; mResetLayer = true; mOptionsFrozen = false; @@ -413,10 +414,12 @@ WebGLContext::DestroyResourcesAndContext() void WebGLContext::Invalidate() { - if (mInvalidated) + if (!mCanvasElement) return; - if (!mCanvasElement) + mCapturedFrameInvalidated = true; + + if (mInvalidated) return; nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); @@ -669,6 +672,7 @@ PopulateCapFallbackQueue(const SurfaceCaps& baseCaps, static bool CreateOffscreen(GLContext* gl, const WebGLContextOptions& options, const nsCOMPtr& gfxInfo, WebGLContext* webgl, + layers::LayersBackend layersBackend, layers::ISurfaceAllocator* surfAllocator) { SurfaceCaps baseCaps; @@ -686,7 +690,7 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options, if (gl->IsANGLE() || (gl->GetContextType() == GLContextType::GLX && - gfxPlatform::GetPlatform()->GetCompositorBackend() == LayersBackend::LAYERS_OPENGL)) + layersBackend == LayersBackend::LAYERS_OPENGL)) { // We can't use no-alpha formats on ANGLE yet because of: // https://code.google.com/p/angleproject/issues/detail?id=764 @@ -760,7 +764,8 @@ WebGLContext::CreateOffscreenGL(bool forceEnabled) if (!gl) break; - if (!CreateOffscreen(gl, mOptions, gfxInfo, this, surfAllocator)) + if (!CreateOffscreen(gl, mOptions, gfxInfo, this, + GetCompositorBackendType(), surfAllocator)) break; if (!InitAndValidateGL()) @@ -1278,6 +1283,17 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder, return canvasLayer.forget(); } +layers::LayersBackend +WebGLContext::GetCompositorBackendType() const +{ + nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc()); + if (docWidget) { + layers::LayerManager* layerManager = docWidget->GetLayerManager(); + return layerManager->GetCompositorBackendType(); + } + return LayersBackend::LAYERS_NONE; +} + void WebGLContext::GetContextAttributes(dom::Nullable& retval) { diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index 7bd055474a..845d813df2 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -315,6 +315,10 @@ public: // contents of the buffer. void MarkContextClean() override { mInvalidated = false; } + void MarkContextCleanForFrameCapture() override { mCapturedFrameInvalidated = false; } + + bool IsContextCleanForFrameCapture() override { return !mCapturedFrameInvalidated; } + gl::GLContext* GL() const { return gl; } bool IsPremultAlpha() const { return mOptions.premultipliedAlpha; } @@ -362,6 +366,8 @@ public: return IsContextLost() ? 0 : mHeight; } + layers::LayersBackend GetCompositorBackendType() const; + void GetContextAttributes(dom::Nullable& retval); @@ -1031,6 +1037,7 @@ protected: WebGLContextOptions mOptions; bool mInvalidated; + bool mCapturedFrameInvalidated; bool mResetLayer; bool mOptionsFrozen; bool mMinCapability; diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h index 801fd2651b..8849756816 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.h +++ b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -134,6 +134,13 @@ public: virtual void MarkContextClean() = 0; + // Called when a frame is captured. + virtual void MarkContextCleanForFrameCapture() = 0; + + // Whether the context is clean or has been invalidated since the last frame + // was captured. + virtual bool IsContextCleanForFrameCapture() = 0; + // Redraw the dirty rectangle of this canvas. NS_IMETHOD Redraw(const gfxRect &dirty) = 0; diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index 2186ece72b..8c226b62bd 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -1033,6 +1033,21 @@ HTMLCanvasElement::MarkContextClean() mCurrentContext->MarkContextClean(); } +void +HTMLCanvasElement::MarkContextCleanForFrameCapture() +{ + if (!mCurrentContext) + return; + + mCurrentContext->MarkContextCleanForFrameCapture(); +} + +bool +HTMLCanvasElement::IsContextCleanForFrameCapture() +{ + return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture(); +} + already_AddRefed HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha) { diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index b6387c8763..2d90da704b 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -214,6 +214,13 @@ public: // take a snapshot of the canvas that needs to be "live" (e.g. -moz-element). void MarkContextClean(); + // Call this after capturing a frame, so we can avoid unnecessary surface + // copies for future frames when no drawing has occurred. + void MarkContextCleanForFrameCapture(); + + // Starts returning false when something is drawn. + bool IsContextCleanForFrameCapture(); + nsresult GetContext(const nsAString& aContextId, nsISupports** aContext); protected: diff --git a/dom/media/CanvasCaptureMediaStream.cpp b/dom/media/CanvasCaptureMediaStream.cpp index d8f06b790e..c46134e78a 100644 --- a/dom/media/CanvasCaptureMediaStream.cpp +++ b/dom/media/CanvasCaptureMediaStream.cpp @@ -8,9 +8,10 @@ #include "gfxPlatform.h" #include "ImageContainer.h" #include "MediaStreamGraph.h" -#include "mozilla/Mutex.h" #include "mozilla/dom/CanvasCaptureMediaStreamBinding.h" #include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Mutex.h" #include "nsContentUtils.h" using namespace mozilla::layers; @@ -204,15 +205,42 @@ public: return NS_ERROR_FAILURE; } - RefPtr opt = gfxPlatform::GetPlatform() - ->ScreenReferenceDrawTarget()->OptimizeSourceSurface(snapshot); - if (!opt) { + RefPtr data = snapshot->GetDataSurface(); + if (!data) { return NS_ERROR_FAILURE; } + RefPtr copy; + + { + DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ); + if (!read.IsMapped()) { + return NS_ERROR_FAILURE; + } + + copy = Factory::CreateDataSourceSurfaceWithStride(data->GetSize(), + data->GetFormat(), + read.GetStride()); + if (!copy) { + return NS_ERROR_FAILURE; + } + + DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE); + if (!write.IsMapped()) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(read.GetStride() == write.GetStride()); + MOZ_ASSERT(data->GetSize() == copy->GetSize()); + MOZ_ASSERT(data->GetFormat() == copy->GetFormat()); + + memcpy(write.GetData(), read.GetData(), + write.GetStride() * copy->GetSize().height); + } + CairoImage::Data imageData; - imageData.mSize = opt->GetSize(); - imageData.mSourceSurface = opt; + imageData.mSize = copy->GetSize(); + imageData.mSourceSurface = copy; RefPtr image = new layers::CairoImage(); image->SetData(imageData); diff --git a/dom/media/CanvasCaptureMediaStream.h b/dom/media/CanvasCaptureMediaStream.h index bb4aa13ba5..049f2827a8 100644 --- a/dom/media/CanvasCaptureMediaStream.h +++ b/dom/media/CanvasCaptureMediaStream.h @@ -6,6 +6,9 @@ #ifndef mozilla_dom_CanvasCaptureMediaStream_h_ #define mozilla_dom_CanvasCaptureMediaStream_h_ +#include "DOMMediaStream.h" +#include "StreamBuffer.h" + namespace mozilla { class DOMMediaStream; class MediaStreamListener; @@ -13,7 +16,7 @@ class SourceMediaStream; namespace layers { class Image; -} +} // namespace layers namespace dom { class CanvasCaptureMediaStream; diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 9d99f70485..4ce72f80bb 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -272,10 +272,7 @@ private: DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024); DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500); DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false); - DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, false); - DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false); - DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000); - DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520); + DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true); DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000); DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000); DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false); diff --git a/image/ClippedImage.cpp b/image/ClippedImage.cpp index eeb71263f7..de24f49d34 100644 --- a/image/ClippedImage.cpp +++ b/image/ClippedImage.cpp @@ -220,6 +220,16 @@ ClippedImage::GetFrame(uint32_t aWhichFrame, return GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags); } +NS_IMETHODIMP_(already_AddRefed) +ClippedImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + // XXX(seth): It'd be nice to support downscale-during-decode for this case, + // but right now we just fall back to the intrinsic size. + return GetFrame(aWhichFrame, aFlags); +} + already_AddRefed ClippedImage::GetFrameInternal(const nsIntSize& aSize, const Maybe& aSVGContext, diff --git a/image/ClippedImage.h b/image/ClippedImage.h index 88f7d476f3..c4dba021f7 100644 --- a/image/ClippedImage.h +++ b/image/ClippedImage.h @@ -37,6 +37,10 @@ public: NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override; NS_IMETHOD_(already_AddRefed) GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetFrameAtSize(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) override; NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags) override; NS_IMETHOD_(already_AddRefed) diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp index 209b481bf0..bfd7eb373f 100644 --- a/image/DecodePool.cpp +++ b/image/DecodePool.cpp @@ -47,35 +47,37 @@ public: static void Dispatch(RasterImage* aImage, Progress aProgress, const nsIntRect& aInvalidRect, - uint32_t aFlags) + SurfaceFlags aSurfaceFlags) { MOZ_ASSERT(aImage); nsCOMPtr worker = - new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags); + new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aSurfaceFlags); NS_DispatchToMainThread(worker); } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); - mImage->NotifyProgress(mProgress, mInvalidRect, mFlags); + mImage->NotifyProgress(mProgress, mInvalidRect, mSurfaceFlags); return NS_OK; } private: - NotifyProgressWorker(RasterImage* aImage, Progress aProgress, - const nsIntRect& aInvalidRect, uint32_t aFlags) + NotifyProgressWorker(RasterImage* aImage, + Progress aProgress, + const nsIntRect& aInvalidRect, + SurfaceFlags aSurfaceFlags) : mImage(aImage) , mProgress(aProgress) , mInvalidRect(aInvalidRect) - , mFlags(aFlags) + , mSurfaceFlags(aSurfaceFlags) { } nsRefPtr mImage; const Progress mProgress; const nsIntRect mInvalidRect; - const uint32_t mFlags; + const SurfaceFlags mSurfaceFlags; }; class NotifyDecodeCompleteWorker : public nsRunnable @@ -470,17 +472,17 @@ DecodePool::NotifyProgress(Decoder* aDecoder) MOZ_ASSERT(aDecoder); if (!NS_IsMainThread() || - (aDecoder->GetFlags() & imgIContainer::FLAG_ASYNC_NOTIFY)) { + (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) { NotifyProgressWorker::Dispatch(aDecoder->GetImage(), aDecoder->TakeProgress(), aDecoder->TakeInvalidRect(), - aDecoder->GetDecodeFlags()); + aDecoder->GetSurfaceFlags()); return; } aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(), aDecoder->TakeInvalidRect(), - aDecoder->GetDecodeFlags()); + aDecoder->GetSurfaceFlags()); } void @@ -489,7 +491,7 @@ DecodePool::NotifyDecodeComplete(Decoder* aDecoder) MOZ_ASSERT(aDecoder); if (!NS_IsMainThread() || - (aDecoder->GetFlags() & imgIContainer::FLAG_ASYNC_NOTIFY)) { + (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) { NotifyDecodeCompleteWorker::Dispatch(aDecoder); return; } diff --git a/image/Decoder.cpp b/image/Decoder.cpp index b6199bc62c..21c35044aa 100644 --- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -31,13 +31,11 @@ Decoder::Decoder(RasterImage* aImage) , mFrameCount(0) , mFailCode(NS_OK) , mChunkCount(0) - , mFlags(0) + , mDecoderFlags(DefaultDecoderFlags()) + , mSurfaceFlags(DefaultSurfaceFlags()) , mBytesDecoded(0) , mInitialized(false) , mMetadataDecode(false) - , mSendPartialInvalidations(false) - , mImageIsTransient(false) - , mFirstFrameDecode(false) , mInFrame(false) , mDataDone(false) , mDecodeDone(false) @@ -238,12 +236,28 @@ Decoder::CompleteDecode() // If this image wasn't animated and isn't a transient image, mark its frame // as optimizable. We don't support optimizing animated images and // optimizing transient images isn't worth it. - if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) { + if (!HasAnimation() && + !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) && + mCurrentFrame) { mCurrentFrame->SetOptimizable(); } } } +nsresult +Decoder::SetTargetSize(const nsIntSize& aSize) +{ + // Make sure the size is reasonable. + if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) { + return NS_ERROR_FAILURE; + } + + // Create a downscaler that we'll filter our output through. + mDownscaler.emplace(aSize); + + return NS_OK; +} + nsresult Decoder::AllocateFrame(uint32_t aFrameNum, const nsIntSize& aTargetSize, @@ -252,8 +266,8 @@ Decoder::AllocateFrame(uint32_t aFrameNum, uint8_t aPaletteDepth) { mCurrentFrame = AllocateFrameInternal(aFrameNum, aTargetSize, aFrameRect, - GetDecodeFlags(), aFormat, - aPaletteDepth, mCurrentFrame.get()); + aFormat, aPaletteDepth, + mCurrentFrame.get()); if (mCurrentFrame) { // Gather the raw pointers the decoders will use. @@ -279,7 +293,6 @@ RawAccessFrameRef Decoder::AllocateFrameInternal(uint32_t aFrameNum, const nsIntSize& aTargetSize, const nsIntRect& aFrameRect, - uint32_t aDecodeFlags, SurfaceFormat aFormat, uint8_t aPaletteDepth, imgFrame* aPreviousFrame) @@ -307,8 +320,7 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum, } nsRefPtr frame = new imgFrame(); - bool nonPremult = - aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA; + bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA); if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat, aPaletteDepth, nonPremult))) { NS_WARNING("imgFrame::Init should succeed"); @@ -325,9 +337,8 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum, InsertOutcome outcome = SurfaceCache::Insert(frame, ImageKey(mImage.get()), RasterSurfaceKey(aTargetSize, - aDecodeFlags, - aFrameNum), - Lifetime::Persistent); + mSurfaceFlags, + aFrameNum)); if (outcome == InsertOutcome::FAILURE) { // We couldn't insert the surface, almost certainly due to low memory. We // treat this as a permanent error to help the system recover; otherwise, @@ -440,7 +451,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */, // If we're not sending partial invalidations, then we send an invalidation // here when the first frame is complete. - if (!mSendPartialInvalidations && !HasAnimation()) { + if (!ShouldSendPartialInvalidations() && !HasAnimation()) { mInvalidRect.UnionRect(mInvalidRect, gfx::IntRect(gfx::IntPoint(0, 0), GetSize())); } @@ -457,7 +468,7 @@ Decoder::PostInvalidation(const nsIntRect& aRect, // Record this invalidation, unless we're not sending partial invalidations // or we're past the first frame. - if (mSendPartialInvalidations && !HasAnimation()) { + if (ShouldSendPartialInvalidations() && !HasAnimation()) { mInvalidRect.UnionRect(mInvalidRect, aRect); mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect)); } diff --git a/image/Decoder.h b/image/Decoder.h index 7e3d8d896a..ccd3c4e004 100644 --- a/image/Decoder.h +++ b/image/Decoder.h @@ -10,9 +10,12 @@ #include "RasterImage.h" #include "mozilla/RefPtr.h" #include "DecodePool.h" +#include "DecoderFlags.h" +#include "Downscaler.h" #include "ImageMetadata.h" #include "Orientation.h" #include "SourceBuffer.h" +#include "SurfaceFlags.h" namespace mozilla { @@ -110,19 +113,14 @@ public: * If this decoder supports downscale-during-decode, sets the target size that * this image should be decoded to. * - * If this decoder *doesn't* support downscale-during-decode, returns - * NS_ERROR_NOT_AVAILABLE. If the provided size is unacceptable, returns - * another error. + * If the provided size is unacceptable, an error is returned. * * Returning NS_OK from this method is a promise that the decoder will decode * the image to the requested target size unless it encounters an error. * * This must be called before Init() is called. */ - virtual nsresult SetTargetSize(const nsIntSize& aSize) - { - return NS_ERROR_NOT_AVAILABLE; - } + nsresult SetTargetSize(const nsIntSize& aSize); /** * Set the requested sample size for this decoder. Used to implement the @@ -140,24 +138,6 @@ public: */ virtual void SetResolution(const gfx::IntSize& aResolution) { } - /** - * Set whether should send partial invalidations. - * - * If @aSend is true, we'll send partial invalidations when decoding the first - * frame of the image, so image notifications observers will be able to - * gradually draw in the image as it downloads. - * - * If @aSend is false (the default), we'll only send an invalidation when we - * complete the first frame. - * - * This must be called before Init() is called. - */ - void SetSendPartialInvalidations(bool aSend) - { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mSendPartialInvalidations = aSend; - } - /** * Set an iterator to the SourceBuffer which will feed data to this decoder. * @@ -174,27 +154,21 @@ public: } /** - * Set whether this decoder is associated with a transient image. The decoder - * may choose to avoid certain optimizations that don't pay off for - * short-lived images in this case. + * Should this decoder send partial invalidations? */ - void SetImageIsTransient(bool aIsTransient) + bool ShouldSendPartialInvalidations() const { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mImageIsTransient = aIsTransient; + return !(mDecoderFlags & DecoderFlags::IS_REDECODE); } /** - * Set whether we should stop decoding after the first frame. + * Should we stop decoding after the first frame? */ - void SetIsFirstFrameDecode() + bool IsFirstFrameDecode() const { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mFirstFrameDecode = true; + return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY); } - bool IsFirstFrameDecode() const { return mFirstFrameDecode; } - size_t BytesDecoded() const { return mBytesDecoded; } // The amount of time we've spent inside Write() so far for this decoder. @@ -258,9 +232,26 @@ public: SEQUENTIAL // decode to final image immediately }; - void SetFlags(uint32_t aFlags) { mFlags = aFlags; } - uint32_t GetFlags() const { return mFlags; } - uint32_t GetDecodeFlags() const { return DecodeFlags(mFlags); } + /** + * Get or set the DecoderFlags that influence the behavior of this decoder. + */ + void SetDecoderFlags(DecoderFlags aDecoderFlags) + { + MOZ_ASSERT(!mInitialized); + mDecoderFlags = aDecoderFlags; + } + DecoderFlags GetDecoderFlags() const { return mDecoderFlags; } + + /** + * Get or set the SurfaceFlags that select the kind of output this decoder + * will produce. + */ + void SetSurfaceFlags(SurfaceFlags aSurfaceFlags) + { + MOZ_ASSERT(!mInitialized); + mSurfaceFlags = aSurfaceFlags; + } + SurfaceFlags GetSurfaceFlags() const { return mSurfaceFlags; } bool HasSize() const { return mImageMetadata.HasSize(); } @@ -408,12 +399,13 @@ protected: RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum, const nsIntSize& aTargetSize, const nsIntRect& aFrameRect, - uint32_t aDecodeFlags, gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth, imgFrame* aPreviousFrame); protected: + Maybe mDownscaler; + uint8_t* mImageData; // Pointer to image data in either Cairo or 8bit format uint32_t mImageDataLength; uint32_t* mColormap; // Current colormap to be used in Cairo format @@ -435,14 +427,12 @@ private: TimeDuration mDecodeTime; uint32_t mChunkCount; - uint32_t mFlags; + DecoderFlags mDecoderFlags; + SurfaceFlags mSurfaceFlags; size_t mBytesDecoded; bool mInitialized : 1; bool mMetadataDecode : 1; - bool mSendPartialInvalidations : 1; - bool mImageIsTransient : 1; - bool mFirstFrameDecode : 1; bool mInFrame : 1; bool mDataDone : 1; bool mDecodeDone : 1; diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 79c2690e34..bad977cb02 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -134,34 +134,30 @@ DecoderFactory::CreateDecoder(DecoderType aType, RasterImage* aImage, SourceBuffer* aSourceBuffer, const Maybe& aTargetSize, - uint32_t aFlags, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags, int aSampleSize, - const IntSize& aResolution, - bool aIsRedecode, - bool aImageIsTransient) + const IntSize& aResolution) { if (aType == DecoderType::UNKNOWN) { return nullptr; } - nsRefPtr decoder = GetDecoder(aType, aImage, aIsRedecode); + nsRefPtr decoder = + GetDecoder(aType, aImage, bool(aDecoderFlags & DecoderFlags::IS_REDECODE)); MOZ_ASSERT(decoder, "Should have a decoder now"); // Initialize the decoder. decoder->SetMetadataDecode(false); decoder->SetIterator(aSourceBuffer->Iterator()); - decoder->SetFlags(aFlags); + decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY); + decoder->SetSurfaceFlags(aSurfaceFlags); decoder->SetSampleSize(aSampleSize); decoder->SetResolution(aResolution); - decoder->SetSendPartialInvalidations(!aIsRedecode); - decoder->SetImageIsTransient(aImageIsTransient); - decoder->SetIsFirstFrameDecode(); // Set a target size for downscale-during-decode if applicable. if (aTargetSize) { DebugOnly rv = decoder->SetTargetSize(*aTargetSize); - MOZ_ASSERT(nsresult(rv) != NS_ERROR_NOT_AVAILABLE, - "We're downscale-during-decode but decoder doesn't support it?"); MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?"); } @@ -177,7 +173,8 @@ DecoderFactory::CreateDecoder(DecoderType aType, DecoderFactory::CreateAnimationDecoder(DecoderType aType, RasterImage* aImage, SourceBuffer* aSourceBuffer, - uint32_t aFlags, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags, const IntSize& aResolution) { if (aType == DecoderType::UNKNOWN) { @@ -194,9 +191,9 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType, // Initialize the decoder. decoder->SetMetadataDecode(false); decoder->SetIterator(aSourceBuffer->Iterator()); - decoder->SetFlags(aFlags); + decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE); + decoder->SetSurfaceFlags(aSurfaceFlags); decoder->SetResolution(aResolution); - decoder->SetSendPartialInvalidations(false); decoder->Init(); if (NS_FAILED(decoder->GetDecoderError())) { @@ -238,7 +235,7 @@ DecoderFactory::CreateMetadataDecoder(DecoderType aType, /* static */ already_AddRefed DecoderFactory::CreateAnonymousDecoder(DecoderType aType, SourceBuffer* aSourceBuffer, - uint32_t aFlags) + SurfaceFlags aSurfaceFlags) { if (aType == DecoderType::UNKNOWN) { return nullptr; @@ -251,15 +248,20 @@ DecoderFactory::CreateAnonymousDecoder(DecoderType aType, // Initialize the decoder. decoder->SetMetadataDecode(false); decoder->SetIterator(aSourceBuffer->Iterator()); - decoder->SetFlags(aFlags); - decoder->SetImageIsTransient(true); + + // Anonymous decoders are always transient; we don't want to optimize surfaces + // or do any other expensive work that might be wasted. + DecoderFlags decoderFlags = DecoderFlags::IMAGE_IS_TRANSIENT; // Without an image, the decoder can't store anything in the SurfaceCache, so // callers will only be able to retrieve the most recent frame via // Decoder::GetCurrentFrame(). That means that anonymous decoders should // always be first-frame-only decoders, because nobody ever wants the *last* // frame. - decoder->SetIsFirstFrameDecode(); + decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY; + + decoder->SetDecoderFlags(decoderFlags); + decoder->SetSurfaceFlags(aSurfaceFlags); decoder->Init(); if (NS_FAILED(decoder->GetDecoderError())) { @@ -284,7 +286,7 @@ DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType, // Initialize the decoder. decoder->SetMetadataDecode(true); decoder->SetIterator(aSourceBuffer->Iterator()); - decoder->SetIsFirstFrameDecode(); + decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY); decoder->Init(); if (NS_FAILED(decoder->GetDecoderError())) { diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 2fc55b6fb6..415f9fcc5a 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -7,9 +7,12 @@ #ifndef mozilla_image_DecoderFactory_h #define mozilla_image_DecoderFactory_h +#include "DecoderFlags.h" +#include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include "mozilla/gfx/2D.h" #include "nsCOMPtr.h" +#include "SurfaceFlags.h" class nsACString; @@ -20,6 +23,10 @@ class Decoder; class RasterImage; class SourceBuffer; +/** + * The type of decoder; this is usually determined from a MIME type using + * DecoderFactory::GetDecoderType(). + */ enum class DecoderType { PNG, @@ -44,10 +51,6 @@ public: * (If the image *is* animated, only the first frame will be decoded.) The * decoder will send notifications to @aImage. * - * XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of - * @aFlags. This requires changes to the way that decoder flags work, though. - * See bug 1185800. - * * @param aType Which type of decoder to create - JPEG, PNG, etc. * @param aImage The image will own the decoder and which should receive * notifications as decoding progresses. @@ -57,25 +60,23 @@ public: * be scaled to during decoding. It's an error to specify * a target size for a decoder type which doesn't support * downscale-during-decode. - * @param aFlags Flags specifying what type of output the decoder should - * produce; see GetDecodeFlags() in RasterImage.h. + * @param aDecoderFlags Flags specifying the behavior of this decoder. + * @param aSurfaceFlags Flags specifying the type of output this decoder + * should produce. * @param aSampleSize The sample size requested using #-moz-samplesize (or 0 * if none). * @param aResolution The resolution requested using #-moz-resolution (or an * empty rect if none). - * @param aIsRedecode Specify 'true' if this image has been decoded before. - * @param aImageIsTransient Specify 'true' if this image is transient. */ static already_AddRefed CreateDecoder(DecoderType aType, RasterImage* aImage, SourceBuffer* aSourceBuffer, const Maybe& aTargetSize, - uint32_t aFlags, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags, int aSampleSize, - const gfx::IntSize& aResolution, - bool aIsRedecode, - bool aImageIsTransient); + const gfx::IntSize& aResolution); /** * Creates and initializes a decoder for animated images of type @aType. @@ -86,8 +87,9 @@ public: * notifications as decoding progresses. * @param aSourceBuffer The SourceBuffer which the decoder will read its data * from. - * @param aFlags Flags specifying what type of output the decoder should - * produce; see GetDecodeFlags() in RasterImage.h. + * @param aDecoderFlags Flags specifying the behavior of this decoder. + * @param aSurfaceFlags Flags specifying the type of output this decoder + * should produce. * @param aResolution The resolution requested using #-moz-resolution (or an * empty rect if none). */ @@ -95,7 +97,8 @@ public: CreateAnimationDecoder(DecoderType aType, RasterImage* aImage, SourceBuffer* aSourceBuffer, - uint32_t aFlags, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags, const gfx::IntSize& aResolution); /** @@ -128,13 +131,13 @@ public: * @param aType Which type of decoder to create - JPEG, PNG, etc. * @param aSourceBuffer The SourceBuffer which the decoder will read its data * from. - * @param aFlags Flags specifying what type of output the decoder should - * produce; see GetDecodeFlags() in RasterImage.h. + * @param aSurfaceFlags Flags specifying the type of output this decoder + * should produce. */ static already_AddRefed CreateAnonymousDecoder(DecoderType aType, SourceBuffer* aSourceBuffer, - uint32_t aFlags); + SurfaceFlags aSurfaceFlags); /** * Creates and initializes an anonymous metadata decoder (one which isn't @@ -145,8 +148,6 @@ public: * @param aType Which type of decoder to create - JPEG, PNG, etc. * @param aSourceBuffer The SourceBuffer which the decoder will read its data * from. - * @param aFlags Flags specifying what type of output the decoder should - * produce; see GetDecodeFlags() in RasterImage.h. */ static already_AddRefed CreateAnonymousMetadataDecoder(DecoderType aType, diff --git a/image/DecoderFlags.h b/image/DecoderFlags.h new file mode 100644 index 0000000000..c4a4df0e59 --- /dev/null +++ b/image/DecoderFlags.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_image_DecoderFlags_h +#define mozilla_image_DecoderFlags_h + +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace image { + +/** + * Flags that influence decoder behavior. Note that these flags *don't* + * influence the logical content of the surfaces that the decoder generates, so + * they're not in a factor in SurfaceCache lookups and the like. These flags + * instead either influence which surfaces are generated at all or the tune the + * decoder's behavior for a particular scenario. + */ +enum class DecoderFlags : uint8_t +{ + FIRST_FRAME_ONLY = 1 << 0, + IS_REDECODE = 1 << 1, + IMAGE_IS_TRANSIENT = 1 << 2, + ASYNC_NOTIFY = 1 << 3 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags) + +/** + * @return the default set of decode flags. + */ +inline DecoderFlags +DefaultDecoderFlags() +{ + return DecoderFlags(); +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DecoderFlags_h diff --git a/image/Deinterlacer.cpp b/image/Deinterlacer.cpp new file mode 100644 index 0000000000..96a6512c3a --- /dev/null +++ b/image/Deinterlacer.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "Downscaler.h" + +namespace mozilla { +namespace image { + +Deinterlacer::Deinterlacer(const nsIntSize& aImageSize) + : mImageSize(aImageSize) + , mBuffer(MakeUnique(mImageSize.width * + mImageSize.height * + sizeof(uint32_t))) +{ } + +uint32_t +Deinterlacer::RowSize() const +{ + return mImageSize.width * sizeof(uint32_t); +} + +uint8_t* +Deinterlacer::RowBuffer(uint32_t aRow) +{ + uint32_t offset = aRow * RowSize(); + MOZ_ASSERT(offset < mImageSize.width * mImageSize.height * sizeof(uint32_t), + "Row is outside of image"); + return mBuffer.get() + offset; +} + +void +Deinterlacer::PropagatePassToDownscaler(Downscaler& aDownscaler) +{ + for (int32_t row = 0 ; row < mImageSize.height ; ++row) { + memcpy(aDownscaler.RowBuffer(), RowBuffer(row), RowSize()); + aDownscaler.CommitRow(); + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/Deinterlacer.h b/image/Deinterlacer.h new file mode 100644 index 0000000000..74ffd86ea4 --- /dev/null +++ b/image/Deinterlacer.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + + +/** + * Deinterlacer is a utility class to allow Downscaler to work with interlaced + * images. + + * Since Downscaler needs to receive rows in top-to-bottom or + * bottom-to-top order, it can't natively handle interlaced images, in which the + * rows arrive in an interleaved order. Deinterlacer solves this problem by + * acting as an intermediate buffer that records decoded rows. Unlike + * Downscaler, it allows the rows to be written in arbitrary order. After each + * pass, calling PropagatePassToDownscaler() will downscale every buffered row + * in a single operation. The rows remain in the buffer, so rows that were + * written in one pass will be included in subsequent passes. + */ + + +#ifndef mozilla_image_Deinterlacer_h +#define mozilla_image_Deinterlacer_h + +#include "Downscaler.h" + +namespace mozilla { +namespace image { + +class Deinterlacer +{ +public: + explicit Deinterlacer(const nsIntSize& aImageSize); + + uint8_t* RowBuffer(uint32_t aRow); + void PropagatePassToDownscaler(Downscaler& aDownscaler); + +private: + uint32_t RowSize() const; + + nsIntSize mImageSize; + UniquePtr mBuffer; +}; + + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_Deinterlacer_h diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp index 132feb9a31..43541e1d8e 100644 --- a/image/Downscaler.cpp +++ b/image/Downscaler.cpp @@ -287,5 +287,7 @@ Downscaler::DownscaleInputLine() } } + + } // namespace image } // namespace mozilla diff --git a/image/Downscaler.h b/image/Downscaler.h index 3f1365bf18..53dea0f0a2 100644 --- a/image/Downscaler.h +++ b/image/Downscaler.h @@ -162,6 +162,7 @@ public: #endif // MOZ_ENABLE_SKIA + } // namespace image } // namespace mozilla diff --git a/image/DynamicImage.cpp b/image/DynamicImage.cpp index 52dcf3316a..87608f41bb 100644 --- a/image/DynamicImage.cpp +++ b/image/DynamicImage.cpp @@ -168,10 +168,18 @@ DynamicImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) { gfxIntSize size(mDrawable->Size()); + return GetFrameAtSize(IntSize(size.width, size.height), + aWhichFrame, + aFlags); +} +NS_IMETHODIMP_(already_AddRefed) +DynamicImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ RefPtr dt = gfxPlatform::GetPlatform()-> - CreateOffscreenContentDrawTarget(IntSize(size.width, size.height), - SurfaceFormat::B8G8R8A8); + CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8); if (!dt) { gfxWarning() << "DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget"; @@ -179,7 +187,7 @@ DynamicImage::GetFrame(uint32_t aWhichFrame, } nsRefPtr context = new gfxContext(dt); - auto result = Draw(context, size, ImageRegion::Create(size), + auto result = Draw(context, aSize, ImageRegion::Create(aSize), aWhichFrame, GraphicsFilter::FILTER_NEAREST, Nothing(), aFlags); diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index 316e2e1151..a77a9b667c 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -281,7 +281,7 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum) LookupResult result = SurfaceCache::Lookup(ImageKey(mImage), RasterSurfaceKey(mSize, - 0, // Default decode flags. + DefaultSurfaceFlags(), aFrameNum)); MOZ_ASSERT(!result || !result.DrawableRef()->GetIsPaletted(), "About to return a paletted frame"); @@ -332,7 +332,7 @@ DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface, { // Concoct a SurfaceKey for this surface. SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(), - imgIContainer::DECODE_FLAGS_DEFAULT, + DefaultSurfaceFlags(), /* aFrameNum = */ 0); // Create a counter for this surface. @@ -374,7 +374,7 @@ FrameAnimator::GetRawFrame(uint32_t aFrameNum) const LookupResult result = SurfaceCache::Lookup(ImageKey(mImage), RasterSurfaceKey(mSize, - 0, // Default decode flags. + DefaultSurfaceFlags(), aFrameNum)); return result ? result.DrawableRef()->RawAccessRef() : RawAccessFrameRef(); diff --git a/image/FrozenImage.cpp b/image/FrozenImage.cpp index 628e33d74d..cf0dbf72b5 100644 --- a/image/FrozenImage.cpp +++ b/image/FrozenImage.cpp @@ -44,6 +44,14 @@ FrozenImage::GetFrame(uint32_t aWhichFrame, return InnerImage()->GetFrame(FRAME_FIRST, aFlags); } +NS_IMETHODIMP_(already_AddRefed) +FrozenImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return InnerImage()->GetFrameAtSize(aSize, FRAME_FIRST, aFlags); +} + NS_IMETHODIMP_(bool) FrozenImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) { diff --git a/image/FrozenImage.h b/image/FrozenImage.h index ee6e4bd631..ff54ce666d 100644 --- a/image/FrozenImage.h +++ b/image/FrozenImage.h @@ -37,6 +37,10 @@ public: NS_IMETHOD GetAnimated(bool* aAnimated) override; NS_IMETHOD_(already_AddRefed) GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetFrameAtSize(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) override; NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags) override; NS_IMETHOD_(already_AddRefed) diff --git a/image/Image.h b/image/Image.h index 67ac064f4b..5e5c325e67 100644 --- a/image/Image.h +++ b/image/Image.h @@ -147,16 +147,14 @@ public: * flag is set, INIT_FLAG_DISCARDABLE and INIT_FLAG_DECODE_ONLY_ON_DRAW must * not be set. * - * INIT_FLAG_DOWNSCALE_DURING_DECODE: The container should attempt to - * downscale images during decoding instead of decoding them to their - * intrinsic size. + * INIT_FLAG_SYNC_LOAD: The container is being loaded synchronously, so + * it should avoid relying on async workers to get the container ready. */ static const uint32_t INIT_FLAG_NONE = 0x0; static const uint32_t INIT_FLAG_DISCARDABLE = 0x1; static const uint32_t INIT_FLAG_DECODE_IMMEDIATELY = 0x2; static const uint32_t INIT_FLAG_TRANSIENT = 0x4; - static const uint32_t INIT_FLAG_DOWNSCALE_DURING_DECODE = 0x8; - static const uint32_t INIT_FLAG_SYNC_LOAD = 0x10; + static const uint32_t INIT_FLAG_SYNC_LOAD = 0x8; virtual already_AddRefed GetProgressTracker() = 0; virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {} @@ -214,8 +212,7 @@ public: bool aLastPart) = 0; /** - * Called when the SurfaceCache discards a persistent surface belonging to - * this image. + * Called when the SurfaceCache discards a surface belonging to this image. */ virtual void OnSurfaceDiscarded() = 0; diff --git a/image/ImageFactory.cpp b/image/ImageFactory.cpp index eabeaf29f3..fd053383b4 100644 --- a/image/ImageFactory.cpp +++ b/image/ImageFactory.cpp @@ -32,15 +32,6 @@ namespace image { ImageFactory::Initialize() { } -static bool -ShouldDownscaleDuringDecode(const nsCString& aMimeType) -{ - DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get()); - return type == DecoderType::JPEG || - type == DecoderType::PNG || - type == DecoderType::BMP; -} - static uint32_t ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart) { @@ -49,7 +40,6 @@ ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart) // We default to the static globals. bool isDiscardable = gfxPrefs::ImageMemDiscardable(); bool doDecodeImmediately = gfxPrefs::ImageDecodeImmediatelyEnabled(); - bool doDownscaleDuringDecode = gfxPrefs::ImageDownscaleDuringDecodeEnabled(); // We want UI to be as snappy as possible and not to flicker. Disable // discarding for chrome URLS. @@ -66,15 +56,10 @@ ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart) isDiscardable = false; } - // Downscale-during-decode is only enabled for certain content types. - if (doDownscaleDuringDecode && !ShouldDownscaleDuringDecode(aMimeType)) { - doDownscaleDuringDecode = false; - } - // For multipart/x-mixed-replace, we basically want a direct channel to the // decoder. Disable everything for this case. if (isMultiPart) { - isDiscardable = doDownscaleDuringDecode = false; + isDiscardable = false; } // We have all the information we need. @@ -88,9 +73,6 @@ ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart) if (isMultiPart) { imageFlags |= Image::INIT_FLAG_TRANSIENT; } - if (doDownscaleDuringDecode) { - imageFlags |= Image::INIT_FLAG_DOWNSCALE_DURING_DECODE; - } return imageFlags; } @@ -119,15 +101,14 @@ ImageFactory::CreateImage(nsIRequest* aRequest, } } -// Marks an image as having an error before returning it. Used with macros like -// NS_ENSURE_SUCCESS, since we guarantee to always return an image even if an -// error occurs, but callers need to be able to tell that this happened. +// Marks an image as having an error before returning it. template static already_AddRefed -BadImage(nsRefPtr& image) +BadImage(const char* aMessage, nsRefPtr& aImage) { - image->SetHasError(); - return image.forget(); + NS_WARNING(aMessage); + aImage->SetHasError(); + return aImage.forget(); } /* static */ already_AddRefed @@ -142,7 +123,9 @@ ImageFactory::CreateAnonymousImage(const nsCString& aMimeType) newImage->SetProgressTracker(newTracker); rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_SYNC_LOAD); - NS_ENSURE_SUCCESS(rv, BadImage(newImage)); + if (NS_FAILED(rv)) { + return BadImage("RasterImage::Init failed", newImage); + } return newImage.forget(); } @@ -246,7 +229,9 @@ ImageFactory::CreateRasterImage(nsIRequest* aRequest, } rv = newImage->Init(aMimeType.get(), aImageFlags); - NS_ENSURE_SUCCESS(rv, BadImage(newImage)); + if (NS_FAILED(rv)) { + return BadImage("RasterImage::Init failed", newImage); + } newImage->SetInnerWindowID(aInnerWindowId); @@ -289,12 +274,16 @@ ImageFactory::CreateVectorImage(nsIRequest* aRequest, newImage->SetProgressTracker(aProgressTracker); rv = newImage->Init(aMimeType.get(), aImageFlags); - NS_ENSURE_SUCCESS(rv, BadImage(newImage)); + if (NS_FAILED(rv)) { + return BadImage("VectorImage::Init failed", newImage); + } newImage->SetInnerWindowID(aInnerWindowId); rv = newImage->OnStartRequest(aRequest, nullptr); - NS_ENSURE_SUCCESS(rv, BadImage(newImage)); + if (NS_FAILED(rv)) { + return BadImage("VectorImage::OnStartRequest failed", newImage); + } return newImage.forget(); } diff --git a/image/ImageOps.cpp b/image/ImageOps.cpp index 0a1e460c88..0e7833e258 100644 --- a/image/ImageOps.cpp +++ b/image/ImageOps.cpp @@ -116,7 +116,9 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream, DecoderType decoderType = DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get()); nsRefPtr decoder = - DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aFlags); + DecoderFactory::CreateAnonymousDecoder(decoderType, + sourceBuffer, + ToSurfaceFlags(aFlags)); if (!decoder) { return nullptr; } diff --git a/image/ImageWrapper.cpp b/image/ImageWrapper.cpp index 89878f6fae..02139b7306 100644 --- a/image/ImageWrapper.cpp +++ b/image/ImageWrapper.cpp @@ -174,6 +174,14 @@ ImageWrapper::GetFrame(uint32_t aWhichFrame, return mInnerImage->GetFrame(aWhichFrame, aFlags); } +NS_IMETHODIMP_(already_AddRefed) +ImageWrapper::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags); +} + NS_IMETHODIMP_(bool) ImageWrapper::IsOpaque() { diff --git a/image/OrientedImage.cpp b/image/OrientedImage.cpp index b2d13bee38..5de600b8b3 100644 --- a/image/OrientedImage.cpp +++ b/image/OrientedImage.cpp @@ -122,6 +122,16 @@ OrientedImage::GetFrame(uint32_t aWhichFrame, return target->Snapshot(); } +NS_IMETHODIMP_(already_AddRefed) +OrientedImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + // XXX(seth): It'd be nice to support downscale-during-decode for this case, + // but right now we just fall back to the intrinsic size. + return GetFrame(aWhichFrame, aFlags); +} + NS_IMETHODIMP_(bool) OrientedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) { diff --git a/image/OrientedImage.h b/image/OrientedImage.h index e1f321fc15..cbfe308d20 100644 --- a/image/OrientedImage.h +++ b/image/OrientedImage.h @@ -34,6 +34,10 @@ public: NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override; NS_IMETHOD_(already_AddRefed) GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetFrameAtSize(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) override; NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags) override; NS_IMETHOD_(already_AddRefed) diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index 8417347290..b056ad0302 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -52,6 +52,7 @@ #include "mozilla/Services.h" #include #include "mozilla/TimeStamp.h" +#include "mozilla/Tuple.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/gfx/Scale.h" @@ -74,139 +75,6 @@ using std::min; // used for statistics. static int32_t sMaxDecodeCount = 0; -class ScaleRunner : public nsRunnable -{ - enum ScaleState - { - eNew, - eReady, - eFinish, - eFinishWithError - }; - -public: - ScaleRunner(RasterImage* aImage, - uint32_t aImageFlags, - const IntSize& aSize, - RawAccessFrameRef&& aSrcRef) - : mImage(aImage) - , mSrcRef(Move(aSrcRef)) - , mDstSize(aSize) - , mImageFlags(aImageFlags) - , mState(eNew) - { - MOZ_ASSERT(!mSrcRef->GetIsPaletted()); - MOZ_ASSERT(aSize.width > 0 && aSize.height > 0); - } - - bool Init() - { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(mState == eNew, "Calling Init() twice?"); - - // We'll need a destination frame. It's unconditionally ARGB32 because - // that's what the scaler outputs. - nsRefPtr tentativeDstFrame = new imgFrame(); - nsresult rv = - tentativeDstFrame->InitForDecoder(mDstSize, SurfaceFormat::B8G8R8A8); - if (NS_FAILED(rv)) { - return false; - } - - // We need a strong reference to the raw data for the destination frame. - // (We already got one for the source frame in the constructor.) - RawAccessFrameRef tentativeDstRef = tentativeDstFrame->RawAccessRef(); - if (!tentativeDstRef) { - return false; - } - - // Everything worked, so commit to these objects and mark ourselves ready. - mDstRef = Move(tentativeDstRef); - mState = eReady; - - // Insert the new surface into the cache immediately. We need to do this so - // that we won't start multiple scaling jobs for the same size. - SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()), - RasterSurfaceKey(mDstSize, mImageFlags, 0), - Lifetime::Transient); - - return true; - } - - NS_IMETHOD Run() override - { - if (mState == eReady) { - // Collect information from the frames that we need to scale. - ScalingData srcData = mSrcRef->GetScalingData(); - ScalingData dstData = mDstRef->GetScalingData(); - - // Actually do the scaling. - bool succeeded = - gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height, - srcData.mBytesPerRow, dstData.mRawData, mDstSize.width, - mDstSize.height, dstData.mBytesPerRow, srcData.mFormat); - - if (succeeded) { - // Mark the frame as complete and discardable. - mDstRef->ImageUpdated(mDstRef->GetRect()); - MOZ_ASSERT(mDstRef->IsImageComplete(), - "Incomplete, but just updated the entire frame"); - } - - // We need to send notifications and release our references on the main - // thread, so finish up there. - mState = succeeded ? eFinish : eFinishWithError; - NS_DispatchToMainThread(this); - } else if (mState == eFinish) { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(mDstRef, "Should have a valid scaled frame"); - - // Notify, so observers can redraw. - nsRefPtr image = mImage.get(); - if (image) { - image->NotifyNewScaledFrame(); - } - - // We're done, so release everything. - mSrcRef.reset(); - mDstRef.reset(); - } else if (mState == eFinishWithError) { - MOZ_ASSERT(NS_IsMainThread()); - NS_WARNING("HQ scaling failed"); - - // Remove the frame from the cache since we know we don't need it. - SurfaceCache::RemoveSurface(ImageKey(mImage.get()), - RasterSurfaceKey(mDstSize, - mImageFlags, 0)); - - // Release everything we're holding, too. - mSrcRef.reset(); - mDstRef.reset(); - } else { - // mState must be eNew, which is invalid in Run(). - MOZ_ASSERT(false, "Need to call Init() before dispatching"); - } - - return NS_OK; - } - -private: - virtual ~ScaleRunner() - { - MOZ_ASSERT(!mSrcRef && !mDstRef, - "Should have released strong refs in Run()"); - } - - WeakPtr mImage; - RawAccessFrameRef mSrcRef; - RawAccessFrameRef mDstRef; - const IntSize mDstSize; - uint32_t mImageFlags; - ScaleState mState; -}; - -static nsCOMPtr sScaleWorkerThread = nullptr; - #ifndef DEBUG NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties) #else @@ -233,7 +101,6 @@ RasterImage::RasterImage(ImageURL* aURI /* = nullptr */) : mDiscardable(false), mHasSourceData(false), mHasBeenDecoded(false), - mDownscaleDuringDecode(false), mPendingAnimation(false), mAnimationFinished(false), mWantFullDecode(false) @@ -269,23 +136,15 @@ RasterImage::Init(const char* aMimeType, } // We want to avoid redecodes for transient images. - MOZ_ASSERT(!(aFlags & INIT_FLAG_TRANSIENT) || - (!(aFlags & INIT_FLAG_DISCARDABLE) && - !(aFlags & INIT_FLAG_DOWNSCALE_DURING_DECODE)), - "Illegal init flags for transient image"); + MOZ_ASSERT_IF(aFlags & INIT_FLAG_TRANSIENT, + !(aFlags & INIT_FLAG_DISCARDABLE)); // Store initialization data mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE); mWantFullDecode = !!(aFlags & INIT_FLAG_DECODE_IMMEDIATELY); mTransient = !!(aFlags & INIT_FLAG_TRANSIENT); - mDownscaleDuringDecode = !!(aFlags & INIT_FLAG_DOWNSCALE_DURING_DECODE); mSyncLoad = !!(aFlags & INIT_FLAG_SYNC_LOAD); -#ifndef MOZ_ENABLE_SKIA - // Downscale-during-decode requires Skia. - mDownscaleDuringDecode = false; -#endif - // Use the MIME type to select a decoder type, and make sure there *is* a // decoder for this MIME type. NS_ENSURE_ARG_POINTER(aMimeType); @@ -434,17 +293,18 @@ RasterImage::LookupFrameInternal(uint32_t aFrameNum, } if (mAnim && aFrameNum > 0) { - MOZ_ASSERT(DecodeFlags(aFlags) == DECODE_FLAGS_DEFAULT, - "Can't composite frames with non-default decode flags"); + MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(), + "Can't composite frames with non-default surface flags"); return mAnim->GetCompositedFrame(aFrameNum); } - Maybe alternateFlags; + Maybe alternateFlags; if (IsOpaque()) { // If we're opaque, we can always substitute a frame that was decoded with a // different decode flag for premultiplied alpha, because that can only // matter for frames with transparency. - alternateFlags = Some(aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA); + alternateFlags.emplace(ToSurfaceFlags(aFlags) ^ + SurfaceFlags::NO_PREMULTIPLY_ALPHA); } // We don't want any substitution for sync decodes (except the premultiplied @@ -452,7 +312,7 @@ RasterImage::LookupFrameInternal(uint32_t aFrameNum, if (aFlags & FLAG_SYNC_DECODE) { return SurfaceCache::Lookup(ImageKey(this), RasterSurfaceKey(aSize, - DecodeFlags(aFlags), + ToSurfaceFlags(aFlags), aFrameNum), alternateFlags); } @@ -460,7 +320,7 @@ RasterImage::LookupFrameInternal(uint32_t aFrameNum, // We'll return the best match we can find to the requested frame. return SurfaceCache::LookupBestMatch(ImageKey(this), RasterSurfaceKey(aSize, - DecodeFlags(aFlags), + ToSurfaceFlags(aFlags), aFrameNum), alternateFlags); } @@ -682,7 +542,7 @@ RasterImage::CopyFrame(uint32_t aWhichFrame, uint32_t aFlags) } else { RefPtr srcSurf = frameRef->GetSurface(); if (!srcSurf) { - RecoverFromLossOfFrames(mSize, aFlags); + RecoverFromInvalidFrames(mSize, aFlags); return nullptr; } @@ -703,14 +563,28 @@ NS_IMETHODIMP_(already_AddRefed) RasterImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) { - return GetFrameInternal(aWhichFrame, aFlags).second().forget(); + return GetFrameInternal(mSize, aWhichFrame, aFlags).second().forget(); +} + +NS_IMETHODIMP_(already_AddRefed) +RasterImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget(); } Pair> -RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags) +RasterImage::GetFrameInternal(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) { MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); + if (aSize.IsEmpty()) { + return MakePair(DrawResult::BAD_ARGS, RefPtr()); + } + if (aWhichFrame > FRAME_MAX_VALUE) { return MakePair(DrawResult::BAD_ARGS, RefPtr()); } @@ -723,7 +597,7 @@ RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags) // not waiting for the data to be loaded from the network or not passing // FLAG_SYNC_DECODE DrawableFrameRef frameRef = - LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags); + LookupFrame(GetRequestedFrameIndex(aWhichFrame), aSize, aFlags); if (!frameRef) { // The OS threw this frame away and we couldn't redecode it. return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr()); @@ -732,15 +606,15 @@ RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags) // If this frame covers the entire image, we can just reuse its existing // surface. RefPtr frameSurf; - IntRect frameRect = frameRef->GetRect(); - if (frameRect.x == 0 && frameRect.y == 0 && - frameRect.width == mSize.width && - frameRect.height == mSize.height) { + if (!frameRef->NeedsPadding() && + frameRef->GetSize() == aSize) { frameSurf = frameRef->GetSurface(); } // The image doesn't have a usable surface because it's been optimized away or - // because it's a partial update frame from an animation. Create one. + // because it's a partial update frame from an animation. Create one. (In this + // case we fall back to returning a surface at our intrinsic size, even if a + // different size was originally specified.) if (!frameSurf) { frameSurf = CopyFrame(aWhichFrame, aFlags); } @@ -758,17 +632,20 @@ RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags) MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aContainer); - auto result = GetFrameInternal(FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY); - if (!result.second()) { + DrawResult drawResult; + RefPtr surface; + Tie(drawResult, surface) = + GetFrameInternal(mSize, FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY); + if (!surface) { // The OS threw out some or all of our buffer. We'll need to wait for the // redecode (which was automatically triggered by GetFrame) to complete. - return MakePair(result.first(), nsRefPtr()); + return MakePair(drawResult, nsRefPtr()); } CairoImage::Data cairoData; GetWidth(&cairoData.mSize.width); GetHeight(&cairoData.mSize.height); - cairoData.mSourceSurface = result.second(); + cairoData.mSourceSurface = surface; nsRefPtr image = aContainer->CreateImage(ImageFormat::CAIRO_SURFACE); @@ -776,7 +653,7 @@ RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags) static_cast(image.get())->SetData(cairoData); - return MakePair(result.first(), Move(image)); + return MakePair(drawResult, Move(image)); } NS_IMETHODIMP_(bool) @@ -828,18 +705,19 @@ RasterImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) // We need a new ImageContainer, so create one. container = LayerManager::CreateImageContainer(); - auto result = GetCurrentImage(container, aFlags); - if (!result.second()) { - // We couldn't get an Image. + DrawResult drawResult; + nsRefPtr image; + Tie(drawResult, image) = GetCurrentImage(container, aFlags); + if (!image) { return nullptr; } - // |result.second()| holds a reference to a SourceSurface which in turn holds - // a lock on the current frame's VolatileBuffer, ensuring that it doesn't get - // freed as long as the layer system keeps this ImageContainer alive. - container->SetCurrentImageInTransaction(result.second()); + // |image| holds a reference to a SourceSurface which in turn holds a lock on + // the current frame's VolatileBuffer, ensuring that it doesn't get freed as + // long as the layer system keeps this ImageContainer alive. + container->SetCurrentImageInTransaction(image); - mLastImageContainerDrawResult = result.first(); + mLastImageContainerDrawResult = drawResult; mImageContainer = container; return container.forget(); @@ -855,14 +733,15 @@ RasterImage::UpdateImageContainer() return; } - auto result = GetCurrentImage(container, FLAG_NONE); - if (!result.second()) { - // We couldn't get an Image. + DrawResult drawResult; + nsRefPtr image; + Tie(drawResult, image) = GetCurrentImage(container, FLAG_NONE); + if (!image) { return; } - mLastImageContainerDrawResult = result.first(); - container->SetCurrentImage(result.second()); + mLastImageContainerDrawResult = drawResult; + container->SetCurrentImage(image); } size_t @@ -986,7 +865,7 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata, // discovered that it actually was during the full decode. This is a // rare failure that only occurs for corrupt images. To recover, we need // to discard all existing surfaces and redecode. - RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT); + RecoverFromInvalidFrames(mSize, DECODE_FLAGS_DEFAULT); } } @@ -1327,10 +1206,6 @@ RasterImage::RequestDecodeForSize(const IntSize& aSize, uint32_t aFlags) return NS_OK; } - // Fall back to our intrinsic size if we don't support - // downscale-during-decode. - IntSize targetSize = mDownscaleDuringDecode ? aSize : mSize; - // Decide whether to sync decode images we can decode quickly. Here we are // explicitly trading off flashing for responsiveness in the case that we're // redecoding an image (see bug 845147). @@ -1343,7 +1218,7 @@ RasterImage::RequestDecodeForSize(const IntSize& aSize, uint32_t aFlags) // Look up the first frame of the image, which will implicitly start decoding // if it's not available right now. - LookupFrame(0, targetSize, flags); + LookupFrame(0, aSize, flags); return NS_OK; } @@ -1393,35 +1268,42 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags) return NS_OK; } - if (mDownscaleDuringDecode) { - // We're about to decode again, which may mean that some of the previous - // sizes we've decoded at aren't useful anymore. We can allow them to - // expire from the cache by unlocking them here. When the decode finishes, - // it will send an invalidation that will cause all instances of this image - // to redraw. If this image is locked, any surfaces that are still useful - // will become locked again when LookupFrame touches them, and the remainder - // will eventually expire. - SurfaceCache::UnlockSurfaces(ImageKey(this)); - } - - MOZ_ASSERT(mDownscaleDuringDecode || aSize == mSize, - "Can only decode to our intrinsic size if we're not allowed to " - "downscale-during-decode"); + // We're about to decode again, which may mean that some of the previous sizes + // we've decoded at aren't useful anymore. We can allow them to expire from + // the cache by unlocking them here. When the decode finishes, it will send an + // invalidation that will cause all instances of this image to redraw. If this + // image is locked, any surfaces that are still useful will become locked + // again when LookupFrame touches them, and the remainder will eventually + // expire. + SurfaceCache::UnlockSurfaces(ImageKey(this)); Maybe targetSize = mSize != aSize ? Some(aSize) : Nothing(); + // Determine which flags we need to decode this image with. + DecoderFlags decoderFlags = DefaultDecoderFlags(); + if (aFlags & FLAG_ASYNC_NOTIFY) { + decoderFlags |= DecoderFlags::ASYNC_NOTIFY; + } + if (mTransient) { + decoderFlags |= DecoderFlags::IMAGE_IS_TRANSIENT; + } + if (mHasBeenDecoded) { + decoderFlags |= DecoderFlags::IS_REDECODE; + } + // Create a decoder. nsRefPtr decoder; if (mAnim) { decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this, - mSourceBuffer, aFlags, + mSourceBuffer, decoderFlags, + ToSurfaceFlags(aFlags), mRequestedResolution); } else { decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, - targetSize, aFlags, + targetSize, decoderFlags, + ToSurfaceFlags(aFlags), mRequestedSampleSize, - mRequestedResolution, - mHasBeenDecoded, mTransient); + mRequestedResolution); } // Make sure DecoderFactory was able to create a decoder successfully. @@ -1434,7 +1316,7 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags) InsertOutcome outcome = SurfaceCache::InsertPlaceholder(ImageKey(this), RasterSurfaceKey(aSize, - decoder->GetDecodeFlags(), + decoder->GetSurfaceFlags(), /* aFrameNum = */ 0)); if (outcome != InsertOutcome::SUCCESS) { return NS_ERROR_FAILURE; @@ -1481,13 +1363,13 @@ RasterImage::DecodeMetadata(uint32_t aFlags) } void -RasterImage::RecoverFromLossOfFrames(const IntSize& aSize, uint32_t aFlags) +RasterImage::RecoverFromInvalidFrames(const IntSize& aSize, uint32_t aFlags) { if (!mHasSize) { return; } - NS_WARNING("An imgFrame became invalid. Attempting to recover..."); + NS_WARNING("A RasterImage's frames became invalid. Attempting to recover..."); // Discard all existing frames, since they're probably all now invalid. SurfaceCache::RemoveImage(ImageKey(this)); @@ -1504,75 +1386,24 @@ RasterImage::RecoverFromLossOfFrames(const IntSize& aSize, uint32_t aFlags) Decode(aSize, aFlags); } -bool -RasterImage::CanScale(GraphicsFilter aFilter, - const IntSize& aSize, - uint32_t aFlags) +static bool +HaveSkia() { -#ifndef MOZ_ENABLE_SKIA - // The high-quality scaler requires Skia. - return false; +#ifdef MOZ_ENABLE_SKIA + return true; #else - // Check basic requirements: HQ downscaling is enabled, we have all the source - // data and know our size, the flags allow us to do it, and a 'good' filter is - // being used. The flags may ask us not to scale because the caller isn't - // drawing to the window. If we're drawing to something else (e.g. a canvas) - // we usually have no way of updating what we've drawn, so HQ scaling is - // useless. - if (!gfxPrefs::ImageHQDownscalingEnabled() || !mHasSize || !mHasSourceData || - !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) || - aFilter != GraphicsFilter::FILTER_GOOD) { - return false; - } - - // We don't HQ scale images that we can downscale during decode. - if (mDownscaleDuringDecode) { - return false; - } - - // We don't use the scaler for animated or transient images to avoid doing a - // bunch of work on an image that just gets thrown away. - if (mAnim || mTransient) { - return false; - } - - // If target size is 1:1 with original, don't scale. - if (aSize == mSize) { - return false; - } - - // To save memory, don't quality upscale images bigger than the limit. - if (aSize.width > mSize.width || aSize.height > mSize.height) { - uint32_t scaledSize = static_cast(aSize.width * aSize.height); - if (scaledSize > gfxPrefs::ImageHQUpscalingMaxSize()) { - return false; - } - } - - // There's no point in scaling if we can't store the result. - if (!SurfaceCache::CanHold(aSize)) { - return false; - } - - // XXX(seth): It's not clear what this check buys us over - // gfxPrefs::ImageHQUpscalingMaxSize(). - // The default value of this pref is 1000, which means that we never upscale. - // If that's all it's getting us, I'd rather we just forbid that explicitly. - gfx::Size scale(double(aSize.width) / mSize.width, - double(aSize.height) / mSize.height); - gfxFloat minFactor = gfxPrefs::ImageHQDownscalingMinFactor() / 1000.0; - return (scale.width < minFactor || scale.height < minFactor); + return false; #endif } bool RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags) { - // Check basic requirements: downscale-during-decode is enabled for this - // image, we have all the source data and know our size, the flags allow us to - // do it, and a 'good' filter is being used. - if (!mDownscaleDuringDecode || !mHasSize || - !gfxPrefs::ImageHQDownscalingEnabled() || + // Check basic requirements: downscale-during-decode is enabled, Skia is + // available, this image isn't transient, we have all the source data and know + // our size, and the flags allow us to do it. + if (!mHasSize || mTransient || !HaveSkia() || + !gfxPrefs::ImageDownscaleDuringDecodeEnabled() || !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) { return false; } @@ -1600,87 +1431,21 @@ RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags) return true; } -void -RasterImage::NotifyNewScaledFrame() -{ - // Send an invalidation so observers will repaint and can take advantage of - // the new scaled frame if possible. - NotifyProgress(NoProgress, IntRect(0, 0, mSize.width, mSize.height)); -} - -void -RasterImage::RequestScale(imgFrame* aFrame, - uint32_t aFlags, - const IntSize& aSize) -{ - // We don't scale frames which aren't fully decoded. - if (!aFrame->IsImageComplete()) { - return; - } - - // We can't scale frames that need padding or are single pixel. - if (aFrame->NeedsPadding() || aFrame->IsSinglePixel()) { - return; - } - - // We also can't scale if we can't lock the image data for this frame. - RawAccessFrameRef frameRef = aFrame->RawAccessRef(); - if (!frameRef) { - return; - } - - nsRefPtr runner = - new ScaleRunner(this, DecodeFlags(aFlags), aSize, Move(frameRef)); - if (runner->Init()) { - if (!sScaleWorkerThread) { - NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread)); - ClearOnShutdown(&sScaleWorkerThread); - } - - sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL); - } -} - DrawResult -RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef, - gfxContext* aContext, - const IntSize& aSize, - const ImageRegion& aRegion, - GraphicsFilter aFilter, - uint32_t aFlags) -{ - DrawableFrameRef frameRef; - - if (CanScale(aFilter, aSize, aFlags)) { - LookupResult result = - SurfaceCache::Lookup(ImageKey(this), - RasterSurfaceKey(aSize, - DecodeFlags(aFlags), - 0)); - if (!result) { - // We either didn't have a matching scaled frame or the OS threw it away. - // Request a new one so we'll be ready next time. For now, we'll fall back - // to aFrameRef below. - RequestScale(aFrameRef.get(), aFlags, aSize); - } - if (result && result.DrawableRef()->IsImageComplete()) { - frameRef = Move(result.DrawableRef()); // The scaled version is ready. - } - } - +RasterImage::DrawInternal(DrawableFrameRef&& aFrameRef, + gfxContext* aContext, + const IntSize& aSize, + const ImageRegion& aRegion, + GraphicsFilter aFilter, + uint32_t aFlags) +{ gfxContextMatrixAutoSaveRestore saveMatrix(aContext); ImageRegion region(aRegion); - bool frameIsComplete = true; // We already checked HQ scaled frames. - if (!frameRef) { - // There's no HQ scaled frame available, so we'll have to use the frame - // provided by the caller. - frameRef = Move(aFrameRef); - frameIsComplete = frameRef->IsImageComplete(); - } + bool frameIsComplete = aFrameRef->IsImageComplete(); // By now we may have a frame with the requested size. If not, we need to // adjust the drawing parameters accordingly. - IntSize finalSize = frameRef->GetImageSize(); + IntSize finalSize = aFrameRef->GetImageSize(); bool couldRedecodeForBetterFrame = false; if (finalSize != aSize) { gfx::Size scale(double(aSize.width) / finalSize.width, @@ -1688,12 +1453,11 @@ RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef, aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height)); region.Scale(1.0 / scale.width, 1.0 / scale.height); - couldRedecodeForBetterFrame = mDownscaleDuringDecode && - CanDownscaleDuringDecode(aSize, aFlags); + couldRedecodeForBetterFrame = CanDownscaleDuringDecode(aSize, aFlags); } - if (!frameRef->Draw(aContext, region, aFilter, aFlags)) { - RecoverFromLossOfFrames(aSize, aFlags); + if (!aFrameRef->Draw(aContext, region, aFilter, aFlags)) { + RecoverFromInvalidFrames(aSize, aFlags); return DrawResult::TEMPORARY_ERROR; } if (!frameIsComplete) { @@ -1735,7 +1499,7 @@ RasterImage::Draw(gfxContext* aContext, // Illegal -- you can't draw with non-default decode flags. // (Disabling colorspace conversion might make sense to allow, but // we don't currently.) - if (DecodeFlags(aFlags) != DECODE_FLAGS_DEFAULT) { + if (ToSurfaceFlags(aFlags) != DefaultSurfaceFlags()) { return DrawResult::BAD_ARGS; } @@ -1763,8 +1527,8 @@ RasterImage::Draw(gfxContext* aContext, return DrawResult::NOT_READY; } - auto result = DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize, - aRegion, aFilter, flags); + auto result = DrawInternal(Move(ref), aContext, aSize, + aRegion, aFilter, flags); return result; } @@ -1916,14 +1680,15 @@ RasterImage::GetFramesNotified(uint32_t* aFramesNotified) void RasterImage::NotifyProgress(Progress aProgress, const IntRect& aInvalidRect /* = IntRect() */, - uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */) + SurfaceFlags aSurfaceFlags + /* = DefaultSurfaceFlags() */) { MOZ_ASSERT(NS_IsMainThread()); // Ensure that we stay alive long enough to finish notifying. nsRefPtr image(this); - bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT; + bool wasDefaultFlags = aSurfaceFlags == DefaultSurfaceFlags(); if (!aInvalidRect.IsEmpty() && wasDefaultFlags) { // Update our image container since we're invalidating. @@ -1968,7 +1733,7 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) // Send out any final notifications. NotifyProgress(aDecoder->TakeProgress(), aDecoder->TakeInvalidRect(), - aDecoder->GetDecodeFlags()); + aDecoder->GetSurfaceFlags()); bool wasMetadata = aDecoder->IsMetadataDecode(); bool done = aDecoder->GetDecodeDone(); @@ -2060,28 +1825,9 @@ RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, if (aFilter == GraphicsFilter::FILTER_GOOD && CanDownscaleDuringDecode(destSize, aFlags)) { return destSize; - } else if (CanScale(aFilter, destSize, aFlags)) { - LookupResult result = - SurfaceCache::Lookup(ImageKey(this), - RasterSurfaceKey(destSize, - DecodeFlags(aFlags), - 0)); - - if (result && result.DrawableRef()->IsImageComplete()) { - return destSize; // We have an existing HQ scale for this size. - } - if (!result) { - // We could HQ scale to this size, but we haven't. Request a scale now. - DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame), - mSize, aFlags); - if (ref) { - RequestScale(ref.get(), aFlags, destSize); - } - } } - // We either can't HQ scale to this size or the scaled version isn't ready - // yet. Use our intrinsic size for now. + // We can't scale to this size. Use our intrinsic size for now. return mSize; } diff --git a/image/RasterImage.h b/image/RasterImage.h index b2aa41d17d..4aad6e72eb 100644 --- a/image/RasterImage.h +++ b/image/RasterImage.h @@ -135,17 +135,6 @@ class FrameAnimator; class ImageMetadata; class SourceBuffer; -/** - * Given a set of imgIContainer FLAG_* flags, returns those flags that can - * affect the output of decoders. - */ -inline MOZ_CONSTEXPR uint32_t -DecodeFlags(uint32_t aFlags) -{ - return aFlags & (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | - imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION); -} - class RasterImage final : public ImageResource , public nsIProperties , public SupportsWeakPtr @@ -196,13 +185,13 @@ public: * * @param aProgress The progress notifications to send. * @param aInvalidRect An invalidation rect to send. - * @param aFlags The decode flags used by the decoder that generated - * these notifications, or DECODE_FLAGS_DEFAULT if the + * @param aFlags The surface flags used by the decoder that generated + * these notifications, or DefaultSurfaceFlags() if the * notifications don't come from a decoder. */ void NotifyProgress(Progress aProgress, const nsIntRect& aInvalidRect = nsIntRect(), - uint32_t aFlags = DECODE_FLAGS_DEFAULT); + SurfaceFlags aSurfaceFlags = DefaultSurfaceFlags()); /** * Records telemetry and does final teardown of the provided decoder. @@ -265,18 +254,20 @@ public: private: nsresult Init(const char* aMimeType, uint32_t aFlags); - DrawResult DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef, - gfxContext* aContext, - const nsIntSize& aSize, - const ImageRegion& aRegion, - GraphicsFilter aFilter, - uint32_t aFlags); + DrawResult DrawInternal(DrawableFrameRef&& aFrameRef, + gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + GraphicsFilter aFilter, + uint32_t aFlags); already_AddRefed CopyFrame(uint32_t aWhichFrame, uint32_t aFlags); Pair> - GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags); + GetFrameInternal(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags); LookupResult LookupFrameInternal(uint32_t aFrameNum, const gfx::IntSize& aSize, @@ -314,10 +305,6 @@ private: * * It's an error to call Decode() before this image's intrinsic size is * available. A metadata decode must successfully complete first. - * - * If downscale-during-decode is not enabled for this image (i.e., if - * mDownscaleDuringDecode is false), it is an error to pass an @aSize value - * different from this image's intrinsic size. */ NS_IMETHOD Decode(const gfx::IntSize& aSize, uint32_t aFlags); @@ -341,11 +328,17 @@ private: nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode); /** - * In catastrophic circumstances like a GPU driver crash, we may lose our - * frames even if they're locked. RecoverFromLossOfFrames discards all - * existing frames and redecodes using the provided @aSize and @aFlags. + * In catastrophic circumstances like a GPU driver crash, the contents of our + * frames may become invalid. If the information we gathered during the + * metadata decode proves to be wrong due to image corruption, the frames we + * have may violate this class's invariants. Either way, we need to + * immediately discard the invalid frames and redecode so that callers don't + * perceive that we've entered an invalid state. + * + * RecoverFromInvalidFrames discards all existing frames and redecodes using + * the provided @aSize and @aFlags. */ - void RecoverFromLossOfFrames(const nsIntSize& aSize, uint32_t aFlags); + void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags); private: // data nsIntSize mSize; @@ -401,7 +394,6 @@ private: // data bool mDiscardable:1; // Is container discardable? bool mHasSourceData:1; // Do we have source data? bool mHasBeenDecoded:1; // Decoded at least once? - bool mDownscaleDuringDecode:1; // Whether we're waiting to start animation. If we get a StartAnimation() call // but we don't yet have more than one frame, mPendingAnimation is set so that @@ -423,22 +415,10 @@ private: // data // Scaling. ////////////////////////////////////////////////////////////////////////////// - // Initiates an HQ scale for the given frame, if possible. - void RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize); - - // Determines whether we can perform an HQ scale with the given parameters. - bool CanScale(GraphicsFilter aFilter, const nsIntSize& aSize, - uint32_t aFlags); - // Determines whether we can downscale during decode with the given // parameters. bool CanDownscaleDuringDecode(const nsIntSize& aSize, uint32_t aFlags); - // Called by the HQ scaler when a new scaled frame is ready. - void NotifyNewScaledFrame(); - - friend class ScaleRunner; - // Error handling. void DoError(); diff --git a/image/SurfaceCache.cpp b/image/SurfaceCache.cpp index 31e9f6bd21..3a9c49e9cb 100644 --- a/image/SurfaceCache.cpp +++ b/image/SurfaceCache.cpp @@ -19,6 +19,7 @@ #include "mozilla/Pair.h" #include "mozilla/RefPtr.h" #include "mozilla/StaticPtr.h" +#include "mozilla/Tuple.h" #include "nsIMemoryReporter.h" #include "gfx2DGlue.h" #include "gfxPattern.h" // Workaround for flaw in bug 921753 part 2. @@ -133,17 +134,14 @@ public: CachedSurface(imgFrame* aSurface, const Cost aCost, const ImageKey aImageKey, - const SurfaceKey& aSurfaceKey, - const Lifetime aLifetime) + const SurfaceKey& aSurfaceKey) : mSurface(aSurface) , mCost(aCost) , mImageKey(aImageKey) , mSurfaceKey(aSurfaceKey) - , mLifetime(aLifetime) { - MOZ_ASSERT(!IsPlaceholder() || - (mCost == sPlaceholderCost && mLifetime == Lifetime::Transient), - "Placeholder should have trivial cost and transient lifetime"); + MOZ_ASSERT(!IsPlaceholder() || mCost == sPlaceholderCost, + "Placeholder should have trivial cost"); MOZ_ASSERT(mImageKey, "Must have a valid image key"); } @@ -163,7 +161,7 @@ public: return; // Can't lock a placeholder. } - if (aLocked && mLifetime == Lifetime::Persistent) { + if (aLocked) { // This may fail, and that's OK. We make no guarantees about whether // locking is successful if you call SurfaceCache::LockImage() after // SurfaceCache::Insert(). @@ -180,7 +178,6 @@ public: SurfaceKey GetSurfaceKey() const { return mSurfaceKey; } CostEntry GetCostEntry() { return image::CostEntry(this, mCost); } nsExpirationState* GetExpirationState() { return &mExpirationState; } - Lifetime GetLifetime() const { return mLifetime; } bool IsDecoded() const { @@ -228,7 +225,6 @@ private: const Cost mCost; const ImageKey mImageKey; const SurfaceKey mSurfaceKey; - const Lifetime mLifetime; }; /** @@ -257,9 +253,8 @@ public: void Insert(const SurfaceKey& aKey, CachedSurface* aSurface) { MOZ_ASSERT(aSurface, "Should have a surface"); - MOZ_ASSERT(!mLocked || aSurface->GetLifetime() != Lifetime::Persistent || - aSurface->IsLocked(), - "Inserting an unlocked persistent surface for a locked image"); + MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(), + "Inserting an unlocked surface for a locked image"); mSurfaces.Put(aKey, aSurface); } @@ -279,10 +274,9 @@ public: return surface.forget(); } - MOZ_WARN_UNUSED_RESULT // See bug 1185044. Pair, MatchType> LookupBestMatch(const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags) + const Maybe& aAlternateFlags) { // Try for an exact match first. nsRefPtr exactMatch; @@ -334,13 +328,13 @@ private: struct MatchContext { MatchContext(const SurfaceKey& aIdealKey, - const Maybe& aAlternateFlags) + const Maybe& aAlternateFlags) : mIdealKey(aIdealKey) , mAlternateFlags(aAlternateFlags) { } const SurfaceKey& mIdealKey; - const Maybe mAlternateFlags; + const Maybe mAlternateFlags; nsRefPtr mBestMatch; }; @@ -468,8 +462,7 @@ public: InsertOutcome Insert(imgFrame* aSurface, const Cost aCost, const ImageKey aImageKey, - const SurfaceKey& aSurfaceKey, - Lifetime aLifetime) + const SurfaceKey& aSurfaceKey) { // If this is a duplicate surface, refuse to replace the original. // XXX(seth): Calling Lookup() and then RemoveSurface() does the lookup @@ -510,12 +503,12 @@ public: } nsRefPtr surface = - new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime); + new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey); - // We require that locking succeed if the image is locked and the surface is - // persistent; the caller may need to know this to handle errors correctly. - if (cache->IsLocked() && aLifetime == Lifetime::Persistent) { - MOZ_ASSERT(!surface->IsPlaceholder(), "Placeholders should be transient"); + // We require that locking succeed if the image is locked and we're not + // inserting a placeholder; the caller may need to know this to handle + // errors correctly. + if (cache->IsLocked() && !surface->IsPlaceholder()) { surface->SetLocked(true); if (!surface->IsLocked()) { return InsertOutcome::FAILURE; @@ -538,8 +531,8 @@ public: nsRefPtr cache = GetImageCache(imageKey); MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache"); - // If the surface was persistent, tell its image that we discarded it. - if (aSurface->GetLifetime() == Lifetime::Persistent) { + // If the surface was not a placeholder, tell its image that we discarded it. + if (!aSurface->IsPlaceholder()) { static_cast(imageKey)->OnSurfaceDiscarded(); } @@ -640,7 +633,7 @@ public: LookupResult LookupBestMatch(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags) + const Maybe& aAlternateFlags) { nsRefPtr cache = GetImageCache(aImageKey); if (!cache) { @@ -658,11 +651,8 @@ public: DrawableFrameRef ref; MatchType matchType = MatchType::NOT_FOUND; while (true) { - // XXX(seth): This code is begging for std::tie. See bug 1184385. - Pair, MatchType> lookupResult = + Tie(surface, matchType) = cache->LookupBestMatch(aSurfaceKey, aAlternateFlags); - surface = lookupResult.first(); - matchType = lookupResult.second(); if (!surface) { return LookupResult(matchType); // Lookup in the per-image cache missed. @@ -776,9 +766,8 @@ public: void DiscardAll() { // Remove in order of cost because mCosts is an array and the other data - // structures are all hash tables. Note that locked surfaces (persistent - // surfaces belonging to locked images) are not removed, since they aren't - // present in mCosts. + // structures are all hash tables. Note that locked surfaces are not + // removed, since they aren't present in mCosts. while (!mCosts.IsEmpty()) { Remove(mCosts.LastElement().GetSurface()); } @@ -812,8 +801,7 @@ public: void LockSurface(CachedSurface* aSurface) { - if (aSurface->GetLifetime() == Lifetime::Transient || - aSurface->IsLocked()) { + if (aSurface->IsPlaceholder() || aSurface->IsLocked()) { return; } @@ -836,8 +824,7 @@ public: CachedSurface* aSurface, void* aCache) { - if (aSurface->GetLifetime() == Lifetime::Transient || - !aSurface->IsLocked()) { + if (aSurface->IsPlaceholder() || !aSurface->IsLocked()) { return PL_DHASH_NEXT; } @@ -912,9 +899,9 @@ private: // This is similar to CanHold() except that it takes into account the costs of // locked surfaces. It's used internally in Insert(), but it's not exposed - // publicly because if we start permitting multithreaded access to the surface - // cache, which seems likely, then the result would be meaningless: another - // thread could insert a persistent surface or lock an image at any time. + // publicly because we permit multithreaded access to the surface cache, which + // means that the result would be meaningless: another thread could insert a + // surface or lock an image at any time. bool CanHoldAfterDiscarding(const Cost aCost) const { return aCost <= mMaxCost - mLockedCost; @@ -1051,7 +1038,8 @@ SurfaceCache::Shutdown() /* static */ LookupResult SurfaceCache::Lookup(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags /* = Nothing() */) + const Maybe& aAlternateFlags + /* = Nothing() */) { if (!sInstance) { return LookupResult(MatchType::NOT_FOUND); @@ -1071,7 +1059,7 @@ SurfaceCache::Lookup(const ImageKey aImageKey, /* static */ LookupResult SurfaceCache::LookupBestMatch(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags + const Maybe& aAlternateFlags /* = Nothing() */) { if (!sInstance) { @@ -1085,8 +1073,7 @@ SurfaceCache::LookupBestMatch(const ImageKey aImageKey, /* static */ InsertOutcome SurfaceCache::Insert(imgFrame* aSurface, const ImageKey aImageKey, - const SurfaceKey& aSurfaceKey, - Lifetime aLifetime) + const SurfaceKey& aSurfaceKey) { if (!sInstance) { return InsertOutcome::FAILURE; @@ -1094,7 +1081,7 @@ SurfaceCache::Insert(imgFrame* aSurface, MutexAutoLock lock(sInstance->GetMutex()); Cost cost = ComputeCost(aSurface->GetSize(), aSurface->GetBytesPerPixel()); - return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime); + return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey); } /* static */ InsertOutcome @@ -1106,8 +1093,7 @@ SurfaceCache::InsertPlaceholder(const ImageKey aImageKey, } MutexAutoLock lock(sInstance->GetMutex()); - return sInstance->Insert(nullptr, sPlaceholderCost, aImageKey, aSurfaceKey, - Lifetime::Transient); + return sInstance->Insert(nullptr, sPlaceholderCost, aImageKey, aSurfaceKey); } /* static */ bool diff --git a/image/SurfaceCache.h b/image/SurfaceCache.h index 772badee0f..b8f8f8f346 100644 --- a/image/SurfaceCache.h +++ b/image/SurfaceCache.h @@ -19,6 +19,7 @@ #include "nsCOMPtr.h" // for already_AddRefed #include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize #include "mozilla/gfx/2D.h" // for SourceSurface +#include "SurfaceFlags.h" #include "SVGImageContext.h" // for SVGImageContext namespace mozilla { @@ -59,16 +60,16 @@ public: { uint32_t hash = HashGeneric(mSize.width, mSize.height); hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0)); - hash = AddToHash(hash, mAnimationTime, mFlags); + hash = AddToHash(hash, mAnimationTime, uint32_t(mFlags)); return hash; } IntSize Size() const { return mSize; } Maybe SVGContext() const { return mSVGContext; } float AnimationTime() const { return mAnimationTime; } - uint32_t Flags() const { return mFlags; } + SurfaceFlags Flags() const { return mFlags; } - SurfaceKey WithNewFlags(uint32_t aFlags) const + SurfaceKey WithNewFlags(SurfaceFlags aFlags) const { return SurfaceKey(mSize, mSVGContext, mAnimationTime, aFlags); } @@ -77,7 +78,7 @@ private: SurfaceKey(const IntSize& aSize, const Maybe& aSVGContext, const float aAnimationTime, - const uint32_t aFlags) + const SurfaceFlags aFlags) : mSize(aSize) , mSVGContext(aSVGContext) , mAnimationTime(aAnimationTime) @@ -88,7 +89,9 @@ private: return aSIC.Hash(); } - friend SurfaceKey RasterSurfaceKey(const IntSize&, uint32_t, uint32_t); + friend SurfaceKey RasterSurfaceKey(const IntSize&, + SurfaceFlags, + uint32_t); friend SurfaceKey VectorSurfaceKey(const IntSize&, const Maybe&, float); @@ -96,12 +99,12 @@ private: IntSize mSize; Maybe mSVGContext; float mAnimationTime; - uint32_t mFlags; + SurfaceFlags mFlags; }; inline SurfaceKey RasterSurfaceKey(const gfx::IntSize& aSize, - uint32_t aFlags, + SurfaceFlags aFlags, uint32_t aFrameNum) { return SurfaceKey(aSize, Nothing(), float(aFrameNum), aFlags); @@ -115,14 +118,9 @@ VectorSurfaceKey(const gfx::IntSize& aSize, // We don't care about aFlags for VectorImage because none of the flags we // have right now influence VectorImage's rendering. If we add a new flag that // *does* affect how a VectorImage renders, we'll have to change this. - return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0); + return SurfaceKey(aSize, aSVGContext, aAnimationTime, DefaultSurfaceFlags()); } -enum class Lifetime : uint8_t { - Transient, - Persistent -}; - enum class InsertOutcome : uint8_t { SUCCESS, // Success (but see Insert documentation). FAILURE, // Couldn't insert (e.g., for capacity reasons). @@ -143,9 +141,8 @@ enum class InsertOutcome : uint8_t { * cache. This is most often because losing the data could harm the user * experience (for example, we often don't want to allow surfaces that are * currently visible to expire) or because it's not possible to rematerialize - * the surface. SurfaceCache supports this through the use of image locking and - * surface lifetimes; see the comments for Insert() and LockImage() for more - * details. + * the surface. SurfaceCache supports this through the use of image locking; see + * the comments for Insert() and LockImage() for more details. * * Any image which stores surfaces in the SurfaceCache *must* ensure that it * calls RemoveImage() before it is destroyed. See the comments for @@ -175,8 +172,8 @@ struct SurfaceCache * If the imgFrame was found in the cache, but had stored its surface in a * volatile buffer which was discarded by the OS, then it is automatically * removed from the cache and an empty LookupResult is returned. Note that - * this will never happen to persistent surfaces associated with a locked - * image; the cache keeps a strong reference to such surfaces internally. + * this will never happen to surfaces associated with a locked image; the + * cache keeps a strong reference to such surfaces internally. * * @param aImageKey Key data identifying which image the surface belongs * to. @@ -196,7 +193,8 @@ struct SurfaceCache */ static LookupResult Lookup(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags = Nothing()); + const Maybe& aAlternateFlags + = Nothing()); /** * Looks up the best matching surface in the cache and returns a drawable @@ -224,7 +222,7 @@ struct SurfaceCache */ static LookupResult LookupBestMatch(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, - const Maybe& aAlternateFlags + const Maybe& aAlternateFlags = Nothing()); /** @@ -232,32 +230,30 @@ struct SurfaceCache * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT. * If a matching placeholder is already present, the placeholder is removed. * - * Each surface in the cache has a lifetime, either Transient or Persistent. - * Transient surfaces can expire from the cache at any time. Persistent - * surfaces, on the other hand, will never expire as long as they remain - * locked, but if they become unlocked, can expire just like transient - * surfaces. When it is first inserted, a persistent surface is locked if its - * associated image is locked. When that image is later unlocked, the surface - * becomes unlocked too. To become locked again at that point, two things must - * happen: the image must become locked again (via LockImage()), and the - * surface must be touched again (via one of the Lookup() functions). + * Surfaces will never expire as long as they remain locked, but if they + * become unlocked, they can expire either because the SurfaceCache runs out + * of capacity or because they've gone too long without being used. When it + * is first inserted, a surface is locked if its associated image is locked. + * When that image is later unlocked, the surface becomes unlocked too. To + * become locked again at that point, two things must happen: the image must + * become locked again (via LockImage()), and the surface must be touched + * again (via one of the Lookup() functions). * * All of this means that a very particular procedure has to be followed for * surfaces which cannot be rematerialized. First, they must be inserted - * with a persistent lifetime *after* the image is locked with LockImage(); if - * you use the other order, the surfaces might expire before LockImage() gets - * called or before the surface is touched again by Lookup(). Second, the - * image they are associated with must never be unlocked. + * *after* the image is locked with LockImage(); if you use the other order, + * the surfaces might expire before LockImage() gets called or before the + * surface is touched again by Lookup(). Second, the image they are associated + * with must never be unlocked. * * If a surface cannot be rematerialized, it may be important to know whether * it was inserted into the cache successfully. Insert() returns FAILURE if it * failed to insert the surface, which could happen because of capacity - * reasons, or because it was already freed by the OS. If you aren't inserting - * a surface with persistent lifetime, or if the surface isn't associated with - * a locked image, checking for SUCCESS or FAILURE is useless: the surface - * might expire immediately after being inserted, even though Insert() - * returned SUCCESS. Thus, many callers do not need to check the result of - * Insert() at all. + * reasons, or because it was already freed by the OS. If the surface isn't + * associated with a locked image, checking for SUCCESS or FAILURE is useless: + * the surface might expire immediately after being inserted, even though + * Insert() returned SUCCESS. Thus, many callers do not need to check the + * result of Insert() at all. * * @param aTarget The new surface (wrapped in an imgFrame) to insert into * the cache. @@ -265,9 +261,6 @@ struct SurfaceCache * to. * @param aSurfaceKey Key data which uniquely identifies the requested * surface. - * @param aLifetime Whether this is a transient surface that can always be - * allowed to expire, or a persistent surface that - * shouldn't expire if the image is locked. * @return SUCCESS if the surface was inserted successfully. (But see above * for more information about when you should check this.) * FAILURE if the surface could not be inserted, e.g. for capacity @@ -278,8 +271,7 @@ struct SurfaceCache */ static InsertOutcome Insert(imgFrame* aSurface, const ImageKey aImageKey, - const SurfaceKey& aSurfaceKey, - Lifetime aLifetime); + const SurfaceKey& aSurfaceKey); /** * Insert a placeholder for a surface into the cache. If a surface with the @@ -329,17 +321,16 @@ struct SurfaceCache static bool CanHold(size_t aSize); /** - * Locks an image. Any of the image's persistent surfaces which are either - * inserted or accessed while the image is locked will not expire. + * Locks an image. Any of the image's surfaces which are either inserted or + * accessed while the image is locked will not expire. * * Locking an image does not automatically lock that image's existing - * surfaces. A call to LockImage() guarantees that persistent surfaces which - * are inserted afterward will not expire before the next call to - * UnlockImage() or UnlockSurfaces() for that image. Surfaces that are - * accessed via Lookup() or LookupBestMatch() after a LockImage() call will - * also not expire until the next UnlockImage() or UnlockSurfaces() call for - * that image. Any other surfaces owned by the image may expire at any time, - * whether they are persistent or transient. + * surfaces. A call to LockImage() guarantees that surfaces which are inserted + * afterward will not expire before the next call to UnlockImage() or + * UnlockSurfaces() for that image. Surfaces that are accessed via Lookup() or + * LookupBestMatch() after a LockImage() call will also not expire until the + * next UnlockImage() or UnlockSurfaces() call for that image. Any other + * surfaces owned by the image may expire at any time. * * Regardless of locking, any of an image's surfaces may be removed using * RemoveSurface(), and all of an image's surfaces are removed by @@ -377,10 +368,10 @@ struct SurfaceCache * expiring. * * This is intended to be used in situations where it's no longer clear that - * all of the persistent surfaces owned by an image are needed. Calling - * UnlockSurfaces() and then taking some action that will cause Lookup() to - * touch any surfaces that are still useful will permit the remaining surfaces - * to expire from the cache. + * all of the surfaces owned by an image are needed. Calling UnlockSurfaces() + * and then taking some action that will cause Lookup() to touch any surfaces + * that are still useful will permit the remaining surfaces to expire from the + * cache. * * If the image is unlocked, this has no effect. * @@ -421,9 +412,9 @@ struct SurfaceCache /** * Evicts all evictable surfaces from the cache. * - * All surfaces are evictable except for persistent surfaces associated with - * locked images. Non-evictable surfaces can only be removed by - * RemoveSurface() or RemoveImage(). + * All surfaces are evictable except for surfaces associated with locked + * images. Non-evictable surfaces can only be removed by RemoveSurface() or + * RemoveImage(). */ static void DiscardAll(); diff --git a/image/SurfaceFlags.h b/image/SurfaceFlags.h new file mode 100644 index 0000000000..ac1c5e6646 --- /dev/null +++ b/image/SurfaceFlags.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_image_SurfaceFlags_h +#define mozilla_image_SurfaceFlags_h + +#include "imgIContainer.h" +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace image { + +/** + * Flags that change the output a decoder generates. Because different + * combinations of these flags result in logically different surfaces, these + * flags must be taken into account in SurfaceCache lookups. + */ +enum class SurfaceFlags : uint8_t +{ + NO_PREMULTIPLY_ALPHA = 1 << 0, + NO_COLORSPACE_CONVERSION = 1 << 1 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags) + +/** + * @return the default set of surface flags. + */ +inline SurfaceFlags +DefaultSurfaceFlags() +{ + return SurfaceFlags(); +} + +/** + * Given a set of imgIContainer FLAG_* flags, returns a set of SurfaceFlags with + * the corresponding flags set. + */ +inline SurfaceFlags +ToSurfaceFlags(uint32_t aFlags) +{ + SurfaceFlags flags = DefaultSurfaceFlags(); + if (aFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) { + flags |= SurfaceFlags::NO_PREMULTIPLY_ALPHA; + } + if (aFlags & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) { + flags |= SurfaceFlags::NO_COLORSPACE_CONVERSION; + } + return flags; +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfaceFlags_h diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index 15262e5055..c9bcca1706 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -659,19 +659,8 @@ VectorImage::IsOpaque() /* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame, * in uint32_t aFlags; */ NS_IMETHODIMP_(already_AddRefed) -VectorImage::GetFrame(uint32_t aWhichFrame, - uint32_t aFlags) +VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) { - MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); - - if (aWhichFrame > FRAME_MAX_VALUE) { - return nullptr; - } - - if (mError || !mIsFullyLoaded) { - return nullptr; - } - // Look up height & width // ---------------------- SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem(); @@ -686,12 +675,32 @@ VectorImage::GetFrame(uint32_t aWhichFrame, return nullptr; } + return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags); +} + +NS_IMETHODIMP_(already_AddRefed) +VectorImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); + + if (aSize.IsEmpty()) { + return nullptr; + } + + if (aWhichFrame > FRAME_MAX_VALUE) { + return nullptr; + } + + if (mError || !mIsFullyLoaded) { + return nullptr; + } + // Make our surface the size of what will ultimately be drawn to it. // (either the full image size, or the restricted region) RefPtr dt = gfxPlatform::GetPlatform()-> - CreateOffscreenContentDrawTarget(IntSize(imageIntSize.width, - imageIntSize.height), - SurfaceFormat::B8G8R8A8); + CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8); if (!dt) { NS_ERROR("Could not create a DrawTarget"); return nullptr; @@ -699,8 +708,8 @@ VectorImage::GetFrame(uint32_t aWhichFrame, nsRefPtr context = new gfxContext(dt); - auto result = Draw(context, imageIntSize, - ImageRegion::Create(imageIntSize), + auto result = Draw(context, aSize, + ImageRegion::Create(aSize), aWhichFrame, GraphicsFilter::FILTER_NEAREST, Nothing(), aFlags); @@ -922,8 +931,7 @@ VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams) SurfaceCache::Insert(frame, ImageKey(this), VectorSurfaceKey(aParams.size, aParams.svgContext, - aParams.animationTime), - Lifetime::Persistent); + aParams.animationTime)); // Draw. nsRefPtr drawable = diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp index c136b246eb..e8674dc047 100644 --- a/image/decoders/nsBMPDecoder.cpp +++ b/image/decoders/nsBMPDecoder.cpp @@ -64,20 +64,6 @@ nsBMPDecoder::~nsBMPDecoder() } } -nsresult -nsBMPDecoder::SetTargetSize(const nsIntSize& aSize) -{ - // Make sure the size is reasonable. - if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) { - return NS_ERROR_FAILURE; - } - - // Create a downscaler that we'll filter our output through. - mDownscaler.emplace(aSize); - - return NS_OK; -} - // Sets whether or not the BMP will use alpha data void nsBMPDecoder::SetUseAlphaData(bool useAlphaData) diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h index ad02e73123..f803302e01 100644 --- a/image/decoders/nsBMPDecoder.h +++ b/image/decoders/nsBMPDecoder.h @@ -9,7 +9,6 @@ #include "BMPFileHeaders.h" #include "Decoder.h" -#include "Downscaler.h" #include "gfxColor.h" #include "nsAutoPtr.h" @@ -25,8 +24,6 @@ class nsBMPDecoder : public Decoder public: ~nsBMPDecoder(); - nsresult SetTargetSize(const nsIntSize& aSize) override; - // Specifies whether or not the BMP file will contain alpha data // If set to true and the BMP is 32BPP, the alpha data will be // retrieved from the 4th byte of image data per pixel @@ -43,6 +40,7 @@ public: // Obtains the internal output image buffer uint32_t* GetImageData(); + size_t GetImageDataLength() const { return mImageDataLength; } // Obtains the size of the compressed image resource int32_t GetCompressedImageSize() const; @@ -77,8 +75,6 @@ private: char mRawBuf[BIH_INTERNAL_LENGTH::WIN_V3]; //< If this is changed, // WriteInternal() MUST be updated - Maybe mDownscaler; - uint32_t mLOH; //< Length of the header uint32_t mNumColors; //< The number of used colors, i.e. the number of diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp index 46bb35442e..76b6453ce0 100644 --- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -99,6 +99,40 @@ nsGIFDecoder2::~nsGIFDecoder2() moz_free(mGIFStruct.hold); } +uint8_t* +nsGIFDecoder2::GetCurrentRowBuffer() +{ + if (!mDownscaler) { + MOZ_ASSERT(!mDeinterlacer, "Deinterlacer without downscaler?"); + uint32_t bpp = mGIFStruct.images_decoded == 0 ? sizeof(uint32_t) + : sizeof(uint8_t); + return mImageData + mGIFStruct.irow * mGIFStruct.width * bpp; + } + + if (!mDeinterlacer) { + return mDownscaler->RowBuffer(); + } + + return mDeinterlacer->RowBuffer(mGIFStruct.irow); +} + +uint8_t* +nsGIFDecoder2::GetRowBuffer(uint32_t aRow) +{ + MOZ_ASSERT(mGIFStruct.images_decoded == 0, + "Calling GetRowBuffer on a frame other than the first suggests " + "we're deinterlacing animated frames"); + MOZ_ASSERT(!mDownscaler || mDeinterlacer, + "Can't get buffer for a specific row if downscaling " + "but not deinterlacing"); + + if (mDownscaler) { + return mDeinterlacer->RowBuffer(aRow); + } + + return mImageData + aRow * mGIFStruct.width * sizeof(uint32_t); +} + void nsGIFDecoder2::FinishInternal() { @@ -128,6 +162,15 @@ nsGIFDecoder2::FlushImageData(uint32_t fromRow, uint32_t rows) void nsGIFDecoder2::FlushImageData() { + if (mDownscaler) { + if (mDownscaler->HasInvalidation()) { + DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); + PostInvalidation(invalidRect.mOriginalSizeRect, + Some(invalidRect.mTargetSizeRect)); + } + return; + } + switch (mCurrentPass - mLastFlushedPass) { case 0: // same pass if (mCurrentRow - mLastFlushedRow) { @@ -202,21 +245,44 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) CheckForTransparency(frameRect); + // Make sure there's no animation if we're downscaling. + MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation()); + + IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() + : GetSize(); + + // Rescale the frame rect for the target size. + IntRect targetFrameRect = frameRect; + if (mDownscaler) { + IntSize originalSize = GetSize(); + targetFrameRect.ScaleRoundOut(double(targetSize.width) / originalSize.width, + double(targetSize.height) / originalSize.height); + } + // Use correct format, RGB for first frame, PAL for following frames // and include transparency to allow for optimization of opaque images nsresult rv = NS_OK; if (mGIFStruct.images_decoded) { // Image data is stored with original depth and palette. - rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(), - frameRect, format, aDepth); + rv = AllocateFrame(mGIFStruct.images_decoded, targetSize, + targetFrameRect, format, aDepth); } else { // Regardless of depth of input, the first frame is decoded into 24bit RGB. - rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(), - frameRect, format); + rv = AllocateFrame(mGIFStruct.images_decoded, targetSize, + targetFrameRect, format); } mCurrentFrameIndex = mGIFStruct.images_decoded; + if (NS_FAILED(rv)) { + return rv; + } + + if (mDownscaler) { + rv = mDownscaler->BeginFrame(frameRect.Size(), mImageData, + mGIFStruct.is_transparent); + } + return rv; } @@ -237,10 +303,15 @@ nsGIFDecoder2::EndImageFrame() // This will clear the remaining bits of the placeholder. (Bug 37589) const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset; if (realFrameHeight < mGIFStruct.screen_height) { - nsIntRect r(0, realFrameHeight, - mGIFStruct.screen_width, - mGIFStruct.screen_height - realFrameHeight); - PostInvalidation(r); + if (mDownscaler) { + IntRect targetRect = IntRect(IntPoint(), mDownscaler->TargetSize()); + PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect)); + } else { + nsIntRect r(0, realFrameHeight, + mGIFStruct.screen_width, + mGIFStruct.screen_height - realFrameHeight); + PostInvalidation(r); + } } // The first frame was preallocated with alpha; if it wasn't transparent, we @@ -291,8 +362,11 @@ nsGIFDecoder2::EndImageFrame() uint32_t nsGIFDecoder2::OutputRow() { - int drow_start, drow_end; - drow_start = drow_end = mGIFStruct.irow; + // Initialize the region in which we're duplicating rows (for the + // Haeberli-inspired hack below) to |irow|, which is the row we're writing to + // now. + int drow_start = mGIFStruct.irow; + int drow_end = mGIFStruct.irow; // Protect against too much image data if ((unsigned)drow_start >= mGIFStruct.height) { @@ -329,8 +403,7 @@ nsGIFDecoder2::OutputRow() } // Row to process - const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width; - uint8_t* rowp = mImageData + (mGIFStruct.irow * bpr); + uint8_t* rowp = GetCurrentRowBuffer(); // Convert color indices to Cairo pixels uint8_t* from = rowp + mGIFStruct.width; @@ -351,12 +424,24 @@ nsGIFDecoder2::OutputRow() } } - // Duplicate rows + // If we're downscaling but not deinterlacing, we're done with this row and + // can commit it now. Otherwise, we'll let Deinterlacer do the committing + // when we call PropagatePassToDownscaler() at the end of this pass. + if (mDownscaler && !mDeinterlacer) { + mDownscaler->CommitRow(); + } + if (drow_end > drow_start) { - // irow is the current row filled + // Duplicate rows if needed to reduce the "venetian blind" effect mentioned + // above. This writes out scanlines of the image in a way that isn't ordered + // vertically, which is incompatible with the filter that we use for + // downscale-during-decode, so we can't do this if we're downscaling. + MOZ_ASSERT_IF(mDownscaler, mDeinterlacer); + const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width; for (int r = drow_start; r <= drow_end; r++) { + // Skip the row we wrote to above; that's what we're copying *from*. if (r != int(mGIFStruct.irow)) { - memcpy(mImageData + (r * bpr), rowp, bpr); + memcpy(GetRowBuffer(r), rowp, bpr); } } } @@ -369,9 +454,12 @@ nsGIFDecoder2::OutputRow() } if (!mGIFStruct.interlaced) { + MOZ_ASSERT(!mDeinterlacer); mGIFStruct.irow++; } else { static const uint8_t kjump[5] = { 1, 8, 8, 4, 2 }; + int currentPass = mGIFStruct.ipass; + do { // Row increments resp. per 8,8,4,2 rows mGIFStruct.irow += kjump[mGIFStruct.ipass]; @@ -381,6 +469,15 @@ nsGIFDecoder2::OutputRow() mGIFStruct.ipass++; } } while (mGIFStruct.irow >= mGIFStruct.height); + + // We've finished a pass. If we're downscaling, it's time to propagate the + // rows we've decoded so far from our Deinterlacer to our Downscaler. + if (mGIFStruct.ipass > currentPass && mDownscaler) { + MOZ_ASSERT(mDeinterlacer); + mDeinterlacer->PropagatePassToDownscaler(*mDownscaler); + FlushImageData(); + mDownscaler->ResetForNextProgressivePass(); + } } return --mGIFStruct.rows_remaining; @@ -413,17 +510,13 @@ nsGIFDecoder2::DoLzw(const uint8_t* q) uint8_t* stack = mGIFStruct.stack; uint8_t* rowp = mGIFStruct.rowp; - uint32_t bpr = mGIFStruct.width; - if (!mGIFStruct.images_decoded) { - bpr *= sizeof(uint32_t); - } - uint8_t* rowend = mImageData + (bpr * mGIFStruct.irow) + mGIFStruct.width; + uint8_t* rowend = GetCurrentRowBuffer() + mGIFStruct.width; #define OUTPUT_ROW() \ PR_BEGIN_MACRO \ if (!OutputRow()) \ goto END; \ - rowp = mImageData + mGIFStruct.irow * bpr; \ + rowp = GetCurrentRowBuffer(); \ rowend = rowp + mGIFStruct.width; \ PR_END_MACRO @@ -835,10 +928,15 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) mGIFStruct.is_transparent = *q & 0x1; mGIFStruct.tpixel = q[3]; mGIFStruct.disposal_method = ((*q) >> 2) & 0x7; - // Some specs say 3rd bit (value 4), other specs say value 3 - // Let's choose 3 (the more popular) + if (mGIFStruct.disposal_method == 4) { + // Some specs say 3rd bit (value 4), other specs say value 3. + // Let's choose 3 (the more popular). mGIFStruct.disposal_method = 3; + } else if (mGIFStruct.disposal_method > 4) { + // This GIF is using a disposal method which is undefined in the spec. + // Treat it as DisposalMethod::NOT_SPECIFIED. + mGIFStruct.disposal_method = 0; } { @@ -929,12 +1027,19 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) // below, so that RasterImage can detect that this happened. PostIsAnimated(/* aFirstFrameTimeout = */ 0); } + if (IsFirstFrameDecode()) { // We're about to get a second frame, but we only want the first. Stop // decoding now. mGIFStruct.state = gif_done; break; } + + if (mDownscaler) { + MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode " + "for an animated image?"); + mDownscaler.reset(); + } } // Get image offsets, with respect to the screen origin @@ -1020,14 +1125,22 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) // offset. Otherwise, the area may never be refreshed and the // placeholder will remain on the screen. (Bug 37589) if (mGIFStruct.y_offset > 0) { - nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset); - PostInvalidation(r); + if (mDownscaler) { + IntRect targetRect = IntRect(IntPoint(), mDownscaler->TargetSize()); + PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect)); + } else { + nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset); + PostInvalidation(r); + } } } if (q[8] & 0x40) { mGIFStruct.interlaced = true; mGIFStruct.ipass = 1; + if (mDownscaler) { + mDeinterlacer.emplace(mDownscaler->OriginalSize()); + } } else { mGIFStruct.interlaced = false; mGIFStruct.ipass = 0; @@ -1039,7 +1152,7 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) // Clear state from last image mGIFStruct.irow = 0; mGIFStruct.rows_remaining = mGIFStruct.height; - mGIFStruct.rowp = mImageData; + mGIFStruct.rowp = GetCurrentRowBuffer(); // Depth of colors is determined by colormap // (q[8] & 0x80) indicates local colormap diff --git a/image/decoders/nsGIFDecoder2.h b/image/decoders/nsGIFDecoder2.h index 44aff60b8b..5a54b17599 100644 --- a/image/decoders/nsGIFDecoder2.h +++ b/image/decoders/nsGIFDecoder2.h @@ -8,7 +8,7 @@ #define mozilla_image_decoders_nsGIFDecoder2_h #include "Decoder.h" - +#include "Deinterlacer.h" #include "GIF2.h" #include "nsCOMPtr.h" @@ -34,6 +34,9 @@ private: // Decoders should only be instantiated via DecoderFactory. explicit nsGIFDecoder2(RasterImage* aImage); + uint8_t* GetCurrentRowBuffer(); + uint8_t* GetRowBuffer(uint32_t aRow); + // These functions will be called when the decoder has a decoded row, // frame size information, etc. void BeginGIF(); @@ -67,6 +70,7 @@ private: bool mSawTransparency; gif_struct mGIFStruct; + Maybe mDeinterlacer; }; } // namespace image diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index 0531395b59..b35351efc9 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -14,13 +14,14 @@ #include "RasterImage.h" +using namespace mozilla::gfx; + namespace mozilla { namespace image { -#define ICONCOUNTOFFSET 4 -#define DIRENTRYOFFSET 6 -#define BITMAPINFOSIZE 40 -#define PREFICONSIZE 16 +// Constants. +static const uint32_t ICOHEADERSIZE = 6; +static const uint32_t BITMAPINFOSIZE = 40; // ---------------------------------------- // Actual Data Processing @@ -57,22 +58,20 @@ nsICODecoder::GetNumColors() return numColors; } - nsICODecoder::nsICODecoder(RasterImage* aImage) - : Decoder(aImage) -{ - mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0; - mIsPNG = false; - mRow = nullptr; - mOldLine = mCurLine = 1; // Otherwise decoder will never start -} - -nsICODecoder::~nsICODecoder() -{ - if (mRow) { - moz_free(mRow); - } -} + : Decoder(aImage) + , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE)) + , mBiggestResourceColorDepth(0) + , mBestResourceDelta(INT_MIN) + , mBestResourceColorDepth(0) + , mNumIcons(0) + , mCurrIcon(0) + , mBPP(0) + , mMaskRowSize(0) + , mCurrMaskLine(0) + , mIsCursor(false) + , mHasMaskAlpha(false) +{ } void nsICODecoder::FinishInternal() @@ -80,11 +79,6 @@ nsICODecoder::FinishInternal() // We shouldn't be called in error cases MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!"); - // Finish the internally used decoder as well. - if (mContainedDecoder && !mContainedDecoder->HasError()) { - mContainedDecoder->FinishInternal(); - } - GetFinalStateFromContainedDecoder(); } @@ -101,6 +95,9 @@ nsICODecoder::GetFinalStateFromContainedDecoder() return; } + // Finish the internally used decoder. + mContainedDecoder->CompleteDecode(); + mDecodeDone = mContainedDecoder->GetDecodeDone(); mDataError = mDataError || mContainedDecoder->HasDataError(); mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError() @@ -109,6 +106,8 @@ nsICODecoder::GetFinalStateFromContainedDecoder() mProgress |= mContainedDecoder->TakeProgress(); mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); mCurrentFrame = mContainedDecoder->GetCurrentFrameRef(); + + MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete()); } // Returns a buffer filled with the bitmap file header in little endian: @@ -206,10 +205,11 @@ nsICODecoder::CheckAndFixBitmapSize(int8_t* bih) } // The BMP information header's bits per pixel should be trusted -// more than what we have. Usually the ICO's BPP is set to 0 +// more than what we have. Usually the ICO's BPP is set to 0. int32_t -nsICODecoder::ExtractBPPFromBitmap(int8_t* bih) +nsICODecoder::ReadBPP(const char* aBIH) { + const int8_t* bih = reinterpret_cast(aBIH); int32_t bitsPerPixel; memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel)); NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1); @@ -217,393 +217,514 @@ nsICODecoder::ExtractBPPFromBitmap(int8_t* bih) } int32_t -nsICODecoder::ExtractBIHSizeFromBitmap(int8_t* bih) +nsICODecoder::ReadBIHSize(const char* aBIH) { + const int8_t* bih = reinterpret_cast(aBIH); int32_t headerSize; memcpy(&headerSize, bih, sizeof(headerSize)); NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1); return headerSize; } -void -nsICODecoder::SetHotSpotIfCursor() +LexerTransition +nsICODecoder::ReadHeader(const char* aData) { - if (!mIsCursor) { - return; + // If the third byte is 1, this is an icon. If 2, a cursor. + if ((aData[2] != 1) && (aData[2] != 2)) { + return Transition::Terminate(ICOState::FAILURE); } + mIsCursor = (aData[2] == 2); + + // The fifth and sixth bytes specify the number of resources in the file. + mNumIcons = + LittleEndian::readUint16(reinterpret_cast(aData + 4)); + if (mNumIcons == 0) { + return Transition::Terminate(ICOState::SUCCESS); // Nothing to do. + } + + // Downscale-during-decode can end up decoding different resources in the ICO + // file depending on the target size. Since the resources are not necessarily + // scaled versions of the same image, some may be transparent and some may not + // be. We could be precise about transparency if we decoded the metadata of + // every resource, but for now we don't and it's safest to assume that + // transparency could be present. + PostHasTransparency(); - mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot); + return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); } -void -nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) +size_t +nsICODecoder::FirstResourceOffset() const { - MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!"); - MOZ_ASSERT(aBuffer); - MOZ_ASSERT(aCount > 0); + MOZ_ASSERT(mNumIcons > 0, + "Calling FirstResourceOffset before processing header"); - while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons. - if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor - if ((*aBuffer != 1) && (*aBuffer != 2)) { - PostDataError(); - return; - } - mIsCursor = (*aBuffer == 2); + // The first resource starts right after the directory, which starts right + // after the ICO header. + return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE; +} + +LexerTransition +nsICODecoder::ReadDirEntry(const char* aData) +{ + mCurrIcon++; + + // Read the directory entry. + IconDirEntry e; + memset(&e, 0, sizeof(e)); + memcpy(&e.mWidth, aData, sizeof(e.mWidth)); + memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight)); + memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount)); + memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved)); + memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes)); + e.mPlanes = LittleEndian::readUint16(&e.mPlanes); + memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount)); + e.mBitCount = LittleEndian::readUint16(&e.mBitCount); + memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes)); + e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes); + memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset)); + e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset); + + // Determine if this is the biggest resource we've seen so far. We always use + // the biggest resource for the intrinsic size, and if we're not downscaling, + // we select it as the best resource as well. + IntSize entrySize(GetRealWidth(e), GetRealHeight(e)); + if (e.mBitCount >= mBiggestResourceColorDepth && + entrySize.width * entrySize.height >= + mBiggestResourceSize.width * mBiggestResourceSize.height) { + mBiggestResourceSize = entrySize; + mBiggestResourceColorDepth = e.mBitCount; + mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot); + + if (!mDownscaler) { + mDirEntry = e; } - mPos++; aBuffer++; aCount--; } - if (mPos == ICONCOUNTOFFSET && aCount >= 2) { - mNumIcons = - LittleEndian::readUint16(reinterpret_cast(aBuffer)); - aBuffer += 2; - mPos += 2; - aCount -= 2; + if (mDownscaler) { + // Calculate the delta between this resource's size and the desired size, so + // we can see if it is better than our current-best option. In the case of + // several equally-good resources, we use the last one. "Better" in this + // case is determined by |delta|, a measure of the difference in size + // between the entry we've found and the downscaler's target size. We will + // choose the smallest resource that is >= the target size (i.e. we assume + // it's better to downscale a larger icon than to upscale a smaller one). + IntSize desiredSize = mDownscaler->TargetSize(); + int32_t delta = entrySize.width - desiredSize.width + + entrySize.height - desiredSize.height; + if (e.mBitCount >= mBestResourceColorDepth && + ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) || + (delta >= 0 && delta <= mBestResourceDelta))) { + mBestResourceDelta = delta; + mBestResourceColorDepth = e.mBitCount; + mDirEntry = e; + } } - if (mNumIcons == 0) { - return; // Nothing to do. - } + if (mCurrIcon == mNumIcons) { + // Ensure the resource we selected has an offset past the ICO headers. + if (mDirEntry.mImageOffset < FirstResourceOffset()) { + return Transition::Terminate(ICOState::FAILURE); + } + + // If this is a cursor, set the hotspot. We use the hotspot from the biggest + // resource since we also use that resource for the intrinsic size. + if (mIsCursor) { + mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width, + mBiggestResourceHotSpot.height); + } + + // We always report the biggest resource's size as the intrinsic size; this + // is necessary for downscale-during-decode to work since we won't even + // attempt to *upscale* while decoding. + PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height); + if (IsMetadataDecode()) { + return Transition::Terminate(ICOState::SUCCESS); + } - uint16_t colorDepth = 0; + // If the resource we selected matches the downscaler's target size + // perfectly, we don't need to do any downscaling. + if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) { + mDownscaler.reset(); + } - // If we didn't get a #-moz-resolution, default to PREFICONSIZE. - if (mResolution.width == 0 && mResolution.height == 0) { - mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE); + size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset(); + return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, + ICOState::SKIP_TO_RESOURCE, + offsetToResource); } - // A measure of the difference in size between the entry we've found - // and the requested size. We will choose the smallest image that is - // >= requested size (i.e. we assume it's better to downscale a larger - // icon than to upscale a smaller one). - int32_t diff = INT_MIN; + return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); +} - // Loop through each entry's dir entry - while (mCurrIcon < mNumIcons) { - if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) && - mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) { - uint32_t toCopy = sizeof(mDirEntryArray) - - (mPos - DIRENTRYOFFSET - mCurrIcon * - sizeof(mDirEntryArray)); - if (toCopy > aCount) { - toCopy = aCount; - } - memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy); - mPos += toCopy; - aCount -= toCopy; - aBuffer += toCopy; +LexerTransition +nsICODecoder::SniffResource(const char* aData) +{ + // We use the first PNGSIGNATURESIZE bytes to determine whether this resource + // is a PNG or a BMP. + bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes, + PNGSIGNATURESIZE); + if (isPNG) { + // Create a PNG decoder which will do the rest of the work for us. + mContainedDecoder = new nsPNGDecoder(mImage); + mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); + mContainedDecoder->SetDecoderFlags(GetDecoderFlags()); + mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags()); + if (mDownscaler) { + mContainedDecoder->SetTargetSize(mDownscaler->TargetSize()); } - if (aCount == 0) { - return; // Need more data + mContainedDecoder->Init(); + + if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) { + return Transition::Terminate(ICOState::FAILURE); } - IconDirEntry e; - if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) + - (mCurrIcon * sizeof(mDirEntryArray))) { - mCurrIcon++; - ProcessDirEntry(e); - // We can't use GetRealWidth and GetRealHeight here because those operate - // on mDirEntry, here we are going through each item in the directory. - // Calculate the delta between this image's size and the desired size, - // so we can see if it is better than our current-best option. - // In the case of several equally-good images, we use the last one. - int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - mResolution.width + - (e.mHeight == 0 ? 256 : e.mHeight) - mResolution.height; - if (e.mBitCount >= colorDepth && - ((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) { - diff = delta; - mImageOffset = e.mImageOffset; - - // ensure mImageOffset is >= size of the direntry headers (bug #245631) - uint32_t minImageOffset = DIRENTRYOFFSET + - mNumIcons * sizeof(mDirEntryArray); - if (mImageOffset < minImageOffset) { - PostDataError(); - return; - } - - colorDepth = e.mBitCount; - memcpy(&mDirEntry, &e, sizeof(IconDirEntry)); - } + if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) { + return Transition::Terminate(ICOState::FAILURE); } - } - if (mPos < mImageOffset) { - // Skip to (or at least towards) the desired image offset - uint32_t toSkip = mImageOffset - mPos; - if (toSkip > aCount) { - toSkip = aCount; + // Read in the rest of the PNG unbuffered. + size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE; + return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, + ICOState::READ_PNG, + toRead); + } else { + // Create a BMP decoder which will do most of the work for us; the exception + // is the AND mask, which isn't present in standalone BMPs. + nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage); + mContainedDecoder = bmpDecoder; + bmpDecoder->SetUseAlphaData(true); + mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); + mContainedDecoder->SetDecoderFlags(GetDecoderFlags()); + mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags()); + if (mDownscaler) { + mContainedDecoder->SetTargetSize(mDownscaler->TargetSize()); + } + mContainedDecoder->Init(); + + // Make sure we have a sane size for the bitmap information header. + int32_t bihSize = ReadBIHSize(aData); + if (bihSize != static_cast(BITMAPINFOSIZE)) { + return Transition::Terminate(ICOState::FAILURE); } - mPos += toSkip; - aBuffer += toSkip; - aCount -= toSkip; + // Buffer the first part of the bitmap information header. + memcpy(mBIHraw, aData, PNGSIGNATURESIZE); + + // Read in the rest of the bitmap information header. + return Transition::To(ICOState::READ_BIH, + BITMAPINFOSIZE - PNGSIGNATURESIZE); } +} - // If we are within the first PNGSIGNATURESIZE bytes of the image data, - // then we have either a BMP or a PNG. We use the first PNGSIGNATURESIZE - // bytes to determine which one we have. - if (mCurrIcon == mNumIcons && mPos >= mImageOffset && - mPos < mImageOffset + PNGSIGNATURESIZE) { - uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset); - if (toCopy > aCount) { - toCopy = aCount; - } +LexerTransition +nsICODecoder::ReadPNG(const char* aData, uint32_t aLen) +{ + if (!WriteToContainedDecoder(aData, aLen)) { + return Transition::Terminate(ICOState::FAILURE); + } - memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy); - mPos += toCopy; - aCount -= toCopy; - aBuffer += toCopy; - - mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes, - PNGSIGNATURESIZE); - if (mIsPNG) { - mContainedDecoder = new nsPNGDecoder(mImage); - mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); - mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations); - if (mFirstFrameDecode) { - mContainedDecoder->SetIsFirstFrameDecode(); - } - mContainedDecoder->Init(); - if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) { - return; - } - } + // Raymond Chen says that 32bpp only are valid PNG ICOs + // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx + if (!static_cast(mContainedDecoder.get())->IsValidICO()) { + return Transition::Terminate(ICOState::FAILURE); } - // If we have a PNG, let the PNG decoder do all of the rest of the work - if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) { - if (!WriteToContainedDecoder(aBuffer, aCount)) { - return; - } + return Transition::ContinueUnbuffered(ICOState::READ_PNG); +} - if (!HasSize() && mContainedDecoder->HasSize()) { - nsIntSize size = mContainedDecoder->GetSize(); - PostSize(size.width, size.height); - } +LexerTransition +nsICODecoder::ReadBIH(const char* aData) +{ + // Buffer the rest of the bitmap information header. + memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE); + + // Extracting the BPP from the BIH header; it should be trusted over the one + // we have from the ICO header. + mBPP = ReadBPP(mBIHraw); + + // The ICO format when containing a BMP does not include the 14 byte + // bitmap file header. To use the code of the BMP decoder we need to + // generate this header ourselves and feed it to the BMP decoder. + int8_t bfhBuffer[BMPFILEHEADERSIZE]; + if (!FillBitmapFileHeaderBuffer(bfhBuffer)) { + return Transition::Terminate(ICOState::FAILURE); + } - mPos += aCount; - aBuffer += aCount; - aCount = 0; + if (!WriteToContainedDecoder(reinterpret_cast(bfhBuffer), + sizeof(bfhBuffer))) { + return Transition::Terminate(ICOState::FAILURE); + } - // Raymond Chen says that 32bpp only are valid PNG ICOs - // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx - if (!IsMetadataDecode() && - !static_cast(mContainedDecoder.get())->IsValidICO()) { - PostDataError(); - } - return; + // Verify that the BIH width and height values match the ICO directory entry, + // and fix the BIH height value to compensate for the fact that the underlying + // BMP decoder doesn't know about AND masks. + if (!CheckAndFixBitmapSize(reinterpret_cast(mBIHraw))) { + return Transition::Terminate(ICOState::FAILURE); + } + + // Write out the BMP's bitmap info header. + if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) { + return Transition::Terminate(ICOState::FAILURE); } - // We've processed all of the icon dir entries and are within the - // bitmap info size - if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset && - mPos >= mImageOffset + PNGSIGNATURESIZE && - mPos < mImageOffset + BITMAPINFOSIZE) { + // Sometimes the ICO BPP header field is not filled out so we should trust the + // contained resource over our own information. + // XXX(seth): Is this ever different than the value we obtained from + // ReadBPP() above? + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + mBPP = bmpDecoder->GetBitsPerPixel(); + + // Check to make sure we have valid color settings. + uint16_t numColors = GetNumColors(); + if (numColors == uint16_t(-1)) { + return Transition::Terminate(ICOState::FAILURE); + } - // As we were decoding, we did not know if we had a PNG signature or the - // start of a bitmap information header. At this point we know we had - // a bitmap information header and not a PNG signature, so fill the bitmap - // information header with the data it should already have. - memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE); + // Do we have an AND mask on this BMP? If so, we need to read it after we read + // the BMP data itself. + uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors; + bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes; + ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK + : ICOState::FINISHED_RESOURCE; + + // Read in the rest of the BMP unbuffered. + return Transition::ToUnbuffered(afterBMPState, + ICOState::READ_BMP, + bmpDataLength); +} - // We've found the icon. - uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset); - if (toCopy > aCount) { - toCopy = aCount; - } +LexerTransition +nsICODecoder::ReadBMP(const char* aData, uint32_t aLen) +{ + if (!WriteToContainedDecoder(aData, aLen)) { + return Transition::Terminate(ICOState::FAILURE); + } + + return Transition::ContinueUnbuffered(ICOState::READ_BMP); +} - memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy); - mPos += toCopy; - aCount -= toCopy; - aBuffer += toCopy; +LexerTransition +nsICODecoder::PrepareForMask() +{ + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + + uint16_t numColors = GetNumColors(); + MOZ_ASSERT(numColors != uint16_t(-1)); + + // Determine the length of the AND mask. + uint32_t bmpLengthWithHeader = + BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors; + MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes); + uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader; + + // If we have a 32-bpp BMP with alpha data, we ignore the AND mask. We can + // also obviously ignore it if the image has zero width or zero height. + if ((bmpDecoder->GetBitsPerPixel() == 32 && bmpDecoder->HasAlphaData()) || + GetRealWidth() == 0 || GetRealHeight() == 0) { + return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, + ICOState::SKIP_MASK, + maskLength); } - // If we have a BMP inside the ICO and we have read the BIH header - if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) { + // Compute the row size for the mask. + mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up - // Make sure we have a sane value for the bitmap information header - int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast - (mBIHraw)); - if (bihSize != BITMAPINFOSIZE) { - PostDataError(); - return; - } - // We are extracting the BPP from the BIH header as it should be trusted - // over the one we have from the icon header - mBPP = ExtractBPPFromBitmap(reinterpret_cast(mBIHraw)); + // If the expected size of the AND mask is larger than its actual size, then + // we must have a truncated (and therefore corrupt) AND mask. + uint32_t expectedLength = mMaskRowSize * GetRealHeight(); + if (maskLength < expectedLength) { + return Transition::Terminate(ICOState::FAILURE); + } - // Init the bitmap decoder which will do most of the work for us - // It will do everything except the AND mask which isn't present in bitmaps - // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder - nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage); - mContainedDecoder = bmpDecoder; - bmpDecoder->SetUseAlphaData(true); - mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); - mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations); - if (mFirstFrameDecode) { - mContainedDecoder->SetIsFirstFrameDecode(); + // If we're downscaling, the mask is the wrong size for the surface we've + // produced, so we need to downscale the mask into a temporary buffer and then + // combine the mask's alpha values with the color values from the image. + if (mDownscaler) { + MOZ_ASSERT(bmpDecoder->GetImageDataLength() == + mDownscaler->TargetSize().width * + mDownscaler->TargetSize().height * + sizeof(uint32_t)); + mMaskBuffer = MakeUnique(bmpDecoder->GetImageDataLength()); + nsresult rv = mDownscaler->BeginFrame(GetRealSize(), + mMaskBuffer.get(), + /* aHasAlpha = */ true, + /* aFlipVertically = */ true); + if (NS_FAILED(rv)) { + return Transition::Terminate(ICOState::FAILURE); } - mContainedDecoder->Init(); + } - // The ICO format when containing a BMP does not include the 14 byte - // bitmap file header. To use the code of the BMP decoder we need to - // generate this header ourselves and feed it to the BMP decoder. - int8_t bfhBuffer[BMPFILEHEADERSIZE]; - if (!FillBitmapFileHeaderBuffer(bfhBuffer)) { - PostDataError(); - return; + mCurrMaskLine = GetRealHeight(); + return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); +} + + +LexerTransition +nsICODecoder::ReadMaskRow(const char* aData) +{ + mCurrMaskLine--; + + uint8_t sawTransparency = 0; + + // Get the mask row we're reading. + const uint8_t* mask = reinterpret_cast(aData); + const uint8_t* maskRowEnd = mask + mMaskRowSize; + + // Get the corresponding row of the mask buffer (if we're downscaling) or the + // decoded image data (if we're not). + uint32_t* decoded = nullptr; + if (mDownscaler) { + // Initialize the row to all white and fully opaque. + memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t)); + + decoded = reinterpret_cast(mDownscaler->RowBuffer()); + } else { + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + uint32_t* imageData = bmpDecoder->GetImageData(); + if (!imageData) { + return Transition::Terminate(ICOState::FAILURE); } - if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) { - return; + + decoded = imageData + mCurrMaskLine * GetRealWidth(); + } + + MOZ_ASSERT(decoded); + uint32_t* decodedRowEnd = decoded + GetRealWidth(); + + // Iterate simultaneously through the AND mask and the image data. + while (mask < maskRowEnd) { + uint8_t idx = *mask++; + sawTransparency |= idx; + for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) { + // Clear pixel completely for transparency. + if (idx & bit) { + *decoded = 0; + } + decoded++; } + } + + if (mDownscaler) { + mDownscaler->CommitRow(); + } + + // If any bits are set in sawTransparency, then we know at least one pixel was + // transparent. + if (sawTransparency) { + mHasMaskAlpha = true; + } - // Setup the cursor hot spot if one is present - SetHotSpotIfCursor(); + if (mCurrMaskLine == 0) { + return Transition::To(ICOState::FINISH_MASK, 0); + } + + return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); +} - // Verify that the BIH width and height values match the ICO directory entry, - // and fix the BIH height value to compensate for the fact that the underlying - // BMP decoder doesn't know about AND masks. - if (!CheckAndFixBitmapSize(reinterpret_cast(mBIHraw))) { - PostDataError(); - return; +LexerTransition +nsICODecoder::FinishMask() +{ + // If we're downscaling, we now have the appropriate alpha values in + // mMaskBuffer. We just need to transfer them to the image. + if (mDownscaler) { + // Retrieve the image data. + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + uint8_t* imageData = reinterpret_cast(bmpDecoder->GetImageData()); + if (!imageData) { + return Transition::Terminate(ICOState::FAILURE); } - // Write out the BMP's bitmap info header - if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) { - return; + // Iterate through the alpha values, copying from mask to image. + MOZ_ASSERT(mMaskBuffer); + MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0); + for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) { + imageData[i] = mMaskBuffer[i]; } + } - nsIntSize size = mContainedDecoder->GetSize(); - PostSize(size.width, size.height); + // If the mask contained any transparent pixels, record that fact. + if (mHasMaskAlpha) { + PostHasTransparency(); - // We have the size. If we're doing a metadata decode, we're done. - if (IsMetadataDecode()) { - return; - } + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + bmpDecoder->SetHasAlphaData(); + } - // Sometimes the ICO BPP header field is not filled out - // so we should trust the contained resource over our own - // information. - mBPP = bmpDecoder->GetBitsPerPixel(); + return Transition::To(ICOState::FINISHED_RESOURCE, 0); +} - // Check to make sure we have valid color settings - uint16_t numColors = GetNumColors(); - if (numColors == (uint16_t)-1) { - PostDataError(); - return; - } +LexerTransition +nsICODecoder::FinishResource() +{ + // Make sure the actual size of the resource matches the size in the directory + // entry. If not, we consider the image corrupt. + if (mContainedDecoder->HasSize() && + mContainedDecoder->GetSize() != GetRealSize()) { + return Transition::Terminate(ICOState::FAILURE); } - // If we have a BMP - if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) { - uint16_t numColors = GetNumColors(); - if (numColors == (uint16_t)-1) { - PostDataError(); - return; - } - // Feed the actual image data (not including headers) into the BMP decoder - uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE; - uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE + - static_cast(mContainedDecoder.get())-> - GetCompressedImageSize() + - 4 * numColors; - - // If we are feeding in the core image data, but we have not yet - // reached the ICO's 'AND buffer mask' - if (mPos >= bmpDataOffset && mPos < bmpDataEnd) { - - // Figure out how much data the BMP decoder wants - uint32_t toFeed = bmpDataEnd - mPos; - if (toFeed > aCount) { - toFeed = aCount; - } + return Transition::Terminate(ICOState::SUCCESS); +} - if (!WriteToContainedDecoder(aBuffer, toFeed)) { - return; +void +nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!"); + MOZ_ASSERT(aBuffer); + MOZ_ASSERT(aCount > 0); + + Maybe terminalState = + mLexer.Lex(aBuffer, aCount, + [=](ICOState aState, const char* aData, size_t aLength) { + switch (aState) { + case ICOState::HEADER: + return ReadHeader(aData); + case ICOState::DIR_ENTRY: + return ReadDirEntry(aData); + case ICOState::SKIP_TO_RESOURCE: + return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE); + case ICOState::FOUND_RESOURCE: + return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE); + case ICOState::SNIFF_RESOURCE: + return SniffResource(aData); + case ICOState::READ_PNG: + return ReadPNG(aData, aLength); + case ICOState::READ_BIH: + return ReadBIH(aData); + case ICOState::READ_BMP: + return ReadBMP(aData, aLength); + case ICOState::PREPARE_FOR_MASK: + return PrepareForMask(); + case ICOState::READ_MASK_ROW: + return ReadMaskRow(aData); + case ICOState::FINISH_MASK: + return FinishMask(); + case ICOState::SKIP_MASK: + return Transition::ContinueUnbuffered(ICOState::SKIP_MASK); + case ICOState::FINISHED_RESOURCE: + return FinishResource(); + default: + MOZ_ASSERT_UNREACHABLE("Unknown ICOState"); + return Transition::Terminate(ICOState::FAILURE); } + }); - mPos += toFeed; - aCount -= toFeed; - aBuffer += toFeed; - } + if (!terminalState) { + return; // Need more data. + } - // If the bitmap is fully processed, treat any left over data as the ICO's - // 'AND buffer mask' which appears after the bitmap resource. - if (!mIsPNG && mPos >= bmpDataEnd) { - nsRefPtr bmpDecoder = - static_cast(mContainedDecoder.get()); - - // There may be an optional AND bit mask after the data. This is - // only used if the alpha data is not already set. The alpha data - // is used for 32bpp bitmaps as per the comment in ICODecoder.h - // The alpha mask should be checked in all other cases. - if (bmpDecoder->GetBitsPerPixel() != 32 || !bmpDecoder->HasAlphaData()) { - uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up - if (mPos == bmpDataEnd) { - mPos++; - mRowBytes = 0; - mCurLine = GetRealHeight(); - mRow = (uint8_t*)moz_realloc(mRow, rowSize); - if (!mRow) { - PostDecoderError(NS_ERROR_OUT_OF_MEMORY); - return; - } - } - - // Ensure memory has been allocated before decoding. - MOZ_ASSERT(mRow, "mRow is null"); - if (!mRow) { - PostDataError(); - return; - } - - uint8_t sawTransparency = 0; - - while (mCurLine > 0 && aCount > 0) { - uint32_t toCopy = std::min(rowSize - mRowBytes, aCount); - if (toCopy) { - memcpy(mRow + mRowBytes, aBuffer, toCopy); - aCount -= toCopy; - aBuffer += toCopy; - mRowBytes += toCopy; - } - if (rowSize == mRowBytes) { - mCurLine--; - mRowBytes = 0; - - uint32_t* imageData = bmpDecoder->GetImageData(); - if (!imageData) { - PostDataError(); - return; - } - uint32_t* decoded = imageData + mCurLine * GetRealWidth(); - uint32_t* decoded_end = decoded + GetRealWidth(); - uint8_t* p = mRow; - uint8_t* p_end = mRow + rowSize; - while (p < p_end) { - uint8_t idx = *p++; - sawTransparency |= idx; - for (uint8_t bit = 0x80; bit && decoded>= 1) { - // Clear pixel completely for transparency. - if (idx & bit) { - *decoded = 0; - } - decoded++; - } - } - } - } - - // If any bits are set in sawTransparency, then we know at least one - // pixel was transparent. - if (sawTransparency) { - PostHasTransparency(); - bmpDecoder->SetHasAlphaData(); - } - } - } + if (*terminalState == ICOState::FAILURE) { + PostDataError(); + return; } + + MOZ_ASSERT(*terminalState == ICOState::SUCCESS); } bool @@ -613,7 +734,7 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount) mProgress |= mContainedDecoder->TakeProgress(); mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); if (mContainedDecoder->HasDataError()) { - mDataError = mContainedDecoder->HasDataError(); + PostDataError(); } if (mContainedDecoder->HasDecoderError()) { PostDecoderError(mContainedDecoder->GetDecoderError()); @@ -621,24 +742,5 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount) return !HasError(); } -void -nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget) -{ - memset(&aTarget, 0, sizeof(aTarget)); - memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth)); - memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight)); - memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount)); - memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved)); - memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes)); - aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes); - memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount)); - aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount); - memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes)); - aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes); - memcpy(&aTarget.mImageOffset, mDirEntryArray + 12, - sizeof(aTarget.mImageOffset)); - aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset); -} - } // namespace image } // namespace mozilla diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h index 27bf7e6055..ad7f63b5d7 100644 --- a/image/decoders/nsICODecoder.h +++ b/image/decoders/nsICODecoder.h @@ -8,6 +8,7 @@ #define mozilla_image_decoders_nsICODecoder_h #include "nsAutoPtr.h" +#include "StreamingLexer.h" #include "Decoder.h" #include "imgFrame.h" #include "nsBMPDecoder.h" @@ -19,28 +20,57 @@ namespace image { class RasterImage; +enum class ICOState +{ + SUCCESS, + FAILURE, + HEADER, + DIR_ENTRY, + SKIP_TO_RESOURCE, + FOUND_RESOURCE, + SNIFF_RESOURCE, + READ_PNG, + READ_BIH, + READ_BMP, + PREPARE_FOR_MASK, + READ_MASK_ROW, + FINISH_MASK, + SKIP_MASK, + FINISHED_RESOURCE +}; + class nsICODecoder : public Decoder { public: - virtual ~nsICODecoder(); + virtual ~nsICODecoder() { } - // Obtains the width of the icon directory entry - uint32_t GetRealWidth() const + /// @return the width of the icon directory entry @aEntry. + static uint32_t GetRealWidth(const IconDirEntry& aEntry) { - return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth; + return aEntry.mWidth == 0 ? 256 : aEntry.mWidth; } - // Obtains the height of the icon directory entry - uint32_t GetRealHeight() const + /// @return the width of the selected directory entry (mDirEntry). + uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); } + + /// @return the height of the icon directory entry @aEntry. + static uint32_t GetRealHeight(const IconDirEntry& aEntry) { - return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight; + return aEntry.mHeight == 0 ? 256 : aEntry.mHeight; } - virtual void SetResolution(const gfx::IntSize& aResolution) override + /// @return the height of the selected directory entry (mDirEntry). + uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); } + + /// @return the size of the selected directory entry (mDirEntry). + gfx::IntSize GetRealSize() const { - mResolution = aResolution; + return gfx::IntSize(GetRealWidth(), GetRealHeight()); } + /// @return The offset from the beginning of the ICO to the first resource. + size_t FirstResourceOffset() const; + virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override; virtual void FinishInternal() override; virtual void FinishWithErrorInternal() override; @@ -58,10 +88,6 @@ private: // Gets decoder state from the contained decoder so it's visible externally. void GetFinalStateFromContainedDecoder(); - // Processes a single dir entry of the icon resource - void ProcessDirEntry(IconDirEntry& aTarget); - // Sets the hotspot property of if we have a cursor - void SetHotSpotIfCursor(); // Creates a bitmap file header buffer, returns true if successful bool FillBitmapFileHeaderBuffer(int8_t* bfh); /** @@ -74,36 +100,42 @@ private: */ bool CheckAndFixBitmapSize(int8_t* aBIH); // Extract bitmap info header size count from BMP information header - int32_t ExtractBIHSizeFromBitmap(int8_t* bih); + int32_t ReadBIHSize(const char* aBIH); // Extract bit count from BMP information header - int32_t ExtractBPPFromBitmap(int8_t* bih); + int32_t ReadBPP(const char* aBIH); // Calculates the row size in bytes for the AND mask table uint32_t CalcAlphaRowSize(); // Obtains the number of colors from the BPP, mBPP must be filled in uint16_t GetNumColors(); - gfx::IntSize mResolution; // The requested -moz-resolution for this icon. - uint16_t mBPP; // Stores the images BPP - uint32_t mPos; // Keeps track of the position we have decoded up until - uint16_t mNumIcons; // Stores the number of icons in the ICO file - uint16_t mCurrIcon; // Stores the current dir entry index we are processing - uint32_t mImageOffset; // Stores the offset of the image data we want - uint8_t* mRow; // Holds one raw line of the image - int32_t mCurLine; // Line index of the image that's currently being decoded - uint32_t mRowBytes; // How many bytes of the row were already received - int32_t mOldLine; // Previous index of the line - nsRefPtr mContainedDecoder; // Contains either a BMP or PNG resource - - char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer - IconDirEntry mDirEntry; // Holds a decoded dir entry - // Holds the potential bytes that can be a PNG signature - char mSignature[PNGSIGNATURESIZE]; - // Holds the potential bytes for a bitmap information header - char mBIHraw[40]; - // Stores whether or not the icon file we are processing has type 1 (icon) - bool mIsCursor; - // Stores whether or not the contained resource is a PNG - bool mIsPNG; + LexerTransition ReadHeader(const char* aData); + LexerTransition ReadDirEntry(const char* aData); + LexerTransition SniffResource(const char* aData); + LexerTransition ReadPNG(const char* aData, uint32_t aLen); + LexerTransition ReadBIH(const char* aData); + LexerTransition ReadBMP(const char* aData, uint32_t aLen); + LexerTransition PrepareForMask(); + LexerTransition ReadMaskRow(const char* aData); + LexerTransition FinishMask(); + LexerTransition FinishResource(); + + StreamingLexer mLexer; // The lexer. + nsRefPtr mContainedDecoder; // Either a BMP or PNG decoder. + UniquePtr mMaskBuffer; // A temporary buffer for the alpha mask. + char mBIHraw[40]; // The bitmap information header. + IconDirEntry mDirEntry; // The dir entry for the selected resource. + gfx::IntSize mBiggestResourceSize; // Used to select the intrinsic size. + gfx::IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size. + uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size. + int32_t mBestResourceDelta; // Used to select the best resource. + uint16_t mBestResourceColorDepth; // Used to select the best resource. + uint16_t mNumIcons; // Stores the number of icons in the ICO file. + uint16_t mCurrIcon; // Stores the current dir entry index we are processing. + uint16_t mBPP; // The BPP of the resource we're decoding. + uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask. + uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing. + bool mIsCursor; // Is this ICO a cursor? + bool mHasMaskAlpha; // Did the BMP alpha mask have any transparency? }; } // namespace image diff --git a/image/decoders/nsIconDecoder.cpp b/image/decoders/nsIconDecoder.cpp index a14a4add05..55666f67dd 100644 --- a/image/decoders/nsIconDecoder.cpp +++ b/image/decoders/nsIconDecoder.cpp @@ -12,15 +12,20 @@ #include "RasterImage.h" #include +using namespace mozilla::gfx; + +using std::min; + namespace mozilla { namespace image { nsIconDecoder::nsIconDecoder(RasterImage* aImage) - : Decoder(aImage), - mWidth(-1), - mHeight(-1), - mPixBytesRead(0), - mState(iconStateStart) + : Decoder(aImage) + , mExpectedDataLength(0) + , mPixBytesRead(0) + , mState(iconStateStart) + , mWidth(-1) + , mHeight(-1) { // Nothing to do } @@ -33,10 +38,6 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) { MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!"); - // We put this here to avoid errors about crossing initialization with case - // jumps on linux. - uint32_t bytesToRead = 0; - // Loop until the input data is gone while (aCount > 0) { switch (mState) { @@ -73,9 +74,16 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) break; } + // The input is 32bpp, so we expect 4 bytes of data per pixel. + mExpectedDataLength = mWidth * mHeight * 4; + { MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); - nsresult rv = AllocateBasicFrame(); + IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() + : GetSize(); + nsresult rv = AllocateFrame(0, targetSize, + IntRect(IntPoint(), targetSize), + gfx::SurfaceFormat::B8G8R8A8); if (NS_FAILED(rv)) { mState = iconStateFinished; return; @@ -84,6 +92,16 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_ASSERT(mImageData, "Should have a buffer now"); + if (mDownscaler) { + nsresult rv = mDownscaler->BeginFrame(GetSize(), + mImageData, + /* aHasAlpha = */ true); + if (NS_FAILED(rv)) { + mState = iconStateFinished; + return; + } + } + // Book Keeping aBuffer++; aCount--; @@ -93,25 +111,60 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount) case iconStateReadPixels: { // How many bytes are we reading? - bytesToRead = std::min(aCount, mImageDataLength - mPixBytesRead); - - // Copy the bytes - memcpy(mImageData + mPixBytesRead, aBuffer, bytesToRead); + uint32_t bytesToRead = min(aCount, mExpectedDataLength - mPixBytesRead); + + if (mDownscaler) { + uint8_t* row = mDownscaler->RowBuffer(); + const uint32_t bytesPerRow = mWidth * 4; + const uint32_t rowOffset = mPixBytesRead % bytesPerRow; + + // Update global state; we're about to read |bytesToRead| bytes. + aCount -= bytesToRead; + mPixBytesRead += bytesToRead; + + if (rowOffset > 0) { + // Finish the current row. + const uint32_t remaining = bytesPerRow - rowOffset; + memcpy(row + rowOffset, aBuffer, remaining); + aBuffer += remaining; + bytesToRead -= remaining; + mDownscaler->CommitRow(); + } - // Performance isn't critical here, so our update rectangle is - // always the full icon - nsIntRect r(0, 0, mWidth, mHeight); + // Copy the bytes a row at a time. + while (bytesToRead > bytesPerRow) { + memcpy(row, aBuffer, bytesPerRow); + aBuffer += bytesPerRow; + bytesToRead -= bytesPerRow; + mDownscaler->CommitRow(); + } - // Invalidate - PostInvalidation(r); + // Copy any leftover bytes. (Leaving the current row incomplete.) + if (bytesToRead > 0) { + memcpy(row, aBuffer, bytesToRead); + aBuffer += bytesPerRow; + bytesToRead -= bytesPerRow; + } - // Book Keeping - aBuffer += bytesToRead; - aCount -= bytesToRead; - mPixBytesRead += bytesToRead; + if (mDownscaler->HasInvalidation()) { + DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); + PostInvalidation(invalidRect.mOriginalSizeRect, + Some(invalidRect.mTargetSizeRect)); + } + } else { + // Copy all the bytes at once. + memcpy(mImageData + mPixBytesRead, aBuffer, bytesToRead); + aBuffer += bytesToRead; + aCount -= bytesToRead; + mPixBytesRead += bytesToRead; + + // Invalidate. Performance isn't critical here, so our update + // rectangle is always the full icon. + PostInvalidation(IntRect(0, 0, mWidth, mHeight)); + } // If we've got all the pixel bytes, we're finished - if (mPixBytesRead == mImageDataLength) { + if (mPixBytesRead == mExpectedDataLength) { PostFrameStop(); PostDecodeDone(); mState = iconStateFinished; diff --git a/image/decoders/nsIconDecoder.h b/image/decoders/nsIconDecoder.h index 78bef54671..45aadd19d5 100644 --- a/image/decoders/nsIconDecoder.h +++ b/image/decoders/nsIconDecoder.h @@ -47,11 +47,11 @@ private: // Decoders should only be instantiated via DecoderFactory. explicit nsIconDecoder(RasterImage* aImage); -public: - uint8_t mWidth; - uint8_t mHeight; + uint32_t mExpectedDataLength; uint32_t mPixBytesRead; uint32_t mState; + uint8_t mWidth; + uint8_t mHeight; }; enum { diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp index ed1eb9aa83..61d32c0d53 100644 --- a/image/decoders/nsJPEGDecoder.cpp +++ b/image/decoders/nsJPEGDecoder.cpp @@ -136,25 +136,11 @@ nsJPEGDecoder::SpeedHistogram() return Telemetry::IMAGE_DECODE_SPEED_JPEG; } -nsresult -nsJPEGDecoder::SetTargetSize(const nsIntSize& aSize) -{ - // Make sure the size is reasonable. - if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) { - return NS_ERROR_FAILURE; - } - - // Create a downscaler that we'll filter our output through. - mDownscaler.emplace(aSize); - - return NS_OK; -} - void nsJPEGDecoder::InitInternal() { mCMSMode = gfxPlatform::GetCMSMode(); - if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) { + if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) { mCMSMode = eCMSMode_Off; } diff --git a/image/decoders/nsJPEGDecoder.h b/image/decoders/nsJPEGDecoder.h index d476b43e32..f916c1c8fb 100644 --- a/image/decoders/nsJPEGDecoder.h +++ b/image/decoders/nsJPEGDecoder.h @@ -15,7 +15,6 @@ #include "Decoder.h" -#include "Downscaler.h" #include "nsAutoPtr.h" #include "nsIInputStream.h" @@ -55,8 +54,6 @@ class nsJPEGDecoder : public Decoder public: virtual ~nsJPEGDecoder(); - virtual nsresult SetTargetSize(const nsIntSize& aSize) override; - virtual void SetSampleSize(int aSampleSize) override { mSampleSize = aSampleSize; @@ -73,8 +70,6 @@ protected: Orientation ReadOrientationFromEXIF(); void OutputScanlines(bool* suspend); - Maybe mDownscaler; - private: friend class DecoderFactory; diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp index 445734ce9b..8fde776a05 100644 --- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -148,20 +148,6 @@ nsPNGDecoder::~nsPNGDecoder() } } -nsresult -nsPNGDecoder::SetTargetSize(const nsIntSize& aSize) -{ - // Make sure the size is reasonable. - if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) { - return NS_ERROR_FAILURE; - } - - // Create a downscaler that we'll filter our output through. - mDownscaler.emplace(aSize); - - return NS_OK; -} - void nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat, const IntRect& aFrameRect) @@ -262,11 +248,11 @@ void nsPNGDecoder::InitInternal() { mCMSMode = gfxPlatform::GetCMSMode(); - if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) { + if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) { mCMSMode = eCMSMode_Off; } mDisablePremultipliedAlpha = - GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA; + bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA); #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED static png_byte color_chunks[]= diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h index edd2ef6cef..1061efa651 100644 --- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -8,7 +8,6 @@ #define mozilla_image_decoders_nsPNGDecoder_h #include "Decoder.h" -#include "Downscaler.h" #include "gfxTypes.h" @@ -27,8 +26,6 @@ class nsPNGDecoder : public Decoder public: virtual ~nsPNGDecoder(); - virtual nsresult SetTargetSize(const nsIntSize& aSize) override; - virtual void InitInternal() override; virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override; virtual Telemetry::ID SpeedHistogram() override; @@ -85,7 +82,6 @@ private: public: png_structp mPNG; - Maybe mDownscaler; png_infop mInfo; nsIntRect mFrameRect; uint8_t* mCMSLine; diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp index 8e5b31d73e..f45a954a71 100644 --- a/image/imgFrame.cpp +++ b/image/imgFrame.cpp @@ -205,7 +205,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize, static_cast(moz_malloc(PaletteDataLength() + (mSize.width * mSize.height))); if (!mPalettedImageData) { - NS_WARNING("Call to malloc for paletted image data should succeed"); + NS_WARNING("malloc for paletted image data should succeed"); } NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY); } else { @@ -796,96 +796,10 @@ imgFrame::LockImageData() return NS_OK; } - double imgPixelSize = mSize.width * mSize.height; - if (imgPixelSize < (8092 * 8092)) { - // We should be safe to Deoptimize at this size (<64Mpix) - return Deoptimize(); - } - + MOZ_ASSERT_UNREACHABLE("It's illegal to re-lock an optimized imgFrame"); return NS_ERROR_FAILURE; } -nsresult -imgFrame::Deoptimize() -{ - MOZ_ASSERT(NS_IsMainThread()); - mMonitor.AssertCurrentThreadOwns(); - MOZ_ASSERT(!mImageSurface); - - if (!mImageSurface) { - if (mVBuf) { - VolatileBufferPtr ref(mVBuf); - if (ref.WasBufferPurged()) { - return NS_ERROR_FAILURE; - } - - mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat); - if (!mImageSurface) { - return NS_ERROR_OUT_OF_MEMORY; - } - } - if (mOptSurface || mSinglePixel || mFormat == SurfaceFormat::R5G6B5) { - SurfaceFormat format = mFormat; - if (mFormat == SurfaceFormat::R5G6B5) { - format = SurfaceFormat::B8G8R8A8; - } - - // Recover the pixels - RefPtr buf = - AllocateBufferForImage(mSize, format); - if (!buf) { - return NS_ERROR_OUT_OF_MEMORY; - } - - RefPtr surf = - CreateLockedSurface(buf, mSize, format); - if (!surf) { - return NS_ERROR_OUT_OF_MEMORY; - } - - DataSourceSurface::MappedSurface mapping; - if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) { - gfxCriticalError() << "imgFrame::Deoptimize failed to map surface"; - return NS_ERROR_FAILURE; - } - - RefPtr target = - Factory::CreateDrawTargetForData(BackendType::CAIRO, - mapping.mData, - mSize, - mapping.mStride, - format); - if (!target) { - gfxWarning() << - "imgFrame::Deoptimize failed in CreateDrawTargetForData"; - return NS_ERROR_OUT_OF_MEMORY; - } - - Rect rect(0, 0, mSize.width, mSize.height); - if (mSinglePixel) { - target->FillRect(rect, ColorPattern(mSinglePixelColor), - DrawOptions(1.0f, CompositionOp::OP_SOURCE)); - } else if (mFormat == SurfaceFormat::R5G6B5) { - target->DrawSurface(mImageSurface, rect, rect); - } else { - target->DrawSurface(mOptSurface, rect, rect, - DrawSurfaceOptions(), - DrawOptions(1.0f, CompositionOp::OP_SOURCE)); - } - target->Flush(); - surf->Unmap(); - - mFormat = format; - mVBuf = buf; - mImageSurface = surf; - mOptSurface = nullptr; - } - } - - mVBufPtr = mVBuf; - return NS_OK; -} - void imgFrame::AssertImageDataLocked() const { diff --git a/image/imgFrame.h b/image/imgFrame.h index 89724c46f4..e5f62d01aa 100644 --- a/image/imgFrame.h +++ b/image/imgFrame.h @@ -274,7 +274,6 @@ private: // methods nsresult LockImageData(); nsresult UnlockImageData(); nsresult Optimize(); - nsresult Deoptimize(); void AssertImageDataLocked() const; @@ -448,6 +447,9 @@ private: * This may be considerably more expensive than is necessary just for drawing, * so only use this when you need to read or write the raw underlying image data * that the imgFrame holds. + * + * Once all an imgFrame's RawAccessFrameRefs go out of scope, new + * RawAccessFrameRefs cannot be created. */ class RawAccessFrameRef final { diff --git a/image/imgIContainer.idl b/image/imgIContainer.idl index 6495d86312..53d2a1829a 100644 --- a/image/imgIContainer.idl +++ b/image/imgIContainer.idl @@ -119,7 +119,7 @@ native nsIntSizeByVal(nsIntSize); * * Internally, imgIContainer also manages animation of images. */ -[scriptable, builtinclass, uuid(4880727a-5673-44f7-b248-f6c86e22a434)] +[scriptable, builtinclass, uuid(4e5a0547-6c54-4051-8b52-1f2fdd667696)] interface imgIContainer : nsISupports { /** @@ -267,6 +267,21 @@ interface imgIContainer : nsISupports [noscript, notxpcom] TempRefSourceSurface getFrame(in uint32_t aWhichFrame, in uint32_t aFlags); + /** + * Get a surface for the given frame at the specified size. Matching the + * requested size is best effort; it's not guaranteed that the surface you get + * will be a perfect match. (Some reasons you may get a surface of a different + * size include: if you requested upscaling, if downscale-during-decode is + * disabled, or if you didn't request the first frame.) + * + * @param aSize The desired size. + * @param aWhichFrame Frame specifier of the FRAME_* variety. + * @param aFlags Flags of the FLAG_* variety + */ + [noscript, notxpcom] TempRefSourceSurface getFrameAtSize([const] in nsIntSize aSize, + in uint32_t aWhichFrame, + in uint32_t aFlags); + /** * Whether this image is opaque (i.e., needs a background painted behind it). */ diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index c926a22c87..206f904a81 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -268,9 +268,10 @@ private: surfacePathPrefix.Append("@"); surfacePathPrefix.AppendFloat(counter.Key().AnimationTime()); - if (counter.Key().Flags() != imgIContainer::DECODE_FLAGS_DEFAULT) { + if (counter.Key().Flags() != DefaultSurfaceFlags()) { surfacePathPrefix.Append(", flags:"); - surfacePathPrefix.AppendInt(counter.Key().Flags(), /* aRadix = */ 16); + surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()), + /* aRadix = */ 16); } } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) { surfacePathPrefix.Append(", compositing frame"); diff --git a/image/imgTools.cpp b/image/imgTools.cpp index 6a2688b3a7..6cbe950fc3 100644 --- a/image/imgTools.cpp +++ b/image/imgTools.cpp @@ -198,26 +198,27 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer, return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream); } - // Use frame 0 from the image container. - RefPtr frame = - aContainer->GetFrame(imgIContainer::FRAME_FIRST, - imgIContainer::FLAG_SYNC_DECODE); - NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); - - int32_t frameWidth = frame->GetSize().width; - int32_t frameHeight = frame->GetSize().height; + // Retrieve the image's size. + int32_t imageWidth = 0; + int32_t imageHeight = 0; + aContainer->GetWidth(&imageWidth); + aContainer->GetHeight(&imageHeight); // If the given width or height is zero we'll replace it with the image's // original dimensions. - if (aScaledWidth == 0) { - aScaledWidth = frameWidth; - } else if (aScaledHeight == 0) { - aScaledHeight = frameHeight; - } + IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth, + aScaledHeight == 0 ? imageHeight : aScaledHeight); + + // Use frame 0 from the image container. + RefPtr frame = + aContainer->GetFrameAtSize(scaledSize, + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_HIGH_QUALITY_SCALING | + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); RefPtr dataSurface = - Factory::CreateDataSourceSurface(IntSize(aScaledWidth, aScaledHeight), - SurfaceFormat::B8G8R8A8); + Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8); if (NS_WARN_IF(!dataSurface)) { return NS_ERROR_FAILURE; } @@ -238,9 +239,10 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer, return NS_ERROR_OUT_OF_MEMORY; } + IntSize frameSize = frame->GetSize(); dt->DrawSurface(frame, - Rect(0, 0, aScaledWidth, aScaledHeight), - Rect(0, 0, frameWidth, frameHeight), + Rect(0, 0, scaledSize.width, scaledSize.height), + Rect(0, 0, frameSize.width, frameSize.height), DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); diff --git a/image/moz.build b/image/moz.build index 605a30eec9..093276e66c 100644 --- a/image/moz.build +++ b/image/moz.build @@ -48,6 +48,7 @@ EXPORTS += [ 'IProgressObserver.h', 'Orientation.h', 'SurfaceCache.h', + 'SurfaceFlags.h', ] UNIFIED_SOURCES += [ @@ -55,6 +56,7 @@ UNIFIED_SOURCES += [ 'DecodePool.cpp', 'Decoder.cpp', 'DecoderFactory.cpp', + 'Deinterlacer.cpp', 'DynamicImage.cpp', 'FrameAnimator.cpp', 'FrozenImage.cpp', diff --git a/image/test/crashtests/crashtests.list b/image/test/crashtests/crashtests.list index d914a54964..8d9ff40cd7 100644 --- a/image/test/crashtests/crashtests.list +++ b/image/test/crashtests/crashtests.list @@ -46,4 +46,14 @@ load multiple-png-hassize.ico # Asserts in the debug build load 856616.gif -skip-if(AddressSanitizer) load 944353.jpg +skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg + +# Bug 1160801: Ensure that we handle invalid disposal types. +load invalid-disposal-method-1.gif +load invalid-disposal-method-2.gif +load invalid-disposal-method-3.gif + +# Ensure we handle ICO directory entries which specify the wrong size for the +# contained resource. +load invalid_ico_height.ico +load invalid_ico_width.ico diff --git a/image/test/crashtests/invalid-disposal-method-1.gif b/image/test/crashtests/invalid-disposal-method-1.gif new file mode 100644 index 0000000000000000000000000000000000000000..30c61de188b9c63ea179c008c5171efad638f853 GIT binary patch literal 167 zcmZ?wbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!o|SAp!iSFxhOTUBsE2$JhLQ2!QIn0 zfI$Z+0o0=1008) != downscale-2e.html?205,53,bottom about:blank == downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal == downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal -# RUN TESTS WITH HIGH QUALITY DOWNSCALING ENABLED: -# ================================================ -# High-quality downscaling enabled: -default-preferences pref(image.high_quality_downscaling.enabled,true) +# RUN TESTS WITH DOWNSCALE-DURING-DECODE ENABLED: +# =============================================== +default-preferences pref(image.downscale-during-decode.enabled,true) fuzzy(31,127) fuzzy-if(d2d,31,147) == downscale-1.html downscale-1-ref.html # intermittently 147 pixels on win7 accelerated only (not win8) diff --git a/image/test/reftest/ico/cur/pointer.cur b/image/test/reftest/ico/cur/pointer.cur index d37f2f1cc0534d9aec717a087a13dd4eb5b83a8f..025ebaed1ff12279d4e07cfcaf3bd1cbbeb0884b 100644 GIT binary patch delta 58 zcmeyCvrkcnfq@YS6%;_U0>cUc1_m((1_liv1`07a05M1$3MLdOch$P-^vk|Znuo18kuo18kuo18kuo18kuo18k zuo18k_+KN?eXjF9WC+p=xg9q@!TjmV@G!l8`GTJJKBFg(9})U)#=#G8c0;mrb90o- z6VU%k+E&&!1V!J&ZxDijK&ec(RGL+{+EzJsMw$mcnxb;w=K z!Ji@7#l=M`7K_q{Z)0OT&(U*C(BP@m>vhV{&T1d-Fc|91TrNu=eoTBfF7}##dwZL_KJ>qTN3~jwv=8uS*tFPd{+*p2a{18Y zdueTLjaF7x4txMRuk)6|=Zd0%sj|DfOCBF+V`Jlx5Bgrx>w%w3YE6$s!Ok|Be{XM( z+VP=OD#~g0IQJb1Q5=n7`Ry9~zB@d@hw17K~&+ieLLsUS2kq{W$(= z`M1&RNAbsjy%m34*!}q9#O~#f8@ro7j_jKMZ|#rq&lzU*YrOxAdJpW!b<#e?AKCu> zq@1nh@!we@A&l3T6ldqYZYq6M>5J$0`_C#Z)cgZ2QkcL|@ajI*55?mhN~`k<@A(pH zJf(D>(yVa2JRDjc4lNIdmN?J}H7}(!|4u6jc>uA$(?Sqzvk|Znuo18kh(!dj|B*E= z=vREz>`7Q$GJIA>sw>u+8#4tJg~YR&RucUh~$0~eX>56->-iK zS{q{fo12^NIu+N~9L{B{Ja(XUHMY++4mZPxw{K*931flRX4pRG_}vVvtE;lUg0Vw& zmD9ct*}l%vYX+{7Xa=s0xUR*i*F)GoZ&{CG$V^QI_J63h--;nOGZUC?zLswLUWQ@_ zd2O$K&9IF1hq1tGNPhOi%$a!YhiTh;{lm8HwjYIUZ9gj8+I|@KIotAUeE&fr%k%Jh pZ?`p4L@ZYzT@cZy1jz_aNJvRQ>oFqY`~~MOIB&r@iv%PE`3FlvvXTG* diff --git a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list index 69176e192f..1b4cb8ee2d 100644 --- a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list +++ b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list @@ -8,8 +8,3 @@ == wrapper.html?invalid-compression-RLE8.ico about:blank # Invalid compression value == wrapper.html?invalid-compression.ico about:blank - -# Invalid ICO width and heigth should be ignored if the -# contained BMP is correct. -== invalid_ico_height.ico 16x16.png -== invalid_ico_width.ico 16x16.png diff --git a/image/test/reftest/ico/ico-mixed/reftest.list b/image/test/reftest/ico/ico-mixed/reftest.list index ddd837530c..36134e40ab 100644 --- a/image/test/reftest/ico/ico-mixed/reftest.list +++ b/image/test/reftest/ico/ico-mixed/reftest.list @@ -1,14 +1,3 @@ # ICO BMP and PNG mixed tests -== mixed-bmp-png.ico mixed-bmp-png.png - -# Using media fragments to select different resolutions - -== mixed-bmp-png.ico#-moz-resolution=8,8 mixed-bmp-png.png -== mixed-bmp-png.ico#test=true&-moz-resolution=8,8&other mixed-bmp-png.png -== mixed-bmp-png.ico#-moz-resolution=32,32 mixed-bmp-png32.png -== mixed-bmp-png.ico#-moz-resolution=39,39 mixed-bmp-png48.png -== mixed-bmp-png.ico#-moz-resolution=40,40 mixed-bmp-png48.png -== mixed-bmp-png.ico#-moz-resolution=48,48 mixed-bmp-png48.png -== mixed-bmp-png.ico#-moz-resolution=64,64 mixed-bmp-png48.png -== mixed-bmp-png.ico#-moz-resolution=64 mixed-bmp-png.png # Bad syntax will fall back to lowest resolution +== mixed-bmp-png.ico mixed-bmp-png48.png diff --git a/image/test/reftest/ico/ico-png/transparent-png.ico b/image/test/reftest/ico/ico-png/transparent-png.ico index df422207e7b9918f582896c7d7889c5103adf8f7..cc8a4a31db973df11dd9c21b56d12eabab61cb62 100644 GIT binary patch literal 1150 zcmaiyxl6=Q5XPTq?-pWXVNeSj1+fu4{{&I-s)Fchxt?I<)e353Ct|&btrx77E+|Ai z)<#%eEo2d6Y(zxH`No|P9xP6nyky?|zL__PF(rJ(VutUInJ6=+(il_E&LNKc#d$G? z_x`iXxN+H2>|{A&ZZV$soSUf%?|!w5P7-zE*NK>|fp-tNQ5_<3J~BuC)%hv4wRVzI zT`S($&_wg2y>zqDW9#@)4d%Td2nhLLXQt<9eQhK3t*vePdd(7gV@};e56rskluYi1 zb)hjhFig;dK?~Zqw;9SGuPKk!y~}*{7CpdGTc4o;&4@;7W#LU~AWwUj0N)M{ z4(aSHt*@aL0@=-O>7%3NS@zKz_-K9z79LO=IZsc|G7rq6rs{#xr=OC6*`sDL(;Da% zmfLoPM-M^pBQwO=iy7vC`pZizl7cyK{S0Sg2F`N0pBD}EVY50v=H>q>@DB67YG57ed!eEJ9j@Ch zX9kBJpqYF8_L+CUsxbqNu#cz~dTMXE5Y6U>9C94x?3MR051DCHqY!iL{P%KWPD+hg PEctVL-+tFTFkpWH3iMVW literal 3084 zcmcJRc{tQv8^?df3}dUw-Xz8h3Jv2)C=oM*vB#h+Nuh--MfPQ8h>;4ZC`)3j*+pV( zMJf{WC@EWJvSw+fY(w68>yP)J_qv|Ho=#FDbbcfp38??9(=RCNRZNVI27KK>8yM+mUYX3bNdbr6r;j zcNKF-yVK^0JEQWZvzPSS7A2m0E}=Y&@TQcbq<252=JB}ai#S?S?5*5Qz701a`}evx z2r`kGd_w9(-l-Vt=j6BS+tq?fAS+OyQ1GL3g2QTFU{f+6T`~?w z63$&t)haR-7%4^vT~gvy*oi3#G`07N$OIf2UZmqyJx1=f<$Pn-HO2o5&Uk8bLDgk2 ziG&*1GHBPLpMGNJjr@^^k&O5*KKjOh<*!IY7iVP7_LdJH>o2l(+6dG9`QP_5H?hH2 zVm&DT2Nxc2fb4HvCPEF?c;?^3<;@>l?m9O6y}-a6b1bf&ZGTcnV$h}l4X_uVW7$EF zZa>s&1Hh|HKp6tP0&wGJbtzg`C(X2$5gqi%rL{$ZqvO;nC)l44J^_ft71Zl=ry=hN zF(ffgi^>Wi;-9Ksyly8B*qHXB)8<7>w+yr2&)dyR7X*@&Nf2!q3iuhTuKO^N$Peb< zrE0pURY2(DlvJfE0Vv_Mu&^a+YVyZBK*0V8S5FiC;P-5go5|YQbZ^!Js1MtvFhaoB z4{tOD_oaAcjj&zfSG`Uh_ekML7|DB9e^i_Zor5e2Ry}M|D{&Vv&&;!A8E-}o=EwZy z*!jSpXzpwjMkdh(dL3yI5`Iq3Ud{{fh1p^VR`(E z_76?a!)NvPCo!v@tva;yq&ABkR!-RWw|Lr(JR!J`>|tyX*ryidefR0(M`hE{@W~f{ z+h!^e@MhoO3a2%M&kl~u`r9?~`J;3gL<4%XEO-n7EHMaV1b zqBotXgEy#|{i&Uq{IY@ITNpFao(VBMkd3B+??HWozxO*K3vl^>qVU}P79$zWmuu91Ok|db%ajZF> zYK$cL>cI-r_IFF{K3~!NZOI#p-;J7{+mZhlcE{Dl#-l>J=pwz@p-MHVC8Etsd>|{F zauoMjaD0VIy|?QT76fk_rh6F(EV)$m2G67dpQ6$qA68MtR1f2B)f_>1%zGS~iwlid zP`+Qif}H0^T0Idg8<4|Z?e-Y5x-=vL?oXiSQheM>fUl5d_`Pjd>Mp(tGxy_sG0f5n z%x@B3*q5?ozwHAFYnG^xV`GXX1XWVp9==WjVk;P<@}HxwkMVD7#(9^&BnxB6?bfRa z8$h=e^PO1G#(LR$Sj^*RA&m&0Du!kBQu8)z`MSMb+b29q6*~XaUQFBzOEJxCB^lydd(T?UU3`B5qkx>(tZb{V^2ag zr3M?v4#>zW+tQ`%@~-VwNlc&3diri-yfltA_!F(-YGU~{xnFV z!A(V}+dw|3YvSPKQn$ib5=F-rD7N?q?s>m7fcee+3DA1tzsWs+)gSI#oO^0$7*Qqg zV&uu8+km1T7(3A!jtvx$04c&KIFe|lD3n}pOxH4fep=q~)X%BAW^?yUTrnnTUp6+^ zyQShO4w+I;po8TDzTvkJ z<)ZorZmjX$(6`5(`Oqg%#O^-f;ZzB~UKn&MB3fws@)bxtD5EnDBygtko2ug;jg20z+-CsF!g(AUy2hq}fPWhqF zvV7h|>!n>-DYM9?-h(0h!J=K$8=~A5LjROBaJc&Z?3FV9@FI-NE7jzcdb_Q!LhMxL zP3S~C$>~fzB+m!^&Nd`>58nvVR_1Y5ge&muBm|!A`e3#eEIO0#uO=n!3cTlfFAwC} z8YOES`P41mP9-(yd)3(@$8EW((>Gef(R>_hqsSTYowqe}!h7f9NcVjdpgud@3N&|r z3+N8*oxP{X>LLE@Fjf9)2$R%z%l4z82b88%JwhC|qswA+Uwht}AcOp;w>`Bnh9X06 z1D>djaYsARZ1|+?RR6^LEn4Go8tI0%VOv==rE5x^tHo(*=9V@iKr5TN#Z}SO9(wLM z%X6FTH&Lz~WlLZ#rCuzXKD8ETptRbmu!|idK~4ljGwk-%c$UMau?*JGf>I0t2#<9b ze=74a^dztzzzu^|`~BF##x?J&brTswz9Nc*#U8g#Hm<|9#BdMW4)%yR>I+$O>~XB^UH zl&jXZ)4S4L*SODD5&6dG#F5Pw2t)ZtIP`MtyEZrON&^+PV--@5>b5mK)Oqnba~DP~ zDW#eVZUo5Z9#1-32~k!Q7FN`PV8`&c`-XH7`Nsa*Nr-FZ3p)27ZiYOztSH%;fPAUO zAHUrDUMKX!0;Q;VFJI$r+(E~zbj*rfGx-K{sV^&C&Y_ zQj}@M^1p+b*}sKz$+e_ovU&elfoKiiQ+TZQ3%F9i{O{nmVs1sr?y~<{57m4r*&X)e=>Px# delta 465 zcmV;?0WSWV2(}2YZ~}i{fIkC%4g5ROv>j{3{twf2oqt)hwVL8BbVYp>GMJI1ju;B< zBYEYyBPZqxE9AIujWL+);pKF&%5dkGzZLJOtMc2LN-@4Y9&J6M2y$umV8w77XdTKS|;qF24@X1(?RHRT0B!7KLL_t(IjYZRaESv`b$MHwHgig#dMQ4m*QjIR!YPV+VqBA7K zOr@Px*`}Ql4SJVzA=u(=8Kn!Q6BWH#6R*`(=Z1N?3){uG3Z)#^bJiQtjAc@qx##)r zkK4=d-(T|m?;{|AB!OtP^6w^%D>U`$Xc`!!u4j~Q`g9nUR)7BcMXQJ;NdO3vB(Y=t z3jo9FAIO_DeAF<4;?^%T=uMPs=SXkXl9*G@SCy4KURkt7B#tH{;&$-G=mbNRdcI+VcLd>f*58%Y8|+Sz38S{mPF9HXK5 zB0Y6k+$nvJ&wpB4$s3*H{OzB}*DaB*>LD^NjDhy!tWQL8udf7g=PAO@_B#6FNR|uy zSuP5~sPv`xV^6Bel@xS#b7^=Q+2{h3W1m>M~~Y z{CFsHV@ct~vciYOlDDX>P?CE6HqkYAc{U;k;ZO?4WGbTMRSdO+BN}5F?>dcOR%J4y z@M0;)gSk&V>3G|Pl5^pFQdUoV?Ene&S_02iAo!iaF))$Pm;|P~4r4WkF?sU@!bD*r zQ<)xEa({j34|Sr^-;ScB_eoS-;cV_zWEIzmNc|E!_Xq@s5K5J2*y;~pQ~wGxKU_c< zt;}WO{EJv}yqG%e%w*Iq?xns;PgW>H$}lv=aU{jXBG?7t=omyteFBUG@p$A7rbep} zOxk`1V_w1{_r#R#hB?)liFjwKUvnfU*pox9et!tUUUm!K$YO$-Z4Kn<*g@9jlbO_Y zAc)&r+&LS;_{rz6D7`Vs+*nq4qE7N6C3qii`b2QlBZ!LV{Ve4BVs3PRwJ7wO1lCp_ zAOM($e?%3qo9SqGj2C>EPj%zBJbxY*9OOan5e)eum~;J@&9-A}+!v$nFuH-OfP`SR zihls;>uafdeivOX0)syGOdNONeyl6TWOqzyFR&=LXT5a~hP&SM-b`nE>ra~nNfZ(P ziOjUM(0E`!H=G=3c6x>uS9@*+*wb@jH`??)^nVe;U|T6qHaBdbMA4>E6h%bQ7I|W^ zp!>Rx?v!NOqrz#AkDy!eA;Zm8nCJBWlNlCmr2hsR7{(cPY4g_r0000 Ch09w2 delta 926 zcmV;P17ZBO2eb!}B!7EJL_t(IjYZRKOw>2Z5rLN+6EM|F;)t zzW+VE`Ur?1iXs3aFacNwNBBW!B)6)T?DA^zf34^KqsL6o#D8Y8PdfsLAWkBRQ*{45 zjN(!i!58%;Yg)+fe#rTTK4OcE_(y-i$&(q34D~;~c{U(UFxXPX_2L57Mw}+>yBnmH zYDp>8aoIdTX7gQ?W%Wd!%An+eFYOh+Jm@TfsS!X#VCo6YXF|{?$jJLDiqg!(XfynY z&%Hv@4HE~-YJWLWZRV3JLu2tf+^h28exDIxxcesN&%Nl1b)-Am35!Zf z)1gJ&$_OIq_)+%#s70Prg>y(c{yBPr&#EcRT*uJOb$|4jE+O2hOv9YKl&`6lgl4*T4@-g@7L^q3 zW?R%NUw~8m+B2H3Og(cCk&xW2w&~NMwczDneWc2?aOFSa-}cVnu-4H+}7>p@o*mk z;xu=vbzFPfjyjnG?Lks3hn(nANa=`O#-E9+=}7RRGuD|_r4>WJEvCQjU21jbK%7KC z1b-$cXt@mq5Y5UNTjxc-7an}5!cA!`D7MiPvnHU>-hH1v3 zI6h8WzM5}5-MMBzm!kQvQX;XY!qW!5%$8e`j%ZU?q0diceDuL{O#f3BMVOvuY~U_+ z-(IF7Y(Ir^8HN5b%8!Onqdv!AZ!1hcmlp`|ANF3#ZPy`Xs{jB107*qoM6N<$g0t+$ Apa1{> diff --git a/image/test/unit/test_imgtools.js b/image/test/unit/test_imgtools.js index e9af09322d..64361adfee 100644 --- a/image/test/unit/test_imgtools.js +++ b/image/test/unit/test_imgtools.js @@ -169,7 +169,7 @@ var encodedBytes = streamToArray(istream); var refName = "image1png16x16.jpg"; var refFile = do_get_file(refName); istream = getFileInputStream(refFile); -do_check_eq(istream.available(), 1078); +do_check_eq(istream.available(), 1051); var referenceBytes = streamToArray(istream); // compare the encoder's output to the reference file. @@ -228,7 +228,7 @@ encodedBytes = streamToArray(istream); refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png"; refFile = do_get_file(refName); istream = getFileInputStream(refFile); -do_check_eq(istream.available(), 948); +do_check_eq(istream.available(), 950); referenceBytes = streamToArray(istream); // compare the encoder's output to the reference file. @@ -691,8 +691,10 @@ var errsrc = "none"; try { container = imgTools.decodeImage(istream, inMimeType); - // We should never hit this - decodeImage throws an assertion because the - // image decoded doesn't have enough frames. + // We expect to hit an error during encoding because the ICO header of the + // image is fine, but the actual resources are corrupt. Since decodeImage() + // only performs a metadata decode, it doesn't decode far enough to realize + // this, but we'll find out when we do a full decode during encodeImage(). try { istream = imgTools.encodeImage(container, "image/png"); } catch (e) { @@ -704,7 +706,7 @@ try { errsrc = "decode"; } -do_check_eq(errsrc, "decode"); +do_check_eq(errsrc, "encode"); checkExpectedError(/NS_ERROR_FAILURE/, err); diff --git a/layout/tools/reftest/reftest-preferences.js b/layout/tools/reftest/reftest-preferences.js index 576d147675..13058a07a5 100644 --- a/layout/tools/reftest/reftest-preferences.js +++ b/layout/tools/reftest/reftest-preferences.js @@ -22,8 +22,8 @@ branch.setBoolPref("extensions.blocklist.enabled", false); // Make url-classifier updates so rare that they won't affect tests branch.setIntPref("urlclassifier.updateinterval", 172800); - // Disable high-quality downscaling, since it makes reftests more difficult. - branch.setBoolPref("image.high_quality_downscaling.enabled", false); + // Disable downscale-during-decode, since it makes reftests more difficult. + branch.setBoolPref("image.downscale-during-decode.enabled", false); // Disable the single-color optimization, since it can cause intermittent // oranges and it causes many of our tests to test a different code path // than the one that normal images on the web use. diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 014a185b6b..0f70034197 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4297,21 +4297,11 @@ pref("image.cache.timeweight", 500); pref("image.decode-immediately.enabled", false); // Whether we attempt to downscale images during decoding. -pref("image.downscale-during-decode.enabled", false); +pref("image.downscale-during-decode.enabled", true); // The default Accept header sent for images loaded over HTTP(S) pref("image.http.accept", "image/webp,image/png,image/*;q=0.8,*/*;q=0.5"); -pref("image.high_quality_downscaling.enabled", true); - -// The minimum percent downscaling we'll use high-quality downscaling on, -// interpreted as a floating-point number / 1000. -pref("image.high_quality_downscaling.min_factor", 335); - -// The maximum memory size which we'll use high-quality uspcaling on, -// interpreted as number of decoded bytes. -pref("image.high_quality_upscaling.max_size", 20971520); - // The threshold for inferring that changes to an element's |src| // attribute by JavaScript represent an animation, in milliseconds. If the |src| // attribute is changing more frequently than this value, then we enter a diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png index c3a4aee616675d212178f70a97d8ce73fc1813e2..5722223c26fe0024d8c4d18f198114d5fde708bb 100644 GIT binary patch delta 928 zcmV;R17G~K2et>0B!7KLL_t(IjYZRaESv`b$MHwHgig#dMQ4m*QjIR!YPV+VqBA7K zOr@Px*`}Ql4SJVzA=u(=8Kn!Q6BWH#6R*`(=Z1N?3){uG3Z)#^bJiQtjAc@qx##)r zkK4=d-(T|m?;{|AB!OtP^6w^%D>U`$Xc`!!u4j~Q`g9nUR)7BcMXQJ;NdO3vB(Y=t z3jo9FAIO_DeAF<4;?^%T=uMPs=SXkXl9*G@SCy4KURkt7B#tH{;&$-G=mbNRdcI+VcLd>f*58%Y8|+Sz38S{mPF9HXK5 zB0Y6k+$nvJ&wpB4$s3*H{OzB}*DaB*>LD^NjDhy!tWQL8udf7g=PAO@_B#6FNR|uy zSuP5~sPv`xV^6Bel@xS#b7^=Q+2{h3W1m>M~~Y z{CFsHV@ct~vciYOlDDX>P?CE6HqkYAc{U;k;ZO?4WGbTMRSdO+BN}5F?>dcOR%J4y z@M0;)gSk&V>3G|Pl5^pFQdUoV?Ene&S_02iAo!iaF))$Pm;|P~4r4WkF?sU@!bD*r zQ<)xEa({j34|Sr^-;ScB_eoS-;cV_zWEIzmNc|E!_Xq@s5K5J2*y;~pQ~wGxKU_c< zt;}WO{EJv}yqG%e%w*Iq?xns;PgW>H$}lv=aU{jXBG?7t=omyteFBUG@p$A7rbep} zOxk`1V_w1{_r#R#hB?)liFjwKUvnfU*pox9et!tUUUm!K$YO$-Z4Kn<*g@9jlbO_Y zAc)&r+&LS;_{rz6D7`Vs+*nq4qE7N6C3qii`b2QlBZ!LV{Ve4BVs3PRwJ7wO1lCp_ zAOM($e?%3qo9SqGj2C>EPj%zBJbxY*9OOan5e)eum~;J@&9-A}+!v$nFuH-OfP`SR zihls;>uafdeivOX0)syGOdNONeyl6TWOqzyFR&=LXT5a~hP&SM-b`nE>ra~nNfZ(P ziOjUM(0E`!H=G=3c6x>uS9@*+*wb@jH`??)^nVe;U|T6qHaBdbMA4>E6h%bQ7I|W^ zp!>Rx?v!NOqrz#AkDy!eA;Zm8nCJBWlNlCmr2hsR7{(cPY4g_r0000 Ch09w2 delta 926 zcmV;P17ZBO2eb!}B!7EJL_t(IjYZRKOw>2Z5rLN+6EM|F;)t zzW+VE`Ur?1iXs3aFacNwNBBW!B)6)T?DA^zf34^KqsL6o#D8Y8PdfsLAWkBRQ*{45 zjN(!i!58%;Yg)+fe#rTTK4OcE_(y-i$&(q34D~;~c{U(UFxXPX_2L57Mw}+>yBnmH zYDp>8aoIdTX7gQ?W%Wd!%An+eFYOh+Jm@TfsS!X#VCo6YXF|{?$jJLDiqg!(XfynY z&%Hv@4HE~-YJWLWZRV3JLu2tf+^h28exDIxxcesN&%Nl1b)-Am35!Zf z)1gJ&$_OIq_)+%#s70Prg>y(c{yBPr&#EcRT*uJOb$|4jE+O2hOv9YKl&`6lgl4*T4@-g@7L^q3 zW?R%NUw~8m+B2H3Og(cCk&xW2w&~NMwczDneWc2?aOFSa-}cVnu-4H+}7>p@o*mk z;xu=vbzFPfjyjnG?Lks3hn(nANa=`O#-E9+=}7RRGuD|_r4>WJEvCQjU21jbK%7KC z1b-$cXt@mq5Y5UNTjxc-7an}5!cA!`D7MiPvnHU>-hH1v3 zI6h8WzM5}5-MMBzm!kQvQX;XY!qW!5%$8e`j%ZU?q0diceDuL{O#f3BMVOvuY~U_+ z-(IF7Y(Ir^8HN5b%8!Onqdv!AZ!1hcmlp`|ANF3#ZPy`Xs{jB107*qoM6N<$g0t+$ Apa1{> diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png b/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png index 03a01c6de84a633c5a4a73a9b0187d92f4ba9125..238973189040030ed5071fb3f9ed8c141a802211 100644 GIT binary patch delta 957 zcmV;u148`d2Ga+SB!8MoL_t(Ijct>EOwJmI1V^Q0v@d&lWZtMLqkxI zKw)0BNHAcx108b5uMk8)GDWlkq)eEN;mAn@K~N|lxs#A76ne^!1Ood3EcmAa?)!b8 z{&2EcJ%7H>-p`&to`;BtKqS#>wbryEosUb*g~ZUpwvrdpntzW{*!j1K&Uid_QAk$w ze^U5myQ}q=Yocm~77SOW@%hj5sJt@={TFu-k#>4!Q{dh*A}5FcKqfy6jz16;QADu~ zV-c;hIM>;Q(#95{X7pF#h_eoOQLGVp!PjrNQ=fSTaAeRF&eCvy$NE{xvuM zJnkjT=c$(GGUez=aOIO_?{Q({mpE|UjEoy*PP=xOg9=)BtFh8D(zXT*EnbV-xVY!2 zE8TDN)G?pM@eGAHmhLW&89jI;Rn5O=yo}RDsfeuq9)Drwt|Z zW$wMSO~C9c+>DHlj^)vY%Jdx!l+)r`i=0G`)#_dGHiF1gl>VfvW34Z%ZK|XsS zfA&pmjDIxp{br4b*-d==gu?)JZ*%_Yw`@0ln5VXU6Kux~o&-Zu5`yxtAS$mO2hY@D ztG)u(%IzTA5R_)V#psX;7+=NL&u(m|dM{b=RH_n_@48!tUSG|lJBS}7twwix0PYzz zm?-c@l6Ds)Rxg5VUgJyEDI$98zyn)QTOL(Lhkxklfd1|F%TtGy;)FpZPU-A17;Dd! z&)Xu)-y6P)mq4@@Bnf0=cqqo2Y?=FgdH%sZ%bpOI2qGfMMdznF9wsKj6yW) z+7ICAnKme`9311Nlw9l}y?Dw|+Ec2Mb`|~?=MlMsEaG{@vf&MHN_Jaas_f0VlYj3vEh+urn;pRkUON>d zG&|3GV?VHeC`Q%`o#;}k0{;me@;vMoF=muG zxg6GVHS9t}EFvPtgn#+puoG2rGF@xuQfm=iZ#PnTEW*y75lqECDgtM4cMNiPe?G}z zIu^ZblCzDgK1}#BZ^EkLz*_eKlHWhT^qLKl1^N|_lT+&D>Yxs; z)e(L3?u*j*kbOMS{8w{mgNdFsTHMdov&A1`>%JaJ{2@wxU!Yr7g2tN9yREyq^1g{{ zb6R>2=6{Yztw_~OO$MssAnLX-wOLRV2QiP6S;bC5aT09bju>+hZ4TiBg(Qy}SQvD% zd7btZ5s}o9O{v5^HdMt)VwaieN-LcXJqNSXFfYwRbUBDNhfk}r`6Hqwc_a&HLPXYy zh)B0aew-|~qBhuwd#uDgOPOtOlDGOXT#gnN8GlL$xL1&B zg5!)!!@1%N#>&!B8*RirX{cU1iI?sr(XSK64Vvoqab)_X#ek!MiD6~Hyb{AHQhM&vfVVg#bM|YktamN`fu}} X%mBPK59a(S00000NkvXXu0mjfquQ!& From e7535dc94b5e664225fcbcded5bb0bf5c5e194bb Mon Sep 17 00:00:00 2001 From: roytam1 Date: Fri, 20 May 2022 09:45:08 +0800 Subject: [PATCH 4/4] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1187401 (Part 1) - Simplify the condition that determines whether we set RasterImage::mHasBeenDecoded. r=tn (69be36e7ef) - Bug 1187401 (Part 2) - Eliminate the nsresult return value from RasterImage::SetMetadata, since it's not used anymore. r=tn (dc521c4b9f) - Bug 1187401 (Part 3) - For consistency, call DoError if SetMetadata sees a negative size. r=tn (d9ca8ec91b) - Bug 1207183 - micro-optimize removing work items from DecodePool's queues; r=seth (6bd2717e3a) - No bug - Fix out-of-date comment in Decoder.cpp. r=me (54fdbbd444) - Bug 1181324 - Eliminate the duplicate mRefCnt member in MultipartImage. r=seth (c4512a443e) - Bug 1180105 - Do not leak the SourceSurface returned from imgIContainer::GetFrame in BlockUntilDecodedAndFinishObserving; r=seth (1cadbffc53) - Bug 1181909 - Fix potential null dereference in NextPartObserver. r=tn (cfd8ad0119) - Bug 108603 - Remove NS_IMPL_QUERY_INTERFACE_INHERITED0. r=mccr8, r=froydnj (4bfa6771fc) - Bug 1159502 - Don't block onload for multipart images. r=tn (8b50eadf39) - Bug 1200413 - Part 1: Re-write RefCountedInsideLambdaChecker to use captures instead of checking for DeclRef instances, r=ehsan (80ef99efe2) - Bug 1200413 - Part 2: Make lambdas in ProgressTracker.cpp capture strong references, r=seth (9e4d96dffa) - Bug 1194557 - Ensure that if the image was locked before RecoverFromLossOfFrames() was called, it's still locked afterwards. r=tn (ea4dc6ea9f) - code style (ad3773ba42) - Bug 1167590 - Mark imgRequestProxy::mListener as MOZ_UNSAFE_REF. r=seth (946ffaed8a) - Bug 1148397 - Fix data race on imgRequest::mHadInsecureRedirect. r=tanvi (e73d0664f3) - No bug - Tweak formatting of logging statement in imgRequest. r=me (cab2bcb014) - Bug 1180126 - Read content disposition regardless of content type in imgRequest::PrepareForNewPart. r=tn (2934597743) - Bug 1139225 - Followup - Remove duplicate multiPartChannel variable. (7f7f555a0b) - Bug 1141398 - Do not always revalidate image cache entries for file URIs. r=tn (31d73cb508) - Bug 1183563 - Fix incorrect mixed content warning after internal redirects. r=tanvi, r=seth (12a6c8a15f) - Bug 1150127 - Stop leaking windows via imgCacheValidator. r=baku (a7809c5fa7) - bits of Bug 1102048 (Part 20, imgLoader) (b2098c8a5a) - (No bug) - Correct blatantly lying comment in imgLoader.cpp. r=me DONTBUILD (64c42a5b09) - Bug 1160592 - Report image source size again in about:memory. r=dholbert (4e04cf3c3e) - Add an assertion for the first argument of NewImageChannel, no bug (4c8f087a8f) - Bug 1127534 - Remove assertion before creating a channel (r=sicking) (988692dc91) - Bug 1175371 - Make VectorImage wait to deliver LOAD_COMPLETE until its size is available. r=dholbert (3c81e0daff) - Bug 1181323 - Move nsSVGRenderingObserver's isupports/refcounting decl to subclasses, since one subclass (nsSVGFilterReference) already has its own redundant copy of the decl. r=dholbert (6171171c2c) - Bug 1161722 - If we're shutting down, don't warn about untracked unlocked surfaces. r=dholbert (f7e18ce481) - Bug 1170877 - Track how many times the SurfaceCache has overflowed and report it in about:memory. r=dholbert (884176cb1d) - Bug 1161743 - Upgrade 'WARNING: Not expiration-tracking an unlocked surface' to an assertion. r=dholbert (9900169e7b) - Bug 1167557 - Crash when a null surface is passed to SurfaceCache::Insert. r=dholbert (b3c4cf60aa) - remove bypass cache not fonud either in FF nor TF (3ed4056a27) - Missing bit Bug 1102048 (Part 25, header guards) - Make image/src files comply (cb8ed2428f) - No bug - Remove obsolete comment in SourceBuffer.h. r=me (6e9c233448) - coding style (94b7269690) - Bug 1157065 - GFX: 2D: Add Loongson3 MMI helpers. r=jrmuizel (ebce946c91) - reverto to FF52 and TFF settings (e147a8c7b5) - Bug 1134599 - Fix rpi build target. r=jrmuizel, r=shuang (b9722f860c) - Bug 1129147 - Part 1. Take CanvasPath into a separate file, to avoid circular dependency. r=roc (859bcad807) - Bug 1129147 - Part 2. Path option to addHitRegion. r=ehsan r=gw280 (b2ab08a8a1) - Bug 1206076: Use a specialized PersistentBufferProvider for Canvas2D when using a SkiaGL DrawTarget. r=jrmuizel (859589caf8) - Bug 1188752 - Addendum: Make PersistentBufferProviderBasic constructor explicit. r=bustage on a CLOSED TREE (a27a4dc974) - style (72a65dcb26) - Bug 1198574 - Remove unnecessary argument for PersistentBufferProvider. r=bas (dca718bba8) - Bug 1163124 - The initial value of the canvas filter property should be "none". r=roc (59df6a01d8) --- b2g/app/b2g.js | 2 +- build/clang-plugin/clang-plugin.cpp | 24 ++- .../tests/TestNoRefcountedInsideLambdas.cpp | 8 +- dom/bindings/Bindings.conf | 2 +- dom/canvas/CanvasPath.h | 91 ++++++++ dom/canvas/CanvasRenderingContext2D.cpp | 30 +-- dom/canvas/CanvasRenderingContext2D.h | 68 +----- dom/canvas/moz.build | 1 + dom/canvas/test/test_hitregion_event.html | 18 +- dom/system/gonk/AudioManager.cpp | 6 + dom/webidl/CanvasRenderingContext2D.webidl | 1 + gfx/2d/MMIHelpers.h | 196 ++++++++++++++++++ gfx/2d/SourceSurfaceSkia.cpp | 2 + gfx/2d/image_operations.cpp | 16 +- gfx/layers/Layers.cpp | 4 +- gfx/layers/PersistentBufferProvider.cpp | 4 +- gfx/layers/PersistentBufferProvider.h | 12 +- gfx/thebes/gfxPlatform.cpp | 2 +- image/DecodePool.cpp | 2 +- image/Decoder.cpp | 5 +- image/Decoder.h | 5 - image/MultipartImage.cpp | 37 +++- image/MultipartImage.h | 2 +- image/Orientation.h | 4 +- image/ProgressTracker.cpp | 12 +- image/RasterImage.cpp | 36 ++-- image/RasterImage.h | 10 +- image/SVGDocumentWrapper.h | 4 +- image/SourceBuffer.h | 9 +- image/SurfaceCache.cpp | 20 +- image/VectorImage.cpp | 86 ++++---- image/VectorImage.h | 8 +- image/imgLoader.cpp | 68 ++++-- image/imgRequest.cpp | 50 +++-- image/imgRequest.h | 10 +- image/imgRequestProxy.h | 5 +- image/moz.build | 2 - image/test/mochitest/bug1180105-waiter.sjs | 24 +++ image/test/mochitest/bug1180105.sjs | 63 ++++++ image/test/mochitest/mochitest.ini | 3 + image/test/mochitest/test_bug1180105.html | 46 ++++ layout/svg/nsSVGEffects.cpp | 5 +- layout/svg/nsSVGEffects.h | 7 +- modules/libpref/init/all.js | 4 +- xpcom/glue/nsISupportsImpl.h | 8 +- 45 files changed, 748 insertions(+), 274 deletions(-) create mode 100644 dom/canvas/CanvasPath.h create mode 100644 gfx/2d/MMIHelpers.h create mode 100644 image/test/mochitest/bug1180105-waiter.sjs create mode 100644 image/test/mochitest/bug1180105.sjs create mode 100644 image/test/mochitest/test_bug1180105.html diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 909d5a43f2..fde9582102 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -904,7 +904,7 @@ pref("devtools.debugger.unix-domain-socket", "/data/local/debugger-socket"); // falling back to Skia/software for smaller canvases #ifdef MOZ_WIDGET_GONK pref("gfx.canvas.azure.backends", "skia"); -pref("gfx.canvas.azure.accelerated", true); +pref("gfx.canvas.azure.accelerated", false); #endif // Turn on dynamic cache size for Skia diff --git a/build/clang-plugin/clang-plugin.cpp b/build/clang-plugin/clang-plugin.cpp index 29e2ddd448..a2d64924f6 100644 --- a/build/clang-plugin/clang-plugin.cpp +++ b/build/clang-plugin/clang-plugin.cpp @@ -951,13 +951,7 @@ DiagnosticsMatcher::DiagnosticsMatcher() { // lambda, where the declaration they reference is not inside the lambda. // This excludes arguments and local variables, leaving only captured // variables. - astMatcher.addMatcher( - lambdaExpr(hasDescendant( - declRefExpr(hasType(pointerType(pointee(isRefCounted()))), - to(decl().bind("decl"))) - .bind("declref")), - unless(hasDescendant(decl(equalsBoundNode("decl"))))), - &refCountedInsideLambdaChecker); + astMatcher.addMatcher(lambdaExpr().bind("lambda"), &refCountedInsideLambdaChecker); // Older clang versions such as the ones used on the infra recognize these // conversions as 'operator _Bool', but newer clang versions recognize these @@ -1241,11 +1235,19 @@ void DiagnosticsMatcher::RefCountedInsideLambdaChecker::run( "Refcounted variable %0 of type %1 cannot be captured by a lambda"); unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID( DiagnosticIDs::Note, "Please consider using a smart pointer"); - const DeclRefExpr *declref = Result.Nodes.getNodeAs("declref"); + const LambdaExpr *Lambda = Result.Nodes.getNodeAs("lambda"); + + for (const LambdaCapture Capture : Lambda->captures()) { + if (Capture.capturesVariable()) { + QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType(); - Diag.Report(declref->getLocStart(), errorID) - << declref->getFoundDecl() << declref->getType()->getPointeeType(); - Diag.Report(declref->getLocStart(), noteID); + if (!Pointee.isNull() && isClassRefCounted(Pointee)) { + Diag.Report(Capture.getLocation(), errorID) + << Capture.getCapturedVar() << Pointee; + Diag.Report(Capture.getLocation(), noteID); + } + } + } } void DiagnosticsMatcher::ExplicitOperatorBoolChecker::run( diff --git a/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp b/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp index 20486bbcae..35aac9dd60 100644 --- a/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp +++ b/build/clang-plugin/tests/TestNoRefcountedInsideLambdas.cpp @@ -67,9 +67,9 @@ void foo() { take(argsp); take(localsp); }); - take([ptr](R* argptr) { + take([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} R* localptr; - ptr->method(); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + ptr->method(); argptr->method(); localptr->method(); }); @@ -79,9 +79,9 @@ void foo() { argsp->method(); localsp->method(); }); - take([ptr](R* argptr) { + take([ptr](R* argptr) { // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} R* localptr; - take(ptr); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}} + take(ptr); take(argptr); take(localptr); }); diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 8e091627dd..5fab24455c 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -900,7 +900,7 @@ DOMInterfaces = { 'Path2D': { 'nativeType': 'mozilla::dom::CanvasPath', - 'headerFile': 'CanvasRenderingContext2D.h' + 'headerFile': 'CanvasPath.h' }, 'PeerConnectionImpl': { diff --git a/dom/canvas/CanvasPath.h b/dom/canvas/CanvasPath.h new file mode 100644 index 0000000000..e31b375cc3 --- /dev/null +++ b/dom/canvas/CanvasPath.h @@ -0,0 +1,91 @@ +/* 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/. */ + +#ifndef CanvasPath_h +#define CanvasPath_h + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "nsWrapperCache.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/ErrorResult.h" + +namespace mozilla { +namespace dom { + +enum class CanvasWindingRule : uint32_t; +class SVGMatrix; + +class CanvasPath final : + public nsWrapperCache +{ +public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CanvasPath) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CanvasPath) + + nsCOMPtr GetParentObject() { return mParent; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + static already_AddRefed Constructor(const GlobalObject& aGlobal, + ErrorResult& rv); + static already_AddRefed Constructor(const GlobalObject& aGlobal, + CanvasPath& aCanvasPath, + ErrorResult& rv); + static already_AddRefed Constructor(const GlobalObject& aGlobal, + const nsAString& aPathString, + ErrorResult& rv); + + void ClosePath(); + void MoveTo(double x, double y); + void LineTo(double x, double y); + void QuadraticCurveTo(double cpx, double cpy, double x, double y); + void BezierCurveTo(double cp1x, double cp1y, + double cp2x, double cp2y, + double x, double y); + void ArcTo(double x1, double y1, double x2, double y2, double radius, + ErrorResult& error); + void Rect(double x, double y, double w, double h); + void Arc(double x, double y, double radius, + double startAngle, double endAngle, bool anticlockwise, + ErrorResult& error); + void Ellipse(double x, double y, double radiusX, double radiusY, + double rotation, double startAngle, double endAngle, + bool anticlockwise, ErrorResult& error); + + void LineTo(const gfx::Point& aPoint); + void BezierTo(const gfx::Point& aCP1, + const gfx::Point& aCP2, + const gfx::Point& aCP3); + + already_AddRefed GetPath(const CanvasWindingRule& aWinding, + const gfx::DrawTarget* aTarget) const; + + explicit CanvasPath(nsISupports* aParent); + // already_AddRefed arg because the return value from Path::CopyToBuilder() + // is passed directly and we can't drop the only ref to have a raw pointer. + CanvasPath(nsISupports* aParent, + already_AddRefed aPathBuilder); + + void AddPath(CanvasPath& aCanvasPath, + const Optional>& aMatrix); + +private: + virtual ~CanvasPath() {} + + nsCOMPtr mParent; + static gfx::Float ToFloat(double aValue) { return gfx::Float(aValue); } + + mutable RefPtr mPath; + mutable RefPtr mPathBuilder; + + void EnsurePathBuilder() const; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* CanvasPath_h */ + diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index e1cc382ba5..046c29772a 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -101,6 +101,7 @@ #include "nsCCUncollectableMarker.h" #include "nsWrapperCacheInlines.h" #include "mozilla/dom/CanvasRenderingContext2DBinding.h" +#include "mozilla/dom/CanvasPath.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLVideoElement.h" #include "mozilla/dom/SVGMatrix.h" @@ -1402,13 +1403,14 @@ CanvasRenderingContext2D::EnsureTarget(RenderingMode aRenderingMode) DemoteOldestContextIfNecessary(); mBufferProvider = nullptr; +#if USE_SKIA_GPU SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); -#if USE_SKIA_GPU if (glue && glue->GetGrContext() && glue->GetGLContext()) { mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format); if (mTarget) { AddDemotableContext(this); + mBufferProvider = new PersistentBufferProviderBasic(mTarget); } else { printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n"); mode = RenderingMode::SoftwareBackendMode; @@ -3360,15 +3362,24 @@ CanvasRenderingContext2D::MeasureText(const nsAString& rawText, void CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& options, ErrorResult& error) { - // check if the path is valid - EnsureUserSpacePath(CanvasWindingRule::Nonzero); - if(!mPath) { + RefPtr path; + if (options.mPath) { + path = options.mPath->GetPath(CanvasWindingRule::Nonzero, mTarget); + } + + if (!path) { + // check if the path is valid + EnsureUserSpacePath(CanvasWindingRule::Nonzero); + path = mPath; + } + + if(!path) { error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } // get the bounds of the current path. They are relative to the canvas - mgfx::Rect bounds(mPath->GetBounds(mTarget->GetTransform())); + mgfx::Rect bounds(path->GetBounds(mTarget->GetTransform())); if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) { // The specified region has no pixels. error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); @@ -3397,7 +3408,7 @@ CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& options, ErrorRes RegionInfo info; info.mId = options.mId; info.mElement = options.mControl; - RefPtr pathBuilder = mPath->TransformedCopyToBuilder(mTarget->GetTransform()); + RefPtr pathBuilder = path->TransformedCopyToBuilder(mTarget->GetTransform()); info.mPath = pathBuilder->Finish(); mHitRegionsOptions.InsertElementAt(0, info); @@ -5598,12 +5609,7 @@ CanvasRenderingContext2D::GetBufferProvider(LayerManager* aManager) return nullptr; } - mBufferProvider = aManager->CreatePersistentBufferProvider(mTarget->GetSize(), mTarget->GetFormat()); - - RefPtr surf = mTarget->Snapshot(); - - mTarget = mBufferProvider->GetDT(IntRect(IntPoint(), mTarget->GetSize())); - mTarget->CopySurface(surf, IntRect(IntPoint(), mTarget->GetSize()), IntPoint()); + mBufferProvider = new PersistentBufferProviderBasic(mTarget); return mBufferProvider; } diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index dee05fd671..311086a6f3 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -44,77 +44,12 @@ class StringOrCanvasGradientOrCanvasPattern; class OwningStringOrCanvasGradientOrCanvasPattern; class TextMetrics; class CanvasFilterChainObserver; +class CanvasPath; extern const mozilla::gfx::Float SIGMA_MAX; template class Optional; -class CanvasPath final : - public nsWrapperCache -{ -public: - NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CanvasPath) - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CanvasPath) - - nsCOMPtr GetParentObject() { return mParent; } - - JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto); - - static already_AddRefed Constructor(const GlobalObject& aGlobal, - ErrorResult& rv); - static already_AddRefed Constructor(const GlobalObject& aGlobal, - CanvasPath& aCanvasPath, - ErrorResult& rv); - static already_AddRefed Constructor(const GlobalObject& aGlobal, - const nsAString& aPathString, - ErrorResult& rv); - - void ClosePath(); - void MoveTo(double x, double y); - void LineTo(double x, double y); - void QuadraticCurveTo(double cpx, double cpy, double x, double y); - void BezierCurveTo(double cp1x, double cp1y, - double cp2x, double cp2y, - double x, double y); - void ArcTo(double x1, double y1, double x2, double y2, double radius, - ErrorResult& error); - void Rect(double x, double y, double w, double h); - void Arc(double x, double y, double radius, - double startAngle, double endAngle, bool anticlockwise, - ErrorResult& error); - void Ellipse(double x, double y, double radiusX, double radiusY, - double rotation, double startAngle, double endAngle, - bool anticlockwise, ErrorResult& error); - - void LineTo(const gfx::Point& aPoint); - void BezierTo(const gfx::Point& aCP1, - const gfx::Point& aCP2, - const gfx::Point& aCP3); - - already_AddRefed GetPath(const CanvasWindingRule& aWinding, - const gfx::DrawTarget* aTarget) const; - - explicit CanvasPath(nsISupports* aParent); - // already_AddRefed arg because the return value from Path::CopyToBuilder() - // is passed directly and we can't drop the only ref to have a raw pointer. - CanvasPath(nsISupports* aParent, - already_AddRefed aPathBuilder); - - void AddPath(CanvasPath& aCanvasPath, - const Optional>& aMatrix); - -private: - virtual ~CanvasPath() {} - - nsCOMPtr mParent; - static gfx::Float ToFloat(double aValue) { return gfx::Float(aValue); } - - mutable RefPtr mPath; - mutable RefPtr mPathBuilder; - - void EnsurePathBuilder() const; -}; - struct CanvasBidiProcessor; class CanvasRenderingContext2DUserData; class CanvasDrawObserver; @@ -977,6 +912,7 @@ protected: fillRule(mozilla::gfx::FillRule::FILL_WINDING), lineCap(mozilla::gfx::CapStyle::BUTT), lineJoin(mozilla::gfx::JoinStyle::MITER_OR_BEVEL), + filterString(MOZ_UTF16("none")), imageSmoothingEnabled(true), fontExplicitLanguage(false) { } diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 117eb3c795..07d5b27507 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -28,6 +28,7 @@ EXPORTS.mozilla.ipc += [ EXPORTS.mozilla.dom += [ 'CanvasGradient.h', + 'CanvasPath.h', 'CanvasPattern.h', 'CanvasRenderingContext2D.h', 'CanvasUtils.h', diff --git a/dom/canvas/test/test_hitregion_event.html b/dom/canvas/test/test_hitregion_event.html index 69df682cf7..4f74d82007 100644 --- a/dom/canvas/test/test_hitregion_event.html +++ b/dom/canvas/test/test_hitregion_event.html @@ -32,16 +32,29 @@ SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}, functi ctx.rect(20, 20, 100, 75); ctx.fill(); ctx.addHitRegion({id: "a"}); + ctx.beginPath(); ctx.fillStyle = "red"; ctx.rect(60, 40, 100, 75); ctx.fill(); ctx.addHitRegion({id: "b"}); + + var mypath = new Path2D(); + mypath.rect(80, 60, 10, 10); + ctx.beginPath(); ctx.fillStyle = "yellow"; ctx.rect(80, 60, 10, 10); ctx.fill(); - ctx.addHitRegion({id: "c"}); + ctx.addHitRegion({id: "c", path: mypath}); + + ctx.beginPath(); + ctx.fillStyle = "green"; + ctx.rect(60, 60, 10, 10); // This region is on purpose not the hit region + ctx.fill(); + var mypath = new Path2D(); + mypath.rect(70, 30, 10, 10); + ctx.addHitRegion({id: "d", path: mypath}); synthesizeMouse(input, 25,25, {type: "mousedown"}); is(regionId, "a", "Hit region a", ". Found: " + regionId); @@ -55,6 +68,9 @@ SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}, functi synthesizeMouse(input, 85,65, {type: "mousedown"}); is(regionId, "c", "Hit region c", ". Found: " + regionId); + synthesizeMouse(input, 75,35, {type: "mousedown"}); + is(regionId, "d", "Hit region d", ". Found: " + regionId); + ctx.removeHitRegion("c"); synthesizeMouse(input, 85,65, {type: "mousedown"}); is(regionId, "b", "Hit region b", ". Found: " + regionId); diff --git a/dom/system/gonk/AudioManager.cpp b/dom/system/gonk/AudioManager.cpp index 949a320760..4df3951c56 100644 --- a/dom/system/gonk/AudioManager.cpp +++ b/dom/system/gonk/AudioManager.cpp @@ -81,10 +81,14 @@ static int sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = { }; // A bitwise variable for recording what kind of headset is attached. static int sHeadsetState; +#if defined(MOZ_B2G_BT) || ANDROID_VERSION >= 17 static bool sBluetoothA2dpEnabled; +#endif static const int kBtSampleRate = 8000; static bool sSwitchDone = true; +#ifdef MOZ_B2G_BT static bool sA2dpSwitchDone = true; +#endif namespace mozilla { namespace dom { @@ -216,6 +220,7 @@ static void ProcessDelayedAudioRoute(SwitchState aState) sSwitchDone = true; } +#ifdef MOZ_B2G_BT static void ProcessDelayedA2dpRoute(audio_policy_dev_state_t aState, const nsCString aAddress) { if (sA2dpSwitchDone) @@ -228,6 +233,7 @@ static void ProcessDelayedA2dpRoute(audio_policy_dev_state_t aState, const nsCSt AudioSystem::setParameters(0, cmd); sA2dpSwitchDone = true; } +#endif NS_IMPL_ISUPPORTS(AudioManager, nsIAudioManager, nsIObserver) diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index 8b64a7e87a..dd758b2e1d 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -21,6 +21,7 @@ dictionary ContextAttributes2D { }; dictionary HitRegionOptions { + Path2D? path = null; DOMString id = ""; Element? control = null; }; diff --git a/gfx/2d/MMIHelpers.h b/gfx/2d/MMIHelpers.h new file mode 100644 index 0000000000..2a9b16a9d2 --- /dev/null +++ b/gfx/2d/MMIHelpers.h @@ -0,0 +1,196 @@ +/* + ============================================================================ + Name : MMIHelpers.h + Author : Heiher + Version : 0.0.1 + Copyright : Copyright (c) 2015 everyone. + Description : The helpers for x86 SSE to Loongson MMI. + ============================================================================ + */ + +#ifndef __MMI_HELPERS_H__ +#define __MMI_HELPERS_H__ + +#define __mm_packxxxx(_f, _D, _d, _s, _t) \ + #_f" %["#_t"], %["#_d"h], %["#_s"h] \n\t" \ + #_f" %["#_D"l], %["#_d"l], %["#_s"l] \n\t" \ + "punpckhwd %["#_D"h], %["#_D"l], %["#_t"] \n\t" \ + "punpcklwd %["#_D"l], %["#_D"l], %["#_t"] \n\t" + +#define _mm_or(_D, _d, _s) \ + "or %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "or %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +#define _mm_xor(_D, _d, _s) \ + "xor %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "xor %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +#define _mm_and(_D, _d, _s) \ + "and %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "and %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pandn */ +#define _mm_pandn(_D, _d, _s) \ + "pandn %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pandn %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pshuflw */ +#define _mm_pshuflh(_D, _d, _s) \ + "mov.d %["#_D"h], %["#_d"h] \n\t" \ + "pshufh %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psllw (bits) */ +#define _mm_psllh(_D, _d, _s) \ + "psllh %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psllh %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: pslld (bits) */ +#define _mm_psllw(_D, _d, _s) \ + "psllw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psllw %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psllq (bits) */ +#define _mm_pslld(_D, _d, _s) \ + "dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: pslldq (bytes) */ +#define _mm_psllq(_D, _d, _s, _s64, _tf) \ + "subu %["#_tf"], %["#_s64"], %["#_s"] \n\t" \ + "dsrl %["#_tf"], %["#_d"l], %["#_tf"] \n\t" \ + "dsll %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsll %["#_D"l], %["#_d"l], %["#_s"] \n\t" \ + "or %["#_D"h], %["#_D"h], %["#_tf"] \n\t" + +/* SSE: psrlw (bits) */ +#define _mm_psrlh(_D, _d, _s) \ + "psrlh %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psrlh %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psrld (bits) */ +#define _mm_psrlw(_D, _d, _s) \ + "psrlw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psrlw %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psrlq (bits) */ +#define _mm_psrld(_D, _d, _s) \ + "dsrl %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsrl %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: psrldq (bytes) */ +#define _mm_psrlq(_D, _d, _s, _s64, _tf) \ + "subu %["#_tf"], %["#_s64"], %["#_s"] \n\t" \ + "dsll %["#_tf"], %["#_d"h], %["#_tf"] \n\t" \ + "dsrl %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "dsrl %["#_D"l], %["#_d"l], %["#_s"] \n\t" \ + "or %["#_D"l], %["#_D"l], %["#_tf"] \n\t" + +/* SSE: psrad */ +#define _mm_psraw(_D, _d, _s) \ + "psraw %["#_D"h], %["#_d"h], %["#_s"] \n\t" \ + "psraw %["#_D"l], %["#_d"l], %["#_s"] \n\t" + +/* SSE: paddb */ +#define _mm_paddb(_D, _d, _s) \ + "paddb %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "paddb %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: paddw */ +#define _mm_paddh(_D, _d, _s) \ + "paddh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "paddh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: paddd */ +#define _mm_paddw(_D, _d, _s) \ + "paddw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "paddw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: paddq */ +#define _mm_paddd(_D, _d, _s) \ + "dadd %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "dadd %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: psubw */ +#define _mm_psubh(_D, _d, _s) \ + "psubh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "psubh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: psubd */ +#define _mm_psubw(_D, _d, _s) \ + "psubw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "psubw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmaxub */ +#define _mm_pmaxub(_D, _d, _s) \ + "pmaxub %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmaxub %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmullw */ +#define _mm_pmullh(_D, _d, _s) \ + "pmullh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmullh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmulhw */ +#define _mm_pmulhh(_D, _d, _s) \ + "pmulhh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmulhh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: pmuludq */ +#define _mm_pmuluw(_D, _d, _s) \ + "pmuluw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" \ + "pmuluw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: packsswb */ +#define _mm_packsshb(_D, _d, _s, _t) \ + __mm_packxxxx(packsshb, _D, _d, _s, _t) + +/* SSE: packssdw */ +#define _mm_packsswh(_D, _d, _s, _t) \ + __mm_packxxxx(packsswh, _D, _d, _s, _t) + +/* SSE: packuswb */ +#define _mm_packushb(_D, _d, _s, _t) \ + __mm_packxxxx(packushb, _D, _d, _s, _t) + +/* SSE: punpcklbw */ +#define _mm_punpcklbh(_D, _d, _s) \ + "punpckhbh %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \ + "punpcklbh %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: punpcklwd */ +#define _mm_punpcklhw(_D, _d, _s) \ + "punpckhhw %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \ + "punpcklhw %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: punpckldq */ +#define _mm_punpcklwd(_D, _d, _s) \ + "punpckhwd %["#_D"h], %["#_d"l], %["#_s"l] \n\t" \ + "punpcklwd %["#_D"l], %["#_d"l], %["#_s"l] \n\t" + +/* SSE: punpcklqdq */ +#define _mm_punpckldq(_D, _d, _s) \ + "mov.d %["#_D"h], %["#_s"l] \n\t" \ + "mov.d %["#_D"l], %["#_d"l] \n\t" + +/* SSE: punpckhbw */ +#define _mm_punpckhbh(_D, _d, _s) \ + "punpcklbh %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \ + "punpckhbh %["#_D"h], %["#_d"h], %["#_s"h] \n\t" + +/* SSE: punpckhwd */ +#define _mm_punpckhhw(_D, _d, _s) \ + "punpcklhw %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \ + "punpckhhw %["#_D"h], %["#_d"h], %["#_s"h] \n\t" + +/* SSE: punpckhdq */ +#define _mm_punpckhwd(_D, _d, _s) \ + "punpcklwd %["#_D"l], %["#_d"h], %["#_s"h] \n\t" \ + "punpckhwd %["#_D"h], %["#_d"h], %["#_s"h] \n\t" + +/* SSE: punpckhqdq */ +#define _mm_punpckhdq(_D, _d, _s) \ + "mov.d %["#_D"l], %["#_d"h] \n\t" \ + "mov.d %["#_D"h], %["#_s"h] \n\t" + +#endif /* __MMI_HELPERS_H__ */ + diff --git a/gfx/2d/SourceSurfaceSkia.cpp b/gfx/2d/SourceSurfaceSkia.cpp index e55b01b556..2de51f681c 100644 --- a/gfx/2d/SourceSurfaceSkia.cpp +++ b/gfx/2d/SourceSurfaceSkia.cpp @@ -101,6 +101,7 @@ SourceSurfaceSkia::InitFromTexture(DrawTargetSkia* aOwner, SurfaceFormat aFormat) { MOZ_ASSERT(aOwner, "null GrContext"); +#ifdef USE_SKIA_GPU GrBackendTextureDesc skiaTexGlue; mSize.width = skiaTexGlue.fWidth = aSize.width; mSize.height = skiaTexGlue.fHeight = aSize.height; @@ -117,6 +118,7 @@ SourceSurfaceSkia::InitFromTexture(DrawTargetSkia* aOwner, mBitmap.setPixelRef(texRef); mFormat = aFormat; mStride = mBitmap.rowBytes(); +#endif mDrawTarget = aOwner; return true; diff --git a/gfx/2d/image_operations.cpp b/gfx/2d/image_operations.cpp index fc6eabc1b2..7d9b44da04 100644 --- a/gfx/2d/image_operations.cpp +++ b/gfx/2d/image_operations.cpp @@ -168,15 +168,19 @@ ImageOperations::ResizeMethod ResizeMethodToAlgorithmMethod( // GPU-acceleration in the cases where it is possible. So now we just // pick the appropriate software method for each resize quality. switch (method) { + // Users of RESIZE_GOOD are willing to trade a lot of quality to + // get speed, allowing the use of linear resampling to get hardware + // acceleration (SRB). Hence any of our "good" software filters + // will be acceptable, and we use the fastest one, Hamming-1. case ImageOperations::RESIZE_GOOD: - // In visual tests we see that Hamming-1 is not as good as - // Lanczos-2, however it is about 40% faster, and Lanczos-2 itself is + // Users of RESIZE_BETTER are willing to trade some quality in order + // to improve performance, but are guaranteed not to devolve to a linear + // resampling. In visual tests we see that Hamming-1 is not as good as + // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed - // an unacceptable trade-off between quality and speed due to the limited - // pixel space it operates in (<50%) before switching to HQ scaling - // becomes necessary to retain the fidelity of images. + // an acceptable trade-off between quality and speed. case ImageOperations::RESIZE_BETTER: - return ImageOperations::RESIZE_LANCZOS2; + return ImageOperations::RESIZE_HAMMING1; default: return ImageOperations::RESIZE_LANCZOS3; } diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 6aab327c4d..4af02ce4f1 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -165,12 +165,12 @@ LayerManager::CreatePersistentBufferProvider(const mozilla::gfx::IntSize &aSize, mozilla::gfx::SurfaceFormat aFormat) { RefPtr bufferProvider = - new PersistentBufferProviderBasic(this, aSize, aFormat, + new PersistentBufferProviderBasic(aSize, aFormat, gfxPlatform::GetPlatform()->GetPreferredCanvasBackend()); if (!bufferProvider->IsValid()) { bufferProvider = - new PersistentBufferProviderBasic(this, aSize, aFormat, + new PersistentBufferProviderBasic(aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend()); } diff --git a/gfx/layers/PersistentBufferProvider.cpp b/gfx/layers/PersistentBufferProvider.cpp index 9e7fe379e2..a0ddd17cee 100644 --- a/gfx/layers/PersistentBufferProvider.cpp +++ b/gfx/layers/PersistentBufferProvider.cpp @@ -16,8 +16,8 @@ using namespace gfx; namespace layers { -PersistentBufferProviderBasic::PersistentBufferProviderBasic(LayerManager* aManager, gfx::IntSize aSize, - gfx::SurfaceFormat aFormat, gfx::BackendType aBackend) +PersistentBufferProviderBasic::PersistentBufferProviderBasic(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend) { mDrawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(aBackend, aSize, aFormat); } diff --git a/gfx/layers/PersistentBufferProvider.h b/gfx/layers/PersistentBufferProvider.h index 3a48755c70..35835b7e94 100644 --- a/gfx/layers/PersistentBufferProvider.h +++ b/gfx/layers/PersistentBufferProvider.h @@ -7,7 +7,7 @@ #define MOZILLA_GFX_PersistentBUFFERPROVIDER_H #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc -#include "mozilla/RefPtr.h" // for RefPtr, TemporaryRef, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed, etc #include "mozilla/layers/LayersTypes.h" #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/gfx/Types.h" @@ -57,8 +57,9 @@ class PersistentBufferProviderBasic : public PersistentBufferProvider public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PersistentBufferProviderBasic) - PersistentBufferProviderBasic(LayerManager* aManager, gfx::IntSize aSize, - gfx::SurfaceFormat aFormat, gfx::BackendType aBackend); + PersistentBufferProviderBasic(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend); + explicit PersistentBufferProviderBasic(gfx::DrawTarget* aTarget) : mDrawTarget(aTarget) {} bool IsValid() { return !!mDrawTarget; } virtual LayersBackend GetType() { return LayersBackend::LAYERS_BASIC; } @@ -69,6 +70,7 @@ private: RefPtr mDrawTarget; }; -} -} +} // namespace layers +} // namespace mozilla + #endif diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 83ed6e6aaf..48179f941b 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -1104,6 +1104,7 @@ void gfxPlatform::InitializeSkiaCacheLimits() { if (UseAcceleratedSkiaCanvas()) { +#ifdef USE_SKIA_GPU bool usingDynamicCache = gfxPrefs::CanvasSkiaGLDynamicCache(); int cacheItemLimit = gfxPrefs::CanvasSkiaGLCacheItems(); int cacheSizeLimit = gfxPrefs::CanvasSkiaGLCacheSize(); @@ -1125,7 +1126,6 @@ gfxPlatform::InitializeSkiaCacheLimits() printf_stderr("Determined SkiaGL cache limits: Size %i, Items: %i\n", cacheSizeLimit, cacheItemLimit); #endif -#ifdef USE_SKIA_GPU mSkiaGlue->GetGrContext()->setResourceCacheLimits(cacheItemLimit, cacheSizeLimit); #endif } diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp index bfd7eb373f..52dbe20255 100644 --- a/image/DecodePool.cpp +++ b/image/DecodePool.cpp @@ -246,7 +246,7 @@ private: { Work work; work.mType = Work::Type::DECODE; - work.mDecoder = aQueue.LastElement(); + work.mDecoder = aQueue.LastElement().forget(); aQueue.RemoveElementAt(aQueue.Length() - 1); return work; diff --git a/image/Decoder.cpp b/image/Decoder.cpp index 21c35044aa..437672b1fd 100644 --- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -431,9 +431,10 @@ Decoder::PostIsAnimated(int32_t aFirstFrameTimeout) } void -Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */, +Decoder::PostFrameStop(Opacity aFrameOpacity + /* = Opacity::SOME_TRANSPARENCY */, DisposalMethod aDisposalMethod - /* = DisposalMethod::KEEP */, + /* = DisposalMethod::KEEP */, int32_t aTimeout /* = 0 */, BlendMethod aBlendMethod /* = BlendMethod::OVER */) { diff --git a/image/Decoder.h b/image/Decoder.h index ccd3c4e004..c9b68d24ec 100644 --- a/image/Decoder.h +++ b/image/Decoder.h @@ -196,7 +196,6 @@ public: bool HasDecoderError() const { return NS_FAILED(mFailCode); } bool ShouldReportError() const { return mShouldReportError; } nsresult GetDecoderError() const { return mFailCode; } - void PostResizeError() { PostDataError(); } /// Did we finish decoding enough that calling Decode() again would be useless? bool GetDecodeDone() const @@ -205,10 +204,6 @@ public: HasError() || mDataDone; } - /// Did we finish decoding enough to set |RasterImage::mHasBeenDecoded|? - // XXX(seth): This will be removed in bug 1187401. - bool GetDecodeTotallyDone() const { return mDecodeDone && !IsMetadataDecode(); } - /// Are we in the middle of a frame right now? Used for assertions only. bool InFrame() const { return mInFrame; } diff --git a/image/MultipartImage.cpp b/image/MultipartImage.cpp index b849d2afe9..1778e629f0 100644 --- a/image/MultipartImage.cpp +++ b/image/MultipartImage.cpp @@ -38,10 +38,16 @@ public: void BlockUntilDecodedAndFinishObserving() { // Use GetFrame() to block until our image finishes decoding. - mImage->GetFrame(imgIContainer::FRAME_CURRENT, - imgIContainer::FLAG_SYNC_DECODE); - - FinishObserving(); + nsRefPtr surface = + mImage->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // GetFrame() should've sent synchronous notifications that would have + // caused us to call FinishObserving() (and null out mImage) already. If for + // some reason it didn't, we should do so here. + if (mImage) { + FinishObserving(); + } } virtual void Notify(int32_t aType, @@ -129,9 +135,7 @@ MultipartImage::~MultipartImage() mTracker->ResetImage(); } -NS_IMPL_QUERY_INTERFACE_INHERITED0(MultipartImage, ImageWrapper) -NS_IMPL_ADDREF(MultipartImage) -NS_IMPL_RELEASE(MultipartImage) +NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage, ImageWrapper) void MultipartImage::BeginTransitionToPart(Image* aNextPart) @@ -154,7 +158,16 @@ MultipartImage::BeginTransitionToPart(Image* aNextPart) mNextPart->IncrementAnimationConsumers(); } -void MultipartImage::FinishTransition() +static Progress +FilterProgress(Progress aProgress) +{ + // Filter out onload blocking notifications, since we don't want to block + // onload for multipart images. + return aProgress & ~(FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED); +} + +void +MultipartImage::FinishTransition() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mNextPart, "Should have a next part here"); @@ -169,7 +182,8 @@ void MultipartImage::FinishTransition() mTracker->ResetForNewRequest(); nsRefPtr currentPartTracker = InnerImage()->GetProgressTracker(); - mTracker->SyncNotifyProgress(currentPartTracker->GetProgress()); + mTracker + ->SyncNotifyProgress(FilterProgress(currentPartTracker->GetProgress())); return; } @@ -189,8 +203,9 @@ void MultipartImage::FinishTransition() // Finally, send all the notifications for the new current part and send a // FRAME_UPDATE notification so that observers know to redraw. - mTracker->SyncNotifyProgress(newCurrentPartTracker->GetProgress(), - GetMaxSizedIntRect()); + mTracker + ->SyncNotifyProgress(FilterProgress(newCurrentPartTracker->GetProgress()), + GetMaxSizedIntRect()); } already_AddRefed diff --git a/image/MultipartImage.h b/image/MultipartImage.h index 95fce0ce0a..b041e72135 100644 --- a/image/MultipartImage.h +++ b/image/MultipartImage.h @@ -25,7 +25,7 @@ class MultipartImage { public: MOZ_DECLARE_REFCOUNTED_TYPENAME(MultipartImage) - NS_DECL_ISUPPORTS + NS_DECL_ISUPPORTS_INHERITED void BeginTransitionToPart(Image* aNextPart); diff --git a/image/Orientation.h b/image/Orientation.h index f18ced8542..a0f2e1b75a 100644 --- a/image/Orientation.h +++ b/image/Orientation.h @@ -56,7 +56,7 @@ struct Orientation Flip flip; }; -} -} +} // namespace image +} // namespace mozilla #endif // mozilla_image_Orientation_h diff --git a/image/ProgressTracker.cpp b/image/ProgressTracker.cpp index b0a76a762e..65e8d39435 100644 --- a/image/ProgressTracker.cpp +++ b/image/ProgressTracker.cpp @@ -441,13 +441,14 @@ void ProgressTracker::AddObserver(IProgressObserver* aObserver) { MOZ_ASSERT(NS_IsMainThread()); + nsRefPtr observer = aObserver; mObservers.Write([=](ObserverTable* aTable) { - MOZ_ASSERT(!aTable->Get(aObserver, nullptr), + MOZ_ASSERT(!aTable->Get(observer, nullptr), "Adding duplicate entry for image observer"); - WeakPtr weakPtr = aObserver; - aTable->Put(aObserver, weakPtr); + WeakPtr weakPtr = observer.get(); + aTable->Put(observer, weakPtr); }); } @@ -455,11 +456,12 @@ bool ProgressTracker::RemoveObserver(IProgressObserver* aObserver) { MOZ_ASSERT(NS_IsMainThread()); + nsRefPtr observer = aObserver; // Remove the observer from the list. bool removed = mObservers.Write([=](ObserverTable* aTable) { - bool removed = aTable->Get(aObserver, nullptr); - aTable->Remove(aObserver); + bool removed = aTable->Get(observer, nullptr); + aTable->Remove(observer); return removed; }); diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index b056ad0302..ce84fe0698 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -819,20 +819,22 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount, } } -nsresult +void RasterImage::SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode) { MOZ_ASSERT(NS_IsMainThread()); if (mError) { - return NS_ERROR_FAILURE; + return; } if (aMetadata.HasSize()) { IntSize size = aMetadata.GetSize(); if (size.width < 0 || size.height < 0) { - return NS_ERROR_INVALID_ARG; + NS_WARNING("Image has negative intrinsic size"); + DoError(); + return; } MOZ_ASSERT(aMetadata.HasOrientation()); @@ -843,7 +845,7 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata, NS_WARNING("Image changed size or orientation on redecode! " "This should not happen!"); DoError(); - return NS_ERROR_UNEXPECTED; + return; } // Set the size and flag that we have it. @@ -887,8 +889,6 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata, Set("hotspotX", intwrapx); Set("hotspotY", intwrapy); } - - return NS_OK; } NS_IMETHODIMP @@ -1074,7 +1074,7 @@ RasterImage::NotifyForLoadEvent(Progress aProgress) if (mError) { aProgress |= FLAG_HAS_ERROR; } - + // Notify our listeners, which will fire this image's load event. NotifyProgress(aProgress); } @@ -1374,6 +1374,11 @@ RasterImage::RecoverFromInvalidFrames(const IntSize& aSize, uint32_t aFlags) // Discard all existing frames, since they're probably all now invalid. SurfaceCache::RemoveImage(ImageKey(this)); + // Relock the image if it's supposed to be locked. + if (mLockCount > 0) { + SurfaceCache::LockImage(ImageKey(this)); + } + // Animated images require some special handling, because we normally require // that they never be discarded. if (mAnim) { @@ -1707,22 +1712,20 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) MOZ_ASSERT(aDecoder->HasError() || !aDecoder->InFrame(), "Finalizing a decoder in the middle of a frame"); + bool wasMetadata = aDecoder->IsMetadataDecode(); + bool done = aDecoder->GetDecodeDone(); + // If the decoder detected an error, log it to the error console. if (aDecoder->ShouldReportError() && !aDecoder->WasAborted()) { ReportDecoderError(aDecoder); } // Record all the metadata the decoder gathered about this image. - nsresult rv = SetMetadata(aDecoder->GetImageMetadata(), - aDecoder->IsMetadataDecode()); - if (NS_FAILED(rv)) { - aDecoder->PostResizeError(); - } - + SetMetadata(aDecoder->GetImageMetadata(), wasMetadata); MOZ_ASSERT(mError || mHasSize || !aDecoder->HasSize(), - "Should have handed off size by now"); + "SetMetadata should've gotten a size"); - if (aDecoder->GetDecodeTotallyDone() && !mError) { + if (!wasMetadata && aDecoder->GetDecodeDone() && !aDecoder->WasAborted()) { // Flag that we've been decoded before. mHasBeenDecoded = true; if (mAnim) { @@ -1735,9 +1738,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) aDecoder->TakeInvalidRect(), aDecoder->GetSurfaceFlags()); - bool wasMetadata = aDecoder->IsMetadataDecode(); - bool done = aDecoder->GetDecodeDone(); - if (!wasMetadata && aDecoder->ChunkCount()) { /*Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, aDecoder->ChunkCount());*/ diff --git a/image/RasterImage.h b/image/RasterImage.h index 4aad6e72eb..c962cf69c8 100644 --- a/image/RasterImage.h +++ b/image/RasterImage.h @@ -325,7 +325,7 @@ private: * @param aFromMetadataDecode True if this metadata came from a metadata * decode; false if it came from a full decode. */ - nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode); + void SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode); /** * In catastrophic circumstances like a GPU driver crash, the contents of our @@ -347,10 +347,10 @@ private: // data /// If this has a value, we're waiting for SetSize() to send the load event. Maybe mLoadProgress; - nsCOMPtr mProperties; + nsCOMPtr mProperties; /// If this image is animated, a FrameAnimator which manages its animation. - UniquePtr mAnim; + UniquePtr mAnim; // Image locking. uint32_t mLockCount; @@ -360,7 +360,7 @@ private: // data // How many times we've decoded this image. // This is currently only used for statistics - int32_t mDecodeCount; + int32_t mDecodeCount; // If the image contains multiple resolutions, a hint as to which one // should be used @@ -378,7 +378,7 @@ private: // data DrawResult mLastImageContainerDrawResult; #ifdef DEBUG - uint32_t mFramesNotified; + uint32_t mFramesNotified; #endif // The source data for this image. diff --git a/image/SVGDocumentWrapper.h b/image/SVGDocumentWrapper.h index dec656004d..7d7586ae37 100644 --- a/image/SVGDocumentWrapper.h +++ b/image/SVGDocumentWrapper.h @@ -35,8 +35,8 @@ class SVGSVGElement; namespace image { class SVGDocumentWrapper final : public nsIStreamListener, - public nsIObserver, - nsSupportsWeakReference + public nsIObserver, + nsSupportsWeakReference { public: SVGDocumentWrapper(); diff --git a/image/SourceBuffer.h b/image/SourceBuffer.h index b87ea6f029..e0cdd926c8 100644 --- a/image/SourceBuffer.h +++ b/image/SourceBuffer.h @@ -120,7 +120,8 @@ public: /// If at the end, returns the status passed to SourceBuffer::Complete(). nsresult CompletionStatus() const { - MOZ_ASSERT(mState == COMPLETE, "Calling CompletionStatus() in the wrong state"); + MOZ_ASSERT(mState == COMPLETE, + "Calling CompletionStatus() in the wrong state"); return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK; } @@ -209,12 +210,6 @@ private: * keep a list of consumers which are waiting for new data, and to resume them * when the producer appends more. All consumers must implement the IResumable * interface to make this possible. - * - * XXX(seth): We should add support for compacting a SourceBuffer. To do this, - * we need to have SourceBuffer keep track of how many live - * SourceBufferIterator's point to it. When the SourceBuffer is complete and no - * live SourceBufferIterator's for it remain, we can compact its contents into a - * single chunk. */ class SourceBuffer final { diff --git a/image/SurfaceCache.cpp b/image/SurfaceCache.cpp index 3a9c49e9cb..6ffc76b3bf 100644 --- a/image/SurfaceCache.cpp +++ b/image/SurfaceCache.cpp @@ -35,6 +35,7 @@ #include "nsSize.h" #include "nsTArray.h" #include "prsystem.h" +#include "ShutdownTracker.h" #include "SVGImageContext.h" using std::max; @@ -436,6 +437,7 @@ public: , mMaxCost(aSurfaceCacheSize) , mAvailableCost(aSurfaceCacheSize) , mLockedCost(0) + , mOverflowCount(0) { nsCOMPtr os = services::GetObserverService(); if (os) { @@ -483,6 +485,7 @@ public: // If this is bigger than we can hold after discarding everything we can, // refuse to cache it. if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(aCost))) { + mOverflowCount++; return InsertOutcome::FAILURE; } @@ -582,7 +585,8 @@ public: } else { // Our call to AddObject must have failed in StartTracking; most likely // we're in XPCOM shutdown right now. - NS_WARNING("Not expiration-tracking an unlocked surface!"); + NS_ASSERTION(ShutdownTracker::ShutdownHasStarted(), + "Not expiration-tracking an unlocked surface!"); } DebugOnly foundInCosts = mCosts.RemoveElementSorted(costEntry); @@ -863,6 +867,14 @@ public: "imagelib surface cache."); NS_ENSURE_SUCCESS(rv, rv); + rv = MOZ_COLLECT_REPORT("imagelib-surface-cache-overflow-count", + KIND_OTHER, UNITS_COUNT, + mOverflowCount, + "Count of how many times the surface cache has hit " + "its capacity and been unable to insert a new " + "surface."); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } @@ -962,6 +974,7 @@ private: const Cost mMaxCost; Cost mAvailableCost; Cost mLockedCost; + size_t mOverflowCount; }; NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter) @@ -1079,6 +1092,11 @@ SurfaceCache::Insert(imgFrame* aSurface, return InsertOutcome::FAILURE; } + // Refuse null surfaces. + if (!aSurface) { + MOZ_CRASH("Don't pass null surfaces to SurfaceCache::Insert"); + } + MutexAutoLock lock(sInstance->GetMutex()); Cost cost = ComputeCost(aSurface->GetSize(), aSurface->GetBytesPerPixel()); return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey); diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index c9bcca1706..5ce1060bc6 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -9,7 +9,6 @@ #include "gfxContext.h" #include "gfxDrawable.h" #include "gfxPlatform.h" -#include "gfxPrefs.h" // for surface cache size #include "gfxUtils.h" #include "imgFrame.h" #include "mozilla/AutoRestore.h" @@ -49,6 +48,8 @@ namespace image { // Helper-class: SVGRootRenderingObserver class SVGRootRenderingObserver final : public nsSVGRenderingObserver { public: + NS_DECL_ISUPPORTS + SVGRootRenderingObserver(SVGDocumentWrapper* aDocWrapper, VectorImage* aVectorImage) : nsSVGRenderingObserver() @@ -67,10 +68,6 @@ public: mInObserverList = true; } - virtual ~SVGRootRenderingObserver() - { - StopListening(); - } void ResumeHonoringInvalidations() { @@ -78,6 +75,11 @@ public: } protected: + virtual ~SVGRootRenderingObserver() + { + StopListening(); + } + virtual Element* GetTarget() override { return mDocWrapper->GetRootSVGElem(); @@ -115,6 +117,8 @@ protected: bool mHonoringInvalidations; }; +NS_IMPL_ISUPPORTS(SVGRootRenderingObserver, nsIMutationObserver) + class SVGParseCompleteListener final : public nsStubDocumentObserver { public: NS_DECL_ISUPPORTS @@ -418,13 +422,18 @@ VectorImage::OnImageDataComplete(nsIRequest* aRequest, if (NS_FAILED(aStatus)) { finalStatus = aStatus; } + + Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus); - // Actually fire OnStopRequest. - if (mProgressTracker) { - mProgressTracker->SyncNotifyProgress(LoadCompleteProgress(aLastPart, - mError, - finalStatus)); + if (mIsFullyLoaded || mError) { + // Our document is loaded, so we're ready to notify now. + mProgressTracker->SyncNotifyProgress(loadProgress); + } else { + // Record our progress so far; we'll actually send the notifications in + // OnSVGDocumentLoaded or OnSVGDocumentError. + mLoadProgress = Some(loadProgress); } + return finalStatus; } @@ -867,32 +876,12 @@ VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams) nsRefPtr svgDrawable = new gfxCallbackDrawable(cb, aParams.size); - // We take an early exit without using the surface cache if too large, - // because for vector images this can cause bad perf issues if large sizes - // are scaled repeatedly (a rather common scenario) that can quickly exhaust - // the cache. - // Similar to max image size calculations, this has a max cap and size check. - // max cap = 8000 (pixels); size check = 5% of cache - int32_t maxDimension = 8000; - int32_t maxCacheElemSize = (gfxPrefs::ImageMemSurfaceCacheMaxSizeKB() * 1024) / 20; - bool bypassCache = bool(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) || // Refuse to cache animated images: // XXX(seth): We may remove this restriction in bug 922893. mHaveAnimations || // The image is too big to fit in the cache: - !SurfaceCache::CanHold(aParams.size) || - // Image x or y is larger than our cache cap: - aParams.size.width > maxDimension || - aParams.size.height > maxDimension; - if (!bypassCache) { - // This is separated out to make sure width and height are sane at this point - // and the result can't overflow. Note: keep maxDimension low enough so that - // (maxDimension)^2 x 4 < INT32_MAX. - // Assuming surface size for any rendered vector image is RGBA, so 4Bpp. - bypassCache = (aParams.size.width * aParams.size.height * 4) > maxCacheElemSize; - } - + !SurfaceCache::CanHold(aParams.size); if (bypassCache) { return Show(svgDrawable, aParams); } @@ -1193,12 +1182,19 @@ VectorImage::OnSVGDocumentLoaded() // Tell *our* observers that we're done loading. if (mProgressTracker) { - mProgressTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE | - FLAG_HAS_TRANSPARENCY | - FLAG_FRAME_COMPLETE | - FLAG_DECODE_COMPLETE | - FLAG_ONLOAD_UNBLOCKED, - GetMaxSizedIntRect()); + Progress progress = FLAG_SIZE_AVAILABLE | + FLAG_HAS_TRANSPARENCY | + FLAG_FRAME_COMPLETE | + FLAG_DECODE_COMPLETE | + FLAG_ONLOAD_UNBLOCKED; + + // Merge in any saved progress from OnImageDataComplete. + if (mLoadProgress) { + progress |= *mLoadProgress; + mLoadProgress = Nothing(); + } + + mProgressTracker->SyncNotifyProgress(progress, GetMaxSizedIntRect()); } EvaluateAnimation(); @@ -1215,10 +1211,18 @@ VectorImage::OnSVGDocumentError() mError = true; if (mProgressTracker) { - // Unblock page load. - mProgressTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE | - FLAG_ONLOAD_UNBLOCKED | - FLAG_HAS_ERROR); + // Notify observers about the error and unblock page load. + Progress progress = FLAG_DECODE_COMPLETE | + FLAG_ONLOAD_UNBLOCKED | + FLAG_HAS_ERROR; + + // Merge in any saved progress from OnImageDataComplete. + if (mLoadProgress) { + progress |= *mLoadProgress; + mLoadProgress = Nothing(); + } + + mProgressTracker->SyncNotifyProgress(progress); } } diff --git a/image/VectorImage.h b/image/VectorImage.h index 9d8699b526..8d918f67a4 100644 --- a/image/VectorImage.h +++ b/image/VectorImage.h @@ -23,7 +23,7 @@ class SVGLoadEventListener; class SVGParseCompleteListener; class VectorImage final : public ImageResource, - public nsIStreamListener + public nsIStreamListener { public: NS_DECL_ISUPPORTS @@ -102,6 +102,12 @@ private: /// Count of locks on this image (roughly correlated to visible instances). uint32_t mLockCount; + // Stored result from the Necko load of the image, which we save in + // OnImageDataComplete if the underlying SVG document isn't loaded. If we save + // this, we actually notify this progress (and clear this value) in + // OnSVGDocumentLoaded or OnSVGDocumentError. + Maybe mLoadProgress; + bool mIsInitialized; // Have we been initialized? bool mDiscardable; // Are we discardable? bool mIsFullyLoaded; // Has the SVG document finished diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 206f904a81..cd9d4c338a 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -219,6 +219,8 @@ private: const char* aPathPrefix, const ImageMemoryCounter& aCounter) { + nsresult rv; + nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/")); pathPrefix.Append(aPathPrefix); pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER @@ -239,7 +241,13 @@ private: pathPrefix.Append(")/"); - return ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter); + rv = ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values()); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; } static nsresult ReportSurfaces(nsIHandleReportCallback* aHandleReport, @@ -344,10 +352,7 @@ private: { nsresult rv; - rv = ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, - "source", - "Raster image source data and vector image documents.", - aCounter.Source()); + rv = ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter); NS_ENSURE_SUCCESS(rv, rv); rv = ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, @@ -365,6 +370,21 @@ private: return NS_OK; } + static nsresult ReportSourceValue(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const nsACString& aPathPrefix, + const MemoryCounter& aCounter) + { + nsresult rv; + + rv = ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, + "source", + "Raster image source data and vector image documents.", + aCounter.Source()); + + return rv; + } + static nsresult ReportValue(nsIHandleReportCallback* aHandleReport, nsISupports* aData, int32_t aKind, @@ -706,6 +726,8 @@ NewImageChannel(nsIChannel** aResult, nsIPrincipal* aLoadingPrincipal, nsISupports* aRequestingContext) { + MOZ_ASSERT(aResult); + nsresult rv; nsCOMPtr newHttpChannel; @@ -768,7 +790,9 @@ NewImageChannel(nsIChannel** aResult, // we should always have a requestingNode, or we are loading something // outside a document, in which case the triggeringPrincipal // should always be the systemPrincipal. - MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(triggeringPrincipal)); + // However, there are two exceptions: one is Notifications and the + // other one is Favicons which create a channel in the parent prcoess + // in which case we can't get a requestingNode. rv = NS_NewChannel(aResult, aURI, triggeringPrincipal, @@ -1446,9 +1470,8 @@ imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::PutIntoCache", "uri", aKey.Spec()); - // Check to see if this request already exists in the cache and is being - // loaded on a different thread. If so, don't allow this entry to be added to - // the cache. + // Check to see if this request already exists in the cache. If so, we'll + // replace the old version. nsRefPtr tmpCacheEntry; if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) { MOZ_LOG(GetImgLog(), LogLevel::Debug, @@ -1813,7 +1836,7 @@ imgLoader::ValidateEntry(imgCacheEntry* aEntry, // XXX: nullptr seems to be a 'special' key value that indicates that NO // validation is required. // - void *key = (void*)aCX; + void *key = (void*) aCX; if (request->LoadId() != key) { // If we would need to revalidate this entry, but we're being told to // bypass the cache, we don't allow this entry to be used. @@ -2976,6 +2999,9 @@ imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) { + // We may be holding on to a document, so ensure that it's released. + nsCOMPtr context = mContext.forget(); + // If for some reason we don't still have an existing request (probably // because OnStartRequest got delivered more than once), just bail. if (!mRequest) { @@ -3024,7 +3050,7 @@ imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) // We don't need to load this any more. aRequest->Cancel(NS_BINDING_ABORTED); - mRequest->SetLoadId(mContext); + mRequest->SetLoadId(context); mRequest->SetValidator(nullptr); mRequest = nullptr; @@ -3067,7 +3093,7 @@ imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) nsCOMPtr originalURI; channel->GetOriginalURI(getter_AddRefs(originalURI)); mNewRequest->Init(originalURI, uri, mHadInsecureRedirect, aRequest, channel, - mNewEntry, mContext, loadingPrincipal, corsmode, refpol); + mNewEntry, context, loadingPrincipal, corsmode, refpol); mDestListener = new ProxyListener(mNewRequest); @@ -3093,11 +3119,19 @@ imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) return mDestListener->OnStartRequest(aRequest, ctxt); } -/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ -NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) +/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, + in nsresult status); */ +NS_IMETHODIMP +imgCacheValidator::OnStopRequest(nsIRequest* aRequest, + nsISupports* ctxt, + nsresult status) { - if (!mDestListener) + // Be sure we've released the document that we may have been holding on to. + mContext = nullptr; + + if (!mDestListener) { return NS_OK; + } return mDestListener->OnStopRequest(aRequest, ctxt, status); } @@ -3180,7 +3214,9 @@ imgCacheValidator:: if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) || NS_FAILED(oldURI->SchemeIs("https", &isHttps)) || NS_FAILED(oldURI->SchemeIs("chrome", &isChrome)) || - NS_FAILED(NS_URIChainHasFlags(oldURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal)) || + NS_FAILED(NS_URIChainHasFlags(oldURI, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &schemeLocal)) || (!isHttps && !isChrome && !schemeLocal)) { mHadInsecureRedirect = true; } diff --git a/image/imgRequest.cpp b/image/imgRequest.cpp index e1606283af..273d9fc5e0 100644 --- a/image/imgRequest.cpp +++ b/image/imgRequest.cpp @@ -634,17 +634,6 @@ imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest) aCacheEntry->SetMustValidate(bMustRevalidate); } } - - // We always need to validate file URIs. - nsCOMPtr channel = do_QueryInterface(aRequest); - if (channel) { - nsCOMPtr uri; - channel->GetURI(getter_AddRefs(uri)); - bool isfile = false; - uri->SchemeIs("file", &isfile); - if (isfile) - aCacheEntry->SetMustValidate(isfile); - } } } @@ -718,6 +707,13 @@ imgRequest::GetMultipart() const return mIsMultiPartChannel; } +bool +imgRequest::HadInsecureRedirect() const +{ + MutexAutoLock lock(mMutex); + return mHadInsecureRedirect; +} + /** nsIRequestObserver methods **/ NS_IMETHODIMP @@ -737,7 +733,7 @@ imgRequest::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) mIsMultiPartChannel = bool(multiPartChannel); } - // If we're not multipart, we shouldn't have an image yet + // If we're not multipart, we shouldn't have an image yet. if (image && !multiPartChannel) { MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request"); Cancel(NS_IMAGELIB_ERROR_FAILURE); @@ -752,7 +748,6 @@ imgRequest::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 */ if (!mRequest) { - nsCOMPtr multiPartChannel = do_QueryInterface(aRequest); MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart"); nsCOMPtr baseChannel; multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); @@ -950,20 +945,20 @@ PrepareForNewPart(nsIRequest* aRequest, nsIInputStream* aInStr, uint32_t aCount, nsCOMPtr chan(do_QueryInterface(aRequest)); if (result.mContentType.IsEmpty()) { - nsresult rv = NS_ERROR_FAILURE; - if (chan) { - rv = chan->GetContentType(result.mContentType); - chan->GetContentDispositionHeader(result.mContentDisposition); - } - + nsresult rv = chan ? chan->GetContentType(result.mContentType) + : NS_ERROR_FAILURE; if (NS_FAILED(rv)) { MOZ_LOG(GetImgLog(), - LogLevel::Error, ("imgRequest::PrepareForNewPart " - "-- Content type unavailable from the channel\n")); + LogLevel::Error, ("imgRequest::PrepareForNewPart -- " + "Content type unavailable from the channel\n")); return result; } } + if (chan) { + chan->GetContentDispositionHeader(result.mContentDisposition); + } + MOZ_LOG(GetImgLog(), LogLevel::Debug, ("imgRequest::PrepareForNewPart -- Got content type %s\n", result.mContentType.get())); @@ -1269,7 +1264,18 @@ imgRequest::OnRedirectVerifyCallback(nsresult result) nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) || (!isHttps && !isChrome && !schemeLocal)) { - mHadInsecureRedirect = true; + MutexAutoLock lock(mMutex); + + // The csp directive upgrade-insecure-requests performs an internal redirect + // to upgrade all requests from http to https before any data is fetched from + // the network. Do not pollute mHadInsecureRedirect in case of such an internal + // redirect. + nsCOMPtr loadInfo = mChannel->GetLoadInfo(); + bool upgradeInsecureRequests = loadInfo ? loadInfo->GetUpgradeInsecureRequests() + : false; + if (!upgradeInsecureRequests) { + mHadInsecureRedirect = true; + } } // Update the current URI. diff --git a/image/imgRequest.h b/image/imgRequest.h index 5361934f79..01cce77e21 100644 --- a/image/imgRequest.h +++ b/image/imgRequest.h @@ -44,10 +44,10 @@ class ProgressTracker; struct NewPartResult; class imgRequest final : public nsIStreamListener, - public nsIThreadRetargetableStreamListener, - public nsIChannelEventSink, - public nsIInterfaceRequestor, - public nsIAsyncVerifyRedirectCallback + public nsIThreadRetargetableStreamListener, + public nsIChannelEventSink, + public nsIInterfaceRequestor, + public nsIAsyncVerifyRedirectCallback { typedef mozilla::image::Image Image; typedef mozilla::image::ImageCacheKey ImageCacheKey; @@ -118,7 +118,7 @@ public: // Returns whether we went through an insecure (non-HTTPS) redirect at some // point during loading. This does not consider the current URI. - bool HadInsecureRedirect() const { return mHadInsecureRedirect; } + bool HadInsecureRedirect() const; // The CORS mode for which we loaded this image. int32_t GetCORSMode() const { return mCORSMode; } diff --git a/image/imgRequestProxy.h b/image/imgRequestProxy.h index 84681f37ee..f63cafb3f5 100644 --- a/image/imgRequestProxy.h +++ b/image/imgRequestProxy.h @@ -203,7 +203,10 @@ private: // mListener is only promised to be a weak ref (see imgILoader.idl), // but we actually keep a strong ref to it until we've seen our // first OnStopRequest. - imgINotificationObserver* mListener; + imgINotificationObserver* MOZ_UNSAFE_REF("Observers must call Cancel() or " + "CancelAndForgetObserver() before " + "they are destroyed") mListener; + nsCOMPtr mLoadGroup; nsLoadFlags mLoadFlags; diff --git a/image/moz.build b/image/moz.build index 093276e66c..7d92a204c3 100644 --- a/image/moz.build +++ b/image/moz.build @@ -19,7 +19,6 @@ MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini'] XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] - XPIDL_SOURCES += [ 'imgICache.idl', 'imgIContainer.idl', @@ -36,7 +35,6 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'imglib2' - EXPORTS += [ 'ImageCacheKey.h', 'ImageLogging.h', diff --git a/image/test/mochitest/bug1180105-waiter.sjs b/image/test/mochitest/bug1180105-waiter.sjs new file mode 100644 index 0000000000..4e86ae2879 --- /dev/null +++ b/image/test/mochitest/bug1180105-waiter.sjs @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var timer = Components.classes["@mozilla.org/timer;1"]; +var waitTimer = timer.createInstance(Components.interfaces.nsITimer); + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.processAsync(); + waitForFinish(response); +} + +function waitForFinish(response) { + if (getSharedState("all-parts-done") === "1") { + response.write("done"); + response.finish(); + } else { + waitTimer.initWithCallback(function() {waitForFinish(response);}, 10, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); + } +} diff --git a/image/test/mochitest/bug1180105.sjs b/image/test/mochitest/bug1180105.sjs new file mode 100644 index 0000000000..e138e548ee --- /dev/null +++ b/image/test/mochitest/bug1180105.sjs @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var counter = 100; +var timer = Components.classes["@mozilla.org/timer;1"]; +var partTimer = timer.createInstance(Components.interfaces.nsITimer); + +function getFileAsInputStream(aFilename) { + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + file.append(aFilename); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + return fileStream; +} + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", + "multipart/x-mixed-replace;boundary=BOUNDARYOMG", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + // We're sending parts off in a delayed fashion, to let the tests occur. + response.processAsync(); + response.write("--BOUNDARYOMG\r\n"); + sendParts(response); +} + +function sendParts(response) { + if (counter-- == 0) { + sendClose(response); + setSharedState("all-parts-done", "1"); + return; + } + sendNextPart(response); + partTimer.initWithCallback(function() {sendParts(response);}, 1, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); +} + +function sendClose(response) { + response.write("--BOUNDARYOMG--\r\n"); + response.finish(); +} + +function sendNextPart(response) { + var nextPartHead = "Content-Type: image/jpeg\r\n\r\n"; + var inputStream = getFileAsInputStream("damon.jpg"); + response.bodyOutputStream.write(nextPartHead, nextPartHead.length); + response.bodyOutputStream.writeFrom(inputStream, inputStream.available()); + inputStream.close(); + // Toss in the boundary, so the browser can know this part is complete + response.write("--BOUNDARYOMG\r\n"); +} + diff --git a/image/test/mochitest/mochitest.ini b/image/test/mochitest/mochitest.ini index aab6f2ce2c..3345fd6a3c 100644 --- a/image/test/mochitest/mochitest.ini +++ b/image/test/mochitest/mochitest.ini @@ -30,6 +30,8 @@ support-files = bug89419.sjs bug900200.png bug900200-ref.png + bug1180105.sjs + bug1180105-waiter.sjs clear.gif clear.png clear2.gif @@ -86,6 +88,7 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only skip-if = (toolkit == 'android' && processor == 'x86') #x86 only [test_bug89419-2.html] skip-if = (toolkit == 'android' && processor == 'x86') #x86 only +[test_bug1180105.html] [test_animation_operators.html] [test_drawDiscardedImage.html] skip-if = toolkit == "gonk" #Bug 997034 - canvas.toDataURL() often causes lost connection to device. diff --git a/image/test/mochitest/test_bug1180105.html b/image/test/mochitest/test_bug1180105.html new file mode 100644 index 0000000000..1691b621c2 --- /dev/null +++ b/image/test/mochitest/test_bug1180105.html @@ -0,0 +1,46 @@ + + + + + Test for Bug 1180105 + + + + + +Mozilla Bug 1180105 +

+
+
+
+
> + +
+ + diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp index 95b188d272..ed4dffee4b 100644 --- a/layout/svg/nsSVGEffects.cpp +++ b/layout/svg/nsSVGEffects.cpp @@ -21,9 +21,6 @@ using namespace mozilla; using namespace mozilla::dom; -// nsSVGRenderingObserver impl -NS_IMPL_ISUPPORTS(nsSVGRenderingObserver, nsIMutationObserver) - void nsSVGRenderingObserver::StartListening() { @@ -220,6 +217,8 @@ nsSVGFrameReferenceFromProperty::Get() return mFrame; } +NS_IMPL_ISUPPORTS(nsSVGRenderingObserverProperty, nsIMutationObserver) + void nsSVGRenderingObserverProperty::DoUpdate() { diff --git a/layout/svg/nsSVGEffects.h b/layout/svg/nsSVGEffects.h index 8ebfcec66e..08d81da100 100644 --- a/layout/svg/nsSVGEffects.h +++ b/layout/svg/nsSVGEffects.h @@ -55,9 +55,6 @@ public: : mInObserverList(false) {} - // nsISupports - NS_DECL_ISUPPORTS - // nsIMutationObserver NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED @@ -174,6 +171,8 @@ private: class nsSVGRenderingObserverProperty : public nsSVGIDRenderingObserver { public: + NS_DECL_ISUPPORTS + nsSVGRenderingObserverProperty(nsIURI* aURI, nsIFrame *aFrame, bool aReferenceImage) : nsSVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage) @@ -181,6 +180,8 @@ public: {} protected: + virtual ~nsSVGRenderingObserverProperty() {} + virtual void DoUpdate() override; nsSVGFrameReferenceFromProperty mFrameReference; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 0f70034197..4d0c5774c8 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -698,10 +698,10 @@ pref("gfx.canvas.azure.backends", "direct2d1.1,direct2d,skia,cairo"); pref("gfx.content.azure.backends", "direct2d1.1,direct2d,cairo"); #else #ifdef XP_MACOSX +pref("gfx.content.azure.backends", "cg"); pref("gfx.canvas.azure.backends", "skia,cg"); // Accelerated cg canvas where available (10.7+) -pref("gfx.canvas.azure.accelerated", true); -pref("gfx.content.azure.backends", "cg"); +pref("gfx.canvas.azure.accelerated", false); #else pref("gfx.canvas.azure.backends", "skia,cairo"); pref("gfx.content.azure.backends", "cairo"); diff --git a/xpcom/glue/nsISupportsImpl.h b/xpcom/glue/nsISupportsImpl.h index 6528a57764..51574d5495 100644 --- a/xpcom/glue/nsISupportsImpl.h +++ b/xpcom/glue/nsISupportsImpl.h @@ -1014,11 +1014,6 @@ NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) \ MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass,), (__VA_ARGS__)) \ NS_INTERFACE_TABLE_END -#define NS_IMPL_QUERY_INTERFACE_INHERITED0(aClass, aSuper) \ - NS_INTERFACE_TABLE_HEAD(aClass) \ - NS_INTERFACE_TABLE_INHERITED0(aClass) \ - NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) - #define NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, ...) \ NS_INTERFACE_TABLE_HEAD(aClass) \ NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \ @@ -1043,7 +1038,8 @@ NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) \ NS_IMPL_QUERY_INTERFACE(aClass, __VA_ARGS__) #define NS_IMPL_ISUPPORTS_INHERITED0(aClass, aSuper) \ - NS_IMPL_QUERY_INTERFACE_INHERITED0(aClass, aSuper) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) \ NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ NS_IMPL_RELEASE_INHERITED(aClass, aSuper) \