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