1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "gtest/gtest.h"
7
8 #include "Common.h"
9 #include "imgIContainer.h"
10 #include "ImageFactory.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/StaticPrefs_image.h"
14 #include "nsIInputStream.h"
15 #include "nsString.h"
16 #include "ProgressTracker.h"
17
18 using namespace mozilla;
19 using namespace mozilla::gfx;
20 using namespace mozilla::image;
21
22 class ImageSurfaceCache : public ::testing::Test {
23 protected:
24 AutoInitializeImageLib mInit;
25 };
26
TEST_F(ImageSurfaceCache,Factor2)27 TEST_F(ImageSurfaceCache, Factor2) {
28 ImageTestCase testCase = GreenPNGTestCase();
29
30 // Create an image.
31 RefPtr<Image> image = ImageFactory::CreateAnonymousImage(
32 nsDependentCString(testCase.mMimeType));
33 ASSERT_TRUE(!image->HasError());
34
35 nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
36 ASSERT_TRUE(inputStream);
37
38 // Figure out how much data we have.
39 uint64_t length;
40 nsresult rv = inputStream->Available(&length);
41 ASSERT_TRUE(NS_SUCCEEDED(rv));
42
43 // Ensures we meet the threshold for FLAG_SYNC_DECODE_IF_FAST to do sync
44 // decoding without the implications of FLAG_SYNC_DECODE.
45 ASSERT_LT(length,
46 static_cast<uint64_t>(
47 StaticPrefs::image_mem_decode_bytes_at_a_time_AtStartup()));
48
49 // Write the data into the image.
50 rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
51 static_cast<uint32_t>(length));
52 ASSERT_TRUE(NS_SUCCEEDED(rv));
53
54 // Let the image know we've sent all the data.
55 rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
56 ASSERT_TRUE(NS_SUCCEEDED(rv));
57
58 RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
59 tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
60
61 const uint32_t whichFrame = imgIContainer::FRAME_CURRENT;
62
63 // FLAG_SYNC_DECODE will make RasterImage::LookupFrame use
64 // SurfaceCache::Lookup to force an exact match lookup (and potential decode).
65 const uint32_t exactFlags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
66 imgIContainer::FLAG_SYNC_DECODE;
67
68 // If the data stream is small enough, as we assert above,
69 // FLAG_SYNC_DECODE_IF_FAST will allow us to decode sync, but avoid forcing
70 // SurfaceCache::Lookup. Instead it will use SurfaceCache::LookupBestMatch.
71 const uint32_t bestMatchFlags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
72 imgIContainer::FLAG_SYNC_DECODE_IF_FAST;
73
74 // We need the default threshold to be enabled (otherwise we should disable
75 // this test).
76 int32_t threshold = StaticPrefs::image_cache_factor2_threshold_surfaces();
77 ASSERT_TRUE(threshold >= 0);
78
79 // We need to know what the native sizes are, otherwise factor of 2 mode will
80 // be disabled.
81 size_t nativeSizes = image->GetNativeSizesLength();
82 ASSERT_EQ(nativeSizes, 1u);
83
84 // Threshold is the native size count and the pref threshold added together.
85 // Make sure the image is big enough that we can simply decrement and divide
86 // off the size as we please and not hit unexpected duplicates.
87 int32_t totalThreshold = static_cast<int32_t>(nativeSizes) + threshold;
88 ASSERT_TRUE(testCase.mSize.width > totalThreshold * 4);
89
90 // Request a bunch of slightly different sizes. We won't trip factor of 2 mode
91 // in this loop.
92 IntSize size = testCase.mSize;
93 for (int32_t i = 0; i <= totalThreshold; ++i) {
94 RefPtr<SourceSurface> surf =
95 image->GetFrameAtSize(size, whichFrame, bestMatchFlags);
96 ASSERT_TRUE(surf);
97 EXPECT_EQ(surf->GetSize(), size);
98
99 size.width -= 1;
100 size.height -= 1;
101 }
102
103 // Now let's ask for a new size. Despite this being sync, it will return
104 // the closest factor of 2 size we have and not the requested size.
105 RefPtr<SourceSurface> surf =
106 image->GetFrameAtSize(size, whichFrame, bestMatchFlags);
107 ASSERT_TRUE(surf);
108
109 EXPECT_EQ(surf->GetSize(), testCase.mSize);
110
111 // Now we should be in factor of 2 mode but unless we trigger a decode no
112 // pruning of the old sized surfaces should happen.
113 size = testCase.mSize;
114 for (int32_t i = 0; i < totalThreshold; ++i) {
115 RefPtr<SourceSurface> surf =
116 image->GetFrameAtSize(size, whichFrame, bestMatchFlags);
117 ASSERT_TRUE(surf);
118 EXPECT_EQ(surf->GetSize(), size);
119
120 size.width -= 1;
121 size.height -= 1;
122 }
123
124 // Now force an existing surface to be marked as explicit so that it
125 // won't get freed upon pruning (gets marked in the Lookup).
126 size.width += 1;
127 size.height += 1;
128 surf = image->GetFrameAtSize(size, whichFrame, exactFlags);
129 ASSERT_TRUE(surf);
130 EXPECT_EQ(surf->GetSize(), size);
131
132 // Now force a new decode to happen by getting a new factor of 2 size.
133 size.width = testCase.mSize.width / 2 - 1;
134 size.height = testCase.mSize.height / 2 - 1;
135 surf = image->GetFrameAtSize(size, whichFrame, bestMatchFlags);
136 ASSERT_TRUE(surf);
137 EXPECT_EQ(surf->GetSize().width, testCase.mSize.width / 2);
138 EXPECT_EQ(surf->GetSize().height, testCase.mSize.height / 2);
139
140 // The decode above would have forced a pruning to happen, so now if
141 // we request all of the sizes we used to have decoded, only the explicit
142 // size should have been kept.
143 size = testCase.mSize;
144 for (int32_t i = 0; i < totalThreshold - 1; ++i) {
145 RefPtr<SourceSurface> surf =
146 image->GetFrameAtSize(size, whichFrame, bestMatchFlags);
147 ASSERT_TRUE(surf);
148 EXPECT_EQ(surf->GetSize(), testCase.mSize);
149
150 size.width -= 1;
151 size.height -= 1;
152 }
153
154 // This lookup finds the surface that already existed that we later marked
155 // as explicit. It should still exist after pruning.
156 surf = image->GetFrameAtSize(size, whichFrame, bestMatchFlags);
157 ASSERT_TRUE(surf);
158 EXPECT_EQ(surf->GetSize(), size);
159 }
160