src/platform/perf-main.c (view raw)
1#include "gba-thread.h"
2#include "gba.h"
3#include "renderers/video-software.h"
4
5#include <errno.h>
6#include <fcntl.h>
7#include <signal.h>
8#include <sys/time.h>
9
10#define PERF_OPTIONS "F:NPS:"
11#define PERF_USAGE \
12 "\nBenchmark options:\n" \
13 " -F FRAMES Run for the specified number of FRAMES before exiting\n" \
14 " -N Disable video rendering entirely\n" \
15 " -P CSV output, useful for parsing\n" \
16 " -S SEC Run for SEC in-game seconds before exiting"
17
18struct PerfOpts {
19 bool noVideo;
20 bool csv;
21 unsigned duration;
22 unsigned frames;
23};
24
25static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet);
26static void _GBAPerfShutdown(int signal);
27static bool _parsePerfOpts(struct SubParser* parser, int option, const char* arg);
28
29static struct GBAThread* _thread;
30
31int main(int argc, char** argv) {
32 signal(SIGINT, _GBAPerfShutdown);
33
34 struct GBAVideoSoftwareRenderer renderer;
35 GBAVideoSoftwareRendererCreate(&renderer);
36
37 struct PerfOpts perfOpts = { false, false, 0, 0 };
38 struct SubParser subparser = {
39 .usage = PERF_USAGE,
40 .parse = _parsePerfOpts,
41 .extraOptions = PERF_OPTIONS,
42 .opts = &perfOpts
43 };
44
45 struct StartupOptions opts;
46 if (!parseCommandArgs(&opts, argc, argv, &subparser)) {
47 usage(argv[0], PERF_USAGE);
48 return 1;
49 }
50
51 renderer.outputBuffer = malloc(256 * 256 * 4);
52 renderer.outputBufferStride = 256;
53
54 struct GBAThread context = {
55 .sync.videoFrameWait = 0,
56 .sync.audioWait = 0
57 };
58 _thread = &context;
59
60 if (!perfOpts.noVideo) {
61 context.renderer = &renderer.d;
62 }
63
64 context.debugger = createDebugger(&opts);
65 char gameCode[5] = { 0 };
66
67 GBAMapOptionsToContext(&opts, &context);
68
69 GBAThreadStart(&context);
70 GBAGetGameCode(context.gba, gameCode);
71
72 int frames = perfOpts.frames;
73 if (!frames) {
74 frames = perfOpts.duration * 60;
75 }
76 struct timeval tv;
77 gettimeofday(&tv, 0);
78 uint64_t start = 1000000 * tv.tv_sec + tv.tv_usec;
79 _GBAPerfRunloop(&context, &frames, perfOpts.csv);
80 gettimeofday(&tv, 0);
81 uint64_t end = 1000000 * tv.tv_sec + tv.tv_usec;
82 uint64_t duration = end - start;
83
84 GBAThreadJoin(&context);
85 freeOptions(&opts);
86 free(context.debugger);
87
88 free(renderer.outputBuffer);
89
90 float scaledFrames = frames * 1000000.f;
91 if (perfOpts.csv) {
92 puts("game_code,frames,duration,renderer");
93 const char* rendererName;
94 if (perfOpts.noVideo) {
95 rendererName = "none";
96 } else {
97 rendererName = "software";
98 }
99 printf("%s,%i,%lli,%s\n", gameCode, frames, duration, rendererName);
100 } else {
101 printf("%u frames in %lli microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
102 }
103
104 return 0;
105}
106
107static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) {
108 struct timeval lastEcho;
109 gettimeofday(&lastEcho, 0);
110 int duration = *frames;
111 *frames = 0;
112 int lastFrames = 0;
113 while (context->state < THREAD_EXITING) {
114 if (GBASyncWaitFrameStart(&context->sync, 0)) {
115 ++*frames;
116 ++lastFrames;
117 if (!quiet) {
118 struct timeval currentTime;
119 long timeDiff;
120 gettimeofday(¤tTime, 0);
121 timeDiff = currentTime.tv_sec - lastEcho.tv_sec;
122 timeDiff *= 1000;
123 timeDiff += (currentTime.tv_usec - lastEcho.tv_usec) / 1000;
124 if (timeDiff >= 1000) {
125 printf("\033[2K\rCurrent FPS: %g (%gx)", lastFrames / (timeDiff / 1000.0f), lastFrames / (float) (60 * (timeDiff / 1000.0f)));
126 fflush(stdout);
127 lastEcho = currentTime;
128 lastFrames = 0;
129 }
130 }
131 }
132 GBASyncWaitFrameEnd(&context->sync);
133 if (*frames == duration) {
134 _GBAPerfShutdown(0);
135 }
136 }
137 if (!quiet) {
138 printf("\033[2K\r");
139 }
140}
141
142static void _GBAPerfShutdown(int signal) {
143 UNUSED(signal);
144 pthread_mutex_lock(&_thread->stateMutex);
145 _thread->state = THREAD_EXITING;
146 pthread_mutex_unlock(&_thread->stateMutex);
147}
148
149static bool _parsePerfOpts(struct SubParser* parser, int option, const char* arg) {
150 struct PerfOpts* opts = parser->opts;
151 switch (option) {
152 case 'F':
153 opts->frames = strtoul(arg, 0, 10);
154 return !errno;
155 case 'N':
156 opts->noVideo = true;
157 return true;
158 case 'P':
159 opts->csv = true;
160 return true;
161 case 'S':
162 opts->duration = strtoul(arg, 0, 10);
163 return !errno;
164 default:
165 return false;
166 }
167}