OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright 2016 Google Inc. |
| 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. |
| 6 */ |
| 7 |
| 8 #include "GrContextFactory.h" |
| 9 #include "SkCanvas.h" |
| 10 #include "SkOSFile.h" |
| 11 #include "SkPicture.h" |
| 12 #include "SkStream.h" |
| 13 #include "SkSurface.h" |
| 14 #include "SkSurfaceProps.h" |
| 15 #include "picture_utils.h" |
| 16 #include "flags/SkCommandLineFlags.h" |
| 17 #include "flags/SkCommonFlagsConfig.h" |
| 18 #include <stdlib.h> |
| 19 #include <algorithm> |
| 20 #include <array> |
| 21 #include <chrono> |
| 22 #include <cmath> |
| 23 #include <vector> |
| 24 |
| 25 /** |
| 26 * This is a minimalist program whose sole purpose is to open an skp file, bench
mark it on a single |
| 27 * config, and exit. It is intended to be used through skpbench.py rather than i
nvoked directly. |
| 28 * Limiting the entire process to a single config/skp pair helps to keep the res
ults repeatable. |
| 29 * |
| 30 * No tiling, looping, or other fanciness is used; it just draws the skp whole i
nto a size-matched |
| 31 * render target and syncs the GPU after each draw. |
| 32 * |
| 33 * The results consist of a fixed amount of samples (--samples). A sample is def
ined as the number |
| 34 * of frames rendered within a set amount of time (--sampleMs). |
| 35 * |
| 36 * Currently, only GPU configs are supported. |
| 37 */ |
| 38 |
| 39 DEFINE_int32(samples, 101, "number of samples to collect"); |
| 40 DEFINE_int32(sampleMs, 50, "duration of each sample"); |
| 41 DEFINE_bool(fps, false, "use fps instead of ms"); |
| 42 DEFINE_string(skp, "", "path to a single .skp file to benchmark"); |
| 43 DEFINE_string(png, "", "if set, save a .png proof to disk at this file location"
); |
| 44 DEFINE_int32(verbosity, 4, "level of verbosity (0=none to 5=debug)"); |
| 45 DEFINE_bool(suppressHeader, false, "don't print a header row before the results"
); |
| 46 |
| 47 static const char* header = |
| 48 " median accum max min stddev metric samples sample_ms
config bench"; |
| 49 |
| 50 static const char* resultFormat = |
| 51 "%8.4g %8.4g %8.4g %8.4g %6.3g%% %-6s %7li %9i %-9s %s"; |
| 52 |
| 53 struct Sample { |
| 54 using clock = std::chrono::high_resolution_clock; |
| 55 |
| 56 Sample() : fFrames(0), fDuration(0) {} |
| 57 double seconds() const { return std::chrono::duration<double>(fDuration).cou
nt(); } |
| 58 double ms() const { return std::chrono::duration<double, std::milli>(fDurati
on).count(); } |
| 59 double value() const { return FLAGS_fps ? fFrames / this->seconds() : this->
ms() / fFrames; } |
| 60 static const char* metric() { return FLAGS_fps ? "fps" : "ms"; } |
| 61 |
| 62 int fFrames; |
| 63 clock::duration fDuration; |
| 64 }; |
| 65 |
| 66 enum class ExitErr { |
| 67 kOk = 0, |
| 68 kUsage = 64, |
| 69 kData = 65, |
| 70 kUnavailable = 69, |
| 71 kIO = 74, |
| 72 kSoftware = 70 |
| 73 }; |
| 74 |
| 75 static void draw_skp_and_flush(SkCanvas*, const SkPicture*); |
| 76 static SkPlatformGpuFence insert_verified_fence(const SkGpuFenceSync*); |
| 77 static void wait_fence_and_delete(const SkGpuFenceSync*, SkPlatformGpuFence); |
| 78 static bool mkdir_p(const SkString& name); |
| 79 static SkString join(const SkCommandLineFlags::StringArray&); |
| 80 static void exitf(ExitErr, const char* format, ...); |
| 81 |
| 82 static void run_benchmark(const SkGpuFenceSync* sync, SkCanvas* canvas, const Sk
Picture* skp, |
| 83 std::vector<Sample>* samples) { |
| 84 using clock = Sample::clock; |
| 85 std::chrono::milliseconds sampleMs(FLAGS_sampleMs); |
| 86 |
| 87 samples->clear(); |
| 88 samples->resize(FLAGS_samples); |
| 89 |
| 90 // Prime the graphics pipe. |
| 91 SkPlatformGpuFence frameN_minus_2, frameN_minus_1; |
| 92 { |
| 93 draw_skp_and_flush(canvas, skp); |
| 94 SkPlatformGpuFence frame0 = insert_verified_fence(sync); |
| 95 |
| 96 draw_skp_and_flush(canvas, skp); |
| 97 frameN_minus_2 = insert_verified_fence(sync); |
| 98 |
| 99 draw_skp_and_flush(canvas, skp); |
| 100 frameN_minus_1 = insert_verified_fence(sync); |
| 101 |
| 102 wait_fence_and_delete(sync, frame0); |
| 103 } |
| 104 |
| 105 clock::time_point start = clock::now(); |
| 106 |
| 107 for (Sample& sample : *samples) { |
| 108 clock::time_point end; |
| 109 do { |
| 110 draw_skp_and_flush(canvas, skp); |
| 111 |
| 112 // Sync the GPU. |
| 113 wait_fence_and_delete(sync, frameN_minus_2); |
| 114 frameN_minus_2 = frameN_minus_1; |
| 115 frameN_minus_1 = insert_verified_fence(sync); |
| 116 |
| 117 end = clock::now(); |
| 118 sample.fDuration = end - start; |
| 119 ++sample.fFrames; |
| 120 } while (sample.fDuration < sampleMs); |
| 121 |
| 122 if (FLAGS_verbosity >= 5) { |
| 123 fprintf(stderr, "%.4g%s [ms=%.4g frames=%i]\n", |
| 124 sample.value(), Sample::metric(), sample.ms(), sampl
e.fFrames); |
| 125 } |
| 126 |
| 127 start = end; |
| 128 } |
| 129 |
| 130 sync->deleteFence(frameN_minus_2); |
| 131 sync->deleteFence(frameN_minus_1); |
| 132 } |
| 133 |
| 134 void print_result(const std::vector<Sample>& samples, const char* config, const
char* bench) { |
| 135 if (0 == (samples.size() % 2)) { |
| 136 exitf(ExitErr::kSoftware, "attempted to gather stats on even number of s
amples"); |
| 137 } |
| 138 |
| 139 Sample accum = Sample(); |
| 140 std::vector<double> values; |
| 141 values.reserve(samples.size()); |
| 142 for (const Sample& sample : samples) { |
| 143 accum.fFrames += sample.fFrames; |
| 144 accum.fDuration += sample.fDuration; |
| 145 values.push_back(sample.value()); |
| 146 } |
| 147 std::sort(values.begin(), values.end()); |
| 148 const double median = values[values.size() / 2]; |
| 149 |
| 150 const double meanValue = accum.value(); |
| 151 double variance = 0; |
| 152 for (const Sample& sample : samples) { |
| 153 const double delta = sample.value() - meanValue; |
| 154 variance += delta * delta; |
| 155 } |
| 156 variance /= samples.size(); |
| 157 // Technically, this is the relative standard deviation. |
| 158 const double stddev = 100/*%*/ * sqrt(variance) / meanValue; |
| 159 |
| 160 printf(resultFormat, median, accum.value(), values.back(), values.front(), s
tddev, |
| 161 Sample::metric(), values.size(), FLAGS_sampleMs, config, bench); |
| 162 printf("\n"); |
| 163 fflush(stdout); |
| 164 } |
| 165 |
| 166 int main(int argc, char** argv) { |
| 167 SkCommandLineFlags::SetUsage("Use skpbench.py instead. " |
| 168 "You usually don't want to use this program dir
ectly."); |
| 169 SkCommandLineFlags::Parse(argc, argv); |
| 170 |
| 171 if (!FLAGS_suppressHeader) { |
| 172 printf("%s\n", header); |
| 173 } |
| 174 if (FLAGS_samples <= 0) { |
| 175 exit(0); // This can be used to print the header and quit. |
| 176 } |
| 177 if (0 == FLAGS_samples % 2) { |
| 178 fprintf(stderr, "WARNING: even number of samples requested (%i); " |
| 179 "using %i so there can be a true median.\n", |
| 180 FLAGS_samples, FLAGS_samples + 1); |
| 181 ++FLAGS_samples; |
| 182 } |
| 183 |
| 184 // Parse the config. |
| 185 const SkCommandLineConfigGpu* config = nullptr; // Initialize for spurious w
arning. |
| 186 SkCommandLineConfigArray configs; |
| 187 ParseConfigs(FLAGS_config, &configs); |
| 188 if (configs.count() != 1 || !(config = configs[0]->asConfigGpu())) { |
| 189 exitf(ExitErr::kUsage, "invalid config %s; must specify one (and only on
e) GPU config", |
| 190 join(FLAGS_config).c_str()); |
| 191 } |
| 192 |
| 193 // Parse the skp. |
| 194 if (FLAGS_skp.count() != 1) { |
| 195 exitf(ExitErr::kUsage, "invalid skp \"%s\"; one (and only one) skp must
be specified.", |
| 196 join(FLAGS_skp).c_str()); |
| 197 } |
| 198 const char* skpfile = FLAGS_skp[0]; |
| 199 std::unique_ptr<SkStream> skpstream(SkStream::MakeFromFile(skpfile)); |
| 200 if (!skpstream) { |
| 201 exitf(ExitErr::kIO, "failed to open skp file %s", skpfile); |
| 202 } |
| 203 sk_sp<SkPicture> skp = SkPicture::MakeFromStream(skpstream.get()); |
| 204 if (!skp) { |
| 205 exitf(ExitErr::kData, "failed to parse skp file %s", skpfile); |
| 206 } |
| 207 int width = SkTMin(SkScalarCeilToInt(skp->cullRect().width()), 2048), |
| 208 height = SkTMin(SkScalarCeilToInt(skp->cullRect().height()), 2048); |
| 209 if (FLAGS_verbosity >= 2 && |
| 210 (width != skp->cullRect().width() || height != skp->cullRect().height())
) { |
| 211 fprintf(stderr, "NOTE: %s is too large (%ix%i); cropping to %ix%i.\n", |
| 212 skpfile, SkScalarCeilToInt(skp->cullRect().width()), |
| 213 SkScalarCeilToInt(skp->cullRect().height()), width, heig
ht); |
| 214 } |
| 215 |
| 216 // Create a context. |
| 217 sk_gpu_test::GrContextFactory factory; |
| 218 sk_gpu_test::ContextInfo ctxInfo = |
| 219 factory.getContextInfo(config->getContextType(), config->getContextOptio
ns()); |
| 220 GrContext* ctx = ctxInfo.grContext(); |
| 221 if (!ctx) { |
| 222 exitf(ExitErr::kUnavailable, "failed to create context for config %s", |
| 223 config->getTag().c_str()); |
| 224 } |
| 225 if (ctx->caps()->maxRenderTargetSize() < SkTMax(width, height)) { |
| 226 exitf(ExitErr::kUnavailable, "render target size %ix%i not supported by
platform (max: %i)", |
| 227 width, height, ctx->caps()->maxRenderTarget
Size()); |
| 228 } |
| 229 if (ctx->caps()->maxSampleCount() < config->getSamples()) { |
| 230 exitf(ExitErr::kUnavailable, "sample count %i not supported by platform
(max: %i)", |
| 231 config->getSamples(), ctx->caps()->maxSampl
eCount()); |
| 232 } |
| 233 sk_gpu_test::TestContext* testCtx = ctxInfo.testContext(); |
| 234 if (!testCtx) { |
| 235 exitf(ExitErr::kSoftware, "testContext is null"); |
| 236 } |
| 237 if (!testCtx->fenceSyncSupport()) { |
| 238 exitf(ExitErr::kUnavailable, "GPU does not support fence sync"); |
| 239 } |
| 240 |
| 241 // Create a render target. |
| 242 SkImageInfo info = SkImageInfo::Make(width, height, config->getColorType(), |
| 243 kPremul_SkAlphaType, sk_ref_sp(config->
getColorSpace())); |
| 244 uint32_t flags = config->getUseDIText() ? SkSurfaceProps::kUseDeviceIndepend
entFonts_Flag : 0; |
| 245 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType); |
| 246 sk_sp<SkSurface> surface = |
| 247 SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, config->getSampl
es(), &props); |
| 248 if (!surface) { |
| 249 exitf(ExitErr::kUnavailable, "failed to create %ix%i render target for c
onfig %s", |
| 250 width, height, config->getTag().c_str()); |
| 251 } |
| 252 |
| 253 // Run the benchmark. |
| 254 std::vector<Sample> samples; |
| 255 SkCanvas* canvas = surface->getCanvas(); |
| 256 canvas->translate(-skp->cullRect().x(), -skp->cullRect().y()); |
| 257 run_benchmark(testCtx->fenceSync(), canvas, skp.get(), &samples); |
| 258 print_result(samples, config->getTag().c_str(), SkOSPath::Basename(skpfile).
c_str()); |
| 259 |
| 260 // Save a proof (if one was requested). |
| 261 if (!FLAGS_png.isEmpty()) { |
| 262 SkBitmap bmp; |
| 263 bmp.setInfo(info); |
| 264 if (!surface->getCanvas()->readPixels(&bmp, 0, 0)) { |
| 265 exitf(ExitErr::kUnavailable, "failed to read canvas pixels for png")
; |
| 266 } |
| 267 const SkString &dirname = SkOSPath::Dirname(FLAGS_png[0]), |
| 268 &basename = SkOSPath::Basename(FLAGS_png[0]); |
| 269 if (!mkdir_p(dirname)) { |
| 270 exitf(ExitErr::kIO, "failed to create directory \"%s\" for png", dir
name.c_str()); |
| 271 } |
| 272 if (!sk_tools::write_bitmap_to_disk(bmp, dirname, nullptr, basename)) { |
| 273 exitf(ExitErr::kIO, "failed to save png to \"%s\"", FLAGS_png[0]); |
| 274 } |
| 275 } |
| 276 |
| 277 exit(0); |
| 278 } |
| 279 |
| 280 static void draw_skp_and_flush(SkCanvas* canvas, const SkPicture* skp) { |
| 281 canvas->drawPicture(skp); |
| 282 canvas->flush(); |
| 283 } |
| 284 |
| 285 static SkPlatformGpuFence insert_verified_fence(const SkGpuFenceSync* sync) { |
| 286 SkPlatformGpuFence fence = sync->insertFence(); |
| 287 if (kInvalidPlatformGpuFence == fence) { |
| 288 exitf(ExitErr::kUnavailable, "failed to insert fence"); |
| 289 } |
| 290 return fence; |
| 291 } |
| 292 |
| 293 static void wait_fence_and_delete(const SkGpuFenceSync* sync, SkPlatformGpuFence
fence) { |
| 294 if (kInvalidPlatformGpuFence == fence) { |
| 295 exitf(ExitErr::kSoftware, "attempted to wait on invalid fence"); |
| 296 } |
| 297 if (!sync->waitFence(fence)) { |
| 298 exitf(ExitErr::kUnavailable, "failed to wait for fence"); |
| 299 } |
| 300 sync->deleteFence(fence); |
| 301 } |
| 302 |
| 303 bool mkdir_p(const SkString& dirname) { |
| 304 if (dirname.isEmpty()) { |
| 305 return true; |
| 306 } |
| 307 return mkdir_p(SkOSPath::Dirname(dirname.c_str())) && sk_mkdir(dirname.c_str
()); |
| 308 } |
| 309 |
| 310 static SkString join(const SkCommandLineFlags::StringArray& stringArray) { |
| 311 SkString joined; |
| 312 for (int i = 0; i < FLAGS_config.count(); ++i) { |
| 313 joined.appendf(i ? " %s" : "%s", FLAGS_config[i]); |
| 314 } |
| 315 return joined; |
| 316 } |
| 317 |
| 318 static void exitf(ExitErr err, const char* format, ...) { |
| 319 fprintf(stderr, ExitErr::kSoftware == err ? "INTERNAL ERROR: " : "ERROR: "); |
| 320 va_list args; |
| 321 va_start(args, format); |
| 322 vfprintf(stderr, format, args); |
| 323 va_end(args); |
| 324 fprintf(stderr, ExitErr::kSoftware == err ? "; this should never happen.\n":
".\n"); |
| 325 exit((int)err); |
| 326 } |
OLD | NEW |