all repos — mgba @ bd74fa1fbcd810452602a03682a7677c2efb30d4

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 <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(&currentTime, 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	errno = 0;
153	switch (option) {
154	case 'F':
155		opts->frames = strtoul(arg, 0, 10);
156		return !errno;
157	case 'N':
158		opts->noVideo = true;
159		return true;
160	case 'P':
161		opts->csv = true;
162		return true;
163	case 'S':
164		opts->duration = strtoul(arg, 0, 10);
165		return !errno;
166	default:
167		return false;
168	}
169}