all repos — mgba @ 3f92b1e67f30abfc056df1df141de7c7ccb6df61

mGBA Game Boy Advance Emulator

src/feature/video-logger.c (view raw)

  1/* Copyright (c) 2013-2017 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "video-logger.h"
  7
  8#include <mgba/core/core.h>
  9#include <mgba-util/memory.h>
 10#include <mgba-util/vfs.h>
 11
 12#ifdef M_CORE_GBA
 13#include <mgba/gba/core.h>
 14#endif
 15
 16const char mVL_MAGIC[] = "mVL\0";
 17
 18const static struct mVLDescriptor {
 19	enum mPlatform platform;
 20	struct mCore* (*open)(void);
 21} _descriptors[] = {
 22#ifdef M_CORE_GBA
 23	{ PLATFORM_GBA, GBAVideoLogPlayerCreate },
 24#endif
 25	{ PLATFORM_NONE, 0 }
 26};
 27
 28static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
 29static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length);
 30static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
 31
 32static inline size_t _roundUp(size_t value, int shift) {
 33	value += (1 << shift) - 1;
 34	return value >> shift;
 35}
 36
 37void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly) {
 38	if (readonly) {
 39		logger->writeData = _writeNull;
 40		logger->block = true;
 41	} else {
 42		logger->writeData = _writeData;
 43	}
 44	logger->readData = _readData;
 45	logger->vf = NULL;
 46
 47	logger->init = NULL;
 48	logger->deinit = NULL;
 49	logger->reset = NULL;
 50
 51	logger->lock = NULL;
 52	logger->unlock = NULL;
 53	logger->wait = NULL;
 54	logger->wake = NULL;
 55}
 56
 57void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
 58	logger->palette = anonymousMemoryMap(logger->paletteSize);
 59	logger->vram = anonymousMemoryMap(logger->vramSize);
 60	logger->oam = anonymousMemoryMap(logger->oamSize);
 61
 62	logger->vramDirtyBitmap = calloc(_roundUp(logger->vramSize, 17), sizeof(uint32_t));
 63	logger->oamDirtyBitmap = calloc(_roundUp(logger->oamSize, 6), sizeof(uint32_t));
 64
 65	if (logger->init) {
 66		logger->init(logger);
 67	}
 68}
 69
 70void mVideoLoggerRendererDeinit(struct mVideoLogger* logger) {
 71	if (logger->deinit) {
 72		logger->deinit(logger);
 73	}
 74
 75	mappedMemoryFree(logger->palette, logger->paletteSize);
 76	mappedMemoryFree(logger->vram, logger->vramSize);
 77	mappedMemoryFree(logger->oam, logger->oamSize);
 78
 79	free(logger->vramDirtyBitmap);
 80	free(logger->oamDirtyBitmap);
 81}
 82
 83void mVideoLoggerRendererReset(struct mVideoLogger* logger) {
 84	memset(logger->vramDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->vramSize, 17));
 85	memset(logger->oamDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->oamSize, 6));
 86
 87	if (logger->reset) {
 88		logger->reset(logger);
 89	}
 90}
 91
 92void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 93	struct mVideoLoggerDirtyInfo dirty = {
 94		DIRTY_REGISTER,
 95		address,
 96		value,
 97		0xDEADBEEF,
 98	};
 99	logger->writeData(logger, &dirty, sizeof(dirty));
100}
101
102void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address) {
103	int bit = 1 << (address >> 12);
104	if (logger->vramDirtyBitmap[address >> 17] & bit) {
105		return;
106	}
107	logger->vramDirtyBitmap[address >> 17] |= bit;
108}
109
110void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
111	struct mVideoLoggerDirtyInfo dirty = {
112		DIRTY_PALETTE,
113		address,
114		value,
115		0xDEADBEEF,
116	};
117	logger->writeData(logger, &dirty, sizeof(dirty));
118}
119
120void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
121	struct mVideoLoggerDirtyInfo dirty = {
122		DIRTY_OAM,
123		address,
124		value,
125		0xDEADBEEF,
126	};
127	logger->writeData(logger, &dirty, sizeof(dirty));
128}
129
130void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y) {
131	size_t i;
132	for (i = 0; i < _roundUp(logger->vramSize, 17); ++i) {
133		if (logger->vramDirtyBitmap[i]) {
134			uint32_t bitmap = logger->vramDirtyBitmap[i];
135			logger->vramDirtyBitmap[i] = 0;
136			int j;
137			for (j = 0; j < mVL_MAX_CHANNELS; ++j) {
138				if (!(bitmap & (1 << j))) {
139					continue;
140				}
141				struct mVideoLoggerDirtyInfo dirty = {
142					DIRTY_VRAM,
143					j * 0x1000,
144					0xABCD,
145					0xDEADBEEF,
146				};
147				logger->writeData(logger, &dirty, sizeof(dirty));
148				logger->writeData(logger, logger->vramBlock(logger, j * 0x1000), 0x1000);
149			}
150		}
151	}
152	struct mVideoLoggerDirtyInfo dirty = {
153		DIRTY_SCANLINE,
154		y,
155		0,
156		0xDEADBEEF,
157	};
158	logger->writeData(logger, &dirty, sizeof(dirty));
159}
160
161void mVideoLoggerRendererFlush(struct mVideoLogger* logger) {
162	struct mVideoLoggerDirtyInfo dirty = {
163		DIRTY_FLUSH,
164		0,
165		0,
166		0xDEADBEEF,
167	};
168	logger->writeData(logger, &dirty, sizeof(dirty));
169}
170
171bool mVideoLoggerRendererRun(struct mVideoLogger* logger) {
172	struct mVideoLoggerDirtyInfo item = {0};
173	while (logger->readData(logger, &item, sizeof(item), false)) {
174		switch (item.type) {
175		case DIRTY_REGISTER:
176		case DIRTY_PALETTE:
177		case DIRTY_OAM:
178		case DIRTY_VRAM:
179		case DIRTY_SCANLINE:
180		case DIRTY_FLUSH:
181			if (!logger->parsePacket(logger, &item)) {
182				return true;
183			}
184			break;
185		default:
186			return false;
187		}
188	}
189	return false;
190}
191
192static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
193	return logger->vf->write(logger->vf, data, length) == (ssize_t) length;
194}
195
196static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
197	UNUSED(logger);
198	UNUSED(data);
199	UNUSED(length);
200	return false;
201}
202
203static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
204	UNUSED(block);
205	return logger->vf->read(logger->vf, data, length) == (ssize_t) length;
206}
207
208struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core) {
209	struct mVideoLogContext* context = malloc(sizeof(*context));
210	if (core) {
211		core->startVideoLog(core, context);
212	}
213	return context;
214}
215
216void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext* context) {
217	if (core) {
218		core->endVideoLog(core);
219	}
220	free(context);
221}
222
223void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext* context, struct VFile* vf) {
224	struct mVideoLogHeader header = {{0}};
225	memcpy(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC));
226
227	enum mPlatform platform = core->platform(core);
228	STORE_32LE(platform, 0, &header.platform);
229	STORE_32LE(context->nChannels, 0, &header.nChannels);
230
231	ssize_t pointer = vf->seek(vf, sizeof(header), SEEK_SET);
232	if (context->initialStateSize) {
233		ssize_t written = vf->write(vf, context->initialState, context->initialStateSize);
234		if (written > 0) {
235			STORE_32LE(pointer, 0, &header.initialStatePointer);
236			STORE_32LE(context->initialStateSize, 0, &header.initialStateSize);
237			pointer += written;
238		} else {
239			header.initialStatePointer = 0;
240		}
241	} else {
242		header.initialStatePointer = 0;
243	}
244
245	size_t i;
246	for (i = 0; i < context->nChannels && i < mVL_MAX_CHANNELS; ++i) {
247		struct VFile* channel = context->channels[i].channelData;
248		void* block = channel->map(channel, channel->size(channel), MAP_READ);
249
250		struct mVideoLogChannelHeader chHeader = {0};
251		STORE_32LE(context->channels[i].type, 0, &chHeader.type);
252		STORE_32LE(channel->size(channel), 0, &chHeader.channelSize);
253
254		if (context->channels[i].initialStateSize) {
255			ssize_t written = vf->write(vf, context->channels[i].initialState, context->channels[i].initialStateSize);
256			if (written > 0) {
257				STORE_32LE(pointer, 0, &chHeader.channelInitialStatePointer);
258				STORE_32LE(context->channels[i].initialStateSize, 0, &chHeader.channelInitialStateSize);
259				pointer += written;
260			} else {
261				chHeader.channelInitialStatePointer = 0;
262			}
263		}
264		STORE_32LE(pointer, 0, &header.channelPointers[i]);
265		ssize_t written = vf->write(vf, &chHeader, sizeof(chHeader));
266		if (written != sizeof(chHeader)) {
267			continue;
268		}
269		pointer += written;
270		written = vf->write(vf, block, channel->size(channel));
271		if (written != channel->size(channel)) {
272			break;
273		}
274		pointer += written;
275	}
276	vf->seek(vf, 0, SEEK_SET);
277	vf->write(vf, &header, sizeof(header));
278}
279
280struct mCore* mVideoLogCoreFind(struct VFile* vf) {
281	if (!vf) {
282		return NULL;
283	}
284	struct mVideoLogHeader header = {{0}};
285	vf->seek(vf, 0, SEEK_SET);
286	ssize_t read = vf->read(vf, &header, sizeof(header));
287	if (read != sizeof(header)) {
288		return NULL;
289	}
290	if (memcmp(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC)) != 0) {
291		return NULL;
292	}
293	enum mPlatform platform;
294	LOAD_32LE(platform, 0, &header.platform);
295
296	const struct mVLDescriptor* descriptor;
297	for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
298		if (platform == descriptor->platform) {
299			break;
300		}
301	}
302	struct mCore* core = NULL;
303	if (descriptor->open) {
304		core = descriptor->open();
305	}
306	return core;
307}
308
309bool mVideoLogContextLoad(struct VFile* vf, struct mVideoLogContext* context) {
310	if (!vf) {
311		return false;
312	}
313	struct mVideoLogHeader header = {{0}};
314	vf->seek(vf, 0, SEEK_SET);
315	ssize_t read = vf->read(vf, &header, sizeof(header));
316	if (read != sizeof(header)) {
317		return false;
318	}
319	if (memcmp(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC)) != 0) {
320		return false;
321	}
322
323	// TODO: Error check
324	uint32_t initialStatePointer;
325	uint32_t initialStateSize;
326	LOAD_32LE(initialStatePointer, 0, &header.initialStatePointer);
327	LOAD_32LE(initialStateSize, 0, &header.initialStateSize);
328	void* initialState = anonymousMemoryMap(initialStateSize);
329	vf->read(vf, initialState, initialStateSize);
330	context->initialState = initialState;
331	context->initialStateSize = initialStateSize;
332
333	uint32_t nChannels;
334	LOAD_32LE(nChannels, 0, &header.nChannels);
335	context->nChannels = nChannels;
336
337	size_t i;
338	for (i = 0; i < nChannels && i < mVL_MAX_CHANNELS; ++i) {
339		uint32_t channelPointer;
340		LOAD_32LE(channelPointer, 0, &header.channelPointers[i]);
341		vf->seek(vf, channelPointer, SEEK_SET);
342
343		struct mVideoLogChannelHeader chHeader;
344		vf->read(vf, &chHeader, sizeof(chHeader));
345
346		LOAD_32LE(context->channels[i].type, 0, &chHeader.type);
347		LOAD_32LE(context->channels[i].initialStateSize, 0, &chHeader.channelInitialStateSize);
348
349		LOAD_32LE(channelPointer, 0, &chHeader.channelInitialStatePointer);
350		if (channelPointer) {
351			off_t position = vf->seek(vf, 0, SEEK_CUR);
352			vf->seek(vf, channelPointer, SEEK_SET);
353
354			context->channels[i].initialState = anonymousMemoryMap(context->channels[i].initialStateSize);
355			vf->read(vf, context->channels[i].initialState, context->channels[i].initialStateSize);
356			vf->seek(vf, position, SEEK_SET);
357		}
358
359		uint32_t channelSize;
360		LOAD_32LE(channelSize, 0, &chHeader.channelSize);
361		struct VFile* vfm = VFileMemChunk(0, channelSize);
362
363		while (channelSize) {
364			uint8_t buffer[2048];
365			ssize_t toRead = channelSize;
366			if (toRead > (ssize_t) sizeof(buffer)) {
367				toRead = sizeof(buffer);
368			}
369			toRead = vf->read(vf, buffer, toRead);
370			if (toRead > 0) {
371				channelSize -= toRead;
372			} else {
373				break;
374			}
375			vfm->write(vfm, buffer, toRead);
376		}
377		context->channels[i].channelData = vfm;
378	}
379
380	for (; i < mVL_MAX_CHANNELS; ++i) {
381		context->channels[i].channelData = NULL;
382	}
383	return true;
384}