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}