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