src/platform/perf-main.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "gba/supervisor/thread.h"
7#include "gba/supervisor/config.h"
8#include "gba/gba.h"
9#include "gba/renderers/video-software.h"
10#include "gba/serialize.h"
11
12#include "platform/commandline.h"
13#include "util/vfs.h"
14
15#include <errno.h>
16#include <fcntl.h>
17#include <signal.h>
18#include <inttypes.h>
19#include <sys/time.h>
20
21#define PERF_OPTIONS "F:L:NPS:"
22#define PERF_USAGE \
23 "\nBenchmark options:\n" \
24 " -F FRAMES Run for the specified number of FRAMES before exiting\n" \
25 " -N Disable video rendering entirely\n" \
26 " -P CSV output, useful for parsing\n" \
27 " -S SEC Run for SEC in-game seconds before exiting\n" \
28 " -L FILE Load a savestate when starting the test"
29
30struct PerfOpts {
31 bool noVideo;
32 bool csv;
33 unsigned duration;
34 unsigned frames;
35 char* savestate;
36};
37
38static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet);
39static void _GBAPerfShutdown(int signal);
40static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg);
41static void _loadSavestate(struct GBAThread* context);
42
43static struct GBAThread* _thread;
44static bool _dispatchExiting = false;
45static struct VFile* _savestate = 0;
46
47int main(int argc, char** argv) {
48 signal(SIGINT, _GBAPerfShutdown);
49
50 struct GBAVideoSoftwareRenderer renderer;
51 GBAVideoSoftwareRendererCreate(&renderer);
52
53 struct PerfOpts perfOpts = { false, false, 0, 0, 0 };
54 struct SubParser subparser = {
55 .usage = PERF_USAGE,
56 .parse = _parsePerfOpts,
57 .extraOptions = PERF_OPTIONS,
58 .opts = &perfOpts
59 };
60
61 struct GBAConfig config;
62 GBAConfigInit(&config, "perf");
63 GBAConfigLoad(&config);
64
65 struct GBAOptions opts = {
66 .idleOptimization = IDLE_LOOP_DETECT
67 };
68 GBAConfigLoadDefaults(&config, &opts);
69
70 struct GBAArguments args;
71 bool parsed = parseArguments(&args, &config, argc, argv, &subparser);
72 if (!parsed || args.showHelp) {
73 usage(argv[0], PERF_USAGE);
74 freeArguments(&args);
75 GBAConfigFreeOpts(&opts);
76 GBAConfigDeinit(&config);
77 return !parsed;
78 }
79
80 renderer.outputBuffer = malloc(256 * 256 * 4);
81 renderer.outputBufferStride = 256;
82
83 struct GBAThread context = {};
84 _thread = &context;
85
86 if (!perfOpts.noVideo) {
87 context.renderer = &renderer.d;
88 }
89 if (perfOpts.savestate) {
90 _savestate = VFileOpen(perfOpts.savestate, O_RDONLY);
91 free(perfOpts.savestate);
92 }
93 if (_savestate) {
94 context.startCallback = _loadSavestate;
95 }
96
97 context.debugger = createDebugger(&args, &context);
98 context.overrides = GBAConfigGetOverrides(&config);
99 char gameCode[5] = { 0 };
100
101 GBAConfigMap(&config, &opts);
102 opts.audioSync = false;
103 opts.videoSync = false;
104 GBAMapArgumentsToContext(&args, &context);
105 GBAMapOptionsToContext(&opts, &context);
106
107 int didStart = GBAThreadStart(&context);
108
109 if (!didStart) {
110 goto cleanup;
111 }
112 GBAThreadInterrupt(&context);
113 if (GBAThreadHasCrashed(&context)) {
114 GBAThreadJoin(&context);
115 goto cleanup;
116 }
117
118 GBAGetGameCode(context.gba, gameCode);
119 GBAThreadContinue(&context);
120
121 int frames = perfOpts.frames;
122 if (!frames) {
123 frames = perfOpts.duration * 60;
124 }
125 struct timeval tv;
126 gettimeofday(&tv, 0);
127 uint64_t start = 1000000LL * tv.tv_sec + tv.tv_usec;
128 _GBAPerfRunloop(&context, &frames, perfOpts.csv);
129 gettimeofday(&tv, 0);
130 uint64_t end = 1000000LL * tv.tv_sec + tv.tv_usec;
131 uint64_t duration = end - start;
132
133 GBAThreadJoin(&context);
134
135 float scaledFrames = frames * 1000000.f;
136 if (perfOpts.csv) {
137 puts("game_code,frames,duration,renderer");
138 const char* rendererName;
139 if (perfOpts.noVideo) {
140 rendererName = "none";
141 } else {
142 rendererName = "software";
143 }
144 printf("%s,%i,%" PRIu64 ",%s\n", gameCode, frames, duration, rendererName);
145 } else {
146 printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
147 }
148
149cleanup:
150 if (_savestate) {
151 _savestate->close(_savestate);
152 }
153 GBAConfigFreeOpts(&opts);
154 freeArguments(&args);
155 GBAConfigDeinit(&config);
156 free(context.debugger);
157 free(renderer.outputBuffer);
158
159 return !didStart || GBAThreadHasCrashed(&context);
160}
161
162static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) {
163 struct timeval lastEcho;
164 gettimeofday(&lastEcho, 0);
165 int duration = *frames;
166 *frames = 0;
167 int lastFrames = 0;
168 while (context->state < THREAD_EXITING) {
169 if (GBASyncWaitFrameStart(&context->sync, 0)) {
170 ++*frames;
171 ++lastFrames;
172 if (!quiet) {
173 struct timeval currentTime;
174 long timeDiff;
175 gettimeofday(¤tTime, 0);
176 timeDiff = currentTime.tv_sec - lastEcho.tv_sec;
177 timeDiff *= 1000;
178 timeDiff += (currentTime.tv_usec - lastEcho.tv_usec) / 1000;
179 if (timeDiff >= 1000) {
180 printf("\033[2K\rCurrent FPS: %g (%gx)", lastFrames / (timeDiff / 1000.0f), lastFrames / (float) (60 * (timeDiff / 1000.0f)));
181 fflush(stdout);
182 lastEcho = currentTime;
183 lastFrames = 0;
184 }
185 }
186 }
187 GBASyncWaitFrameEnd(&context->sync);
188 if (*frames == duration) {
189 _GBAPerfShutdown(0);
190 }
191 if (_dispatchExiting) {
192 GBAThreadEnd(context);
193 }
194 }
195 if (!quiet) {
196 printf("\033[2K\r");
197 }
198}
199
200static void _GBAPerfShutdown(int signal) {
201 UNUSED(signal);
202 // This will come in ON the GBA thread, so we have to handle it carefully
203 _dispatchExiting = true;
204 ConditionWake(&_thread->sync.videoFrameAvailableCond);
205}
206
207static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
208 UNUSED(config);
209 struct PerfOpts* opts = parser->opts;
210 errno = 0;
211 switch (option) {
212 case 'F':
213 opts->frames = strtoul(arg, 0, 10);
214 return !errno;
215 case 'N':
216 opts->noVideo = true;
217 return true;
218 case 'P':
219 opts->csv = true;
220 return true;
221 case 'S':
222 opts->duration = strtoul(arg, 0, 10);
223 return !errno;
224 case 'L':
225 opts->savestate = strdup(arg);
226 return true;
227 default:
228 return false;
229 }
230}
231
232static void _loadSavestate(struct GBAThread* context) {
233 GBALoadStateNamed(context->gba, _savestate);
234 _savestate->close(_savestate);
235 _savestate = 0;
236}