| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Provides a minimal wrapping of the Blink image decoders. Used to perform |
| // a non-threaded, memory-to-memory image decode using micro second accuracy |
| // clocks to measure image decode time. Optionally applies color correction |
| // during image decoding on supported platforms (default off). Usage: |
| // |
| // % ninja -C out/Release image_decode_bench && |
| // ./out/Release/image_decode_bench file [iterations] |
| // |
| // TODO(noel): Consider adding md5 checksum support to WTF. Use it to compute |
| // the decoded image frame md5 and output that value. |
| // |
| // TODO(noel): Consider integrating this tool in Chrome telemetry for realz, |
| // using the image corpii used to assess Blink image decode performance. Refer |
| // to http://crbug.com/398235#c103 and http://crbug.com/258324#c5 |
| |
| #include <memory> |
| #include "base/command_line.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "mojo/edk/embedder/embedder.h" |
| #include "platform/SharedBuffer.h" |
| #include "platform/image-decoders/ImageDecoder.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "public/platform/Platform.h" |
| #include "ui/gfx/test/icc_profiles.h" |
| |
| #if defined(_WIN32) |
| #include <mmsystem.h> |
| #include <sys/stat.h> |
| #include <time.h> |
| #define stat(x, y) _stat(x, y) |
| typedef struct _stat sttype; |
| #else |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| typedef struct stat sttype; |
| #endif |
| |
| namespace blink { |
| |
| namespace { |
| |
| #if defined(_WIN32) |
| |
| // There is no real platform support herein, so adopt the WIN32 performance |
| // counter from WTF |
| // http://trac.webkit.org/browser/trunk/Source/WTF/wtf/CurrentTime.cpp?rev=152438 |
| |
| static double LowResUTCTime() { |
| FILETIME file_time; |
| GetSystemTimeAsFileTime(&file_time); |
| |
| // As per Windows documentation for FILETIME, copy the resulting FILETIME |
| // structure to a ULARGE_INTEGER structure using memcpy (using memcpy instead |
| // of direct assignment can prevent alignment faults on 64-bit Windows). |
| ULARGE_INTEGER date_time; |
| memcpy(&date_time, &file_time, sizeof(date_time)); |
| |
| // Number of 100 nanosecond between January 1, 1601 and January 1, 1970. |
| static const ULONGLONG kEpochBias = 116444736000000000ULL; |
| // Windows file times are in 100s of nanoseconds. |
| static const double kHundredsOfNanosecondsPerMillisecond = 10000; |
| return (date_time.QuadPart - kEpochBias) / |
| kHundredsOfNanosecondsPerMillisecond; |
| } |
| |
| static LARGE_INTEGER g_qpc_frequency; |
| static bool g_synced_time; |
| |
| static double HighResUpTime() { |
| // We use QPC, but only after sanity checking its result, due to bugs: |
| // http://support.microsoft.com/kb/274323 |
| // http://support.microsoft.com/kb/895980 |
| // http://msdn.microsoft.com/en-us/library/ms644904.aspx ("you can get |
| // different results on different processors due to bugs in the basic |
| // input/output system (BIOS) or the hardware abstraction layer (HAL)."). |
| |
| static LARGE_INTEGER qpc_last; |
| static DWORD tick_count_last; |
| static bool inited; |
| |
| LARGE_INTEGER qpc; |
| QueryPerformanceCounter(&qpc); |
| DWORD tick_count = GetTickCount(); |
| |
| if (inited) { |
| __int64 qpc_elapsed = |
| ((qpc.QuadPart - qpc_last.QuadPart) * 1000) / g_qpc_frequency.QuadPart; |
| __int64 tick_count_elapsed; |
| if (tick_count >= tick_count_last) { |
| tick_count_elapsed = (tick_count - tick_count_last); |
| } else { |
| __int64 tick_count_large = tick_count + 0x100000000I64; |
| tick_count_elapsed = tick_count_large - tick_count_last; |
| } |
| |
| // Force a re-sync if QueryPerformanceCounter differs from GetTickCount() by |
| // more than 500ms. (The 500ms value is from |
| // http://support.microsoft.com/kb/274323). |
| __int64 diff = tick_count_elapsed - qpc_elapsed; |
| if (diff > 500 || diff < -500) |
| g_synced_time = false; |
| } else { |
| inited = true; |
| } |
| |
| qpc_last = qpc; |
| tick_count_last = tick_count; |
| |
| return (1000.0 * qpc.QuadPart) / |
| static_cast<double>(g_qpc_frequency.QuadPart); |
| } |
| |
| static bool QpcAvailable() { |
| static bool available; |
| static bool checked; |
| |
| if (checked) |
| return available; |
| |
| available = QueryPerformanceFrequency(&g_qpc_frequency); |
| checked = true; |
| return available; |
| } |
| |
| static double GetCurrentTime() { |
| // Use a combination of ftime and QueryPerformanceCounter. |
| // ftime returns the information we want, but doesn't have sufficient |
| // resolution. QueryPerformanceCounter has high resolution, but is only |
| // usable to measure time intervals. To combine them, we call ftime and |
| // QueryPerformanceCounter initially. Later calls will use |
| // QueryPerformanceCounter by itself, adding the delta to the saved ftime. We |
| // periodically re-sync to correct for drift. |
| static double sync_low_res_utc_time; |
| static double sync_high_res_up_time; |
| static double last_utc_time; |
| |
| double low_res_time = LowResUTCTime(); |
| if (!QpcAvailable()) |
| return low_res_time * (1.0 / 1000.0); |
| |
| double high_res_time = HighResUpTime(); |
| if (!g_synced_time) { |
| timeBeginPeriod(1); // increase time resolution around low-res time getter |
| sync_low_res_utc_time = low_res_time = LowResUTCTime(); |
| timeEndPeriod(1); // restore time resolution |
| sync_high_res_up_time = high_res_time; |
| g_synced_time = true; |
| } |
| |
| double high_res_elapsed = high_res_time - sync_high_res_up_time; |
| double utc = sync_low_res_utc_time + high_res_elapsed; |
| |
| // Force a clock re-sync if we've drifted. |
| double low_res_elapsed = low_res_time - sync_low_res_utc_time; |
| const double kMaximumAllowedDriftMsec = |
| 15.625 * 2.0; // 2x the typical low-res accuracy |
| if (fabs(high_res_elapsed - low_res_elapsed) > kMaximumAllowedDriftMsec) |
| g_synced_time = false; |
| |
| // Make sure time doesn't run backwards (only correct if the difference is < 2 |
| // seconds, since DST or clock changes could occur). |
| const double kBackwardTimeLimit = 2000.0; |
| if (utc < last_utc_time && (last_utc_time - utc) < kBackwardTimeLimit) |
| return last_utc_time * (1.0 / 1000.0); |
| |
| last_utc_time = utc; |
| return utc * (1.0 / 1000.0); |
| } |
| |
| #else |
| |
| static double GetCurrentTime() { |
| struct timeval now; |
| gettimeofday(&now, nullptr); |
| return now.tv_sec + now.tv_usec * (1.0 / 1000000.0); |
| } |
| |
| #endif |
| |
| scoped_refptr<SharedBuffer> ReadFile(const char* file_name) { |
| FILE* fp = fopen(file_name, "rb"); |
| if (!fp) { |
| fprintf(stderr, "Can't open file %s\n", file_name); |
| exit(2); |
| } |
| |
| sttype s; |
| stat(file_name, &s); |
| size_t file_size = s.st_size; |
| if (s.st_size <= 0) |
| return SharedBuffer::Create(); |
| |
| std::unique_ptr<unsigned char[]> buffer = |
| WrapArrayUnique(new unsigned char[file_size]); |
| if (file_size != fread(buffer.get(), 1, file_size, fp)) { |
| fprintf(stderr, "Error reading file %s\n", file_name); |
| exit(2); |
| } |
| |
| fclose(fp); |
| return SharedBuffer::Create(buffer.get(), file_size); |
| } |
| |
| bool DecodeImageData(SharedBuffer* data, |
| bool color_correction, |
| size_t packet_size) { |
| std::unique_ptr<ImageDecoder> decoder = |
| ImageDecoder::Create(data, true, ImageDecoder::kAlphaPremultiplied, |
| color_correction ? ColorBehavior::TransformToSRGB() |
| : ColorBehavior::Ignore()); |
| if (!packet_size) { |
| bool all_data_received = true; |
| decoder->SetData(data, all_data_received); |
| |
| int frame_count = decoder->FrameCount(); |
| for (int i = 0; i < frame_count; ++i) { |
| if (!decoder->DecodeFrameBufferAtIndex(i)) |
| return false; |
| } |
| |
| return !decoder->Failed(); |
| } |
| |
| scoped_refptr<SharedBuffer> packet_data = SharedBuffer::Create(); |
| size_t position = 0; |
| size_t next_frame_to_decode = 0; |
| while (true) { |
| const char* packet; |
| size_t length = data->GetSomeData(packet, position); |
| |
| length = std::min(length, packet_size); |
| packet_data->Append(packet, length); |
| position += length; |
| |
| bool all_data_received = position == data->size(); |
| decoder->SetData(packet_data.get(), all_data_received); |
| |
| size_t frame_count = decoder->FrameCount(); |
| for (; next_frame_to_decode < frame_count; ++next_frame_to_decode) { |
| ImageFrame* frame = |
| decoder->DecodeFrameBufferAtIndex(next_frame_to_decode); |
| if (frame->GetStatus() != ImageFrame::kFrameComplete) |
| break; |
| } |
| |
| if (all_data_received || decoder->Failed()) |
| break; |
| } |
| |
| return !decoder->Failed(); |
| } |
| |
| } // namespace |
| |
| int Main(int argc, char* argv[]) { |
| base::CommandLine::Init(argc, argv); |
| |
| // If the platform supports color correction, allow it to be controlled. |
| |
| bool apply_color_correction = false; |
| |
| if (argc >= 2 && strcmp(argv[1], "--color-correct") == 0) { |
| apply_color_correction = (--argc, ++argv, true); |
| } |
| |
| if (argc < 2) { |
| fprintf(stderr, |
| "Usage: %s [--color-correct] file [iterations] [packetSize]\n", |
| argv[0]); |
| exit(1); |
| } |
| |
| // Control decode bench iterations and packet size. |
| |
| size_t iterations = 1; |
| if (argc >= 3) { |
| char* end = nullptr; |
| iterations = strtol(argv[2], &end, 10); |
| if (*end != '\0' || !iterations) { |
| fprintf(stderr, |
| "Second argument should be number of iterations. " |
| "The default is 1. You supplied %s\n", |
| argv[2]); |
| exit(1); |
| } |
| } |
| |
| size_t packet_size = 0; |
| if (argc >= 4) { |
| char* end = nullptr; |
| packet_size = strtol(argv[3], &end, 10); |
| if (*end != '\0') { |
| fprintf(stderr, |
| "Third argument should be packet size. Default is " |
| "0, meaning to decode the entire image in one packet. You " |
| "supplied %s\n", |
| argv[3]); |
| exit(1); |
| } |
| } |
| |
| // Create a web platform. blink::Platform can't be used directly because its |
| // constructor is protected. |
| |
| class WebPlatform : public blink::Platform {}; |
| |
| Platform::Initialize(new WebPlatform()); |
| |
| // Read entire file content to data, and consolidate the SharedBuffer data |
| // segments into one, contiguous block of memory. |
| |
| scoped_refptr<SharedBuffer> data = ReadFile(argv[1]); |
| if (!data.get() || !data->size()) { |
| fprintf(stderr, "Error reading image data from [%s]\n", argv[1]); |
| exit(2); |
| } |
| |
| data->Data(); |
| |
| // Warm-up: throw out the first iteration for more consistent results. |
| |
| if (!DecodeImageData(data.get(), apply_color_correction, packet_size)) { |
| fprintf(stderr, "Image decode failed [%s]\n", argv[1]); |
| exit(3); |
| } |
| |
| // Image decode bench for iterations. |
| |
| double total_time = 0.0; |
| |
| for (size_t i = 0; i < iterations; ++i) { |
| double start_time = GetCurrentTime(); |
| bool decoded = |
| DecodeImageData(data.get(), apply_color_correction, packet_size); |
| double elapsed_time = GetCurrentTime() - start_time; |
| total_time += elapsed_time; |
| if (!decoded) { |
| fprintf(stderr, "Image decode failed [%s]\n", argv[1]); |
| exit(3); |
| } |
| } |
| |
| // Results to stdout. |
| |
| double average_time = total_time / static_cast<double>(iterations); |
| printf("%f %f\n", total_time, average_time); |
| return 0; |
| } |
| |
| } // namespace blink |
| |
| int main(int argc, char* argv[]) { |
| base::MessageLoop message_loop; |
| mojo::edk::Init(); |
| return blink::Main(argc, argv); |
| } |