all repos — mgba @ 0b91681b2aaa7c7fe08b06a9fe4a0d303084e45a

mGBA Game Boy Advance Emulator

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(&currentTime, 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}