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 "core/config.h"
7#include "core/thread.h"
8#include "gba/core.h"
9#include "gba/gba.h"
10#include "gba/renderers/video-software.h"
11#include "gba/serialize.h"
12
13#include "platform/commandline.h"
14#include "util/string.h"
15#include "util/vfs.h"
16
17#include <errno.h>
18#include <fcntl.h>
19#include <signal.h>
20#include <inttypes.h>
21#include <sys/time.h>
22
23#define PERF_OPTIONS "F:L:NPS:"
24#define PERF_USAGE \
25 "\nBenchmark options:\n" \
26 " -F FRAMES Run for the specified number of FRAMES before exiting\n" \
27 " -N Disable video rendering entirely\n" \
28 " -P CSV output, useful for parsing\n" \
29 " -S SEC Run for SEC in-game seconds before exiting\n" \
30 " -L FILE Load a savestate when starting the test"
31
32struct PerfOpts {
33 bool noVideo;
34 bool csv;
35 unsigned duration;
36 unsigned frames;
37 char* savestate;
38};
39
40static void _GBAPerfRunloop(struct mCoreThread* context, int* frames, bool quiet);
41static void _GBAPerfShutdown(int signal);
42static bool _parsePerfOpts(struct mSubParser* parser, int option, const char* arg);
43static void _loadSavestate(struct mCoreThread* context);
44
45static struct mCoreThread* _thread;
46static bool _dispatchExiting = false;
47static struct VFile* _savestate = 0;
48
49int main(int argc, char** argv) {
50 signal(SIGINT, _GBAPerfShutdown);
51
52 struct PerfOpts perfOpts = { false, false, 0, 0, 0 };
53 struct mSubParser subparser = {
54 .usage = PERF_USAGE,
55 .parse = _parsePerfOpts,
56 .extraOptions = PERF_OPTIONS,
57 .opts = &perfOpts
58 };
59
60 struct mArguments args;
61 bool parsed = parseArguments(&args, argc, argv, &subparser);
62 if (!parsed || args.showHelp) {
63 usage(argv[0], PERF_USAGE);
64 freeArguments(&args);
65 return !parsed;
66 }
67 if (args.showVersion) {
68 version(argv[0]);
69 freeArguments(&args);
70 return 0;
71 }
72
73 void* outputBuffer = malloc(256 * 256 * 4);
74
75 struct mCore* core = mCoreFind(args.fname);
76 if (!core) {
77 freeArguments(&args);
78 return 1;
79 }
80 struct mCoreThread context = {
81 .core = core
82 };
83 _thread = &context;
84
85 if (!perfOpts.noVideo) {
86 core->setVideoBuffer(core, outputBuffer, 256);
87 }
88 if (perfOpts.savestate) {
89 _savestate = VFileOpen(perfOpts.savestate, O_RDONLY);
90 free(perfOpts.savestate);
91 }
92 if (_savestate) {
93 context.startCallback = _loadSavestate;
94 }
95
96 // TODO: Put back debugger
97 char gameCode[5] = { 0 };
98
99 core->init(core);
100 mCoreLoadFile(core, args.fname);
101 mCoreConfigInit(&core->config, "perf");
102 mCoreConfigLoad(&core->config);
103
104 mCoreConfigSetDefaultIntValue(&core->config, "idleOptimization", IDLE_LOOP_REMOVE);
105 struct mCoreOptions opts = {};
106 mCoreConfigMap(&core->config, &opts);
107 opts.audioSync = false;
108 opts.videoSync = false;
109 applyArguments(&args, NULL, &core->config);
110 mCoreConfigLoadDefaults(&core->config, &opts);
111 mCoreLoadConfig(core);
112
113 int didStart = mCoreThreadStart(&context);
114
115 if (!didStart) {
116 goto cleanup;
117 }
118 mCoreThreadInterrupt(&context);
119 if (mCoreThreadHasCrashed(&context)) {
120 mCoreThreadJoin(&context);
121 goto cleanup;
122 }
123
124 GBAGetGameCode(core->board, gameCode);
125 mCoreThreadContinue(&context);
126
127 int frames = perfOpts.frames;
128 if (!frames) {
129 frames = perfOpts.duration * 60;
130 }
131 struct timeval tv;
132 gettimeofday(&tv, 0);
133 uint64_t start = 1000000LL * tv.tv_sec + tv.tv_usec;
134 _GBAPerfRunloop(&context, &frames, perfOpts.csv);
135 gettimeofday(&tv, 0);
136 uint64_t end = 1000000LL * tv.tv_sec + tv.tv_usec;
137 uint64_t duration = end - start;
138
139 mCoreThreadJoin(&context);
140
141 float scaledFrames = frames * 1000000.f;
142 if (perfOpts.csv) {
143 puts("game_code,frames,duration,renderer");
144 const char* rendererName;
145 if (perfOpts.noVideo) {
146 rendererName = "none";
147 } else {
148 rendererName = "software";
149 }
150 printf("%s,%i,%" PRIu64 ",%s\n", gameCode, frames, duration, rendererName);
151 } else {
152 printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
153 }
154
155cleanup:
156 if (_savestate) {
157 _savestate->close(_savestate);
158 }
159 mCoreConfigFreeOpts(&opts);
160 freeArguments(&args);
161 mCoreConfigDeinit(&core->config);
162 free(outputBuffer);
163
164 return !didStart || mCoreThreadHasCrashed(&context);
165}
166
167static void _GBAPerfRunloop(struct mCoreThread* context, int* frames, bool quiet) {
168 struct timeval lastEcho;
169 gettimeofday(&lastEcho, 0);
170 int duration = *frames;
171 *frames = 0;
172 int lastFrames = 0;
173 while (context->state < THREAD_EXITING) {
174 if (mCoreSyncWaitFrameStart(&context->sync)) {
175 ++*frames;
176 ++lastFrames;
177 if (!quiet) {
178 struct timeval currentTime;
179 long timeDiff;
180 gettimeofday(¤tTime, 0);
181 timeDiff = currentTime.tv_sec - lastEcho.tv_sec;
182 timeDiff *= 1000;
183 timeDiff += (currentTime.tv_usec - lastEcho.tv_usec) / 1000;
184 if (timeDiff >= 1000) {
185 printf("\033[2K\rCurrent FPS: %g (%gx)", lastFrames / (timeDiff / 1000.0f), lastFrames / (float) (60 * (timeDiff / 1000.0f)));
186 fflush(stdout);
187 lastEcho = currentTime;
188 lastFrames = 0;
189 }
190 }
191 }
192 mCoreSyncWaitFrameEnd(&context->sync);
193 if (duration > 0 && *frames == duration) {
194 _GBAPerfShutdown(0);
195 }
196 if (_dispatchExiting) {
197 mCoreThreadEnd(context);
198 }
199 }
200 if (!quiet) {
201 printf("\033[2K\r");
202 }
203}
204
205static void _GBAPerfShutdown(int signal) {
206 UNUSED(signal);
207 // This will come in ON the GBA thread, so we have to handle it carefully
208 _dispatchExiting = true;
209 ConditionWake(&_thread->sync.videoFrameAvailableCond);
210}
211
212static bool _parsePerfOpts(struct mSubParser* parser, int option, const char* arg) {
213 struct PerfOpts* opts = parser->opts;
214 errno = 0;
215 switch (option) {
216 case 'F':
217 opts->frames = strtoul(arg, 0, 10);
218 return !errno;
219 case 'N':
220 opts->noVideo = true;
221 return true;
222 case 'P':
223 opts->csv = true;
224 return true;
225 case 'S':
226 opts->duration = strtoul(arg, 0, 10);
227 return !errno;
228 case 'L':
229 opts->savestate = strdup(arg);
230 return true;
231 default:
232 return false;
233 }
234}
235
236static void _loadSavestate(struct mCoreThread* context) {
237 context->core->loadState(context->core, _savestate, 0);
238 _savestate->close(_savestate);
239 _savestate = 0;
240}