| Index: third_party/WebKit/Source/platform/testing/DeferredImageDecodeBench.cpp
|
| diff --git a/third_party/WebKit/Source/platform/testing/DeferredImageDecodeBench.cpp b/third_party/WebKit/Source/platform/testing/DeferredImageDecodeBench.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9f8108fe79e367bdd0dd2c642a762006c5557f7c
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/platform/testing/DeferredImageDecodeBench.cpp
|
| @@ -0,0 +1,473 @@
|
| +// 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 deferred image decoders. Used to
|
| +// perform a non-threaded/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 deferred_image_decode_bench &&
|
| +// ./out/Release/deferred_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 <thread>
|
| +
|
| +#include "base/command_line.h"
|
| +#include "base/test/test_discardable_memory_allocator.h"
|
| +#include "platform/SharedBuffer.h"
|
| +#include "platform/graphics/DeferredImageDecoder.h"
|
| +#include "platform/image-decoders/ImageDecoder.h"
|
| +#include "platform/wtf/PassRefPtr.h"
|
| +#include "platform/wtf/PtrUtil.h"
|
| +#include "platform/wtf/WTF.h"
|
| +#include "platform/wtf/allocator/Partitions.h"
|
| +#include "public/platform/Platform.h"
|
| +#include "third_party/skia/include/core/SkBitmap.h"
|
| +#include "third_party/skia/include/core/SkImage.h"
|
| +#include "ui/gfx/test/icc_profiles.h"
|
| +
|
| +#include <sys/stat.h>
|
| +
|
| +#if defined(_WIN32)
|
| +#include <mmsystem.h>
|
| +#include <time.h>
|
| +#define stat(x, y) _stat(x, y)
|
| +typedef struct _stat sttype;
|
| +#else
|
| +#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, 0);
|
| + return now.tv_sec + now.tv_usec * (1.0 / 1000000.0);
|
| +}
|
| +
|
| +#endif
|
| +
|
| +PassRefPtr<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 ReadPixels(SkImage* image) {
|
| + SkBitmap bitmap;
|
| + bitmap.allocPixels(
|
| + SkImageInfo::MakeN32Premul(image->width(), image->width()));
|
| + return image->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(),
|
| + 0, 0);
|
| +}
|
| +
|
| +struct RealDecodeImages {
|
| + sk_sp<SkImage>* images;
|
| + size_t* frame_index_arr;
|
| + size_t count;
|
| + size_t avail;
|
| +};
|
| +
|
| +void RealDecode(RealDecodeImages* real_decoded_images) {
|
| + size_t to_decod_index = 0;
|
| + size_t current_frame_index = 0;
|
| +
|
| + while (true) {
|
| + bool ret = true;
|
| + bool need_decode = false;
|
| +
|
| + for (size_t i = to_decod_index; i < real_decoded_images->avail; i++) {
|
| + if (real_decoded_images->frame_index_arr[i] != current_frame_index) {
|
| + current_frame_index = real_decoded_images->frame_index_arr[i];
|
| + break;
|
| + }
|
| + need_decode = true;
|
| + to_decod_index = i;
|
| + }
|
| +
|
| + if (need_decode) {
|
| + sk_sp<SkImage>* image = real_decoded_images->images + to_decod_index;
|
| + if (image->get()) {
|
| + ret = ReadPixels(image->get());
|
| + }
|
| + ++to_decod_index;
|
| + }
|
| +
|
| + if (to_decod_index == real_decoded_images->count) {
|
| + if (!ret) {
|
| + fprintf(stderr, "RealDecode failed!\n");
|
| + exit(3);
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +bool DecodeImageData(SharedBuffer* data,
|
| + bool color_correction,
|
| + size_t packet_size) {
|
| + std::unique_ptr<ImageDecoder> info_decoder = ImageDecoder::Create(
|
| + data, true, ImageDecoder::kAlphaPremultiplied,
|
| + color_correction ? ColorBehavior::TransformToTargetForTesting()
|
| + : ColorBehavior::Ignore());
|
| + bool all_data_received = true;
|
| + info_decoder->SetData(data, all_data_received);
|
| +
|
| + int frame_count = info_decoder->FrameCount();
|
| +
|
| + if (!packet_size) {
|
| + std::unique_ptr<DeferredImageDecoder> decoder =
|
| + DeferredImageDecoder::Create(
|
| + data, false, ImageDecoder::kAlphaPremultiplied,
|
| + color_correction ? ColorBehavior::TransformToTargetForTesting()
|
| + : ColorBehavior::Ignore());
|
| +
|
| + bool all_data_received = true;
|
| + decoder->SetData(data, all_data_received);
|
| +
|
| + int frame_count = decoder->FrameCount();
|
| + for (int i = 0; i < frame_count; ++i) {
|
| + sk_sp<SkImage> image = decoder->CreateFrameAtIndex(i);
|
| + if (!image)
|
| + return false;
|
| + bool ret = ReadPixels(image.get());
|
| + if (!ret) {
|
| + fprintf(stderr, "DecodeImageData failed!\n");
|
| + exit(3);
|
| + }
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + size_t cirle_count = data->size() / packet_size + data->size() % packet_size;
|
| + size_t images_count = frame_count * cirle_count;
|
| + sk_sp<SkImage>* images = new sk_sp<SkImage>[images_count];
|
| + size_t* frame_index_arr = new size_t[images_count];
|
| + memset(frame_index_arr, 0, sizeof(size_t[images_count]));
|
| + size_t image_index = 0;
|
| +
|
| + RealDecodeImages real_decode_images{images, frame_index_arr, images_count, 0};
|
| +
|
| + std::thread real_decode_thread(RealDecode, &real_decode_images);
|
| +
|
| + RefPtr<SharedBuffer> packet_data = SharedBuffer::Create();
|
| + size_t position = 0;
|
| + size_t next_frame_to_decode = 0;
|
| +
|
| + std::unique_ptr<DeferredImageDecoder> pakcet_decoder;
|
| +
|
| + 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();
|
| + if (!pakcet_decoder) {
|
| + pakcet_decoder = DeferredImageDecoder::Create(
|
| + packet_data, false, ImageDecoder::kAlphaPremultiplied,
|
| + color_correction ? ColorBehavior::TransformToTargetForTesting()
|
| + : ColorBehavior::Ignore());
|
| + }
|
| + if (!pakcet_decoder) {
|
| + continue;
|
| + }
|
| +
|
| + pakcet_decoder->SetData(packet_data.Get(), all_data_received);
|
| +
|
| + size_t frame_count = pakcet_decoder->FrameCount();
|
| + for (size_t i = next_frame_to_decode; i < frame_count; ++i) {
|
| + images[image_index] = pakcet_decoder->CreateFrameAtIndex(i);
|
| + frame_index_arr[image_index] = i;
|
| + ++image_index;
|
| + if (pakcet_decoder->FrameIsCompleteAtIndex(i)) {
|
| + next_frame_to_decode = i + 1;
|
| + real_decode_images.avail = image_index;
|
| + }
|
| + }
|
| +
|
| + if (all_data_received)
|
| + break;
|
| + }
|
| +
|
| + real_decode_images.count = real_decode_images.avail;
|
| +
|
| + real_decode_thread.join();
|
| +
|
| + delete[] images;
|
| + delete[] frame_index_arr;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +int Main(int argc, char* argv[]) {
|
| + base::CommandLine::Init(argc, argv);
|
| +
|
| + base::TestDiscardableMemoryAllocator memoryAllocator;
|
| + base::DiscardableMemoryAllocator::SetInstance(&memoryAllocator);
|
| +
|
| + WTF::Partitions::Initialize(nullptr);
|
| +
|
| + // 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);
|
| + gfx::ICCProfile profile = gfx::ICCProfileForTestingColorSpin();
|
| + ColorBehavior::SetGlobalTargetColorProfile(profile);
|
| + }
|
| +
|
| + 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 = 0;
|
| + 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 = 0;
|
| + 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.
|
| +
|
| + 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[]) {
|
| + return blink::Main(argc, argv);
|
| +}
|
|
|