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