TestSurfacePipeIntegration.cpp (13438B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 9 #include "mozilla/gfx/2D.h" 10 #include "Common.h" 11 #include "Decoder.h" 12 #include "DecoderFactory.h" 13 #include "SourceBuffer.h" 14 #include "SurfacePipe.h" 15 16 using namespace mozilla; 17 using namespace mozilla::gfx; 18 using namespace mozilla::image; 19 20 namespace mozilla { 21 namespace image { 22 23 class TestSurfacePipeFactory { 24 public: 25 static SurfacePipe SimpleSurfacePipe() { 26 SurfacePipe pipe; 27 return pipe; 28 } 29 30 template <typename T> 31 static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline) { 32 return SurfacePipe{std::move(aPipeline)}; 33 } 34 35 private: 36 TestSurfacePipeFactory() {} 37 }; 38 39 } // namespace image 40 } // namespace mozilla 41 42 void CheckSurfacePipeMethodResults(SurfacePipe* aPipe, image::Decoder* aDecoder, 43 const IntRect& aRect = IntRect(0, 0, 100, 44 100)) { 45 // Check that the pipeline ended up in the state we expect. Note that we're 46 // explicitly testing the SurfacePipe versions of these methods, so we don't 47 // want to use AssertCorrectPipelineFinalState() here. 48 EXPECT_TRUE(aPipe->IsSurfaceFinished()); 49 Maybe<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect(); 50 EXPECT_TRUE(invalidRect.isSome()); 51 EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); 52 EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); 53 54 // Check the generated image. 55 CheckGeneratedImage(aDecoder, aRect); 56 57 // Reset and clear the image before the next test. 58 aPipe->ResetToFirstRow(); 59 EXPECT_FALSE(aPipe->IsSurfaceFinished()); 60 invalidRect = aPipe->TakeInvalidRect(); 61 EXPECT_TRUE(invalidRect.isNothing()); 62 63 uint32_t count = 0; 64 auto result = aPipe->WritePixels<uint32_t>([&]() { 65 ++count; 66 return AsVariant(BGRAColor::Transparent().AsPixel()); 67 }); 68 EXPECT_EQ(WriteState::FINISHED, result); 69 EXPECT_EQ(100u * 100u, count); 70 71 EXPECT_TRUE(aPipe->IsSurfaceFinished()); 72 invalidRect = aPipe->TakeInvalidRect(); 73 EXPECT_TRUE(invalidRect.isSome()); 74 EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); 75 EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); 76 77 aPipe->ResetToFirstRow(); 78 EXPECT_FALSE(aPipe->IsSurfaceFinished()); 79 invalidRect = aPipe->TakeInvalidRect(); 80 EXPECT_TRUE(invalidRect.isNothing()); 81 } 82 83 class ImageSurfacePipeIntegration : public ::testing::Test { 84 protected: 85 AutoInitializeImageLib mInit; 86 }; 87 88 TEST_F(ImageSurfacePipeIntegration, SurfacePipe) { 89 // Test that SurfacePipe objects can be initialized and move constructed. 90 SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe(); 91 92 // Test that SurfacePipe objects can be move assigned. 93 pipe = TestSurfacePipeFactory::SimpleSurfacePipe(); 94 95 // Test that SurfacePipe objects can be initialized with a pipeline. 96 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 97 ASSERT_TRUE(decoder != nullptr); 98 99 auto sink = MakeUnique<SurfaceSink>(); 100 nsresult rv = sink->Configure( 101 SurfaceConfig{decoder, IntSize(100, 100), SurfaceFormat::OS_RGBA, false}); 102 ASSERT_NS_SUCCEEDED(rv); 103 104 pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink); 105 106 // Test that WritePixels() gets passed through to the underlying pipeline. 107 { 108 uint32_t count = 0; 109 auto result = pipe.WritePixels<uint32_t>([&]() { 110 ++count; 111 return AsVariant(BGRAColor::Green().AsPixel()); 112 }); 113 EXPECT_EQ(WriteState::FINISHED, result); 114 EXPECT_EQ(100u * 100u, count); 115 CheckSurfacePipeMethodResults(&pipe, decoder); 116 } 117 118 // Create a buffer the same size as one row of the surface, containing all 119 // green pixels. We'll use this for the WriteBuffer() tests. 120 uint32_t buffer[100]; 121 for (int i = 0; i < 100; ++i) { 122 buffer[i] = BGRAColor::Green().AsPixel(); 123 } 124 125 // Test that WriteBuffer() gets passed through to the underlying pipeline. 126 { 127 uint32_t count = 0; 128 WriteState result = WriteState::NEED_MORE_DATA; 129 while (result == WriteState::NEED_MORE_DATA) { 130 result = pipe.WriteBuffer(buffer); 131 ++count; 132 } 133 EXPECT_EQ(WriteState::FINISHED, result); 134 EXPECT_EQ(100u, count); 135 CheckSurfacePipeMethodResults(&pipe, decoder); 136 } 137 138 // Test that the 3 argument version of WriteBuffer() gets passed through to 139 // the underlying pipeline. 140 { 141 uint32_t count = 0; 142 WriteState result = WriteState::NEED_MORE_DATA; 143 while (result == WriteState::NEED_MORE_DATA) { 144 result = pipe.WriteBuffer(buffer, 0, 100); 145 ++count; 146 } 147 EXPECT_EQ(WriteState::FINISHED, result); 148 EXPECT_EQ(100u, count); 149 CheckSurfacePipeMethodResults(&pipe, decoder); 150 } 151 152 // Test that WritePixelBlocks() gets passed through to the underlying 153 // pipeline. 154 { 155 uint32_t count = 0; 156 WriteState result = pipe.WritePixelBlocks<uint32_t>( 157 [&](uint32_t* aBlockStart, int32_t aLength) { 158 ++count; 159 EXPECT_EQ(int32_t(100), aLength); 160 memcpy(aBlockStart, buffer, 100 * sizeof(uint32_t)); 161 return std::make_tuple(int32_t(100), Maybe<WriteState>()); 162 }); 163 164 EXPECT_EQ(WriteState::FINISHED, result); 165 EXPECT_EQ(100u, count); 166 CheckSurfacePipeMethodResults(&pipe, decoder); 167 } 168 169 // Test that WriteEmptyRow() gets passed through to the underlying pipeline. 170 { 171 uint32_t count = 0; 172 WriteState result = WriteState::NEED_MORE_DATA; 173 while (result == WriteState::NEED_MORE_DATA) { 174 result = pipe.WriteEmptyRow(); 175 ++count; 176 } 177 EXPECT_EQ(WriteState::FINISHED, result); 178 EXPECT_EQ(100u, count); 179 CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0)); 180 } 181 182 // Mark the frame as finished so we don't get an assertion. 183 RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef(); 184 currentFrame->Finish(); 185 } 186 187 TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels) { 188 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 189 ASSERT_TRUE(decoder != nullptr); 190 191 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) { 192 CheckWritePixels(aDecoder, aFilter, 193 /* aOutputRect = */ Some(IntRect(0, 0, 25, 25))); 194 }; 195 196 WithFilterPipeline( 197 decoder, test, 198 DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true}, 199 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA}, 200 SurfaceConfig{decoder, IntSize(25, 25), SurfaceFormat::OS_RGBA, false}); 201 } 202 203 TEST_F(ImageSurfacePipeIntegration, 204 RemoveFrameRectBottomRightDownscaleWritePixels) { 205 // This test case uses a frame rect that extends beyond the borders of the 206 // image to the bottom and to the right. It looks roughly like this (with the 207 // box made of '#'s representing the frame rect): 208 // 209 // +------------+ 210 // + + 211 // + +------------+ 212 // + +############+ 213 // +------+############+ 214 // +############+ 215 // +------------+ 216 217 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 218 ASSERT_TRUE(decoder != nullptr); 219 220 // Note that aInputWriteRect is 100x50 because RemoveFrameRectFilter ignores 221 // trailing rows that don't show up in the output. (Leading rows unfortunately 222 // can't be ignored.) So the action of the pipeline is as follows: 223 // 224 // (1) RemoveFrameRectFilter reads a 100x50 region of the input. 225 // (aInputWriteRect captures this fact.) The remaining 50 rows are ignored 226 // because they extend off the bottom of the image due to the frame rect's 227 // (50, 50) offset. The 50 columns on the right also don't end up in the 228 // output, so ultimately only a 50x50 region in the output contains data 229 // from the input. The filter's output is not 50x50, though, but 100x100, 230 // because what RemoveFrameRectFilter does is introduce blank rows or 231 // columns as necessary to transform an image that needs a frame rect into 232 // an image that doesn't. 233 // 234 // (2) DownscalingFilter reads the output of RemoveFrameRectFilter (100x100) 235 // and downscales it to 20x20. 236 // 237 // (3) The surface owned by SurfaceSink logically has only a 10x10 region 238 // region in it that's non-blank; this is the downscaled version of the 239 // 50x50 region discussed in (1). (aOutputWriteRect captures this fact.) 240 // Some fuzz, as usual, is necessary when dealing with Lanczos 241 // downscaling. 242 243 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) { 244 CheckWritePixels(aDecoder, aFilter, 245 /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), 246 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), 247 /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)), 248 /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), 249 /* aFuzz = */ 0x33); 250 }; 251 252 WithFilterPipeline( 253 decoder, test, RemoveFrameRectConfig{IntRect(50, 50, 100, 100)}, 254 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA}, 255 SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false}); 256 } 257 258 TEST_F(ImageSurfacePipeIntegration, 259 RemoveFrameRectTopLeftDownscaleWritePixels) { 260 // This test case uses a frame rect that extends beyond the borders of the 261 // image to the top and to the left. It looks roughly like this (with the 262 // box made of '#'s representing the frame rect): 263 // 264 // +------------+ 265 // +############+ 266 // +############+------+ 267 // +############+ + 268 // +------------+ + 269 // + + 270 // +------------+ 271 272 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 273 ASSERT_TRUE(decoder != nullptr); 274 275 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) { 276 CheckWritePixels(aDecoder, aFilter, 277 /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), 278 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), 279 /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)), 280 /* aOutputWriteRect = */ Some(IntRect(0, 0, 10, 10)), 281 /* aFuzz = */ 0x21); 282 }; 283 284 WithFilterPipeline( 285 decoder, test, RemoveFrameRectConfig{IntRect(-50, -50, 100, 100)}, 286 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA}, 287 SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false}); 288 } 289 290 TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels) { 291 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 292 ASSERT_TRUE(decoder != nullptr); 293 294 // Note that aInputRect is the full 100x100 size even though 295 // RemoveFrameRectFilter is part of this pipeline, because deinterlacing 296 // requires reading every row. 297 298 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) { 299 CheckWritePixels(aDecoder, aFilter, 300 /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), 301 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), 302 /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), 303 /* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50))); 304 }; 305 306 WithFilterPipeline( 307 decoder, test, 308 DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true}, 309 RemoveFrameRectConfig{IntRect(50, 50, 100, 100)}, 310 SurfaceConfig{decoder, IntSize(100, 100), SurfaceFormat::OS_RGBA, false}); 311 } 312 313 TEST_F(ImageSurfacePipeIntegration, 314 DeinterlaceRemoveFrameRectDownscaleWritePixels) { 315 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 316 ASSERT_TRUE(decoder != nullptr); 317 318 auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) { 319 CheckWritePixels(aDecoder, aFilter, 320 /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), 321 /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), 322 /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), 323 /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), 324 /* aFuzz = */ 33); 325 }; 326 327 WithFilterPipeline( 328 decoder, test, 329 DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true}, 330 RemoveFrameRectConfig{IntRect(50, 50, 100, 100)}, 331 DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA}, 332 SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false}); 333 } 334 335 TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails) { 336 RefPtr<image::Decoder> decoder = CreateTrivialDecoder(); 337 ASSERT_TRUE(decoder != nullptr); 338 339 // When DownscalingFilter is used, we may succeed in allocating an output 340 // surface for huge images, because we only need to store the scaled-down 341 // version of the image. However, regardless of downscaling, 342 // DeinterlacingFilter needs to allocate a buffer as large as the size of the 343 // input. This can cause OOMs on operating systems that allow overcommit. This 344 // test makes sure that we reject such allocations. 345 AssertConfiguringPipelineFails( 346 decoder, DeinterlacingConfig<uint32_t>{/* mProgressiveDisplay = */ true}, 347 DownscalingConfig{IntSize(60000, 60000), SurfaceFormat::OS_RGBA}, 348 SurfaceConfig{decoder, IntSize(600, 600), SurfaceFormat::OS_RGBA, false}); 349 }