OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GpuTimer.h" | 8 #include "GpuTimer.h" |
9 #include "GrContextFactory.h" | 9 #include "GrContextFactory.h" |
10 #include "SkCanvas.h" | 10 #include "SkCanvas.h" |
11 #include "SkOSFile.h" | 11 #include "SkOSFile.h" |
| 12 #include "SkPerlinNoiseShader.h" |
12 #include "SkPicture.h" | 13 #include "SkPicture.h" |
| 14 #include "SkPictureRecorder.h" |
13 #include "SkStream.h" | 15 #include "SkStream.h" |
14 #include "SkSurface.h" | 16 #include "SkSurface.h" |
15 #include "SkSurfaceProps.h" | 17 #include "SkSurfaceProps.h" |
16 #include "picture_utils.h" | 18 #include "picture_utils.h" |
| 19 #include "sk_tool_utils.h" |
17 #include "flags/SkCommandLineFlags.h" | 20 #include "flags/SkCommandLineFlags.h" |
18 #include "flags/SkCommonFlagsConfig.h" | 21 #include "flags/SkCommonFlagsConfig.h" |
19 #include <stdlib.h> | 22 #include <stdlib.h> |
20 #include <algorithm> | 23 #include <algorithm> |
21 #include <array> | 24 #include <array> |
22 #include <chrono> | 25 #include <chrono> |
23 #include <cmath> | 26 #include <cmath> |
24 #include <vector> | 27 #include <vector> |
25 | 28 |
26 /** | 29 /** |
27 * This is a minimalist program whose sole purpose is to open an skp file, bench
mark it on a single | 30 * This is a minimalist program whose sole purpose is to open an skp file, bench
mark it on a single |
28 * config, and exit. It is intended to be used through skpbench.py rather than i
nvoked directly. | 31 * config, and exit. It is intended to be used through skpbench.py rather than i
nvoked directly. |
29 * Limiting the entire process to a single config/skp pair helps to keep the res
ults repeatable. | 32 * Limiting the entire process to a single config/skp pair helps to keep the res
ults repeatable. |
30 * | 33 * |
31 * No tiling, looping, or other fanciness is used; it just draws the skp whole i
nto a size-matched | 34 * No tiling, looping, or other fanciness is used; it just draws the skp whole i
nto a size-matched |
32 * render target and syncs the GPU after each draw. | 35 * render target and syncs the GPU after each draw. |
33 * | 36 * |
34 * Currently, only GPU configs are supported. | 37 * Currently, only GPU configs are supported. |
35 */ | 38 */ |
36 | 39 |
37 DEFINE_int32(duration, 5000, "number of milliseconds to run the benchmark"); | 40 DEFINE_int32(duration, 5000, "number of milliseconds to run the benchmark"); |
38 DEFINE_int32(sampleMs, 50, "minimum duration of a sample"); | 41 DEFINE_int32(sampleMs, 50, "minimum duration of a sample"); |
39 DEFINE_bool(gpuClock, false, "time on the gpu clock (gpu work only)"); | 42 DEFINE_bool(gpuClock, false, "time on the gpu clock (gpu work only)"); |
40 DEFINE_bool(fps, false, "use fps instead of ms"); | 43 DEFINE_bool(fps, false, "use fps instead of ms"); |
41 DEFINE_string(skp, "", "path to a single .skp file to benchmark"); | 44 DEFINE_string(skp, "", "path to a single .skp file, or 'warmup' for a builtin wa
rmup run"); |
42 DEFINE_string(png, "", "if set, save a .png proof to disk at this file location"
); | 45 DEFINE_string(png, "", "if set, save a .png proof to disk at this file location"
); |
43 DEFINE_int32(verbosity, 4, "level of verbosity (0=none to 5=debug)"); | 46 DEFINE_int32(verbosity, 4, "level of verbosity (0=none to 5=debug)"); |
44 DEFINE_bool(suppressHeader, false, "don't print a header row before the results"
); | 47 DEFINE_bool(suppressHeader, false, "don't print a header row before the results"
); |
45 | 48 |
46 static const char* header = | 49 static const char* header = |
47 " accum median max min stddev samples sample_ms clock met
ric config bench"; | 50 " accum median max min stddev samples sample_ms clock met
ric config bench"; |
48 | 51 |
49 static const char* resultFormat = | 52 static const char* resultFormat = |
50 "%8.4g %8.4g %8.4g %8.4g %6.3g%% %7li %9i %-5s %-6s %-9s %s"; | 53 "%8.4g %8.4g %8.4g %8.4g %6.3g%% %7li %9i %-5s %-6s %-9s %s"; |
51 | 54 |
(...skipping 27 matching lines...) Expand all Loading... |
79 enum class ExitErr { | 82 enum class ExitErr { |
80 kOk = 0, | 83 kOk = 0, |
81 kUsage = 64, | 84 kUsage = 64, |
82 kData = 65, | 85 kData = 65, |
83 kUnavailable = 69, | 86 kUnavailable = 69, |
84 kIO = 74, | 87 kIO = 74, |
85 kSoftware = 70 | 88 kSoftware = 70 |
86 }; | 89 }; |
87 | 90 |
88 static void draw_skp_and_flush(SkCanvas*, const SkPicture*); | 91 static void draw_skp_and_flush(SkCanvas*, const SkPicture*); |
| 92 static sk_sp<SkPicture> create_warmup_skp(); |
89 static bool mkdir_p(const SkString& name); | 93 static bool mkdir_p(const SkString& name); |
90 static SkString join(const SkCommandLineFlags::StringArray&); | 94 static SkString join(const SkCommandLineFlags::StringArray&); |
91 static void exitf(ExitErr, const char* format, ...); | 95 static void exitf(ExitErr, const char* format, ...); |
92 | 96 |
93 static void run_benchmark(const sk_gpu_test::FenceSync* fenceSync, SkCanvas* can
vas, | 97 static void run_benchmark(const sk_gpu_test::FenceSync* fenceSync, SkCanvas* can
vas, |
94 const SkPicture* skp, std::vector<Sample>* samples) { | 98 const SkPicture* skp, std::vector<Sample>* samples) { |
95 using clock = std::chrono::high_resolution_clock; | 99 using clock = std::chrono::high_resolution_clock; |
96 const Sample::duration sampleDuration = std::chrono::milliseconds(FLAGS_samp
leMs); | 100 const Sample::duration sampleDuration = std::chrono::milliseconds(FLAGS_samp
leMs); |
97 const clock::duration benchDuration = std::chrono::milliseconds(FLAGS_durati
on); | 101 const clock::duration benchDuration = std::chrono::milliseconds(FLAGS_durati
on); |
98 | 102 |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
223 } | 227 } |
224 if (FLAGS_duration <= 0) { | 228 if (FLAGS_duration <= 0) { |
225 exit(0); // This can be used to print the header and quit. | 229 exit(0); // This can be used to print the header and quit. |
226 } | 230 } |
227 | 231 |
228 // Parse the config. | 232 // Parse the config. |
229 const SkCommandLineConfigGpu* config = nullptr; // Initialize for spurious w
arning. | 233 const SkCommandLineConfigGpu* config = nullptr; // Initialize for spurious w
arning. |
230 SkCommandLineConfigArray configs; | 234 SkCommandLineConfigArray configs; |
231 ParseConfigs(FLAGS_config, &configs); | 235 ParseConfigs(FLAGS_config, &configs); |
232 if (configs.count() != 1 || !(config = configs[0]->asConfigGpu())) { | 236 if (configs.count() != 1 || !(config = configs[0]->asConfigGpu())) { |
233 exitf(ExitErr::kUsage, "invalid config %s, must specify one (and only on
e) GPU config", | 237 exitf(ExitErr::kUsage, "invalid config '%s': must specify one (and only
one) GPU config", |
234 join(FLAGS_config).c_str()); | 238 join(FLAGS_config).c_str()); |
235 } | 239 } |
236 | 240 |
237 // Parse the skp. | 241 // Parse the skp. |
238 if (FLAGS_skp.count() != 1) { | 242 if (FLAGS_skp.count() != 1) { |
239 exitf(ExitErr::kUsage, "invalid skp %s, must specify (and only one) skp
path name.", | 243 exitf(ExitErr::kUsage, "invalid skp '%s': must specify a single skp file
, or 'warmup'", |
240 join(FLAGS_skp).c_str()); | 244 join(FLAGS_skp).c_str()); |
241 } | 245 } |
242 const char* skpfile = FLAGS_skp[0]; | 246 sk_sp<SkPicture> skp; |
243 std::unique_ptr<SkStream> skpstream(SkStream::MakeFromFile(skpfile)); | 247 SkString skpname; |
244 if (!skpstream) { | 248 if (0 == strcmp(FLAGS_skp[0], "warmup")) { |
245 exitf(ExitErr::kIO, "failed to open skp file %s", skpfile); | 249 skp = create_warmup_skp(); |
246 } | 250 skpname = "warmup"; |
247 sk_sp<SkPicture> skp = SkPicture::MakeFromStream(skpstream.get()); | 251 } else { |
248 if (!skp) { | 252 const char* skpfile = FLAGS_skp[0]; |
249 exitf(ExitErr::kData, "failed to parse skp file %s", skpfile); | 253 std::unique_ptr<SkStream> skpstream(SkStream::MakeFromFile(skpfile)); |
| 254 if (!skpstream) { |
| 255 exitf(ExitErr::kIO, "failed to open skp file %s", skpfile); |
| 256 } |
| 257 skp = SkPicture::MakeFromStream(skpstream.get()); |
| 258 if (!skp) { |
| 259 exitf(ExitErr::kData, "failed to parse skp file %s", skpfile); |
| 260 } |
| 261 skpname = SkOSPath::Basename(skpfile); |
250 } | 262 } |
251 int width = SkTMin(SkScalarCeilToInt(skp->cullRect().width()), 2048), | 263 int width = SkTMin(SkScalarCeilToInt(skp->cullRect().width()), 2048), |
252 height = SkTMin(SkScalarCeilToInt(skp->cullRect().height()), 2048); | 264 height = SkTMin(SkScalarCeilToInt(skp->cullRect().height()), 2048); |
253 if (FLAGS_verbosity >= 3 && | 265 if (FLAGS_verbosity >= 3 && |
254 (width != skp->cullRect().width() || height != skp->cullRect().height())
) { | 266 (width != skp->cullRect().width() || height != skp->cullRect().height())
) { |
255 fprintf(stderr, "%s is too large (%ix%i), cropping to %ix%i.\n", | 267 fprintf(stderr, "%s is too large (%ix%i), cropping to %ix%i.\n", |
256 SkOSPath::Basename(skpfile).c_str(), | 268 skpname.c_str(), SkScalarCeilToInt(skp->cullRect().width
()), |
257 SkScalarCeilToInt(skp->cullRect().width()), | |
258 SkScalarCeilToInt(skp->cullRect().height()), width, heig
ht); | 269 SkScalarCeilToInt(skp->cullRect().height()), width, heig
ht); |
259 } | 270 } |
260 | 271 |
261 // Create a context. | 272 // Create a context. |
262 sk_gpu_test::GrContextFactory factory; | 273 sk_gpu_test::GrContextFactory factory; |
263 sk_gpu_test::ContextInfo ctxInfo = | 274 sk_gpu_test::ContextInfo ctxInfo = |
264 factory.getContextInfo(config->getContextType(), config->getContextOptio
ns()); | 275 factory.getContextInfo(config->getContextType(), config->getContextOptio
ns()); |
265 GrContext* ctx = ctxInfo.grContext(); | 276 GrContext* ctx = ctxInfo.grContext(); |
266 if (!ctx) { | 277 if (!ctx) { |
267 exitf(ExitErr::kUnavailable, "failed to create context for config %s", | 278 exitf(ExitErr::kUnavailable, "failed to create context for config %s", |
(...skipping 20 matching lines...) Expand all Loading... |
288 kPremul_SkAlphaType, sk_ref_sp(config->
getColorSpace())); | 299 kPremul_SkAlphaType, sk_ref_sp(config->
getColorSpace())); |
289 uint32_t flags = config->getUseDIText() ? SkSurfaceProps::kUseDeviceIndepend
entFonts_Flag : 0; | 300 uint32_t flags = config->getUseDIText() ? SkSurfaceProps::kUseDeviceIndepend
entFonts_Flag : 0; |
290 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType); | 301 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType); |
291 sk_sp<SkSurface> surface = | 302 sk_sp<SkSurface> surface = |
292 SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, config->getSampl
es(), &props); | 303 SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, config->getSampl
es(), &props); |
293 if (!surface) { | 304 if (!surface) { |
294 exitf(ExitErr::kUnavailable, "failed to create %ix%i render target for c
onfig %s", | 305 exitf(ExitErr::kUnavailable, "failed to create %ix%i render target for c
onfig %s", |
295 width, height, config->getTag().c_str()); | 306 width, height, config->getTag().c_str()); |
296 } | 307 } |
297 | 308 |
| 309 // Run the benchmark. |
298 std::vector<Sample> samples; | 310 std::vector<Sample> samples; |
299 if (FLAGS_sampleMs > 0) { | 311 if (FLAGS_sampleMs > 0) { |
300 // +1 because we might take one more sample in order to have an odd numb
er. | 312 // +1 because we might take one more sample in order to have an odd numb
er. |
301 samples.reserve(1 + (FLAGS_duration + FLAGS_sampleMs - 1) / FLAGS_sample
Ms); | 313 samples.reserve(1 + (FLAGS_duration + FLAGS_sampleMs - 1) / FLAGS_sample
Ms); |
302 } else { | 314 } else { |
303 samples.reserve(2 * FLAGS_duration); | 315 samples.reserve(2 * FLAGS_duration); |
304 } | 316 } |
305 | |
306 // Run the benchmark. | |
307 SkCanvas* canvas = surface->getCanvas(); | 317 SkCanvas* canvas = surface->getCanvas(); |
308 canvas->translate(-skp->cullRect().x(), -skp->cullRect().y()); | 318 canvas->translate(-skp->cullRect().x(), -skp->cullRect().y()); |
309 if (!FLAGS_gpuClock) { | 319 if (!FLAGS_gpuClock) { |
310 run_benchmark(testCtx->fenceSync(), canvas, skp.get(), &samples); | 320 run_benchmark(testCtx->fenceSync(), canvas, skp.get(), &samples); |
311 } else { | 321 } else { |
312 if (!testCtx->gpuTimingSupport()) { | 322 if (!testCtx->gpuTimingSupport()) { |
313 exitf(ExitErr::kUnavailable, "GPU does not support timing"); | 323 exitf(ExitErr::kUnavailable, "GPU does not support timing"); |
314 } | 324 } |
315 run_gpu_time_benchmark(testCtx->gpuTimer(), testCtx->fenceSync(), canvas
, skp.get(), | 325 run_gpu_time_benchmark(testCtx->gpuTimer(), testCtx->fenceSync(), canvas
, skp.get(), |
316 &samples); | 326 &samples); |
317 } | 327 } |
318 print_result(samples, config->getTag().c_str(), SkOSPath::Basename(skpfile).
c_str()); | 328 print_result(samples, config->getTag().c_str(), skpname.c_str()); |
319 | 329 |
320 // Save a proof (if one was requested). | 330 // Save a proof (if one was requested). |
321 if (!FLAGS_png.isEmpty()) { | 331 if (!FLAGS_png.isEmpty()) { |
322 SkBitmap bmp; | 332 SkBitmap bmp; |
323 bmp.setInfo(info); | 333 bmp.setInfo(info); |
324 if (!surface->getCanvas()->readPixels(&bmp, 0, 0)) { | 334 if (!surface->getCanvas()->readPixels(&bmp, 0, 0)) { |
325 exitf(ExitErr::kUnavailable, "failed to read canvas pixels for png")
; | 335 exitf(ExitErr::kUnavailable, "failed to read canvas pixels for png")
; |
326 } | 336 } |
327 const SkString &dirname = SkOSPath::Dirname(FLAGS_png[0]), | 337 const SkString &dirname = SkOSPath::Dirname(FLAGS_png[0]), |
328 &basename = SkOSPath::Basename(FLAGS_png[0]); | 338 &basename = SkOSPath::Basename(FLAGS_png[0]); |
329 if (!mkdir_p(dirname)) { | 339 if (!mkdir_p(dirname)) { |
330 exitf(ExitErr::kIO, "failed to create directory \"%s\" for png", dir
name.c_str()); | 340 exitf(ExitErr::kIO, "failed to create directory \"%s\" for png", dir
name.c_str()); |
331 } | 341 } |
332 if (!sk_tools::write_bitmap_to_disk(bmp, dirname, nullptr, basename)) { | 342 if (!sk_tools::write_bitmap_to_disk(bmp, dirname, nullptr, basename)) { |
333 exitf(ExitErr::kIO, "failed to save png to \"%s\"", FLAGS_png[0]); | 343 exitf(ExitErr::kIO, "failed to save png to \"%s\"", FLAGS_png[0]); |
334 } | 344 } |
335 } | 345 } |
336 | 346 |
337 exit(0); | 347 exit(0); |
338 } | 348 } |
339 | 349 |
340 static void draw_skp_and_flush(SkCanvas* canvas, const SkPicture* skp) { | 350 static void draw_skp_and_flush(SkCanvas* canvas, const SkPicture* skp) { |
341 canvas->drawPicture(skp); | 351 canvas->drawPicture(skp); |
342 canvas->flush(); | 352 canvas->flush(); |
343 } | 353 } |
344 | 354 |
| 355 static sk_sp<SkPicture> create_warmup_skp() { |
| 356 static constexpr SkRect bounds{0, 0, 500, 500}; |
| 357 SkPictureRecorder recorder; |
| 358 SkCanvas* recording = recorder.beginRecording(bounds); |
| 359 |
| 360 recording->clear(SK_ColorWHITE); |
| 361 |
| 362 SkPaint stroke; |
| 363 stroke.setStyle(SkPaint::kStroke_Style); |
| 364 stroke.setStrokeWidth(2); |
| 365 |
| 366 // Use a big path to (theoretically) warmup the CPU. |
| 367 SkPath bigPath; |
| 368 sk_tool_utils::make_big_path(bigPath); |
| 369 recording->drawPath(bigPath, stroke); |
| 370 |
| 371 // Use a perlin shader to warmup the GPU. |
| 372 SkPaint perlin; |
| 373 perlin.setShader(SkPerlinNoiseShader::MakeTurbulence(0.1f, 0.1f, 1, 0, nullp
tr)); |
| 374 recording->drawRect(bounds, perlin); |
| 375 |
| 376 return recorder.finishRecordingAsPicture(); |
| 377 } |
| 378 |
345 bool mkdir_p(const SkString& dirname) { | 379 bool mkdir_p(const SkString& dirname) { |
346 if (dirname.isEmpty()) { | 380 if (dirname.isEmpty()) { |
347 return true; | 381 return true; |
348 } | 382 } |
349 return mkdir_p(SkOSPath::Dirname(dirname.c_str())) && sk_mkdir(dirname.c_str
()); | 383 return mkdir_p(SkOSPath::Dirname(dirname.c_str())) && sk_mkdir(dirname.c_str
()); |
350 } | 384 } |
351 | 385 |
352 static SkString join(const SkCommandLineFlags::StringArray& stringArray) { | 386 static SkString join(const SkCommandLineFlags::StringArray& stringArray) { |
353 SkString joined; | 387 SkString joined; |
354 for (int i = 0; i < FLAGS_config.count(); ++i) { | 388 for (int i = 0; i < stringArray.count(); ++i) { |
355 joined.appendf(i ? " %s" : "%s", FLAGS_config[i]); | 389 joined.appendf(i ? " %s" : "%s", stringArray[i]); |
356 } | 390 } |
357 return joined; | 391 return joined; |
358 } | 392 } |
359 | 393 |
360 static void exitf(ExitErr err, const char* format, ...) { | 394 static void exitf(ExitErr err, const char* format, ...) { |
361 fprintf(stderr, ExitErr::kSoftware == err ? "INTERNAL ERROR: " : "ERROR: "); | 395 fprintf(stderr, ExitErr::kSoftware == err ? "INTERNAL ERROR: " : "ERROR: "); |
362 va_list args; | 396 va_list args; |
363 va_start(args, format); | 397 va_start(args, format); |
364 vfprintf(stderr, format, args); | 398 vfprintf(stderr, format, args); |
365 va_end(args); | 399 va_end(args); |
(...skipping 20 matching lines...) Expand all Loading... |
386 fFenceSync->deleteFence(fFence); | 420 fFenceSync->deleteFence(fFence); |
387 this->updateFence(); | 421 this->updateFence(); |
388 } | 422 } |
389 | 423 |
390 void GpuSync::updateFence() { | 424 void GpuSync::updateFence() { |
391 fFence = fFenceSync->insertFence(); | 425 fFence = fFenceSync->insertFence(); |
392 if (sk_gpu_test::kInvalidFence == fFence) { | 426 if (sk_gpu_test::kInvalidFence == fFence) { |
393 exitf(ExitErr::kUnavailable, "failed to insert fence"); | 427 exitf(ExitErr::kUnavailable, "failed to insert fence"); |
394 } | 428 } |
395 } | 429 } |
OLD | NEW |