all repos — mgba @ 5b6bf9eb646b10b55424c4112e77295163894dfe

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