1 // Copyright (c) 2011 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 // This small program is used to measure the performance of the various 6 // resize algorithms offered by the ImageOperations::Resize function. 7 // It will generate an empty source bitmap, and rescale it to specified 8 // dimensions. It will repeat this operation multiple time to get more accurate 9 // average throughput. Because it uses elapsed time to do its math, it is only 10 // accurate on an idle system (but that approach was deemed more accurate 11 // than the use of the times() call. 12 // To present a single number in MB/s, it calculates the 'speed' by taking 13 // source surface + destination surface and dividing by the elapsed time. 14 // This number is somewhat reasonable way to measure this, given our current 15 // implementation which somewhat scales this way. 16 17 #include <stdio.h> 18 19 #include "base/basictypes.h" 20 #include "base/command_line.h" 21 #include "base/format_macros.h" 22 #include "base/strings/string_number_conversions.h" 23 #include "base/strings/string_split.h" 24 #include "base/strings/string_util.h" 25 #include "base/strings/utf_string_conversions.h" 26 #include "base/time/time.h" 27 #include "skia/ext/image_operations.h" 28 #include "third_party/skia/include/core/SkBitmap.h" 29 #include "third_party/skia/include/core/SkRect.h" 30 31 namespace { 32 33 struct StringMethodPair { 34 const char* name; 35 skia::ImageOperations::ResizeMethod method; 36 }; 37 #define ADD_METHOD(x) { #x, skia::ImageOperations::RESIZE_##x } 38 const StringMethodPair resize_methods[] = { 39 ADD_METHOD(GOOD), 40 ADD_METHOD(BETTER), 41 ADD_METHOD(BEST), 42 ADD_METHOD(BOX), 43 ADD_METHOD(HAMMING1), 44 ADD_METHOD(LANCZOS2), 45 ADD_METHOD(LANCZOS3), 46 ADD_METHOD(SUBPIXEL) 47 }; 48 49 // converts a string into one of the image operation method to resize. 50 // Returns true on success, false otherwise. 51 bool StringToMethod(const std::string& arg, 52 skia::ImageOperations::ResizeMethod* method) { 53 for (size_t i = 0; i < arraysize(resize_methods); ++i) { 54 if (base::strcasecmp(arg.c_str(), resize_methods[i].name) == 0) { 55 *method = resize_methods[i].method; 56 return true; 57 } 58 } 59 return false; 60 } 61 62 const char* MethodToString(skia::ImageOperations::ResizeMethod method) { 63 for (size_t i = 0; i < arraysize(resize_methods); ++i) { 64 if (method == resize_methods[i].method) { 65 return resize_methods[i].name; 66 } 67 } 68 return "unknown"; 69 } 70 71 // Prints all supported resize methods 72 void PrintMethods() { 73 bool print_comma = false; 74 for (size_t i = 0; i < arraysize(resize_methods); ++i) { 75 if (print_comma) { 76 printf(","); 77 } else { 78 print_comma = true; 79 } 80 printf(" %s", resize_methods[i].name); 81 } 82 } 83 84 // Returns the number of bytes that the bitmap has. This number is different 85 // from what SkBitmap::getSize() returns since it does not take into account 86 // the stride. The difference between the stride and the width can be large 87 // because of the alignment constraints on bitmaps created for SRB scaling 88 // (32 pixels) as seen on GTV platforms. Using this metric instead of the 89 // getSize seemed to be a more accurate representation of the work done (even 90 // though in terms of memory bandwidth that might be similar because of the 91 // cache line size). 92 int GetBitmapSize(const SkBitmap* bitmap) { 93 return bitmap->height() * bitmap->bytesPerPixel() * bitmap->width(); 94 } 95 96 // Simple class to represent dimensions of a bitmap (width, height). 97 class Dimensions { 98 public: 99 Dimensions() 100 : width_(0), 101 height_(0) {} 102 103 void set(int w, int h) { 104 width_ = w; 105 height_ = h; 106 } 107 108 int width() const { 109 return width_; 110 } 111 112 int height() const { 113 return height_; 114 } 115 116 bool IsValid() const { 117 return (width_ > 0 && height_ > 0); 118 } 119 120 // On failure, will set its state in such a way that IsValid will return 121 // false. 122 void FromString(const std::string& arg) { 123 std::vector<std::string> strings; 124 base::SplitString(std::string(arg), 'x', &strings); 125 if (strings.size() != 2 || 126 base::StringToInt(strings[0], &width_) == false || 127 base::StringToInt(strings[1], &height_) == false) { 128 width_ = -1; // force the dimension object to be invalid. 129 } 130 } 131 private: 132 int width_; 133 int height_; 134 }; 135 136 // main class used for the benchmarking. 137 class Benchmark { 138 public: 139 static const int kDefaultNumberIterations; 140 static const skia::ImageOperations::ResizeMethod kDefaultResizeMethod; 141 142 Benchmark() 143 : num_iterations_(kDefaultNumberIterations), 144 method_(kDefaultResizeMethod) {} 145 146 // Returns true if command line parsing was successful, false otherwise. 147 bool ParseArgs(const base::CommandLine* command_line); 148 149 // Returns true if successful, false otherwise. 150 bool Run() const; 151 152 static void Usage(); 153 private: 154 int num_iterations_; 155 skia::ImageOperations::ResizeMethod method_; 156 Dimensions source_; 157 Dimensions dest_; 158 }; 159 160 // static 161 const int Benchmark::kDefaultNumberIterations = 1024; 162 const skia::ImageOperations::ResizeMethod Benchmark::kDefaultResizeMethod = 163 skia::ImageOperations::RESIZE_LANCZOS3; 164 165 // argument management 166 void Benchmark::Usage() { 167 printf("image_operations_bench -source wxh -destination wxh " 168 "[-iterations i] [-method m] [-help]\n" 169 " -source wxh: specify source width and height\n" 170 " -destination wxh: specify destination width and height\n" 171 " -iter i: perform i iterations (default:%d)\n" 172 " -method m: use method m (default:%s), which can be:", 173 Benchmark::kDefaultNumberIterations, 174 MethodToString(Benchmark::kDefaultResizeMethod)); 175 PrintMethods(); 176 printf("\n -help: prints this help and exits\n"); 177 } 178 179 bool Benchmark::ParseArgs(const base::CommandLine* command_line) { 180 const base::CommandLine::SwitchMap& switches = command_line->GetSwitches(); 181 bool fNeedHelp = false; 182 183 for (base::CommandLine::SwitchMap::const_iterator iter = switches.begin(); 184 iter != switches.end(); 185 ++iter) { 186 const std::string& s = iter->first; 187 std::string value; 188 #if defined(OS_WIN) 189 value = base::WideToUTF8(iter->second); 190 #else 191 value = iter->second; 192 #endif 193 if (s == "source") { 194 source_.FromString(value); 195 } else if (s == "destination") { 196 dest_.FromString(value); 197 } else if (s == "iterations") { 198 if (base::StringToInt(value, &num_iterations_) == false) { 199 fNeedHelp = true; 200 } 201 } else if (s == "method") { 202 if (!StringToMethod(value, &method_)) { 203 printf("Invalid method '%s' specified\n", value.c_str()); 204 fNeedHelp = true; 205 } 206 } else { 207 fNeedHelp = true; 208 } 209 } 210 211 if (num_iterations_ <= 0) { 212 printf("Invalid number of iterations: %d\n", num_iterations_); 213 fNeedHelp = true; 214 } 215 if (!source_.IsValid()) { 216 printf("Invalid source dimensions specified\n"); 217 fNeedHelp = true; 218 } 219 if (!dest_.IsValid()) { 220 printf("Invalid dest dimensions specified\n"); 221 fNeedHelp = true; 222 } 223 if (fNeedHelp == true) { 224 return false; 225 } 226 return true; 227 } 228 229 // actual benchmark. 230 bool Benchmark::Run() const { 231 SkBitmap source; 232 source.allocN32Pixels(source_.width(), source_.height()); 233 source.eraseARGB(0, 0, 0, 0); 234 235 SkBitmap dest; 236 237 const base::TimeTicks start = base::TimeTicks::Now(); 238 239 for (int i = 0; i < num_iterations_; ++i) { 240 dest = skia::ImageOperations::Resize(source, 241 method_, 242 dest_.width(), dest_.height()); 243 } 244 245 const int64 elapsed_us = (base::TimeTicks::Now() - start).InMicroseconds(); 246 247 const uint64 num_bytes = static_cast<uint64>(num_iterations_) * 248 (GetBitmapSize(&source) + GetBitmapSize(&dest)); 249 250 printf("%" PRIu64 " MB/s,\telapsed = %" PRIu64 " source=%d dest=%d\n", 251 static_cast<uint64>(elapsed_us == 0 ? 0 : num_bytes / elapsed_us), 252 static_cast<uint64>(elapsed_us), 253 GetBitmapSize(&source), GetBitmapSize(&dest)); 254 255 return true; 256 } 257 258 // A small class to automatically call Reset on the global command line to 259 // avoid nasty valgrind complaints for the leak of the global command line. 260 class CommandLineAutoReset { 261 public: 262 CommandLineAutoReset(int argc, char** argv) { 263 base::CommandLine::Init(argc, argv); 264 } 265 ~CommandLineAutoReset() { 266 base::CommandLine::Reset(); 267 } 268 269 const base::CommandLine* Get() const { 270 return base::CommandLine::ForCurrentProcess(); 271 } 272 }; 273 274 } // namespace 275 276 int main(int argc, char** argv) { 277 Benchmark bench; 278 CommandLineAutoReset command_line(argc, argv); 279 280 if (!bench.ParseArgs(command_line.Get())) { 281 Benchmark::Usage(); 282 return 1; 283 } 284 285 if (!bench.Run()) { 286 printf("Failed to run benchmark\n"); 287 return 1; 288 } 289 290 return 0; 291 } 292