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 char* listen, 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;
72
73int main(int argc, char** argv) {
74#ifdef _3DS
75 UNUSED(_mPerfShutdown);
76 gfxInitDefault();
77 osSetSpeedupEnable(true);
78 consoleInit(GFX_BOTTOM, NULL);
79 if (!allocateRomBuffer()) {
80 return 1;
81 }
82#elif defined(__SWITCH__)
83 UNUSED(_mPerfShutdown);
84 consoleInit(NULL);
85#else
86 signal(SIGINT, _mPerfShutdown);
87#endif
88 int didFail = 0;
89
90 struct mLogger logger = { .log = _log };
91 mLogSetDefaultLogger(&logger);
92
93 struct PerfOpts perfOpts = { false, false, false, 0, 0, 0, false };
94 struct mSubParser subparser = {
95 .usage = PERF_USAGE,
96 .parse = _parsePerfOpts,
97 .extraOptions = PERF_OPTIONS,
98 .opts = &perfOpts
99 };
100
101 struct mArguments args = {};
102 bool parsed = parseArguments(&args, argc, argv, &subparser);
103 if (!args.fname) {
104 parsed = false;
105 }
106 if (!parsed || args.showHelp) {
107 usage(argv[0], PERF_USAGE);
108 didFail = !parsed;
109 goto cleanup;
110 }
111
112 if (args.showVersion) {
113 version(argv[0]);
114 goto cleanup;
115 }
116
117 if (perfOpts.savestate) {
118 _savestate = VFileOpen(perfOpts.savestate, O_RDONLY);
119 free(perfOpts.savestate);
120 }
121
122 _outputBuffer = malloc(256 * 256 * 4);
123 if (perfOpts.csv) {
124 puts("game_code,frames,duration,renderer");
125#ifdef __SWITCH__
126 consoleUpdate(NULL);
127#endif
128 }
129 if (perfOpts.server) {
130 didFail = !_mPerfRunServer(args.fname, &args, &perfOpts);
131 } else {
132 didFail = !_mPerfRunCore(args.fname, &args, &perfOpts);
133 }
134 free(_outputBuffer);
135
136 if (_savestate) {
137 _savestate->close(_savestate);
138 }
139 cleanup:
140 freeArguments(&args);
141
142#ifdef _3DS
143 gfxExit();
144 acExit();
145#elif defined(__SWITCH__)
146 consoleExit(NULL);
147#endif
148
149 return didFail;
150}
151
152bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struct PerfOpts* perfOpts) {
153 struct mCore* core = mCoreFind(fname);
154 if (!core) {
155 return false;
156 }
157
158 // TODO: Put back debugger
159 char gameCode[9] = { 0 };
160
161 core->init(core);
162 if (!perfOpts->noVideo) {
163 core->setVideoBuffer(core, _outputBuffer, 256);
164 }
165 mCoreLoadFile(core, fname);
166 mCoreConfigInit(&core->config, "perf");
167 mCoreConfigLoad(&core->config);
168
169 if (perfOpts->threadedVideo) {
170 mCoreConfigSetOverrideIntValue(&core->config, "threadedVideo", 1);
171 } else {
172 mCoreConfigSetOverrideIntValue(&core->config, "threadedVideo", 0);
173 }
174
175 struct mCoreOptions opts = {};
176 mCoreConfigMap(&core->config, &opts);
177 opts.audioSync = false;
178 opts.videoSync = false;
179 applyArguments(args, NULL, &core->config);
180 mCoreConfigLoadDefaults(&core->config, &opts);
181 mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
182 mCoreLoadConfig(core);
183
184 core->reset(core);
185 if (_savestate) {
186 mCoreLoadStateNamed(core, _savestate, 0);
187 }
188
189 core->getGameCode(core, gameCode);
190
191 int frames = perfOpts->frames;
192 if (!frames) {
193 frames = perfOpts->duration * 60;
194 }
195 struct timeval tv;
196 gettimeofday(&tv, 0);
197 uint64_t start = 1000000LL * tv.tv_sec + tv.tv_usec;
198 _mPerfRunloop(core, &frames, perfOpts->csv);
199 gettimeofday(&tv, 0);
200 uint64_t end = 1000000LL * tv.tv_sec + tv.tv_usec;
201 uint64_t duration = end - start;
202
203 mCoreConfigFreeOpts(&opts);
204 mCoreConfigDeinit(&core->config);
205 core->deinit(core);
206
207 float scaledFrames = frames * 1000000.f;
208 if (perfOpts->csv) {
209 char buffer[256];
210 const char* rendererName;
211 if (perfOpts->noVideo) {
212 rendererName = "none";
213 } else if (perfOpts->threadedVideo) {
214 rendererName = "threaded-software";
215 } else {
216 rendererName = "software";
217 }
218 snprintf(buffer, sizeof(buffer), "%s,%i,%" PRIu64 ",%s\n", gameCode, frames, duration, rendererName);
219 printf("%s", buffer);
220 if (_socket != INVALID_SOCKET) {
221 SocketSend(_socket, buffer, strlen(buffer));
222 }
223 } else {
224 printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
225 }
226#ifdef __SWITCH__
227 consoleUpdate(NULL);
228#endif
229
230 return true;
231}
232
233static void _mPerfRunloop(struct mCore* core, int* frames, bool quiet) {
234 struct timeval lastEcho;
235 gettimeofday(&lastEcho, 0);
236 int duration = *frames;
237 *frames = 0;
238 int lastFrames = 0;
239 while (!_dispatchExiting) {
240 core->runFrame(core);
241 ++*frames;
242 ++lastFrames;
243 if (!quiet) {
244 struct timeval currentTime;
245 long timeDiff;
246 gettimeofday(¤tTime, 0);
247 timeDiff = currentTime.tv_sec - lastEcho.tv_sec;
248 timeDiff *= 1000;
249 timeDiff += (currentTime.tv_usec - lastEcho.tv_usec) / 1000;
250 if (timeDiff >= 1000) {
251 printf("\033[2K\rCurrent FPS: %g (%gx)", lastFrames / (timeDiff / 1000.0f), lastFrames / (float) (60 * (timeDiff / 1000.0f)));
252 fflush(stdout);
253#ifdef __SWITCH__
254 consoleUpdate(NULL);
255#endif
256 lastEcho = currentTime;
257 lastFrames = 0;
258 }
259 }
260 if (duration > 0 && *frames == duration) {
261 break;
262 }
263 }
264 if (!quiet) {
265 printf("\033[2K\r");
266 }
267}
268
269static bool _mPerfRunServer(const char* listen, const struct mArguments* args, const struct PerfOpts* perfOpts) {
270 SocketSubsystemInit();
271 Socket server = SocketOpenTCP(7216, NULL);
272 if (SOCKET_FAILED(server)) {
273 SocketSubsystemDeinit();
274 return false;
275 }
276 if (SOCKET_FAILED(SocketListen(server, 0))) {
277 SocketClose(server);
278 SocketSubsystemDeinit();
279 return false;
280 }
281 _socket = SocketAccept(server, NULL);
282 if (perfOpts->csv) {
283 const char* header = "game_code,frames,duration,renderer\n";
284 SocketSend(_socket, header, strlen(header));
285 }
286 char path[PATH_MAX];
287 memset(path, 0, sizeof(path));
288 ssize_t i;
289 while ((i = SocketRecv(_socket, path, sizeof(path) - 1)) > 0) {
290 path[i] = '\0';
291 char* nl = strchr(path, '\n');
292 if (nl == path) {
293 break;
294 }
295 if (nl) {
296 nl[0] = '\0';
297 }
298 if (!_mPerfRunCore(path, args, perfOpts)) {
299 break;
300 }
301 memset(path, 0, sizeof(path));
302 }
303 SocketClose(_socket);
304 SocketClose(server);
305 SocketSubsystemDeinit();
306 return true;
307}
308
309static void _mPerfShutdown(int signal) {
310 UNUSED(signal);
311 _dispatchExiting = true;
312 SocketClose(_socket);
313}
314
315static bool _parsePerfOpts(struct mSubParser* parser, int option, const char* arg) {
316 struct PerfOpts* opts = parser->opts;
317 errno = 0;
318 switch (option) {
319 case 'D':
320 opts->server = true;
321 return true;
322 case 'F':
323 opts->frames = strtoul(arg, 0, 10);
324 return !errno;
325 case 'N':
326 opts->noVideo = true;
327 return true;
328 case 'P':
329 opts->csv = true;
330 return true;
331 case 'S':
332 opts->duration = strtoul(arg, 0, 10);
333 return !errno;
334 case 'T':
335 opts->threadedVideo = true;
336 return true;
337 case 'L':
338 opts->savestate = strdup(arg);
339 return true;
340 default:
341 return false;
342 }
343}
344
345static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
346 UNUSED(log);
347 UNUSED(category);
348 UNUSED(level);
349 UNUSED(format);
350 UNUSED(args);
351}