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