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 GBAConfig* config, 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 GBAConfig config;
50 GBAConfigInit(&config, "perf");
51
52 struct GBAOptions opts = {};
53 struct GBAArguments args = {};
54 if (!parseArguments(&args, &config, argc, argv, &subparser)) {
55 usage(argv[0], PERF_USAGE);
56 freeArguments(&args);
57 GBAConfigFreeOpts(&opts);
58 GBAConfigDeinit(&config);
59 return 1;
60 }
61
62 renderer.outputBuffer = malloc(256 * 256 * 4);
63 renderer.outputBufferStride = 256;
64
65 struct GBAThread context = { };
66 _thread = &context;
67
68 if (!perfOpts.noVideo) {
69 context.renderer = &renderer.d;
70 }
71
72 context.debugger = createDebugger(&args);
73 char gameCode[5] = { 0 };
74
75 GBAConfigMap(&config, &opts);
76 opts.audioSync = false;
77 opts.videoSync = false;
78 GBAMapArgumentsToContext(&args, &context);
79 GBAMapOptionsToContext(&opts, &context);
80
81 GBAThreadStart(&context);
82 GBAGetGameCode(context.gba, gameCode);
83
84 int frames = perfOpts.frames;
85 if (!frames) {
86 frames = perfOpts.duration * 60;
87 }
88 struct timeval tv;
89 gettimeofday(&tv, 0);
90 uint64_t start = 1000000LL * tv.tv_sec + tv.tv_usec;
91 _GBAPerfRunloop(&context, &frames, perfOpts.csv);
92 gettimeofday(&tv, 0);
93 uint64_t end = 1000000LL * tv.tv_sec + tv.tv_usec;
94 uint64_t duration = end - start;
95
96 GBAThreadJoin(&context);
97 GBAConfigFreeOpts(&opts);
98 freeArguments(&args);
99 GBAConfigDeinit(&config);
100 free(context.debugger);
101
102 free(renderer.outputBuffer);
103
104 float scaledFrames = frames * 1000000.f;
105 if (perfOpts.csv) {
106 puts("game_code,frames,duration,renderer");
107 const char* rendererName;
108 if (perfOpts.noVideo) {
109 rendererName = "none";
110 } else {
111 rendererName = "software";
112 }
113 printf("%s,%i,%" PRIu64 ",%s\n", gameCode, frames, duration, rendererName);
114 } else {
115 printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f));
116 }
117
118 return 0;
119}
120
121static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) {
122 struct timeval lastEcho;
123 gettimeofday(&lastEcho, 0);
124 int duration = *frames;
125 *frames = 0;
126 int lastFrames = 0;
127 while (context->state < THREAD_EXITING) {
128 if (GBASyncWaitFrameStart(&context->sync, 0)) {
129 ++*frames;
130 ++lastFrames;
131 if (!quiet) {
132 struct timeval currentTime;
133 long timeDiff;
134 gettimeofday(¤tTime, 0);
135 timeDiff = currentTime.tv_sec - lastEcho.tv_sec;
136 timeDiff *= 1000;
137 timeDiff += (currentTime.tv_usec - lastEcho.tv_usec) / 1000;
138 if (timeDiff >= 1000) {
139 printf("\033[2K\rCurrent FPS: %g (%gx)", lastFrames / (timeDiff / 1000.0f), lastFrames / (float) (60 * (timeDiff / 1000.0f)));
140 fflush(stdout);
141 lastEcho = currentTime;
142 lastFrames = 0;
143 }
144 }
145 }
146 GBASyncWaitFrameEnd(&context->sync);
147 if (*frames == duration) {
148 _GBAPerfShutdown(0);
149 }
150 }
151 if (!quiet) {
152 printf("\033[2K\r");
153 }
154}
155
156static void _GBAPerfShutdown(int signal) {
157 UNUSED(signal);
158 GBAThreadEnd(_thread);
159}
160
161static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
162 UNUSED(config);
163 struct PerfOpts* opts = parser->opts;
164 errno = 0;
165 switch (option) {
166 case 'F':
167 opts->frames = strtoul(arg, 0, 10);
168 return !errno;
169 case 'N':
170 opts->noVideo = true;
171 return true;
172 case 'P':
173 opts->csv = true;
174 return true;
175 case 'S':
176 opts->duration = strtoul(arg, 0, 10);
177 return !errno;
178 default:
179 return false;
180 }
181}