blob: 6abf29524d33e0059a628e4f5900aaa3547f278d [file] [log] [blame]
// 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 "base/command_line.h"
#include "platform/SharedBuffer.h"
#include "platform/image-decoders/ImageDecoder.h"
#include "public/platform/Platform.h"
#include "wtf/PassRefPtr.h"
#include "wtf/PtrUtil.h"
#include <memory>
#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
using namespace blink;
#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 fileTime;
GetSystemTimeAsFileTime(&fileTime);
// 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 dateTime;
memcpy(&dateTime, &fileTime, sizeof(dateTime));
// Number of 100 nanosecond between January 1, 1601 and January 1, 1970.
static const ULONGLONG epochBias = 116444736000000000ULL;
// Windows file times are in 100s of nanoseconds.
static const double hundredsOfNanosecondsPerMillisecond = 10000;
return (dateTime.QuadPart - epochBias) / hundredsOfNanosecondsPerMillisecond;
}
static LARGE_INTEGER qpcFrequency;
static bool syncedTime;
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 qpcLast;
static DWORD tickCountLast;
static bool inited;
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
DWORD tickCount = GetTickCount();
if (inited) {
__int64 qpcElapsed =
((qpc.QuadPart - qpcLast.QuadPart) * 1000) / qpcFrequency.QuadPart;
__int64 tickCountElapsed;
if (tickCount >= tickCountLast) {
tickCountElapsed = (tickCount - tickCountLast);
} else {
__int64 tickCountLarge = tickCount + 0x100000000I64;
tickCountElapsed = tickCountLarge - tickCountLast;
}
// 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 = tickCountElapsed - qpcElapsed;
if (diff > 500 || diff < -500)
syncedTime = false;
} else {
inited = true;
}
qpcLast = qpc;
tickCountLast = tickCount;
return (1000.0 * qpc.QuadPart) / static_cast<double>(qpcFrequency.QuadPart);
}
static bool qpcAvailable() {
static bool available;
static bool checked;
if (checked)
return available;
available = QueryPerformanceFrequency(&qpcFrequency);
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 syncLowResUTCTime;
static double syncHighResUpTime;
static double lastUTCTime;
double lowResTime = lowResUTCTime();
if (!qpcAvailable())
return lowResTime * (1.0 / 1000.0);
double highResTime = highResUpTime();
if (!syncedTime) {
timeBeginPeriod(1); // increase time resolution around low-res time getter
syncLowResUTCTime = lowResTime = lowResUTCTime();
timeEndPeriod(1); // restore time resolution
syncHighResUpTime = highResTime;
syncedTime = true;
}
double highResElapsed = highResTime - syncHighResUpTime;
double utc = syncLowResUTCTime + highResElapsed;
// Force a clock re-sync if we've drifted.
double lowResElapsed = lowResTime - syncLowResUTCTime;
const double maximumAllowedDriftMsec =
15.625 * 2.0; // 2x the typical low-res accuracy
if (fabs(highResElapsed - lowResElapsed) > maximumAllowedDriftMsec)
syncedTime = 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 backwardTimeLimit = 2000.0;
if (utc < lastUTCTime && (lastUTCTime - utc) < backwardTimeLimit)
return lastUTCTime * (1.0 / 1000.0);
lastUTCTime = 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
void getScreenColorProfile(WebVector<char>* profile) {
static unsigned char profileData[] = {
0x00, 0x00, 0x01, 0xea, 0x54, 0x45, 0x53, 0x54, 0x00, 0x00, 0x00, 0x00,
0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x61, 0x63, 0x73, 0x70, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00,
0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x74, 0x65, 0x73, 0x74,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x0d,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x8c,
0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x14,
0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xa0, 0x00, 0x00, 0x00, 0x14,
0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xb4, 0x00, 0x00, 0x00, 0x14,
0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x14,
0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xdc, 0x00, 0x00, 0x00, 0x0e,
0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xdc, 0x00, 0x00, 0x00, 0x0e,
0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xdc, 0x00, 0x00, 0x00, 0x0e,
0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x77, 0x68, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x2e,
0x69, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x52,
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xcc, 0x58, 0x59, 0x5a, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x8d, 0x00, 0x00, 0xa0, 0x2c,
0x00, 0x00, 0x0f, 0x95, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x26, 0x31, 0x00, 0x00, 0x10, 0x2f, 0x00, 0x00, 0xbe, 0x9b,
0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x18,
0x00, 0x00, 0x4f, 0xa5, 0x00, 0x00, 0x04, 0xfc, 0x63, 0x75, 0x72, 0x76,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x33};
static struct WhackedColorProfile {
char* data() { return reinterpret_cast<char*>(profileData); }
const size_t profileSize = 490u;
size_t size() { return profileSize; }
} screenProfile;
profile->assign(screenProfile.data(), screenProfile.size());
}
PassRefPtr<SharedBuffer> readFile(const char* fileName) {
FILE* fp = fopen(fileName, "rb");
if (!fp) {
fprintf(stderr, "Can't open file %s\n", fileName);
exit(2);
}
sttype s;
stat(fileName, &s);
size_t fileSize = s.st_size;
if (s.st_size <= 0)
return SharedBuffer::create();
std::unique_ptr<unsigned char[]> buffer =
wrapArrayUnique(new unsigned char[fileSize]);
if (fileSize != fread(buffer.get(), 1, fileSize, fp)) {
fprintf(stderr, "Error reading file %s\n", fileName);
exit(2);
}
fclose(fp);
return SharedBuffer::create(buffer.get(), fileSize);
}
bool decodeImageData(SharedBuffer* data,
bool colorCorrection,
size_t packetSize) {
std::unique_ptr<ImageDecoder> decoder = ImageDecoder::create(
*data, ImageDecoder::AlphaPremultiplied,
colorCorrection ? ImageDecoder::GammaAndColorProfileApplied
: ImageDecoder::GammaAndColorProfileIgnored);
if (!packetSize) {
bool allDataReceived = true;
decoder->setData(data, allDataReceived);
int frameCount = decoder->frameCount();
for (int i = 0; i < frameCount; ++i) {
if (!decoder->frameBufferAtIndex(i))
return false;
}
return !decoder->failed();
}
RefPtr<SharedBuffer> packetData = SharedBuffer::create();
size_t position = 0;
while (true) {
const char* packet;
size_t length = data->getSomeData(packet, position);
length = std::min(length, packetSize);
packetData->append(packet, length);
position += length;
bool allDataReceived = position == data->size();
decoder->setData(packetData.get(), allDataReceived);
int frameCount = decoder->frameCount();
for (int i = 0; i < frameCount; ++i) {
if (!decoder->frameBufferAtIndex(i))
break;
}
if (allDataReceived || decoder->failed())
break;
}
return !decoder->failed();
}
int main(int argc, char* argv[]) {
base::CommandLine::Init(argc, argv);
// If the platform supports color correction, allow it to be controlled.
bool applyColorCorrection = false;
#if USE(QCMSLIB)
if (argc >= 2 && strcmp(argv[1], "--color-correct") == 0)
applyColorCorrection = (--argc, ++argv, true);
if (argc < 2) {
fprintf(stderr,
"Usage: %s [--color-correct] file [iterations] [packetSize]\n",
argv[0]);
exit(1);
}
#else
if (argc < 2) {
fprintf(stderr, "Usage: %s file [iterations] [packetSize]\n", argv[0]);
exit(1);
}
#endif
// 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 packetSize = 0;
if (argc >= 4) {
char* end = 0;
packetSize = 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 without V8.
class WebPlatform : public blink::Platform {
public:
void screenColorProfile(WebVector<char>* profile) override {
getScreenColorProfile(profile); // Returns a color spin color profile.
}
};
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(), applyColorCorrection, packetSize)) {
fprintf(stderr, "Image decode failed [%s]\n", argv[1]);
exit(3);
}
// Image decode bench for iterations.
double totalTime = 0.0;
for (size_t i = 0; i < iterations; ++i) {
double startTime = getCurrentTime();
bool decoded =
decodeImageData(data.get(), applyColorCorrection, packetSize);
double elapsedTime = getCurrentTime() - startTime;
totalTime += elapsedTime;
if (!decoded) {
fprintf(stderr, "Image decode failed [%s]\n", argv[1]);
exit(3);
}
}
// Results to stdout.
double averageTime = totalTime / static_cast<double>(iterations);
printf("%f %f\n", totalTime, averageTime);
return 0;
}