xref: /aosp_15_r20/external/skia/tools/testrunners/benchmark/target/GaneshBenchmarkTarget.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google LLC
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 "bench/Benchmark.h"
9 #include "include/core/SkSurface.h"
10 #include "include/gpu/ganesh/GrRecordingContext.h"
11 #include "src/gpu/ganesh/GrDirectContextPriv.h"
12 #include "tools/flags/CommandLineFlags.h"
13 #include "tools/gpu/GrContextFactory.h"
14 #include "tools/gpu/TestContext.h"
15 #include "tools/testrunners/benchmark/target/BenchmarkTarget.h"
16 #include "tools/testrunners/common/TestRunner.h"
17 #include "tools/testrunners/common/surface_manager/SurfaceManager.h"
18 
19 // Based on flags found here:
20 // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/tools/flags/CommonFlagsGpu.cpp
21 //
22 // These are the only flags used in CI tasks at the time of writing (2023-09-29), but we can
23 // always backport more flags from //tools/flags/CommonFlagsGpu.cpp as needed.
24 static DEFINE_double(
25         gpuMs,
26         5,
27         "While auto-tuning the number of benchmark runs per sample, increase the number of runs "
28         "until a single sample takes this many milliseconds. Do this for each benchmark.");
29 
30 static DEFINE_bool(gpuStats, false, "Print GPU stats after each GPU benchmark.");
31 
32 static DEFINE_bool(gpuStatsDump,
33                    false,
34                    "Dump GPU stats after each benchmark into the "
35                    "results.json output file, which can be ingested by Perf.");
36 
37 static DEFINE_bool(dmsaaStatsDump,
38                    false,
39                    "Dump DMSAA stats after each benchmark into the "
40                    "results.json output file, which can be ingested by Perf.");
41 
42 // Estimated maximum number of frames the GPU allows to lag, if unknown.
43 //
44 // Based on:
45 // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#131
46 //
47 // TODO(lovisolo): This value is overridden by //bench/nanobench.cpp based on the --loops flag, see
48 // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#1361.
49 static constexpr int ESTIMATED_GPU_FRAME_LAG = 5;
50 
51 GrRecordingContextPriv::DMSAAStats combinedDMSAAStats;
52 
printGlobalStats()53 void BenchmarkTarget::printGlobalStats() {
54     if (FLAGS_dmsaaStatsDump) {
55         TestRunner::Log("<<Total Combined DMSAA Stats>>");
56         combinedDMSAAStats.dump();
57     }
58 }
59 
60 // Based on
61 // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#240.
62 class GaneshBenchmarkTarget : public BenchmarkTarget {
63 public:
GaneshBenchmarkTarget(std::unique_ptr<SurfaceManager> surfaceManager,Benchmark * benchmark)64     GaneshBenchmarkTarget(std::unique_ptr<SurfaceManager> surfaceManager, Benchmark* benchmark)
65             : BenchmarkTarget(std::move(surfaceManager), benchmark) {}
66 
~GaneshBenchmarkTarget()67     ~GaneshBenchmarkTarget() override {
68         // For Vulkan we need to release all our refs to the GrContext before destroy the vulkan
69         // context which happens at the end of this destructor. Thus we need to release the surface
70         // here which holds a ref to the GrContext.
71         fSurfaceManager->getSurface().reset();
72     }
73 
getBackend() const74     Benchmark::Backend getBackend() const override { return Benchmark::Backend::kGanesh; }
75 
76     // TODO(lovisolo): Do we still need this?
setup() const77     void setup() const override {
78         fSurfaceManager->getGaneshContextInfo()->testContext()->makeCurrent();
79         // Make sure we're done with whatever came before.
80         fSurfaceManager->getGaneshContextInfo()->testContext()->finish();
81 
82         BenchmarkTarget::setup();
83     }
84 
85     // TODO(lovisolo): Do we still need this?
onAfterDraw() const86     void onAfterDraw() const override {
87         if (fSurfaceManager->getGaneshContextInfo()->testContext()) {
88             fSurfaceManager->getGaneshContextInfo()->testContext()->flushAndWaitOnSync(
89                     fSurfaceManager->getGaneshContextInfo()->directContext());
90         }
91     }
92 
93     // Based on nanobench's setup_gpu_bench():
94     // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#510.
autoTuneLoops() const95     std::tuple<int, bool> autoTuneLoops() const override {
96         int maxFrameLag = computeMaxFrameLag();
97 
98         // First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs.
99         int loops = 1;
100         double elapsed = 0;
101         do {
102             if (1 << 30 == loops) {
103                 // We're about to wrap. Something's wrong with the bench.
104                 loops = 0;
105                 break;
106             }
107             loops *= 2;
108             // If the GPU lets frames lag at all, we need to make sure we're timing
109             // _this_ round, not still timing last round.
110             for (int i = 0; i < maxFrameLag; i++) {
111                 elapsed = time(loops);
112             }
113         } while (elapsed < FLAGS_gpuMs);
114 
115         // We've overshot at least a little. Scale back linearly.
116         loops = (int)ceil(loops * FLAGS_gpuMs / elapsed);
117 
118         // Make sure we're not still timing our calibration.
119         fSurfaceManager->getGaneshContextInfo()->testContext()->finish();
120 
121         return std::make_tuple(loops, true);
122     }
123 
124     // Based on
125     // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#539.
warmUp(int loops) const126     void warmUp(int loops) const override {
127         // Pretty much the same deal as the calibration: do some warmup to make
128         // sure we're timing steady-state pipelined frames.
129         int maxFrameLag = computeMaxFrameLag();
130         for (int i = 0; i < maxFrameLag; i++) {
131             time(loops);
132         }
133     }
134 
dumpStats(skia_private::TArray<SkString> * keys,skia_private::TArray<double> * values) const135     void dumpStats(skia_private::TArray<SkString>* keys,
136                    skia_private::TArray<double>* values) const override {
137         if (FLAGS_gpuStatsDump) {
138             // TODO cache stats
139             fBenchmark->getGpuStats(getCanvas(), keys, values);
140         }
141         if (FLAGS_dmsaaStatsDump && fBenchmark->getDMSAAStats(getCanvas()->recordingContext())) {
142             const auto& dmsaaStats = getCanvas()->recordingContext()->priv().dmsaaStats();
143             dmsaaStats.dumpKeyValuePairs(keys, values);
144             dmsaaStats.dump();
145             combinedDMSAAStats.merge(dmsaaStats);
146         }
147     }
148 
printStats() const149     void printStats() const override {
150         if (FLAGS_gpuStats) {
151             auto context = fSurfaceManager->getGaneshContextInfo()->directContext();
152 
153             context->priv().printCacheStats();
154             context->priv().printGpuStats();
155             context->priv().printContextStats();
156         }
157     }
158 
159 private:
160     // Based on nanobench's GPUTarget::needsFrameTiming():
161     // https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#264.
computeMaxFrameLag() const162     int computeMaxFrameLag() const {
163         int maxFrameLag;
164         if (!fSurfaceManager->getGaneshContextInfo()->testContext()->getMaxGpuFrameLag(
165                     &maxFrameLag)) {
166             // Frame lag is unknown.
167             maxFrameLag = ESTIMATED_GPU_FRAME_LAG;
168         }
169         return maxFrameLag;
170     }
171 };
172 
FromConfig(std::string surfaceConfig,Benchmark * benchmark)173 std::unique_ptr<BenchmarkTarget> BenchmarkTarget::FromConfig(std::string surfaceConfig,
174                                                              Benchmark* benchmark) {
175     SurfaceOptions surfaceOptions = {
176             .width = benchmark->getSize().width(),
177             .height = benchmark->getSize().height(),
178             .modifyGrContextOptions = [&](GrContextOptions* grContextOptions) {
179                 benchmark->modifyGrContextOptions(grContextOptions);
180             }};
181     std::unique_ptr<SurfaceManager> surfaceManager =
182             SurfaceManager::FromConfig(surfaceConfig, surfaceOptions);
183     if (surfaceManager == nullptr) {
184         SK_ABORT("Unknown --surfaceConfig flag value: %s.", surfaceConfig.c_str());
185     }
186 
187     if (surfaceManager->getGaneshContextInfo()->testContext()->fenceSyncSupport()) {
188         TestRunner::Log(
189                 "WARNING: GL context for config \"%s\" does not support fence sync. "
190                 "Timings might not be accurate.",
191                 surfaceConfig.c_str());
192     }
193 
194     return std::make_unique<GaneshBenchmarkTarget>(std::move(surfaceManager), benchmark);
195 }
196