src/platform/test/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 <mgba/core/blip_buf.h>
7#include <mgba/core/cheats.h>
8#include <mgba/core/config.h>
9#include <mgba/core/core.h>
10#include <mgba/core/serialize.h>
11#include <mgba/gb/core.h>
12#include <mgba/gba/core.h>
13
14#include <mgba/feature/commandline.h>
15#include <mgba-util/socket.h>
16#include <mgba-util/string.h>
17#include <mgba-util/vfs.h>
18
19#ifdef _3DS
20#include <3ds.h>
21#endif
22#ifdef __SWITCH__
23#include <switch.h>
24#endif
25
26#include <errno.h>
27#include <fcntl.h>
28#include <signal.h>
29#include <inttypes.h>
30#include <sys/time.h>
31
32#define PERF_OPTIONS "DF:L:NPS:T"
33#define PERF_USAGE \
34 "\nBenchmark options:\n" \
35 " -F FRAMES Run for the specified number of FRAMES before exiting\n" \
36 " -N Disable video rendering entirely\n" \
37 " -T Use threaded video rendering\n" \
38 " -P CSV output, useful for parsing\n" \
39 " -S SEC Run for SEC in-game seconds before exiting\n" \
40 " -L FILE Load a savestate when starting the test\n" \
41 " -D Act as a server"
42
43struct PerfOpts {
44 bool noVideo;
45 bool threadedVideo;
46 bool csv;
47 unsigned duration;
48 unsigned frames;
49 char* savestate;
50 bool server;
51};
52
53#ifdef _3DS
54extern bool allocateRomBuffer(void);
55FS_Archive sdmcArchive;
56#endif
57#ifdef __SWITCH__
58TimeType __nx_time_type = TimeType_LocalSystemClock;
59#endif
60
61static void _mPerfRunloop(struct mCore* context, int* frames, bool quiet);
62static void _mPerfShutdown(int signal);
63static bool _parsePerfOpts(struct mSubParser* parser, int option, const char* arg);
64static void _log(struct mLogger*, int, enum mLogLevel, const char*, va_list);
65static bool _mPerfRunCore(const char* fname, const struct mArguments*, const struct PerfOpts*);
66static bool _mPerfRunServer(const struct mArguments*, const struct PerfOpts*);
67
68static bool _dispatchExiting = false;
69static struct VFile* _savestate = 0;
70static void* _outputBuffer = NULL;
71static Socket _socket = INVALID_SOCKET;
72static Socket _server = INVALID_SOCKET;
73
74int main(int argc, char** argv) {
75#ifdef _3DS
76 UNUSED(_mPerfShutdown);
77 gfxInitDefault();
78 osSetSpeedupEnable(true);
79 consoleInit(GFX_BOTTOM, NULL);
80 if (!allocateRomBuffer()) {
81 return 1;
82 }
83#elif defined(__SWITCH__)
84 UNUSED(_mPerfShutdown);
85 consoleInit(NULL);
86#else
87 signal(SIGINT, _mPerfShutdown);
88#endif
89 int didFail = 0;
90
91 struct mLogger logger = { .log = _log };
92 mLogSetDefaultLogger(&logger);
93
94 struct PerfOpts perfOpts = { false, false, false, 0, 0, 0, false };
95 struct mSubParser subparser = {
96 .usage = PERF_USAGE,
97 .parse = _parsePerfOpts,
98 .extraOptions = PERF_OPTIONS,
99 .opts = &perfOpts
100 };
101
102 struct mArguments args = {};
103 bool parsed = parseArguments(&args, argc, argv, &subparser);
104 if (!args.fname && !perfOpts.server) {
105 parsed = false;
106 }
107 if (!parsed || args.showHelp) {
108 usage(argv[0], PERF_USAGE);
109 didFail = !parsed;
110 goto cleanup;
111 }
112
113 if (args.showVersion) {
114 version(argv[0]);
115 goto cleanup;
116 }
117
118 if (perfOpts.savestate) {
119 _savestate = VFileOpen(perfOpts.savestate, O_RDONLY);
120 free(perfOpts.savestate);
121 }
122
123 _outputBuffer = malloc(256 * 256 * 4);
124 if (perfOpts.csv) {
125 puts("game_code,frames,duration,renderer");
126#ifdef __SWITCH__
127 consoleUpdate(NULL);
128#endif
129 }
130 if (perfOpts.server) {
131 didFail = !_mPerfRunServer(&args, &perfOpts);
132 } else {
133 didFail = !_mPerfRunCore(args.fname, &args, &perfOpts);
134 }
135 free(_outputBuffer);
136
137 if (_savestate) {
138 _savestate->close(_savestate);
139 }
140 cleanup:
141 freeArguments(&args);
142
143#ifdef _3DS
144 gfxExit();
145 acExit();
146#elif defined(__SWITCH__)
147 consoleExit(NULL);
148#endif
149
150 return didFail;
151}
152
153bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struct PerfOpts* perfOpts) {
154 struct mCore* core = mCoreFind(fname);
155 if (!core) {
156 return false;
157 }
158
159 // TODO: Put back debugger
160 char gameCode[9] = { 0 };
161
162 core->init(core);
163 if (!perfOpts->noVideo) {
164 core->setVideoBuffer(core, _outputBuffer, 256);
165 }
166 mCoreLoadFile(core, fname);
167 mCoreConfigInit(&core->config, "perf");
168 mCoreConfigLoad(&core->config);
169
170 if (perfOpts->threadedVideo) {
171 mCoreConfigSetOverrideIntValue(&core->config, "threadedVideo", 1);
172 } else {
173 mCoreConfigSetOverrideIntValue(&core->config, "threadedVideo", 0);
174 }
175
176 struct mCoreOptions opts = {};
177 mCoreConfigMap(&core->config, &opts);
178 opts.audioSync = false;
179 opts.videoSync = false;
180 applyArguments(args, NULL, &core->config);
181 mCoreConfigLoadDefaults(&core->config, &opts);
182 mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
183 mCoreLoadConfig(core);
184
185 core->reset(core);
186 if (_savestate) {
187 mCoreLoadStateNamed(core, _savestate, 0);
188 }
189
190 core->getGameCode(core, gameCode);
191
192 int frames = perfOpts->frames;
193 if (!frames) {
194 frames = perfOpts->duration * 60;
195 }
196 struct timeval tv;
197 gettimeofday(&tv, 0);
198 uint64_t start = 1000000LL * tv.tv_sec + tv.tv_usec;
199 _mPerfRunloop(core, &frames, perfOpts->csv);
200 gettimeofday(&tv, 0);
201 uint64_t end = 1000000LL * tv.tv_sec + tv.tv_usec;
202 uint64_t duration = end - start;
203
204 mCoreConfigFreeOpts(&opts);
205 mCoreConfigDeinit(&core->config);
206 core->deinit(core);
207
208 float scaledFrames = frames * 1000000.f;
209 if (perfOpts->csv) {
210 char buffer[256];
211 const char* rendererName;
212 if (perfOpts->noVideo) {
213 rendererName = "none";
214 } else if (perfOpts->threadedVideo) {
215 rendererName = "threaded-software";
216 } else {
217 rendererName = "software";
218 }
219 snprintf(buffer, sizeof(buffer), "%s,%i,%" PRIu64 ",%s\n", gameCode, frames, duration, rendererName);
220 printf("%s", buffer);
221 if (_socket != INVALID_SOCKET) {
222 SocketSend(_socket, buffer, strlen(buffer));
223 }
224 } else {
225 printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
226 }
227#ifdef __SWITCH__
228 consoleUpdate(NULL);
229#endif
230
231 return true;
232}
233
234static void _mPerfRunloop(struct mCore* core, int* frames, bool quiet) {
235 struct timeval lastEcho;
236 gettimeofday(&lastEcho, 0);
237 int duration = *frames;
238 *frames = 0;
239 int lastFrames = 0;
240 while (!_dispatchExiting) {
241 core->runFrame(core);
242 ++*frames;
243 ++lastFrames;
244 if (!quiet) {
245 struct timeval currentTime;
246 long timeDiff;
247 gettimeofday(¤tTime, 0);
248 timeDiff = currentTime.tv_sec - lastEcho.tv_sec;
249 timeDiff *= 1000;
250 timeDiff += (currentTime.tv_usec - lastEcho.tv_usec) / 1000;
251 if (timeDiff >= 1000) {
252 printf("\033[2K\rCurrent FPS: %g (%gx)", lastFrames / (timeDiff / 1000.0f), lastFrames / (float) (60 * (timeDiff / 1000.0f)));
253 fflush(stdout);
254#ifdef __SWITCH__
255 consoleUpdate(NULL);
256#endif
257 lastEcho = currentTime;
258 lastFrames = 0;
259 }
260 }
261 if (duration > 0 && *frames == duration) {
262 break;
263 }
264 }
265 if (!quiet) {
266 printf("\033[2K\r");
267 }
268}
269
270static bool _mPerfRunServer(const struct mArguments* args, const struct PerfOpts* perfOpts) {
271 SocketSubsystemInit();
272 _server = SocketOpenTCP(7216, NULL);
273 if (SOCKET_FAILED(_server)) {
274 SocketSubsystemDeinit();
275 return false;
276 }
277 if (SOCKET_FAILED(SocketListen(_server, 0))) {
278 SocketClose(_server);
279 SocketSubsystemDeinit();
280 return false;
281 }
282 _socket = SocketAccept(_server, NULL);
283 if (SOCKET_FAILED(_socket)) {
284 SocketClose(_server);
285 SocketSubsystemDeinit();
286 return false;
287 }
288 if (perfOpts->csv) {
289 const char* header = "game_code,frames,duration,renderer\n";
290 SocketSend(_socket, header, strlen(header));
291 }
292 char path[PATH_MAX];
293 memset(path, 0, sizeof(path));
294 ssize_t i;
295 while ((i = SocketRecv(_socket, path, sizeof(path) - 1)) > 0) {
296 path[i] = '\0';
297 char* nl = strchr(path, '\n');
298 if (nl == path) {
299 break;
300 }
301 if (nl) {
302 nl[0] = '\0';
303 }
304 if (!_mPerfRunCore(path, args, perfOpts)) {
305 break;
306 }
307 memset(path, 0, sizeof(path));
308 }
309 SocketClose(_socket);
310 SocketClose(_server);
311 SocketSubsystemDeinit();
312 return true;
313}
314
315static void _mPerfShutdown(int signal) {
316 UNUSED(signal);
317 _dispatchExiting = true;
318 SocketClose(_socket);
319 SocketClose(_server);
320}
321
322static bool _parsePerfOpts(struct mSubParser* parser, int option, const char* arg) {
323 struct PerfOpts* opts = parser->opts;
324 errno = 0;
325 switch (option) {
326 case 'D':
327 opts->server = true;
328 return true;
329 case 'F':
330 opts->frames = strtoul(arg, 0, 10);
331 return !errno;
332 case 'N':
333 opts->noVideo = true;
334 return true;
335 case 'P':
336 opts->csv = true;
337 return true;
338 case 'S':
339 opts->duration = strtoul(arg, 0, 10);
340 return !errno;
341 case 'T':
342 opts->threadedVideo = true;
343 return true;
344 case 'L':
345 opts->savestate = strdup(arg);
346 return true;
347 default:
348 return false;
349 }
350}
351
352static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
353 UNUSED(log);
354 UNUSED(category);
355 UNUSED(level);
356 UNUSED(format);
357 UNUSED(args);
358}