Core: Video log recording

This commit is contained in:
Vicki Pfau 2017-04-14 18:03:00 -07:00
parent bed6ba1fc4
commit 73947766de
11 changed files with 283 additions and 15 deletions

View File

@ -26,10 +26,10 @@ CXX_GUARD_START
enum mPlatform {
PLATFORM_NONE = -1,
#ifdef M_CORE_GBA
PLATFORM_GBA,
PLATFORM_GBA = 0,
#endif
#ifdef M_CORE_GB
PLATFORM_GB,
PLATFORM_GB = 1,
#endif
};
@ -40,6 +40,7 @@ enum mCoreChecksumType {
struct mCoreConfig;
struct mCoreSync;
struct mStateExtdata;
struct mVideoLogContext;
struct mCore {
void* cpu;
void* board;
@ -145,6 +146,9 @@ struct mCore {
size_t (*listAudioChannels)(const struct mCore*, const struct mCoreChannelInfo**);
void (*enableVideoLayer)(struct mCore*, size_t id, bool enable);
void (*enableAudioChannel)(struct mCore*, size_t id, bool enable);
void (*startVideoLog)(struct mCore*, struct mVideoLogContext*);
void (*endVideoLog)(struct mCore*);
};
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2

View File

@ -27,6 +27,7 @@ struct mVideoLoggerDirtyInfo {
uint32_t padding;
};
struct VFile;
struct mVideoLogger {
bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length);
bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block);
@ -45,8 +46,40 @@ struct mVideoLogger {
uint16_t* vram;
uint16_t* oam;
uint16_t* palette;
struct VFile* vf;
};
struct mVideoLogChannel {
uint32_t type;
void* initialState;
size_t initialStateSize;
struct VFile* channelData;
};
struct mVideoLogContext {
void* initialState;
size_t initialStateSize;
uint32_t nChannels;
struct mVideoLogChannel channels[32];
};
struct mVideoLogHeader {
char magic[4];
uint32_t platform;
uint32_t nChannels;
uint32_t initialStatePointer;
uint32_t channelPointers[32];
};
struct mVideoLogChannelHeader {
uint32_t type;
uint32_t channelInitialStatePointer;
uint32_t channelSize;
uint32_t reserved;
};
void mVideoLoggerRendererCreate(struct mVideoLogger* logger);
void mVideoLoggerRendererInit(struct mVideoLogger* logger);
void mVideoLoggerRendererDeinit(struct mVideoLogger* logger);
void mVideoLoggerRendererReset(struct mVideoLogger* logger);
@ -61,6 +94,11 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);
bool mVideoLoggerRendererRun(struct mVideoLogger* logger);
struct mCore;
struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core);
void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext*);
void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext*, struct VFile*);
CXX_GUARD_END
#endif

View File

@ -31,6 +31,8 @@ struct GBAVideoProxyRenderer {
};
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend);
void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer);
void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer);
CXX_GUARD_END

View File

@ -5,13 +5,26 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/video-logger.h>
#include <mgba/core/core.h>
#include <mgba-util/memory.h>
#include <mgba-util/vfs.h>
const char mVL_MAGIC[] = "mVL\0";
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
static inline size_t _roundUp(size_t value, int shift) {
value += (1 << shift) - 1;
return value >> shift;
}
void mVideoLoggerRendererCreate(struct mVideoLogger* logger) {
logger->writeData = _writeData;
logger->readData = _readData;
logger->vf = NULL;
}
void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
logger->palette = anonymousMemoryMap(logger->paletteSize);
logger->vram = anonymousMemoryMap(logger->vramSize);
@ -134,3 +147,79 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger) {
}
return true;
}
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
return logger->vf->write(logger->vf, data, length) == (ssize_t) length;
}
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
return logger->vf->read(logger->vf, data, length) == (ssize_t) length || !block;
}
struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core) {
struct mVideoLogContext* context = malloc(sizeof(*context));
core->startVideoLog(core, context);
return context;
}
void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext* context) {
if (core) {
core->endVideoLog(core);
}
free(context);
}
void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext* context, struct VFile* vf) {
struct mVideoLogHeader header = {{0}};
memcpy(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC));
enum mPlatform platform = core->platform(core);
STORE_32LE(platform, 0, &header.platform);
STORE_32LE(context->nChannels, 0, &header.nChannels);
ssize_t pointer = vf->seek(vf, sizeof(header), SEEK_SET);
if (context->initialStateSize) {
ssize_t written = vf->write(vf, context->initialState, context->initialStateSize);
if (written > 0) {
STORE_32LE(pointer, 0, &header.initialStatePointer);
pointer += written;
} else {
header.initialStatePointer = 0;
}
} else {
header.initialStatePointer = 0;
}
size_t i;
for (i = 0; i < context->nChannels && i < 32; ++i) {
struct VFile* channel = context->channels[i].channelData;
void* block = channel->map(channel, channel->size(channel), MAP_READ);
struct mVideoLogChannelHeader chHeader = {0};
STORE_32LE(context->channels[i].type, 0, &chHeader.type);
STORE_32LE(channel->size(channel), 0, &chHeader.channelSize);
if (context->channels[i].initialStateSize) {
ssize_t written = vf->write(vf, context->channels[i].initialState, context->channels[i].initialStateSize);
if (written > 0) {
STORE_32LE(pointer, 0, &chHeader.channelInitialStatePointer);
pointer += written;
} else {
chHeader.channelInitialStatePointer = 0;
}
}
STORE_32LE(pointer, 0, &header.channelPointers[i]);
ssize_t written = vf->write(vf, &chHeader, sizeof(chHeader));
if (written != sizeof(chHeader)) {
continue;
}
pointer += written;
written = vf->write(vf, block, channel->size(channel));
if (written != channel->size(channel)) {
break;
}
pointer += written;
}
vf->seek(vf, 0, SEEK_SET);
vf->write(vf, &header, sizeof(header));
}

View File

@ -42,6 +42,8 @@ const static struct mCoreChannelInfo _GBAAudioChannels[] = {
struct GBACore {
struct mCore d;
struct GBAVideoSoftwareRenderer renderer;
struct GBAVideoProxyRenderer logProxy;
struct mVideoLogContext* logContext;
#ifndef DISABLE_THREADING
struct GBAVideoThreadProxyRenderer threadProxy;
int threadedVideo;
@ -69,6 +71,7 @@ static bool _GBACoreInit(struct mCore* core) {
gbacore->overrides = NULL;
gbacore->debuggerPlatform = NULL;
gbacore->cheatDevice = NULL;
gbacore->logContext = NULL;
GBACreate(gba);
// TODO: Restore cheats
@ -646,6 +649,37 @@ static void _GBACoreEnableAudioChannel(struct mCore* core, size_t id, bool enabl
}
}
static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) {
struct GBACore* gbacore = (struct GBACore*) core;
struct GBA* gba = core->board;
gbacore->logContext = context;
GBAVideoProxyRendererCreate(&gbacore->logProxy, gba->video.renderer);
context->initialStateSize = core->stateSize(core);
context->initialState = anonymousMemoryMap(context->initialStateSize);
core->saveState(core, context->initialState);
struct VFile* vf = VFileMemChunk(NULL, 0);
context->nChannels = 1;
context->channels[0].initialState = NULL;
context->channels[0].initialStateSize = 0;
context->channels[0].channelData = vf;
gbacore->logProxy.logger.vf = vf;
gbacore->logProxy.block = false;
GBAVideoProxyRendererShim(&gba->video, &gbacore->logProxy);
}
static void _GBACoreEndVideoLog(struct mCore* core) {
struct GBACore* gbacore = (struct GBACore*) core;
struct GBA* gba = core->board;
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->logProxy);
mappedMemoryFree(gbacore->logContext->initialState, gbacore->logContext->initialStateSize);
gbacore->logContext->channels[0].channelData->close(gbacore->logContext->channels[0].channelData);
}
struct mCore* GBACoreCreate(void) {
struct GBACore* gbacore = malloc(sizeof(*gbacore));
struct mCore* core = &gbacore->d;
@ -718,5 +752,7 @@ struct mCore* GBACoreCreate(void) {
core->listAudioChannels = _GBACoreListAudioChannels;
core->enableVideoLayer = _GBACoreEnableVideoLayer;
core->enableAudioChannel = _GBACoreEnableAudioChannel;
core->startVideoLog = _GBACoreStartVideoLog;
core->endVideoLog = _GBACoreEndVideoLog;
return core;
}

View File

@ -25,6 +25,8 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address);
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend) {
mVideoLoggerRendererCreate(&renderer->logger);
renderer->d.init = GBAVideoProxyRendererInit;
renderer->d.reset = GBAVideoProxyRendererReset;
renderer->d.deinit = GBAVideoProxyRendererDeinit;
@ -43,9 +45,11 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
renderer->d.disableBG[3] = false;
renderer->d.disableOBJ = false;
renderer->init = NULL;
renderer->deinit = NULL;
renderer->reset = NULL;
renderer->logger.context = renderer;
renderer->logger.writeData = NULL;
renderer->logger.readData = NULL;
renderer->logger.parsePacket = _parsePacket;
renderer->logger.vramBlock = _vramBlock;
renderer->logger.paletteSize = SIZE_PALETTE_RAM;
@ -55,9 +59,7 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
renderer->backend = backend;
}
void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
static void _init(struct GBAVideoProxyRenderer* proxyRenderer) {
mVideoLoggerRendererInit(&proxyRenderer->logger);
if (proxyRenderer->block) {
@ -67,20 +69,65 @@ void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
proxyRenderer->backend->cache = NULL;
}
proxyRenderer->init(proxyRenderer);
if (proxyRenderer->init) {
proxyRenderer->init(proxyRenderer);
}
}
static void _reset(struct GBAVideoProxyRenderer* proxyRenderer) {
memcpy(proxyRenderer->logger.oam, &proxyRenderer->d.oam->raw, SIZE_OAM);
memcpy(proxyRenderer->logger.palette, &proxyRenderer->d.palette, SIZE_PALETTE_RAM);
memcpy(proxyRenderer->logger.vram, &proxyRenderer->d.vram, SIZE_VRAM);
mVideoLoggerRendererReset(&proxyRenderer->logger);
if (proxyRenderer->reset) {
proxyRenderer->reset(proxyRenderer);
}
}
void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) {
if (video->renderer != renderer->backend) {
return;
}
renderer->d.cache = video->renderer->cache;
video->renderer = &renderer->d;
renderer->d.palette = video->palette;
renderer->d.vram = video->vram;
renderer->d.oam = &video->oam;
_init(renderer);
_reset(renderer);
}
void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) {
if (video->renderer != &renderer->d) {
return;
}
renderer->backend->cache = video->renderer->cache;
video->renderer = renderer->backend;
renderer->backend->palette = video->palette;
renderer->backend->vram = video->vram;
renderer->backend->oam = &video->oam;
if (renderer->deinit) {
renderer->deinit(renderer);
}
mVideoLoggerRendererDeinit(&renderer->logger);
}
void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
_init(proxyRenderer);
proxyRenderer->backend->init(proxyRenderer->backend);
}
void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
memcpy(proxyRenderer->logger.oam, &renderer->oam->raw, SIZE_OAM);
memcpy(proxyRenderer->logger.palette, renderer->palette, SIZE_PALETTE_RAM);
memcpy(proxyRenderer->logger.vram, renderer->vram, SIZE_VRAM);
mVideoLoggerRendererReset(&proxyRenderer->logger);
proxyRenderer->reset(proxyRenderer);
_reset(proxyRenderer);
proxyRenderer->backend->reset(proxyRenderer->backend);
}
@ -88,7 +135,9 @@ void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
proxyRenderer->deinit(proxyRenderer);
if (proxyRenderer->deinit) {
proxyRenderer->deinit(proxyRenderer);
}
proxyRenderer->backend->deinit(proxyRenderer->backend);

View File

@ -39,6 +39,7 @@ void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* rende
renderer->d.logger.writeData = _writeData;
renderer->d.logger.readData = _readData;
renderer->d.logger.vf = NULL;
}
void GBAVideoThreadProxyRendererInit(struct GBAVideoProxyRenderer* renderer) {

View File

@ -20,6 +20,7 @@
#include <mgba/core/directories.h>
#include <mgba/core/serialize.h>
#include <mgba/core/tile-cache.h>
#include <mgba/core/video-logger.h>
#ifdef M_CORE_GBA
#include <mgba/gba/interface.h>
#include <mgba/internal/gba/gba.h>
@ -71,6 +72,7 @@ GameController::GameController(QObject* parent)
, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
, m_preload(false)
, m_override(nullptr)
, m_vl(nullptr)
{
#ifdef M_CORE_GBA
m_lux.p = this;
@ -1199,6 +1201,27 @@ void GameController::disableLogLevel(int levels) {
m_logLevels &= ~levels;
}
void GameController::startVideoLog(const QString& path) {
if (!isLoaded() || m_vl) {
return;
}
m_vlPath = path;
m_vl = mVideoLoggerCreate(m_threadContext.core);
}
void GameController::endVideoLog() {
if (!m_vl) {
return;
}
if (isLoaded()) {
VFile* vf = VFileDevice::open(m_vlPath, O_WRONLY | O_CREAT | O_TRUNC);
mVideoLoggerWrite(m_threadContext.core, m_vl, vf);
vf->close(vf);
}
mVideoLoggerDestroy(m_threadContext.core, m_vl);
m_vf = nullptr;
}
void GameController::pollEvents() {
if (!m_inputController) {
return;

View File

@ -29,6 +29,7 @@ struct GBAAudio;
struct mCoreConfig;
struct mDebugger;
struct mTileCache;
struct mVideoLogContext;
namespace QGBA {
@ -174,6 +175,9 @@ public slots:
void enableLogLevel(int);
void disableLogLevel(int);
void startVideoLog(const QString& path);
void endVideoLog();
private slots:
void openGame(bool bios = false);
void crashGame(const QString& crashMessage);
@ -242,6 +246,9 @@ private:
mAVStream* m_stream;
mVideoLogContext* m_vl;
QString m_vlPath;
struct GameControllerLux : GBALuminanceSource {
GameController* p;
uint8_t value;

View File

@ -478,6 +478,13 @@ void Window::openAboutScreen() {
openView(about);
}
void Window::startVideoLog() {
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)"));
if (!filename.isEmpty()) {
m_controller->startVideoLog(filename);
}
}
template <typename T, typename A>
std::function<void()> Window::openTView(A arg) {
return [=]() {
@ -1350,6 +1357,16 @@ void Window::setupMenu(QMenuBar* menubar) {
addControlledAction(avMenu, recordGIF, "recordGIF");
#endif
QAction* recordVL = new QAction(tr("Record video log..."), avMenu);
connect(recordVL, SIGNAL(triggered()), this, SLOT(startVideoLog()));
addControlledAction(avMenu, recordVL, "recordVL");
m_gameActions.append(recordVL);
QAction* stopVL = new QAction(tr("Stop video log"), avMenu);
connect(stopVL, SIGNAL(triggered()), m_controller, SLOT(endVideoLog()));
addControlledAction(avMenu, stopVL, "stopVL");
m_gameActions.append(stopVL);
avMenu->addSeparator();
m_videoLayers = avMenu->addMenu(tr("Video layers"));
m_shortcutController->addMenu(m_videoLayers, avMenu);

View File

@ -81,6 +81,8 @@ public slots:
void openSettingsWindow();
void openAboutScreen();
void startVideoLog();
#ifdef USE_DEBUGGERS
void consoleOpen();
#endif