mirror of https://github.com/mgba-emu/mgba.git
Core: Video log recording
This commit is contained in:
parent
bed6ba1fc4
commit
73947766de
|
@ -26,10 +26,10 @@ CXX_GUARD_START
|
||||||
enum mPlatform {
|
enum mPlatform {
|
||||||
PLATFORM_NONE = -1,
|
PLATFORM_NONE = -1,
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
PLATFORM_GBA,
|
PLATFORM_GBA = 0,
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
PLATFORM_GB,
|
PLATFORM_GB = 1,
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ enum mCoreChecksumType {
|
||||||
struct mCoreConfig;
|
struct mCoreConfig;
|
||||||
struct mCoreSync;
|
struct mCoreSync;
|
||||||
struct mStateExtdata;
|
struct mStateExtdata;
|
||||||
|
struct mVideoLogContext;
|
||||||
struct mCore {
|
struct mCore {
|
||||||
void* cpu;
|
void* cpu;
|
||||||
void* board;
|
void* board;
|
||||||
|
@ -145,6 +146,9 @@ struct mCore {
|
||||||
size_t (*listAudioChannels)(const struct mCore*, const struct mCoreChannelInfo**);
|
size_t (*listAudioChannels)(const struct mCore*, const struct mCoreChannelInfo**);
|
||||||
void (*enableVideoLayer)(struct mCore*, size_t id, bool enable);
|
void (*enableVideoLayer)(struct mCore*, size_t id, bool enable);
|
||||||
void (*enableAudioChannel)(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
|
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||||
|
|
|
@ -27,6 +27,7 @@ struct mVideoLoggerDirtyInfo {
|
||||||
uint32_t padding;
|
uint32_t padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VFile;
|
||||||
struct mVideoLogger {
|
struct mVideoLogger {
|
||||||
bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length);
|
bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length);
|
||||||
bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block);
|
bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block);
|
||||||
|
@ -45,8 +46,40 @@ struct mVideoLogger {
|
||||||
uint16_t* vram;
|
uint16_t* vram;
|
||||||
uint16_t* oam;
|
uint16_t* oam;
|
||||||
uint16_t* palette;
|
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 mVideoLoggerRendererInit(struct mVideoLogger* logger);
|
||||||
void mVideoLoggerRendererDeinit(struct mVideoLogger* logger);
|
void mVideoLoggerRendererDeinit(struct mVideoLogger* logger);
|
||||||
void mVideoLoggerRendererReset(struct mVideoLogger* logger);
|
void mVideoLoggerRendererReset(struct mVideoLogger* logger);
|
||||||
|
@ -61,6 +94,11 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);
|
||||||
|
|
||||||
bool mVideoLoggerRendererRun(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
|
CXX_GUARD_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -31,6 +31,8 @@ struct GBAVideoProxyRenderer {
|
||||||
};
|
};
|
||||||
|
|
||||||
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend);
|
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
|
CXX_GUARD_END
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,26 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include <mgba/core/video-logger.h>
|
#include <mgba/core/video-logger.h>
|
||||||
|
|
||||||
|
#include <mgba/core/core.h>
|
||||||
#include <mgba-util/memory.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) {
|
static inline size_t _roundUp(size_t value, int shift) {
|
||||||
value += (1 << shift) - 1;
|
value += (1 << shift) - 1;
|
||||||
return value >> shift;
|
return value >> shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mVideoLoggerRendererCreate(struct mVideoLogger* logger) {
|
||||||
|
logger->writeData = _writeData;
|
||||||
|
logger->readData = _readData;
|
||||||
|
logger->vf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
|
void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
|
||||||
logger->palette = anonymousMemoryMap(logger->paletteSize);
|
logger->palette = anonymousMemoryMap(logger->paletteSize);
|
||||||
logger->vram = anonymousMemoryMap(logger->vramSize);
|
logger->vram = anonymousMemoryMap(logger->vramSize);
|
||||||
|
@ -134,3 +147,79 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger) {
|
||||||
}
|
}
|
||||||
return true;
|
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));
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ const static struct mCoreChannelInfo _GBAAudioChannels[] = {
|
||||||
struct GBACore {
|
struct GBACore {
|
||||||
struct mCore d;
|
struct mCore d;
|
||||||
struct GBAVideoSoftwareRenderer renderer;
|
struct GBAVideoSoftwareRenderer renderer;
|
||||||
|
struct GBAVideoProxyRenderer logProxy;
|
||||||
|
struct mVideoLogContext* logContext;
|
||||||
#ifndef DISABLE_THREADING
|
#ifndef DISABLE_THREADING
|
||||||
struct GBAVideoThreadProxyRenderer threadProxy;
|
struct GBAVideoThreadProxyRenderer threadProxy;
|
||||||
int threadedVideo;
|
int threadedVideo;
|
||||||
|
@ -69,6 +71,7 @@ static bool _GBACoreInit(struct mCore* core) {
|
||||||
gbacore->overrides = NULL;
|
gbacore->overrides = NULL;
|
||||||
gbacore->debuggerPlatform = NULL;
|
gbacore->debuggerPlatform = NULL;
|
||||||
gbacore->cheatDevice = NULL;
|
gbacore->cheatDevice = NULL;
|
||||||
|
gbacore->logContext = NULL;
|
||||||
|
|
||||||
GBACreate(gba);
|
GBACreate(gba);
|
||||||
// TODO: Restore cheats
|
// 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 mCore* GBACoreCreate(void) {
|
||||||
struct GBACore* gbacore = malloc(sizeof(*gbacore));
|
struct GBACore* gbacore = malloc(sizeof(*gbacore));
|
||||||
struct mCore* core = &gbacore->d;
|
struct mCore* core = &gbacore->d;
|
||||||
|
@ -718,5 +752,7 @@ struct mCore* GBACoreCreate(void) {
|
||||||
core->listAudioChannels = _GBACoreListAudioChannels;
|
core->listAudioChannels = _GBACoreListAudioChannels;
|
||||||
core->enableVideoLayer = _GBACoreEnableVideoLayer;
|
core->enableVideoLayer = _GBACoreEnableVideoLayer;
|
||||||
core->enableAudioChannel = _GBACoreEnableAudioChannel;
|
core->enableAudioChannel = _GBACoreEnableAudioChannel;
|
||||||
|
core->startVideoLog = _GBACoreStartVideoLog;
|
||||||
|
core->endVideoLog = _GBACoreEndVideoLog;
|
||||||
return core;
|
return core;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
|
||||||
static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address);
|
static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address);
|
||||||
|
|
||||||
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend) {
|
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend) {
|
||||||
|
mVideoLoggerRendererCreate(&renderer->logger);
|
||||||
|
|
||||||
renderer->d.init = GBAVideoProxyRendererInit;
|
renderer->d.init = GBAVideoProxyRendererInit;
|
||||||
renderer->d.reset = GBAVideoProxyRendererReset;
|
renderer->d.reset = GBAVideoProxyRendererReset;
|
||||||
renderer->d.deinit = GBAVideoProxyRendererDeinit;
|
renderer->d.deinit = GBAVideoProxyRendererDeinit;
|
||||||
|
@ -43,9 +45,11 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
|
||||||
renderer->d.disableBG[3] = false;
|
renderer->d.disableBG[3] = false;
|
||||||
renderer->d.disableOBJ = false;
|
renderer->d.disableOBJ = false;
|
||||||
|
|
||||||
|
renderer->init = NULL;
|
||||||
|
renderer->deinit = NULL;
|
||||||
|
renderer->reset = NULL;
|
||||||
|
|
||||||
renderer->logger.context = renderer;
|
renderer->logger.context = renderer;
|
||||||
renderer->logger.writeData = NULL;
|
|
||||||
renderer->logger.readData = NULL;
|
|
||||||
renderer->logger.parsePacket = _parsePacket;
|
renderer->logger.parsePacket = _parsePacket;
|
||||||
renderer->logger.vramBlock = _vramBlock;
|
renderer->logger.vramBlock = _vramBlock;
|
||||||
renderer->logger.paletteSize = SIZE_PALETTE_RAM;
|
renderer->logger.paletteSize = SIZE_PALETTE_RAM;
|
||||||
|
@ -55,9 +59,7 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
|
||||||
renderer->backend = backend;
|
renderer->backend = backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
|
static void _init(struct GBAVideoProxyRenderer* proxyRenderer) {
|
||||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
|
||||||
|
|
||||||
mVideoLoggerRendererInit(&proxyRenderer->logger);
|
mVideoLoggerRendererInit(&proxyRenderer->logger);
|
||||||
|
|
||||||
if (proxyRenderer->block) {
|
if (proxyRenderer->block) {
|
||||||
|
@ -67,20 +69,65 @@ void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
|
||||||
proxyRenderer->backend->cache = NULL;
|
proxyRenderer->backend->cache = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxyRenderer->init) {
|
||||||
proxyRenderer->init(proxyRenderer);
|
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);
|
proxyRenderer->backend->init(proxyRenderer->backend);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
|
void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
|
||||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) 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);
|
_reset(proxyRenderer);
|
||||||
|
|
||||||
proxyRenderer->reset(proxyRenderer);
|
|
||||||
|
|
||||||
proxyRenderer->backend->reset(proxyRenderer->backend);
|
proxyRenderer->backend->reset(proxyRenderer->backend);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +135,9 @@ void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
|
||||||
void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
|
void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
|
||||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
||||||
|
|
||||||
|
if (proxyRenderer->deinit) {
|
||||||
proxyRenderer->deinit(proxyRenderer);
|
proxyRenderer->deinit(proxyRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
proxyRenderer->backend->deinit(proxyRenderer->backend);
|
proxyRenderer->backend->deinit(proxyRenderer->backend);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* rende
|
||||||
|
|
||||||
renderer->d.logger.writeData = _writeData;
|
renderer->d.logger.writeData = _writeData;
|
||||||
renderer->d.logger.readData = _readData;
|
renderer->d.logger.readData = _readData;
|
||||||
|
renderer->d.logger.vf = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAVideoThreadProxyRendererInit(struct GBAVideoProxyRenderer* renderer) {
|
void GBAVideoThreadProxyRendererInit(struct GBAVideoProxyRenderer* renderer) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <mgba/core/directories.h>
|
#include <mgba/core/directories.h>
|
||||||
#include <mgba/core/serialize.h>
|
#include <mgba/core/serialize.h>
|
||||||
#include <mgba/core/tile-cache.h>
|
#include <mgba/core/tile-cache.h>
|
||||||
|
#include <mgba/core/video-logger.h>
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
#include <mgba/gba/interface.h>
|
#include <mgba/gba/interface.h>
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
|
@ -71,6 +72,7 @@ GameController::GameController(QObject* parent)
|
||||||
, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
|
, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
|
||||||
, m_preload(false)
|
, m_preload(false)
|
||||||
, m_override(nullptr)
|
, m_override(nullptr)
|
||||||
|
, m_vl(nullptr)
|
||||||
{
|
{
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
m_lux.p = this;
|
m_lux.p = this;
|
||||||
|
@ -1199,6 +1201,27 @@ void GameController::disableLogLevel(int levels) {
|
||||||
m_logLevels &= ~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() {
|
void GameController::pollEvents() {
|
||||||
if (!m_inputController) {
|
if (!m_inputController) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct GBAAudio;
|
||||||
struct mCoreConfig;
|
struct mCoreConfig;
|
||||||
struct mDebugger;
|
struct mDebugger;
|
||||||
struct mTileCache;
|
struct mTileCache;
|
||||||
|
struct mVideoLogContext;
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
|
@ -174,6 +175,9 @@ public slots:
|
||||||
void enableLogLevel(int);
|
void enableLogLevel(int);
|
||||||
void disableLogLevel(int);
|
void disableLogLevel(int);
|
||||||
|
|
||||||
|
void startVideoLog(const QString& path);
|
||||||
|
void endVideoLog();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void openGame(bool bios = false);
|
void openGame(bool bios = false);
|
||||||
void crashGame(const QString& crashMessage);
|
void crashGame(const QString& crashMessage);
|
||||||
|
@ -242,6 +246,9 @@ private:
|
||||||
|
|
||||||
mAVStream* m_stream;
|
mAVStream* m_stream;
|
||||||
|
|
||||||
|
mVideoLogContext* m_vl;
|
||||||
|
QString m_vlPath;
|
||||||
|
|
||||||
struct GameControllerLux : GBALuminanceSource {
|
struct GameControllerLux : GBALuminanceSource {
|
||||||
GameController* p;
|
GameController* p;
|
||||||
uint8_t value;
|
uint8_t value;
|
||||||
|
|
|
@ -478,6 +478,13 @@ void Window::openAboutScreen() {
|
||||||
openView(about);
|
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>
|
template <typename T, typename A>
|
||||||
std::function<void()> Window::openTView(A arg) {
|
std::function<void()> Window::openTView(A arg) {
|
||||||
return [=]() {
|
return [=]() {
|
||||||
|
@ -1350,6 +1357,16 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
addControlledAction(avMenu, recordGIF, "recordGIF");
|
addControlledAction(avMenu, recordGIF, "recordGIF");
|
||||||
#endif
|
#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();
|
avMenu->addSeparator();
|
||||||
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
||||||
m_shortcutController->addMenu(m_videoLayers, avMenu);
|
m_shortcutController->addMenu(m_videoLayers, avMenu);
|
||||||
|
|
|
@ -81,6 +81,8 @@ public slots:
|
||||||
void openSettingsWindow();
|
void openSettingsWindow();
|
||||||
void openAboutScreen();
|
void openAboutScreen();
|
||||||
|
|
||||||
|
void startVideoLog();
|
||||||
|
|
||||||
#ifdef USE_DEBUGGERS
|
#ifdef USE_DEBUGGERS
|
||||||
void consoleOpen();
|
void consoleOpen();
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue