| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // Provides a minimal wrapping of the Blink deferred image decoders. Used to |
| 6 // perform a non-threaded/non-threaded, memory-to-memory image decode using |
| 7 // micro second accuracy clocks to measure image decode time. Optionally applies |
| 8 // color correction during image decoding on supported platforms (default off). |
| 9 // Usage: |
| 10 // |
| 11 // % ninja -C out/Release deferred_image_decode_bench && |
| 12 // ./out/Release/deferred_image_decode_bench file [iterations] |
| 13 // |
| 14 // TODO(noel): Consider adding md5 checksum support to WTF. Use it to compute |
| 15 // the decoded image frame md5 and output that value. |
| 16 // |
| 17 // TODO(noel): Consider integrating this tool in Chrome telemetry for realz, |
| 18 // using the image corpii used to assess Blink image decode performance. Refer |
| 19 // to http://crbug.com/398235#c103 and http://crbug.com/258324#c5 |
| 20 |
| 21 #include <memory> |
| 22 #include <thread> |
| 23 |
| 24 #include "base/command_line.h" |
| 25 #include "base/test/test_discardable_memory_allocator.h" |
| 26 #include "platform/SharedBuffer.h" |
| 27 #include "platform/graphics/DeferredImageDecoder.h" |
| 28 #include "platform/image-decoders/ImageDecoder.h" |
| 29 #include "platform/wtf/PassRefPtr.h" |
| 30 #include "platform/wtf/PtrUtil.h" |
| 31 #include "platform/wtf/WTF.h" |
| 32 #include "platform/wtf/allocator/Partitions.h" |
| 33 #include "public/platform/Platform.h" |
| 34 #include "third_party/skia/include/core/SkBitmap.h" |
| 35 #include "third_party/skia/include/core/SkImage.h" |
| 36 #include "ui/gfx/test/icc_profiles.h" |
| 37 |
| 38 #include <sys/stat.h> |
| 39 |
| 40 #if defined(_WIN32) |
| 41 #include <mmsystem.h> |
| 42 #include <time.h> |
| 43 #define stat(x, y) _stat(x, y) |
| 44 typedef struct _stat sttype; |
| 45 #else |
| 46 #include <sys/time.h> |
| 47 typedef struct stat sttype; |
| 48 #endif |
| 49 |
| 50 namespace blink { |
| 51 |
| 52 namespace { |
| 53 |
| 54 #if defined(_WIN32) |
| 55 |
| 56 // There is no real platform support herein, so adopt the WIN32 performance |
| 57 // counter from WTF |
| 58 // http://trac.webkit.org/browser/trunk/Source/WTF/wtf/CurrentTime.cpp?rev=15243
8 |
| 59 |
| 60 static double LowResUTCTime() { |
| 61 FILETIME file_time; |
| 62 GetSystemTimeAsFileTime(&file_time); |
| 63 |
| 64 // As per Windows documentation for FILETIME, copy the resulting FILETIME |
| 65 // structure to a ULARGE_INTEGER structure using memcpy (using memcpy instead |
| 66 // of direct assignment can prevent alignment faults on 64-bit Windows). |
| 67 ULARGE_INTEGER date_time; |
| 68 memcpy(&date_time, &file_time, sizeof(date_time)); |
| 69 |
| 70 // Number of 100 nanosecond between January 1, 1601 and January 1, 1970. |
| 71 static const ULONGLONG kEpochBias = 116444736000000000ULL; |
| 72 // Windows file times are in 100s of nanoseconds. |
| 73 static const double kHundredsOfNanosecondsPerMillisecond = 10000; |
| 74 return (date_time.QuadPart - kEpochBias) / |
| 75 kHundredsOfNanosecondsPerMillisecond; |
| 76 } |
| 77 |
| 78 static LARGE_INTEGER g_qpc_frequency; |
| 79 static bool g_synced_time; |
| 80 |
| 81 static double HighResUpTime() { |
| 82 // We use QPC, but only after sanity checking its result, due to bugs: |
| 83 // http://support.microsoft.com/kb/274323 |
| 84 // http://support.microsoft.com/kb/895980 |
| 85 // http://msdn.microsoft.com/en-us/library/ms644904.aspx ("you can get |
| 86 // different results on different processors due to bugs in the basic |
| 87 // input/output system (BIOS) or the hardware abstraction layer (HAL)."). |
| 88 |
| 89 static LARGE_INTEGER qpc_last; |
| 90 static DWORD tick_count_last; |
| 91 static bool inited; |
| 92 |
| 93 LARGE_INTEGER qpc; |
| 94 QueryPerformanceCounter(&qpc); |
| 95 DWORD tick_count = GetTickCount(); |
| 96 |
| 97 if (inited) { |
| 98 __int64 qpc_elapsed = |
| 99 ((qpc.QuadPart - qpc_last.QuadPart) * 1000) / g_qpc_frequency.QuadPart; |
| 100 __int64 tick_count_elapsed; |
| 101 if (tick_count >= tick_count_last) { |
| 102 tick_count_elapsed = (tick_count - tick_count_last); |
| 103 } else { |
| 104 __int64 tick_count_large = tick_count + 0x100000000I64; |
| 105 tick_count_elapsed = tick_count_large - tick_count_last; |
| 106 } |
| 107 |
| 108 // Force a re-sync if QueryPerformanceCounter differs from GetTickCount() by |
| 109 // more than 500ms. (The 500ms value is from |
| 110 // http://support.microsoft.com/kb/274323). |
| 111 __int64 diff = tick_count_elapsed - qpc_elapsed; |
| 112 if (diff > 500 || diff < -500) |
| 113 g_synced_time = false; |
| 114 } else { |
| 115 inited = true; |
| 116 } |
| 117 |
| 118 qpc_last = qpc; |
| 119 tick_count_last = tick_count; |
| 120 |
| 121 return (1000.0 * qpc.QuadPart) / |
| 122 static_cast<double>(g_qpc_frequency.QuadPart); |
| 123 } |
| 124 |
| 125 static bool QpcAvailable() { |
| 126 static bool available; |
| 127 static bool checked; |
| 128 |
| 129 if (checked) |
| 130 return available; |
| 131 |
| 132 available = QueryPerformanceFrequency(&g_qpc_frequency); |
| 133 checked = true; |
| 134 return available; |
| 135 } |
| 136 |
| 137 static double GetCurrentTime() { |
| 138 // Use a combination of ftime and QueryPerformanceCounter. |
| 139 // ftime returns the information we want, but doesn't have sufficient |
| 140 // resolution. QueryPerformanceCounter has high resolution, but is only |
| 141 // usable to measure time intervals. To combine them, we call ftime and |
| 142 // QueryPerformanceCounter initially. Later calls will use |
| 143 // QueryPerformanceCounter by itself, adding the delta to the saved ftime. We |
| 144 // periodically re-sync to correct for drift. |
| 145 static double sync_low_res_utc_time; |
| 146 static double sync_high_res_up_time; |
| 147 static double last_utc_time; |
| 148 |
| 149 double low_res_time = LowResUTCTime(); |
| 150 if (!QpcAvailable()) |
| 151 return low_res_time * (1.0 / 1000.0); |
| 152 |
| 153 double high_res_time = HighResUpTime(); |
| 154 if (!g_synced_time) { |
| 155 timeBeginPeriod(1); // increase time resolution around low-res time getter |
| 156 sync_low_res_utc_time = low_res_time = LowResUTCTime(); |
| 157 timeEndPeriod(1); // restore time resolution |
| 158 sync_high_res_up_time = high_res_time; |
| 159 g_synced_time = true; |
| 160 } |
| 161 |
| 162 double high_res_elapsed = high_res_time - sync_high_res_up_time; |
| 163 double utc = sync_low_res_utc_time + high_res_elapsed; |
| 164 |
| 165 // Force a clock re-sync if we've drifted. |
| 166 double low_res_elapsed = low_res_time - sync_low_res_utc_time; |
| 167 const double kMaximumAllowedDriftMsec = |
| 168 15.625 * 2.0; // 2x the typical low-res accuracy |
| 169 if (fabs(high_res_elapsed - low_res_elapsed) > kMaximumAllowedDriftMsec) |
| 170 g_synced_time = false; |
| 171 |
| 172 // Make sure time doesn't run backwards (only correct if the difference is < 2 |
| 173 // seconds, since DST or clock changes could occur). |
| 174 const double kBackwardTimeLimit = 2000.0; |
| 175 if (utc < last_utc_time && (last_utc_time - utc) < kBackwardTimeLimit) |
| 176 return last_utc_time * (1.0 / 1000.0); |
| 177 |
| 178 last_utc_time = utc; |
| 179 return utc * (1.0 / 1000.0); |
| 180 } |
| 181 |
| 182 #else |
| 183 |
| 184 static double GetCurrentTime() { |
| 185 struct timeval now; |
| 186 gettimeofday(&now, 0); |
| 187 return now.tv_sec + now.tv_usec * (1.0 / 1000000.0); |
| 188 } |
| 189 |
| 190 #endif |
| 191 |
| 192 PassRefPtr<SharedBuffer> ReadFile(const char* file_name) { |
| 193 FILE* fp = fopen(file_name, "rb"); |
| 194 if (!fp) { |
| 195 fprintf(stderr, "Can't open file %s\n", file_name); |
| 196 exit(2); |
| 197 } |
| 198 |
| 199 sttype s; |
| 200 stat(file_name, &s); |
| 201 size_t file_size = s.st_size; |
| 202 if (s.st_size <= 0) |
| 203 return SharedBuffer::Create(); |
| 204 |
| 205 std::unique_ptr<unsigned char[]> buffer = |
| 206 WrapArrayUnique(new unsigned char[file_size]); |
| 207 if (file_size != fread(buffer.get(), 1, file_size, fp)) { |
| 208 fprintf(stderr, "Error reading file %s\n", file_name); |
| 209 exit(2); |
| 210 } |
| 211 |
| 212 fclose(fp); |
| 213 return SharedBuffer::Create(buffer.get(), file_size); |
| 214 } |
| 215 |
| 216 bool ReadPixels(SkImage* image) { |
| 217 SkBitmap bitmap; |
| 218 bitmap.allocPixels( |
| 219 SkImageInfo::MakeN32Premul(image->width(), image->width())); |
| 220 return image->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), |
| 221 0, 0); |
| 222 } |
| 223 |
| 224 struct RealDecodeImages { |
| 225 sk_sp<SkImage>* images; |
| 226 size_t* frame_index_arr; |
| 227 size_t count; |
| 228 size_t avail; |
| 229 }; |
| 230 |
| 231 void RealDecode(RealDecodeImages* real_decoded_images) { |
| 232 size_t to_decod_index = 0; |
| 233 size_t current_frame_index = 0; |
| 234 |
| 235 while (true) { |
| 236 bool ret = true; |
| 237 bool need_decode = false; |
| 238 |
| 239 for (size_t i = to_decod_index; i < real_decoded_images->avail; i++) { |
| 240 if (real_decoded_images->frame_index_arr[i] != current_frame_index) { |
| 241 current_frame_index = real_decoded_images->frame_index_arr[i]; |
| 242 break; |
| 243 } |
| 244 need_decode = true; |
| 245 to_decod_index = i; |
| 246 } |
| 247 |
| 248 if (need_decode) { |
| 249 sk_sp<SkImage>* image = real_decoded_images->images + to_decod_index; |
| 250 if (image->get()) { |
| 251 ret = ReadPixels(image->get()); |
| 252 } |
| 253 ++to_decod_index; |
| 254 } |
| 255 |
| 256 if (to_decod_index == real_decoded_images->count) { |
| 257 if (!ret) { |
| 258 fprintf(stderr, "RealDecode failed!\n"); |
| 259 exit(3); |
| 260 } |
| 261 break; |
| 262 } |
| 263 } |
| 264 } |
| 265 |
| 266 bool DecodeImageData(SharedBuffer* data, |
| 267 bool color_correction, |
| 268 size_t packet_size) { |
| 269 std::unique_ptr<ImageDecoder> info_decoder = ImageDecoder::Create( |
| 270 data, true, ImageDecoder::kAlphaPremultiplied, |
| 271 color_correction ? ColorBehavior::TransformToTargetForTesting() |
| 272 : ColorBehavior::Ignore()); |
| 273 bool all_data_received = true; |
| 274 info_decoder->SetData(data, all_data_received); |
| 275 |
| 276 int frame_count = info_decoder->FrameCount(); |
| 277 |
| 278 if (!packet_size) { |
| 279 std::unique_ptr<DeferredImageDecoder> decoder = |
| 280 DeferredImageDecoder::Create( |
| 281 data, false, ImageDecoder::kAlphaPremultiplied, |
| 282 color_correction ? ColorBehavior::TransformToTargetForTesting() |
| 283 : ColorBehavior::Ignore()); |
| 284 |
| 285 bool all_data_received = true; |
| 286 decoder->SetData(data, all_data_received); |
| 287 |
| 288 int frame_count = decoder->FrameCount(); |
| 289 for (int i = 0; i < frame_count; ++i) { |
| 290 sk_sp<SkImage> image = decoder->CreateFrameAtIndex(i); |
| 291 if (!image) |
| 292 return false; |
| 293 bool ret = ReadPixels(image.get()); |
| 294 if (!ret) { |
| 295 fprintf(stderr, "DecodeImageData failed!\n"); |
| 296 exit(3); |
| 297 } |
| 298 } |
| 299 |
| 300 return true; |
| 301 } |
| 302 |
| 303 size_t cirle_count = data->size() / packet_size + data->size() % packet_size; |
| 304 size_t images_count = frame_count * cirle_count; |
| 305 sk_sp<SkImage>* images = new sk_sp<SkImage>[images_count]; |
| 306 size_t* frame_index_arr = new size_t[images_count]; |
| 307 memset(frame_index_arr, 0, sizeof(size_t[images_count])); |
| 308 size_t image_index = 0; |
| 309 |
| 310 RealDecodeImages real_decode_images{images, frame_index_arr, images_count, 0}; |
| 311 |
| 312 std::thread real_decode_thread(RealDecode, &real_decode_images); |
| 313 |
| 314 RefPtr<SharedBuffer> packet_data = SharedBuffer::Create(); |
| 315 size_t position = 0; |
| 316 size_t next_frame_to_decode = 0; |
| 317 |
| 318 std::unique_ptr<DeferredImageDecoder> pakcet_decoder; |
| 319 |
| 320 while (true) { |
| 321 const char* packet; |
| 322 size_t length = data->GetSomeData(packet, position); |
| 323 |
| 324 length = std::min(length, packet_size); |
| 325 packet_data->Append(packet, length); |
| 326 position += length; |
| 327 |
| 328 bool all_data_received = position == data->size(); |
| 329 if (!pakcet_decoder) { |
| 330 pakcet_decoder = DeferredImageDecoder::Create( |
| 331 packet_data, false, ImageDecoder::kAlphaPremultiplied, |
| 332 color_correction ? ColorBehavior::TransformToTargetForTesting() |
| 333 : ColorBehavior::Ignore()); |
| 334 } |
| 335 if (!pakcet_decoder) { |
| 336 continue; |
| 337 } |
| 338 |
| 339 pakcet_decoder->SetData(packet_data.Get(), all_data_received); |
| 340 |
| 341 size_t frame_count = pakcet_decoder->FrameCount(); |
| 342 for (size_t i = next_frame_to_decode; i < frame_count; ++i) { |
| 343 images[image_index] = pakcet_decoder->CreateFrameAtIndex(i); |
| 344 frame_index_arr[image_index] = i; |
| 345 ++image_index; |
| 346 if (pakcet_decoder->FrameIsCompleteAtIndex(i)) { |
| 347 next_frame_to_decode = i + 1; |
| 348 real_decode_images.avail = image_index; |
| 349 } |
| 350 } |
| 351 |
| 352 if (all_data_received) |
| 353 break; |
| 354 } |
| 355 |
| 356 real_decode_images.count = real_decode_images.avail; |
| 357 |
| 358 real_decode_thread.join(); |
| 359 |
| 360 delete[] images; |
| 361 delete[] frame_index_arr; |
| 362 |
| 363 return true; |
| 364 } |
| 365 |
| 366 } // namespace |
| 367 |
| 368 int Main(int argc, char* argv[]) { |
| 369 base::CommandLine::Init(argc, argv); |
| 370 |
| 371 base::TestDiscardableMemoryAllocator memoryAllocator; |
| 372 base::DiscardableMemoryAllocator::SetInstance(&memoryAllocator); |
| 373 |
| 374 WTF::Partitions::Initialize(nullptr); |
| 375 |
| 376 // If the platform supports color correction, allow it to be controlled. |
| 377 |
| 378 bool apply_color_correction = false; |
| 379 |
| 380 if (argc >= 2 && strcmp(argv[1], "--color-correct") == 0) { |
| 381 apply_color_correction = (--argc, ++argv, true); |
| 382 gfx::ICCProfile profile = gfx::ICCProfileForTestingColorSpin(); |
| 383 ColorBehavior::SetGlobalTargetColorProfile(profile); |
| 384 } |
| 385 |
| 386 if (argc < 2) { |
| 387 fprintf(stderr, |
| 388 "Usage: %s [--color-correct] file [iterations] [packetSize]\n", |
| 389 argv[0]); |
| 390 exit(1); |
| 391 } |
| 392 |
| 393 // Control decode bench iterations and packet size. |
| 394 |
| 395 size_t iterations = 1; |
| 396 if (argc >= 3) { |
| 397 char* end = 0; |
| 398 iterations = strtol(argv[2], &end, 10); |
| 399 if (*end != '\0' || !iterations) { |
| 400 fprintf(stderr, |
| 401 "Second argument should be number of iterations. " |
| 402 "The default is 1. You supplied %s\n", |
| 403 argv[2]); |
| 404 exit(1); |
| 405 } |
| 406 } |
| 407 |
| 408 size_t packet_size = 0; |
| 409 if (argc >= 4) { |
| 410 char* end = 0; |
| 411 packet_size = strtol(argv[3], &end, 10); |
| 412 if (*end != '\0') { |
| 413 fprintf(stderr, |
| 414 "Third argument should be packet size. Default is " |
| 415 "0, meaning to decode the entire image in one packet. You " |
| 416 "supplied %s\n", |
| 417 argv[3]); |
| 418 exit(1); |
| 419 } |
| 420 } |
| 421 |
| 422 // Create a web platform. blink::Platform can't be used directly because its |
| 423 // constructor is protected. |
| 424 |
| 425 class WebPlatform : public blink::Platform {}; |
| 426 |
| 427 Platform::Initialize(new WebPlatform()); |
| 428 |
| 429 // Read entire file content to data, and consolidate the SharedBuffer data |
| 430 // segments into one, contiguous block of memory. |
| 431 |
| 432 RefPtr<SharedBuffer> data = ReadFile(argv[1]); |
| 433 if (!data.Get() || !data->size()) { |
| 434 fprintf(stderr, "Error reading image data from [%s]\n", argv[1]); |
| 435 exit(2); |
| 436 } |
| 437 |
| 438 data->Data(); |
| 439 |
| 440 // Warm-up: throw out the first iteration for more consistent results. |
| 441 |
| 442 if (!DecodeImageData(data.Get(), apply_color_correction, packet_size)) { |
| 443 fprintf(stderr, "Image decode failed [%s]\n", argv[1]); |
| 444 exit(3); |
| 445 } |
| 446 |
| 447 // Image decode bench for iterations. |
| 448 |
| 449 double total_time = 0.0; |
| 450 |
| 451 for (size_t i = 0; i < iterations; ++i) { |
| 452 double start_time = GetCurrentTime(); |
| 453 bool decoded = |
| 454 DecodeImageData(data.Get(), apply_color_correction, packet_size); |
| 455 double elapsed_time = GetCurrentTime() - start_time; |
| 456 total_time += elapsed_time; |
| 457 if (!decoded) { |
| 458 fprintf(stderr, "Image decode failed [%s]\n", argv[1]); |
| 459 exit(3); |
| 460 } |
| 461 } |
| 462 |
| 463 // Results to stdout. |
| 464 double average_time = total_time / static_cast<double>(iterations); |
| 465 printf("%f %f\n", total_time, average_time); |
| 466 return 0; |
| 467 } |
| 468 |
| 469 } // namespace blink |
| 470 |
| 471 int main(int argc, char* argv[]) { |
| 472 return blink::Main(argc, argv); |
| 473 } |
| OLD | NEW |