Merge branch 'master' (early part) into medusa
16
CHANGES
|
@ -24,14 +24,30 @@ Emulation fixes:
|
|||
- ARM: Fix STR storing PC after address calculation
|
||||
- GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320)
|
||||
- GBA Memory: Misaligned SRAM writes are ignored
|
||||
- GBA Memory: Improve gamepak prefetch timing
|
||||
- GBA Serialize: Fix serializing DMA transfer register
|
||||
- GBA Serialize: Fix audio serialization for desynced FIFOs
|
||||
- GBA Serialize: Fix audio DMA timing deserialization
|
||||
- GBA Video: Fix OAM not invalidating after reset (fixes mgba.io/i/1630)
|
||||
- GBA Video: Latch scanline at end of Hblank (fixes mgba.io/i/1319)
|
||||
- GBA Video: Fix Hblank timing
|
||||
- GBA Video: Fix backdrop blending on lines without sprites (fixes mgba.io/i/1647)
|
||||
- GBA Video: Fix OpenGL sprite flag priority
|
||||
Other fixes:
|
||||
- Core: Fix race condition initializing thread proxy
|
||||
- Qt: Only dynamically reset video scale if a game is running
|
||||
- Qt: Fix race condition with proxied video events
|
||||
- Qt: Force OpenGL paint engine creation thread (fixes mgba.io/i/1642)
|
||||
- Qt: Fix color selection in asset view (fixes mgba.io/i/1648)
|
||||
- Qt: Fix missing OSD messages
|
||||
- Qt: Fix crash unloading shaders
|
||||
- Qt: Fix toggled actions on gamepads (fixes mgba.io/i/1650)
|
||||
- Qt: Fix extraneous dialog (fixes mgba.io/i/1654)
|
||||
- Util: Fix crash reading invalid ELFs
|
||||
Misc:
|
||||
- Qt: Renderer can be changed while a game is running
|
||||
- Qt: Fix non-SDL build (fixes mgba.io/i/1656)
|
||||
- Switch: Make OpenGL scale adjustable while running
|
||||
|
||||
0.8.0: (2020-01-21)
|
||||
Features:
|
||||
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
@ -91,7 +91,6 @@ struct GBA {
|
|||
struct mRTCSource* rtcSource;
|
||||
struct mRumble* rumble;
|
||||
|
||||
struct GBARRContext* rr;
|
||||
bool isPristine;
|
||||
size_t pristineRomSize;
|
||||
size_t yankedRomSize;
|
||||
|
|
|
@ -44,7 +44,6 @@ struct GBAVideoGLAffine {
|
|||
struct GBAVideoGLBackground {
|
||||
GLuint fbo;
|
||||
GLuint tex;
|
||||
GLuint flags;
|
||||
|
||||
unsigned index;
|
||||
int enabled;
|
||||
|
@ -96,7 +95,6 @@ enum {
|
|||
GBA_GL_BG_CHARBASE,
|
||||
GBA_GL_BG_SIZE,
|
||||
GBA_GL_BG_OFFSET,
|
||||
GBA_GL_BG_INFLAGS,
|
||||
GBA_GL_BG_TRANSFORM,
|
||||
GBA_GL_BG_RANGE,
|
||||
GBA_GL_BG_MOSAIC,
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef RR_MGM_H
|
||||
#define RR_MGM_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/gba/rr/rr.h>
|
||||
|
||||
struct GBA;
|
||||
struct VDir;
|
||||
struct VFile;
|
||||
|
||||
enum GBAMGMTag {
|
||||
// Playback tags
|
||||
TAG_INVALID = 0x00,
|
||||
TAG_INPUT = 0x01,
|
||||
TAG_FRAME = 0x02,
|
||||
TAG_LAG = 0x03,
|
||||
TAG_RESET = 0x04,
|
||||
|
||||
// Stream chunking tags
|
||||
TAG_BEGIN = 0x10,
|
||||
TAG_END = 0x11,
|
||||
TAG_PREVIOUSLY = 0x12,
|
||||
TAG_NEXT_TIME = 0x13,
|
||||
TAG_MAX_STREAM = 0x14,
|
||||
|
||||
// Recording information tags
|
||||
TAG_FRAME_COUNT = 0x20,
|
||||
TAG_LAG_COUNT = 0x21,
|
||||
TAG_RR_COUNT = 0x22,
|
||||
TAG_INIT = 0x24,
|
||||
TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO,
|
||||
TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME,
|
||||
TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE,
|
||||
TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH,
|
||||
|
||||
// User metadata tags
|
||||
TAG_AUTHOR = 0x30,
|
||||
TAG_COMMENT = 0x31,
|
||||
|
||||
TAG_EOF = INT_MAX
|
||||
};
|
||||
|
||||
struct GBAMGMContext {
|
||||
struct GBARRContext d;
|
||||
|
||||
// Playback state
|
||||
bool isPlaying;
|
||||
bool autorecord;
|
||||
|
||||
// Recording state
|
||||
bool isRecording;
|
||||
bool inputThisFrame;
|
||||
|
||||
// Metadata
|
||||
uint32_t streamId;
|
||||
|
||||
uint32_t maxStreamId;
|
||||
off_t maxStreamIdOffset;
|
||||
off_t initFromOffset;
|
||||
off_t rrCountOffset;
|
||||
|
||||
// Streaming state
|
||||
struct VDir* streamDir;
|
||||
struct VFile* metadataFile;
|
||||
struct VFile* movieStream;
|
||||
uint16_t currentInput;
|
||||
enum GBAMGMTag peekedTag;
|
||||
uint32_t nextTime;
|
||||
uint32_t previously;
|
||||
};
|
||||
|
||||
void GBAMGMContextCreate(struct GBAMGMContext*);
|
||||
|
||||
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream);
|
||||
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -1,65 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_RR_H
|
||||
#define GBA_RR_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/internal/gba/serialize.h>
|
||||
|
||||
struct VFile;
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GBA_RR);
|
||||
|
||||
enum GBARRInitFrom {
|
||||
INIT_EX_NIHILO = 0,
|
||||
INIT_FROM_SAVEGAME = 1,
|
||||
INIT_FROM_SAVESTATE = 2,
|
||||
INIT_FROM_BOTH = 3,
|
||||
};
|
||||
|
||||
struct GBARRContext {
|
||||
void (*destroy)(struct GBARRContext*);
|
||||
|
||||
bool (*startPlaying)(struct GBARRContext*, bool autorecord);
|
||||
void (*stopPlaying)(struct GBARRContext*);
|
||||
bool (*startRecording)(struct GBARRContext*);
|
||||
void (*stopRecording)(struct GBARRContext*);
|
||||
|
||||
bool (*isPlaying)(const struct GBARRContext*);
|
||||
bool (*isRecording)(const struct GBARRContext*);
|
||||
|
||||
void (*nextFrame)(struct GBARRContext*);
|
||||
void (*logInput)(struct GBARRContext*, uint16_t input);
|
||||
uint16_t (*queryInput)(struct GBARRContext*);
|
||||
bool (*queryReset)(struct GBARRContext*);
|
||||
|
||||
void (*stateSaved)(struct GBARRContext*, struct GBASerializedState*);
|
||||
void (*stateLoaded)(struct GBARRContext*, const struct GBASerializedState*);
|
||||
|
||||
struct VFile* (*openSavedata)(struct GBARRContext* mgm, int flags);
|
||||
struct VFile* (*openSavestate)(struct GBARRContext* mgm, int flags);
|
||||
|
||||
uint32_t frames;
|
||||
uint32_t lagFrames;
|
||||
enum GBARRInitFrom initFrom;
|
||||
|
||||
uint32_t rrCount;
|
||||
|
||||
struct VFile* savedata;
|
||||
};
|
||||
|
||||
void GBARRDestroy(struct GBARRContext*);
|
||||
|
||||
void GBARRInitRecord(struct GBA*);
|
||||
void GBARRInitPlay(struct GBA*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -1,30 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GBA_VBM_H
|
||||
#define GBA_VBM_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/gba/rr/rr.h>
|
||||
|
||||
struct GBAVBMContext {
|
||||
struct GBARRContext d;
|
||||
|
||||
bool isPlaying;
|
||||
|
||||
struct VFile* vbmFile;
|
||||
int32_t inputOffset;
|
||||
};
|
||||
|
||||
void GBAVBMContextCreate(struct GBAVBMContext*);
|
||||
|
||||
bool GBAVBMSetStream(struct GBAVBMContext*, struct VFile*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -92,7 +92,8 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | bits 6 - 7: Reserved
|
||||
* 0x001E0 - 0x001FF: Video miscellaneous state
|
||||
* | 0x001E0 - 0x001E3: Next event
|
||||
* | 0x001E4 - 0x001FB: Reserved
|
||||
* | 0x001E4 - 0x001F7: Reserved
|
||||
* | 0x001F8 - 0x001FB: Miscellaneous flags
|
||||
* | 0x001FC - 0x001FF: Frame counter
|
||||
* 0x00200 - 0x00213: Timer 0
|
||||
* | 0x00200 - 0x00201: Reload value
|
||||
|
@ -192,8 +193,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | 0x002F4 - 0x002F7: GBA BIOS bus prefetch
|
||||
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
|
||||
* | 0x002FC - 0x002FF: CPU prefetch (fetch slot)
|
||||
* 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream)
|
||||
* 0x00304 - 0x00317: Savestate creation time (usec since 1970)
|
||||
* 0x00300 - 0x00317: Reserved (leave zero)
|
||||
* 0x00318 - 0x0031B: Last prefetched program counter
|
||||
* 0x0031C - 0x0031F: Miscellaneous flags
|
||||
* | bit 0: Is CPU halted?
|
||||
|
@ -210,6 +210,9 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* Total size: 0x61000 (397,312) bytes
|
||||
*/
|
||||
|
||||
DECL_BITFIELD(GBASerializedVideoFlags, uint32_t);
|
||||
DECL_BITS(GBASerializedVideoFlags, Mode, 0, 2);
|
||||
|
||||
DECL_BITFIELD(GBASerializedHWFlags1, uint16_t);
|
||||
DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0);
|
||||
DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1);
|
||||
|
@ -267,7 +270,8 @@ struct GBASerializedState {
|
|||
|
||||
struct {
|
||||
int32_t nextEvent;
|
||||
int32_t reserved[6];
|
||||
int32_t reserved[5];
|
||||
GBASerializedVideoFlags flags;
|
||||
int32_t frameCounter;
|
||||
} video;
|
||||
|
||||
|
@ -322,8 +326,7 @@ struct GBASerializedState {
|
|||
uint32_t biosPrefetch;
|
||||
uint32_t cpuPrefetch[2];
|
||||
|
||||
uint32_t associatedStreamId;
|
||||
uint32_t reservedRr[5];
|
||||
uint32_t reservedCpu[6];
|
||||
|
||||
uint32_t lastPrefetchedPc;
|
||||
GBASerializedMiscFlags miscFlags;
|
||||
|
|
|
@ -18,8 +18,9 @@ mLOG_DECLARE_CATEGORY(GBA_VIDEO);
|
|||
|
||||
enum {
|
||||
VIDEO_HBLANK_PIXELS = 68,
|
||||
VIDEO_HDRAW_LENGTH = 1006,
|
||||
VIDEO_HBLANK_LENGTH = 226,
|
||||
VIDEO_HDRAW_LENGTH = 960,
|
||||
VIDEO_HBLANK_LENGTH = 272,
|
||||
VIDEO_HBLANK_FLIP = 46,
|
||||
VIDEO_HORIZONTAL_LENGTH = 1232,
|
||||
|
||||
VIDEO_VBLANK_PIXELS = 68,
|
||||
|
|
|
@ -489,16 +489,16 @@ static void DSVideoSoftwareRendererDrawGBAScanline(struct GBAVideoRenderer* rend
|
|||
}
|
||||
}
|
||||
|
||||
if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) {
|
||||
if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 3) {
|
||||
++softwareRenderer->bg[0].enabled;
|
||||
}
|
||||
if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) {
|
||||
if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 3) {
|
||||
++softwareRenderer->bg[1].enabled;
|
||||
}
|
||||
if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) {
|
||||
if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 3) {
|
||||
++softwareRenderer->bg[2].enabled;
|
||||
}
|
||||
if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) {
|
||||
if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 3) {
|
||||
++softwareRenderer->bg[3].enabled;
|
||||
}
|
||||
|
||||
|
|
|
@ -137,8 +137,10 @@ static void _postEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent event
|
|||
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
|
||||
MutexLock(&proxyRenderer->mutex);
|
||||
proxyRenderer->event = event;
|
||||
ConditionWake(&proxyRenderer->toThreadCond);
|
||||
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
|
||||
while (proxyRenderer->event) {
|
||||
ConditionWake(&proxyRenderer->toThreadCond);
|
||||
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
|
||||
}
|
||||
MutexUnlock(&proxyRenderer->mutex);
|
||||
}
|
||||
|
||||
|
@ -179,6 +181,7 @@ static THREAD_ENTRY _proxyThread(void* logger) {
|
|||
ThreadSetName("Proxy Renderer Thread");
|
||||
|
||||
MutexLock(&proxyRenderer->mutex);
|
||||
ConditionWake(&proxyRenderer->fromThreadCond);
|
||||
while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
|
||||
ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
|
||||
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
|
||||
|
|
|
@ -39,10 +39,7 @@ set(SIO_FILES
|
|||
set(EXTRA_FILES
|
||||
extra/audio-mixer.c
|
||||
extra/battlechip.c
|
||||
extra/proxy.c
|
||||
rr/mgm.c
|
||||
rr/rr.c
|
||||
rr/vbm.c)
|
||||
extra/proxy.c)
|
||||
|
||||
set(DEBUGGER_FILES
|
||||
debugger/cli.c)
|
||||
|
|
|
@ -355,8 +355,9 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c
|
|||
}
|
||||
return;
|
||||
}
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
|
||||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (strcmp("videoScale", option) == 0) {
|
||||
if (config != &core->config) {
|
||||
mCoreConfigCopyValue(&core->config, config, "videoScale");
|
||||
|
@ -369,6 +370,31 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c
|
|||
return;
|
||||
}
|
||||
#endif
|
||||
if (strcmp("hwaccelVideo", option) == 0) {
|
||||
struct GBAVideoRenderer* renderer = NULL;
|
||||
if (gbacore->renderer.outputBuffer) {
|
||||
renderer = &gbacore->renderer.d;
|
||||
}
|
||||
int fakeBool;
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (gbacore->glRenderer.outputTex != (unsigned) -1 && mCoreConfigGetIntValue(&core->config, "hwaccelVideo", &fakeBool) && fakeBool) {
|
||||
mCoreConfigGetIntValue(&core->config, "videoScale", &gbacore->glRenderer.scale);
|
||||
renderer = &gbacore->glRenderer.d;
|
||||
} else {
|
||||
gbacore->glRenderer.scale = 1;
|
||||
}
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
if (renderer && core->videoLogger) {
|
||||
gbacore->proxyRenderer.logger = core->videoLogger;
|
||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
|
||||
renderer = &gbacore->proxyRenderer.d;
|
||||
}
|
||||
#endif
|
||||
if (renderer) {
|
||||
GBAVideoAssociateRenderer(&gba->video, renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _GBACoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
|
||||
|
@ -540,8 +566,10 @@ static void _GBACoreReset(struct mCore* core) {
|
|||
int fakeBool;
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (gbacore->glRenderer.outputTex != (unsigned) -1 && mCoreConfigGetIntValue(&core->config, "hwaccelVideo", &fakeBool) && fakeBool) {
|
||||
renderer = &gbacore->glRenderer.d;
|
||||
mCoreConfigGetIntValue(&core->config, "videoScale", &gbacore->glRenderer.scale);
|
||||
renderer = &gbacore->glRenderer.d;
|
||||
} else {
|
||||
gbacore->glRenderer.scale = 1;
|
||||
}
|
||||
#endif
|
||||
#ifndef DISABLE_THREADING
|
||||
|
|
|
@ -157,8 +157,8 @@ void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
|
|||
if (!proxyRenderer->logger->block) {
|
||||
proxyRenderer->backend->deinit(proxyRenderer->backend);
|
||||
} else {
|
||||
proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_DEINIT);
|
||||
mVideoLoggerRendererFlush(proxyRenderer->logger);
|
||||
proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_DEINIT);
|
||||
}
|
||||
|
||||
mVideoLoggerRendererDeinit(proxyRenderer->logger);
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <mgba/internal/gba/cheats.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
#include <mgba/internal/gba/overrides.h>
|
||||
#include <mgba/internal/gba/rr/rr.h>
|
||||
|
||||
#include <mgba-util/patch.h>
|
||||
#include <mgba-util/crc32.h>
|
||||
|
@ -96,7 +95,6 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
|
|||
gba->luminanceSource = 0;
|
||||
gba->rtcSource = 0;
|
||||
gba->rumble = 0;
|
||||
gba->rr = 0;
|
||||
|
||||
gba->romVf = 0;
|
||||
gba->biosVf = 0;
|
||||
|
@ -170,7 +168,6 @@ void GBADestroy(struct GBA* gba) {
|
|||
GBAVideoDeinit(&gba->video);
|
||||
GBAAudioDeinit(&gba->audio);
|
||||
GBASIODeinit(&gba->sio);
|
||||
gba->rr = 0;
|
||||
mTimingDeinit(&gba->timing);
|
||||
mCoreCallbacksListDeinit(&gba->coreCallbacks);
|
||||
}
|
||||
|
@ -196,10 +193,8 @@ void GBAReset(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
|
||||
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) {
|
||||
gba->memory.savedata.maskWriteback = false;
|
||||
GBASavedataUnmask(&gba->memory.savedata);
|
||||
}
|
||||
gba->memory.savedata.maskWriteback = false;
|
||||
GBASavedataUnmask(&gba->memory.savedata);
|
||||
|
||||
gba->cpuBlocked = false;
|
||||
gba->earlyExit = false;
|
||||
|
@ -812,10 +807,6 @@ void GBAFrameStarted(struct GBA* gba) {
|
|||
void GBAFrameEnded(struct GBA* gba) {
|
||||
GBASavedataClean(&gba->memory.savedata, gba->video.frameCounter);
|
||||
|
||||
if (gba->rr) {
|
||||
gba->rr->nextFrame(gba->rr);
|
||||
}
|
||||
|
||||
if (gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
|
||||
struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
|
||||
size_t i;
|
||||
|
|
42
src/gba/io.c
|
@ -8,7 +8,6 @@
|
|||
#include <mgba/internal/arm/macros.h>
|
||||
#include <mgba/internal/gba/dma.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/rr/rr.h>
|
||||
#include <mgba/internal/gba/serialize.h>
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA_IO, "GBA I/O", "gba.io");
|
||||
|
@ -734,31 +733,24 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
||||
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
||||
} else {
|
||||
uint16_t input = 0;
|
||||
if (gba->keyCallback) {
|
||||
input = gba->keyCallback->readKeys(gba->keyCallback);
|
||||
if (gba->keySource) {
|
||||
*gba->keySource = input;
|
||||
}
|
||||
} else if (gba->keySource) {
|
||||
input = *gba->keySource;
|
||||
if (!gba->allowOpposingDirections) {
|
||||
unsigned rl = input & 0x030;
|
||||
unsigned ud = input & 0x0C0;
|
||||
input &= 0x30F;
|
||||
if (rl != 0x030) {
|
||||
input |= rl;
|
||||
}
|
||||
if (ud != 0x0C0) {
|
||||
input |= ud;
|
||||
}
|
||||
}
|
||||
uint16_t input = 0;
|
||||
if (gba->keyCallback) {
|
||||
input = gba->keyCallback->readKeys(gba->keyCallback);
|
||||
if (gba->keySource) {
|
||||
*gba->keySource = input;
|
||||
}
|
||||
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||
gba->rr->logInput(gba->rr, input);
|
||||
} else if (gba->keySource) {
|
||||
input = *gba->keySource;
|
||||
if (!gba->allowOpposingDirections) {
|
||||
unsigned rl = input & 0x030;
|
||||
unsigned ud = input & 0x0C0;
|
||||
input &= 0x30F;
|
||||
if (rl != 0x030) {
|
||||
input |= rl;
|
||||
}
|
||||
if (ud != 0x0C0) {
|
||||
input |= ud;
|
||||
}
|
||||
}
|
||||
return 0x3FF ^ input;
|
||||
}
|
||||
|
|
|
@ -1626,17 +1626,19 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) {
|
|||
maxLoads -= previousLoads;
|
||||
}
|
||||
|
||||
int32_t s = cpu->memory.activeSeqCycles16 + 1;
|
||||
int32_t s = cpu->memory.activeSeqCycles16;
|
||||
int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1;
|
||||
|
||||
// Figure out how many sequential loads we can jam in
|
||||
int32_t stall = s;
|
||||
int32_t stall = s + 1;
|
||||
int32_t loads = 1;
|
||||
|
||||
while (stall < wait && loads < maxLoads) {
|
||||
stall += s;
|
||||
++loads;
|
||||
}
|
||||
memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1);
|
||||
|
||||
if (stall > wait) {
|
||||
// The wait cannot take less time than the prefetch stalls
|
||||
wait = stall;
|
||||
|
@ -1645,10 +1647,9 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) {
|
|||
// This instruction used to have an N, convert it to an S.
|
||||
wait -= n2s;
|
||||
|
||||
memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1);
|
||||
|
||||
// The next |loads|S waitstates disappear entirely, so long as they're all in a row
|
||||
cpu->cycles -= (s - 1) * loads;
|
||||
wait -= stall - 1;
|
||||
|
||||
return wait;
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,6 @@ static const struct GBAVideoGLUniform _uniformsMode0[] = {
|
|||
{ "charBase", GBA_GL_BG_CHARBASE, },
|
||||
{ "size", GBA_GL_BG_SIZE, },
|
||||
{ "offset", GBA_GL_BG_OFFSET, },
|
||||
{ "inflags", GBA_GL_BG_INFLAGS, },
|
||||
{ "mosaic", GBA_GL_BG_MOSAIC, },
|
||||
{ 0 }
|
||||
};
|
||||
|
@ -130,10 +129,8 @@ static const char* const _renderMode0 =
|
|||
"uniform int charBase;\n"
|
||||
"uniform int size;\n"
|
||||
"uniform int offset[160];\n"
|
||||
"uniform ivec4 inflags;\n"
|
||||
"uniform ivec2 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\n"
|
||||
"OUT(1) out ivec4 flags;\n"
|
||||
|
||||
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||
|
||||
|
@ -171,7 +168,6 @@ static const char* const _renderMode0 =
|
|||
" }\n"
|
||||
" int tile = int(map.a * 15.9) + int(map.b * 15.9) * 16 + (tileFlags & 0x3) * 256;\n"
|
||||
" color = renderTile(tile, int(map.r * 15.9), coord & 7);\n"
|
||||
" flags = inflags;\n"
|
||||
"}";
|
||||
|
||||
static const char* const _fetchTileOverflow =
|
||||
|
@ -199,7 +195,6 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = {
|
|||
{ "screenBase", GBA_GL_BG_SCREENBASE, },
|
||||
{ "charBase", GBA_GL_BG_CHARBASE, },
|
||||
{ "size", GBA_GL_BG_SIZE, },
|
||||
{ "inflags", GBA_GL_BG_INFLAGS, },
|
||||
{ "offset", GBA_GL_BG_OFFSET, },
|
||||
{ "transform", GBA_GL_BG_TRANSFORM, },
|
||||
{ "range", GBA_GL_BG_RANGE, },
|
||||
|
@ -235,12 +230,10 @@ static const char* const _renderMode2 =
|
|||
"uniform int screenBase;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform int size;\n"
|
||||
"uniform ivec4 inflags;\n"
|
||||
"uniform ivec4 transform[160];\n"
|
||||
"uniform ivec2 range;\n"
|
||||
"uniform ivec2 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\n"
|
||||
"OUT(1) out ivec4 flags;\n"
|
||||
|
||||
"vec4 fetchTile(ivec2 coord);\n"
|
||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
||||
|
@ -284,7 +277,6 @@ static const char* const _renderMode2 =
|
|||
" vec2 mixedTransform = interpolate(mat, lin);\n"
|
||||
" vec2 mixedOffset = interpolate(offset, lin);\n"
|
||||
" color = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
|
||||
" flags = inflags;\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsMode35[] = {
|
||||
|
@ -293,7 +285,6 @@ static const struct GBAVideoGLUniform _uniformsMode35[] = {
|
|||
{ "vram", GBA_GL_BG_VRAM, },
|
||||
{ "charBase", GBA_GL_BG_CHARBASE, },
|
||||
{ "size", GBA_GL_BG_SIZE, },
|
||||
{ "inflags", GBA_GL_BG_INFLAGS, },
|
||||
{ "offset", GBA_GL_BG_OFFSET, },
|
||||
{ "transform", GBA_GL_BG_TRANSFORM, },
|
||||
{ "range", GBA_GL_BG_RANGE, },
|
||||
|
@ -306,12 +297,10 @@ static const char* const _renderMode35 =
|
|||
"uniform sampler2D vram;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform ivec2 size;\n"
|
||||
"uniform ivec4 inflags;\n"
|
||||
"uniform ivec4 transform[160];\n"
|
||||
"uniform ivec2 range;\n"
|
||||
"uniform ivec2 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\n"
|
||||
"OUT(1) out ivec4 flags;\n"
|
||||
|
||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
||||
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
|
||||
|
@ -347,7 +336,6 @@ static const char* const _renderMode35 =
|
|||
" ivec4 entry = ivec4(texelFetch(vram, ivec2(address & 255, address >> 8), 0) * 15.9);\n"
|
||||
" int sixteen = (entry.x << 12) | (entry.y << 8) | (entry.z << 4) | entry.w;\n"
|
||||
" color = vec4(float(sixteen & 0x1F) / 31., float((sixteen >> 5) & 0x1F) / 31., float((sixteen >> 10) & 0x1F) / 31., 1.);\n"
|
||||
" flags = inflags;\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsMode4[] = {
|
||||
|
@ -357,7 +345,6 @@ static const struct GBAVideoGLUniform _uniformsMode4[] = {
|
|||
{ "palette", GBA_GL_BG_PALETTE, },
|
||||
{ "charBase", GBA_GL_BG_CHARBASE, },
|
||||
{ "size", GBA_GL_BG_SIZE, },
|
||||
{ "inflags", GBA_GL_BG_INFLAGS, },
|
||||
{ "offset", GBA_GL_BG_OFFSET, },
|
||||
{ "transform", GBA_GL_BG_TRANSFORM, },
|
||||
{ "range", GBA_GL_BG_RANGE, },
|
||||
|
@ -371,12 +358,10 @@ static const char* const _renderMode4 =
|
|||
"uniform int palette[256];\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform ivec2 size;\n"
|
||||
"uniform ivec4 inflags;\n"
|
||||
"uniform ivec4 transform[160];\n"
|
||||
"uniform ivec2 range;\n"
|
||||
"uniform ivec2 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\n"
|
||||
"OUT(1) out ivec4 flags;\n"
|
||||
|
||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
||||
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
|
||||
|
@ -413,7 +398,6 @@ static const char* const _renderMode4 =
|
|||
" ivec2 entry = ivec2(twoEntries[3 - 2 * (address & 1)] * 15.9, twoEntries[2 - 2 * (address & 1)] * 15.9);\n"
|
||||
" int paletteEntry = palette[entry.y * 16 + entry.x];\n"
|
||||
" color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
|
||||
" flags = inflags;\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsObj[] = {
|
||||
|
@ -567,7 +551,7 @@ static const struct GBAVideoGLUniform _uniformsFinalize[] = {
|
|||
{ "maxPos", GBA_GL_VS_MAXPOS, },
|
||||
{ "scale", GBA_GL_FINALIZE_SCALE, },
|
||||
{ "layers", GBA_GL_FINALIZE_LAYERS, },
|
||||
{ "flags", GBA_GL_FINALIZE_FLAGS, },
|
||||
{ "objFlags", GBA_GL_FINALIZE_FLAGS, },
|
||||
{ "window", GBA_GL_FINALIZE_WINDOW, },
|
||||
{ "backdrop", GBA_GL_FINALIZE_BACKDROP, },
|
||||
{ "backdropFlags", GBA_GL_FINALIZE_BACKDROPFLAGS, },
|
||||
|
@ -578,7 +562,7 @@ static const char* const _finalize =
|
|||
"in vec2 texCoord;\n"
|
||||
"uniform int scale;\n"
|
||||
"uniform sampler2D layers[5];\n"
|
||||
"uniform isampler2D flags[5];\n"
|
||||
"uniform isampler2D objFlags;\n"
|
||||
"uniform isampler2D window;\n"
|
||||
"uniform sampler2D backdrop;\n"
|
||||
"uniform isampler2D backdropFlags;\n"
|
||||
|
@ -610,35 +594,35 @@ static const char* const _finalize =
|
|||
" if ((layerWindow & 16) != 0) {\n"
|
||||
" vec4 pix = texelFetch(layers[4], coord, 0);\n"
|
||||
" if (pix.a != 0.) {\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(flags[4], coord, 0));\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(objFlags, coord, 0));\n"
|
||||
" composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if ((layerWindow & 1) != 0) {\n"
|
||||
" vec4 pix = texelFetch(layers[0], coord, 0);\n"
|
||||
" if (pix.a != 0.) {\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(flags[0], coord, 0).xyz, 0);\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(backdropFlags, ivec2(1, texCoord.y), 0));\n"
|
||||
" composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if ((layerWindow & 2) != 0) {\n"
|
||||
" vec4 pix = texelFetch(layers[1], coord, 0);\n"
|
||||
" if (pix.a != 0.) {\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(flags[1], coord, 0).xyz, 0);\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(backdropFlags, ivec2(2, texCoord.y), 0));\n"
|
||||
" composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if ((layerWindow & 4) != 0) {\n"
|
||||
" vec4 pix = texelFetch(layers[2], coord, 0);\n"
|
||||
" if (pix.a != 0.) {\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(flags[2], coord, 0).xyz, 0);\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(backdropFlags, ivec2(3, texCoord.y), 0));\n"
|
||||
" composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if ((layerWindow & 8) != 0) {\n"
|
||||
" vec4 pix = texelFetch(layers[3], coord, 0);\n"
|
||||
" if (pix.a != 0.) {\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(flags[3], coord, 0).xyz, 0);\n"
|
||||
" ivec4 inflags = ivec4(texelFetch(backdropFlags, ivec2(4, texCoord.y), 0));\n"
|
||||
" composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
|
@ -747,7 +731,7 @@ static void _initFramebufferTextureEx(GLuint tex, GLenum internalFormat, GLenum
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scale > 0 ? GBA_VIDEO_HORIZONTAL_PIXELS * scale : 1, GBA_VIDEO_VERTICAL_PIXELS * (scale > 0 ? scale : 1), 0, format, type, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scale > 0 ? GBA_VIDEO_HORIZONTAL_PIXELS * scale : 8, GBA_VIDEO_VERTICAL_PIXELS * (scale > 0 ? scale : 1), 0, format, type, 0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex, 0);
|
||||
}
|
||||
|
||||
|
@ -764,7 +748,7 @@ static void _initFramebuffers(struct GBAVideoGLRenderer* glRenderer) {
|
|||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
|
||||
_initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_BACKDROP_COLOR], GL_RGB, GL_COLOR_ATTACHMENT0, 0);
|
||||
_initFramebufferTextureEx(glRenderer->layers[GBA_GL_TEX_BACKDROP_FLAGS], GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, GL_COLOR_ATTACHMENT1, glRenderer->scale);
|
||||
_initFramebufferTextureEx(glRenderer->layers[GBA_GL_TEX_BACKDROP_FLAGS], GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, GL_COLOR_ATTACHMENT1, 0);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]);
|
||||
_initFramebufferTextureEx(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, GL_COLOR_ATTACHMENT0, glRenderer->scale);
|
||||
|
@ -777,7 +761,6 @@ static void _initFramebuffers(struct GBAVideoGLRenderer* glRenderer) {
|
|||
struct GBAVideoGLBackground* bg = &glRenderer->bg[i];
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, bg->fbo);
|
||||
_initFramebufferTexture(bg->tex, GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale);
|
||||
_initFramebufferTextureEx(bg->flags, GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, GL_COLOR_ATTACHMENT1, glRenderer->scale);
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
@ -825,7 +808,6 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
|
|||
bg->affine.sy = 0;
|
||||
glGenFramebuffers(1, &bg->fbo);
|
||||
glGenTextures(1, &bg->tex);
|
||||
glGenTextures(1, &bg->flags);
|
||||
}
|
||||
|
||||
_initFramebuffers(glRenderer);
|
||||
|
@ -924,7 +906,6 @@ void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer) {
|
|||
struct GBAVideoGLBackground* bg = &glRenderer->bg[i];
|
||||
glDeleteFramebuffers(1, &bg->fbo);
|
||||
glDeleteTextures(1, &bg->tex);
|
||||
glDeleteTextures(1, &bg->flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1400,12 +1381,12 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
|||
#endif
|
||||
glClearStencil(0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]);
|
||||
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->bg[i].fbo);
|
||||
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
|
@ -1428,11 +1409,21 @@ void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) {
|
|||
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
||||
glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xF8) / 248., ((backdrop >> 8) & 0xF8) / 248., (backdrop & 0xF8) / 248., 1.f });
|
||||
glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 });
|
||||
|
||||
glDrawBuffers(2, (GLenum[]) { GL_NONE, GL_COLOR_ATTACHMENT1 });
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
glScissor(i + 1, glRenderer->firstY, i + 2, y - glRenderer->firstY + 1);
|
||||
glClearBufferiv(GL_COLOR, 1, (GLint[]) { glRenderer->bg[i].priority,
|
||||
glRenderer->bg[i].target1 | (glRenderer->bg[i].target2 << 1) | (glRenderer->blendEffect << 2),
|
||||
glRenderer->blda, 0 });
|
||||
}
|
||||
|
||||
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
|
||||
GBAVideoGLRendererDrawWindow(glRenderer, y);
|
||||
if (GBARegisterDISPCNTIsObjEnable(glRenderer->dispcnt) && !glRenderer->d.disableOBJ) {
|
||||
int i;
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glDepthFunc(GL_LESS);
|
||||
|
@ -1615,33 +1606,25 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) {
|
|||
glActiveTexture(GL_TEXTURE0 + 3);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[0].tex);
|
||||
glActiveTexture(GL_TEXTURE0 + 4);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[0].flags);
|
||||
glActiveTexture(GL_TEXTURE0 + 5);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[1].tex);
|
||||
glActiveTexture(GL_TEXTURE0 + 6);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[1].flags);
|
||||
glActiveTexture(GL_TEXTURE0 + 7);
|
||||
glActiveTexture(GL_TEXTURE0 + 5);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[2].tex);
|
||||
glActiveTexture(GL_TEXTURE0 + 8);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[2].flags);
|
||||
glActiveTexture(GL_TEXTURE0 + 9);
|
||||
glActiveTexture(GL_TEXTURE0 + 6);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[3].tex);
|
||||
glActiveTexture(GL_TEXTURE0 + 10);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[3].flags);
|
||||
glActiveTexture(GL_TEXTURE0 + 11);
|
||||
glActiveTexture(GL_TEXTURE0 + 7);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_COLOR]);
|
||||
glActiveTexture(GL_TEXTURE0 + 12);
|
||||
glActiveTexture(GL_TEXTURE0 + 8);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_FLAGS]);
|
||||
|
||||
glUniform2i(uniforms[GBA_GL_VS_LOC], GBA_VIDEO_VERTICAL_PIXELS, 0);
|
||||
glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_SCALE], renderer->scale);
|
||||
glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 5, 7, 9, 1 });
|
||||
glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 });
|
||||
glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 4, 5, 6, 1 });
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_FLAGS], 2);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 11);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 12);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 7);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 8);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
@ -1734,13 +1717,13 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
glStencilFunc(GL_EQUAL, 1, 1);
|
||||
glUseProgram(shader->program);
|
||||
glDrawBuffers(2, (GLenum[]) { GL_NONE, GL_COLOR_ATTACHMENT1 });
|
||||
glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||
glBindVertexArray(shader->vao);
|
||||
glUniform2i(uniforms[GBA_GL_VS_LOC], totalHeight, 0);
|
||||
glUniform2i(uniforms[GBA_GL_VS_MAXPOS], totalWidth, totalHeight);
|
||||
glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c), 0, 0, 0);
|
||||
glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c),
|
||||
(renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (renderer->blendEffect * 4),
|
||||
renderer->blda, GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
}
|
||||
|
@ -1758,10 +1741,7 @@ void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBa
|
|||
} else {
|
||||
glUniform2i(uniforms[GBA_GL_BG_MOSAIC], 0, 0);
|
||||
}
|
||||
glUniform4i(uniforms[GBA_GL_BG_INFLAGS], background->priority,
|
||||
background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4),
|
||||
renderer->blda, 0);
|
||||
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
}
|
||||
|
||||
void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) {
|
||||
|
|
|
@ -234,7 +234,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
}
|
||||
|
||||
#define TEST_LAYER_ENABLED(X) \
|
||||
(softwareRenderer->bg[X].enabled == 4 && \
|
||||
(softwareRenderer->bg[X].enabled == 3 && \
|
||||
(GBAWindowControlIsBg ## X ## Enable(softwareRenderer->currentWindow.packed) || \
|
||||
(GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt) && GBAWindowControlIsBg ## X ## Enable (softwareRenderer->objwin.packed))) && \
|
||||
softwareRenderer->bg[X].priority == priority)
|
||||
|
|
|
@ -691,19 +691,19 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
|
||||
}
|
||||
|
||||
if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) {
|
||||
if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 3) {
|
||||
++softwareRenderer->bg[0].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) {
|
||||
if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 3) {
|
||||
++softwareRenderer->bg[1].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) {
|
||||
if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 3) {
|
||||
++softwareRenderer->bg[2].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) {
|
||||
if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 3) {
|
||||
++softwareRenderer->bg[3].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
|
@ -737,16 +737,16 @@ static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* rendere
|
|||
softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
|
||||
|
||||
if (softwareRenderer->bg[0].enabled > 0) {
|
||||
softwareRenderer->bg[0].enabled = 4;
|
||||
softwareRenderer->bg[0].enabled = 3;
|
||||
}
|
||||
if (softwareRenderer->bg[1].enabled > 0) {
|
||||
softwareRenderer->bg[1].enabled = 4;
|
||||
softwareRenderer->bg[1].enabled = 3;
|
||||
}
|
||||
if (softwareRenderer->bg[2].enabled > 0) {
|
||||
softwareRenderer->bg[2].enabled = 4;
|
||||
softwareRenderer->bg[2].enabled = 3;
|
||||
}
|
||||
if (softwareRenderer->bg[3].enabled > 0) {
|
||||
softwareRenderer->bg[3].enabled = 4;
|
||||
softwareRenderer->bg[3].enabled = 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -773,7 +773,7 @@ static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool ac
|
|||
} else if (!wasActive && active) {
|
||||
if (renderer->nextY == 0 || GBARegisterDISPCNTGetMode(renderer->dispcnt) > 2) {
|
||||
// TODO: Investigate in more depth how switching background works in different modes
|
||||
renderer->bg[bg].enabled = 4;
|
||||
renderer->bg[bg].enabled = 3;
|
||||
} else {
|
||||
renderer->bg[bg].enabled = 1;
|
||||
}
|
||||
|
|
590
src/gba/rr/mgm.c
|
@ -1,590 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/gba/rr/mgm.h>
|
||||
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/serialize.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#define BINARY_EXT ".mgm"
|
||||
#define BINARY_MAGIC "GBAb"
|
||||
#define METADATA_FILENAME "metadata" BINARY_EXT
|
||||
|
||||
enum {
|
||||
INVALID_INPUT = 0x8000
|
||||
};
|
||||
|
||||
static void GBAMGMContextDestroy(struct GBARRContext*);
|
||||
|
||||
static bool GBAMGMStartPlaying(struct GBARRContext*, bool autorecord);
|
||||
static void GBAMGMStopPlaying(struct GBARRContext*);
|
||||
static bool GBAMGMStartRecording(struct GBARRContext*);
|
||||
static void GBAMGMStopRecording(struct GBARRContext*);
|
||||
|
||||
static bool GBAMGMIsPlaying(const struct GBARRContext*);
|
||||
static bool GBAMGMIsRecording(const struct GBARRContext*);
|
||||
|
||||
static void GBAMGMNextFrame(struct GBARRContext*);
|
||||
static void GBAMGMLogInput(struct GBARRContext*, uint16_t input);
|
||||
static uint16_t GBAMGMQueryInput(struct GBARRContext*);
|
||||
static bool GBAMGMQueryReset(struct GBARRContext*);
|
||||
|
||||
static void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||
static void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||
|
||||
static bool _loadStream(struct GBAMGMContext*, uint32_t streamId);
|
||||
static bool _incrementStream(struct GBAMGMContext*, bool recursive);
|
||||
static bool _finishSegment(struct GBAMGMContext*);
|
||||
static bool _skipSegment(struct GBAMGMContext*);
|
||||
static bool _markRerecord(struct GBAMGMContext*);
|
||||
|
||||
static bool _emitMagic(struct GBAMGMContext*, struct VFile* vf);
|
||||
static bool _verifyMagic(struct GBAMGMContext*, struct VFile* vf);
|
||||
static enum GBAMGMTag _readTag(struct GBAMGMContext*, struct VFile* vf);
|
||||
static bool _seekTag(struct GBAMGMContext*, struct VFile* vf, enum GBAMGMTag tag);
|
||||
static bool _emitTag(struct GBAMGMContext*, struct VFile* vf, uint8_t tag);
|
||||
static bool _emitEnd(struct GBAMGMContext*, struct VFile* vf);
|
||||
|
||||
static bool _parseMetadata(struct GBAMGMContext*, struct VFile* vf);
|
||||
|
||||
static bool _markStreamNext(struct GBAMGMContext*, uint32_t newStreamId, bool recursive);
|
||||
static void _streamEndReached(struct GBAMGMContext*);
|
||||
|
||||
static struct VFile* GBAMGMOpenSavedata(struct GBARRContext*, int flags);
|
||||
static struct VFile* GBAMGMOpenSavestate(struct GBARRContext*, int flags);
|
||||
|
||||
void GBAMGMContextCreate(struct GBAMGMContext* mgm) {
|
||||
memset(mgm, 0, sizeof(*mgm));
|
||||
|
||||
mgm->d.destroy = GBAMGMContextDestroy;
|
||||
|
||||
mgm->d.startPlaying = GBAMGMStartPlaying;
|
||||
mgm->d.stopPlaying = GBAMGMStopPlaying;
|
||||
mgm->d.startRecording = GBAMGMStartRecording;
|
||||
mgm->d.stopRecording = GBAMGMStopRecording;
|
||||
|
||||
mgm->d.isPlaying = GBAMGMIsPlaying;
|
||||
mgm->d.isRecording = GBAMGMIsRecording;
|
||||
|
||||
mgm->d.nextFrame = GBAMGMNextFrame;
|
||||
mgm->d.logInput = GBAMGMLogInput;
|
||||
mgm->d.queryInput = GBAMGMQueryInput;
|
||||
mgm->d.queryReset = GBAMGMQueryReset;
|
||||
|
||||
mgm->d.stateSaved = GBAMGMStateSaved;
|
||||
mgm->d.stateLoaded = GBAMGMStateLoaded;
|
||||
|
||||
mgm->d.openSavedata = GBAMGMOpenSavedata;
|
||||
mgm->d.openSavestate = GBAMGMOpenSavestate;
|
||||
}
|
||||
|
||||
void GBAMGMContextDestroy(struct GBARRContext* rr) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (mgm->metadataFile) {
|
||||
mgm->metadataFile->close(mgm->metadataFile);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream) {
|
||||
if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mgm->metadataFile && !mgm->metadataFile->close(mgm->metadataFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mgm->streamDir = stream;
|
||||
mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
|
||||
mgm->currentInput = INVALID_INPUT;
|
||||
if (!_parseMetadata(mgm, mgm->metadataFile)) {
|
||||
mgm->metadataFile->close(mgm->metadataFile);
|
||||
mgm->metadataFile = 0;
|
||||
mgm->maxStreamId = 0;
|
||||
}
|
||||
mgm->streamId = 1;
|
||||
mgm->movieStream = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom) {
|
||||
if (mgm->metadataFile) {
|
||||
mgm->metadataFile->truncate(mgm->metadataFile, 0);
|
||||
} else {
|
||||
mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
|
||||
}
|
||||
_emitMagic(mgm, mgm->metadataFile);
|
||||
|
||||
mgm->d.initFrom = initFrom;
|
||||
mgm->initFromOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_INIT | initFrom);
|
||||
|
||||
mgm->streamId = 0;
|
||||
mgm->maxStreamId = 0;
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM);
|
||||
mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
|
||||
mgm->d.rrCount = 0;
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_RR_COUNT);
|
||||
mgm->rrCountOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _loadStream(struct GBAMGMContext* mgm, uint32_t streamId) {
|
||||
if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
mgm->movieStream = 0;
|
||||
mgm->streamId = streamId;
|
||||
mgm->currentInput = INVALID_INPUT;
|
||||
char buffer[14];
|
||||
snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
|
||||
if (mgm->d.isRecording(&mgm->d)) {
|
||||
int flags = O_CREAT | O_RDWR;
|
||||
if (streamId > mgm->maxStreamId) {
|
||||
flags |= O_TRUNC;
|
||||
}
|
||||
mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, flags);
|
||||
} else if (mgm->d.isPlaying(&mgm->d)) {
|
||||
mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, O_RDONLY);
|
||||
mgm->peekedTag = TAG_INVALID;
|
||||
if (!mgm->movieStream || !_verifyMagic(mgm, mgm->movieStream) || !_seekTag(mgm, mgm->movieStream, TAG_BEGIN)) {
|
||||
mgm->d.stopPlaying(&mgm->d);
|
||||
}
|
||||
}
|
||||
mLOG(GBA_RR, DEBUG, "Loading segment: %u", streamId);
|
||||
mgm->d.frames = 0;
|
||||
mgm->d.lagFrames = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _incrementStream(struct GBAMGMContext* mgm, bool recursive) {
|
||||
uint32_t newStreamId = mgm->maxStreamId + 1;
|
||||
uint32_t oldStreamId = mgm->streamId;
|
||||
if (mgm->d.isRecording(&mgm->d) && mgm->movieStream) {
|
||||
if (!_markStreamNext(mgm, newStreamId, recursive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!_loadStream(mgm, newStreamId)) {
|
||||
return false;
|
||||
}
|
||||
mLOG(GBA_RR, DEBUG, "New segment: %u", newStreamId);
|
||||
_emitMagic(mgm, mgm->movieStream);
|
||||
mgm->maxStreamId = newStreamId;
|
||||
_emitTag(mgm, mgm->movieStream, TAG_PREVIOUSLY);
|
||||
mgm->movieStream->write(mgm->movieStream, &oldStreamId, sizeof(oldStreamId));
|
||||
_emitTag(mgm, mgm->movieStream, TAG_BEGIN);
|
||||
|
||||
mgm->metadataFile->seek(mgm->metadataFile, mgm->maxStreamIdOffset, SEEK_SET);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
mgm->previously = oldStreamId;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAMGMStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||
if (rr->isRecording(rr) || rr->isPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
mgm->isPlaying = true;
|
||||
if (!_loadStream(mgm, 1)) {
|
||||
mgm->isPlaying = false;
|
||||
return false;
|
||||
}
|
||||
mgm->autorecord = autorecord;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAMGMStopPlaying(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
mgm->isPlaying = false;
|
||||
if (mgm->movieStream) {
|
||||
mgm->movieStream->close(mgm->movieStream);
|
||||
mgm->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAMGMStartRecording(struct GBARRContext* rr) {
|
||||
if (rr->isRecording(rr) || rr->isPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (!mgm->maxStreamIdOffset) {
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM);
|
||||
mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
}
|
||||
|
||||
mgm->isRecording = true;
|
||||
return _incrementStream(mgm, false);
|
||||
}
|
||||
|
||||
void GBAMGMStopRecording(struct GBARRContext* rr) {
|
||||
if (!rr->isRecording(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
mgm->isRecording = false;
|
||||
if (mgm->movieStream) {
|
||||
_emitEnd(mgm, mgm->movieStream);
|
||||
mgm->movieStream->close(mgm->movieStream);
|
||||
mgm->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAMGMIsPlaying(const struct GBARRContext* rr) {
|
||||
const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr;
|
||||
return mgm->isPlaying;
|
||||
}
|
||||
|
||||
bool GBAMGMIsRecording(const struct GBARRContext* rr) {
|
||||
const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr;
|
||||
return mgm->isRecording;
|
||||
}
|
||||
|
||||
void GBAMGMNextFrame(struct GBARRContext* rr) {
|
||||
if (!rr->isRecording(rr) && !rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isPlaying(rr)) {
|
||||
while (mgm->peekedTag == TAG_INPUT) {
|
||||
_readTag(mgm, mgm->movieStream);
|
||||
mLOG(GBA_RR, WARN, "Desync detected!");
|
||||
}
|
||||
if (mgm->peekedTag == TAG_LAG) {
|
||||
mLOG(GBA_RR, DEBUG, "Lag frame marked in stream");
|
||||
if (mgm->inputThisFrame) {
|
||||
mLOG(GBA_RR, WARN, "Lag frame in stream does not match movie");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++mgm->d.frames;
|
||||
mLOG(GBA_RR, DEBUG, "Frame: %u", mgm->d.frames);
|
||||
if (!mgm->inputThisFrame) {
|
||||
++mgm->d.lagFrames;
|
||||
mLOG(GBA_RR, DEBUG, "Lag frame: %u", mgm->d.lagFrames);
|
||||
}
|
||||
|
||||
if (rr->isRecording(rr)) {
|
||||
if (!mgm->inputThisFrame) {
|
||||
_emitTag(mgm, mgm->movieStream, TAG_LAG);
|
||||
}
|
||||
_emitTag(mgm, mgm->movieStream, TAG_FRAME);
|
||||
mgm->inputThisFrame = false;
|
||||
} else {
|
||||
if (!_seekTag(mgm, mgm->movieStream, TAG_FRAME)) {
|
||||
_streamEndReached(mgm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBAMGMLogInput(struct GBARRContext* rr, uint16_t keys) {
|
||||
if (!rr->isRecording(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (keys != mgm->currentInput) {
|
||||
_emitTag(mgm, mgm->movieStream, TAG_INPUT);
|
||||
mgm->movieStream->write(mgm->movieStream, &keys, sizeof(keys));
|
||||
mgm->currentInput = keys;
|
||||
}
|
||||
mLOG(GBA_RR, DEBUG, "Input log: %03X", mgm->currentInput);
|
||||
mgm->inputThisFrame = true;
|
||||
}
|
||||
|
||||
uint16_t GBAMGMQueryInput(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (mgm->peekedTag == TAG_INPUT) {
|
||||
_readTag(mgm, mgm->movieStream);
|
||||
}
|
||||
mgm->inputThisFrame = true;
|
||||
if (mgm->currentInput == INVALID_INPUT) {
|
||||
mLOG(GBA_RR, WARN, "Stream did not specify input");
|
||||
}
|
||||
mLOG(GBA_RR, DEBUG, "Input replay: %03X", mgm->currentInput);
|
||||
return mgm->currentInput;
|
||||
}
|
||||
|
||||
bool GBAMGMQueryReset(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
return mgm->peekedTag == TAG_RESET;
|
||||
}
|
||||
|
||||
void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isRecording(rr)) {
|
||||
state->associatedStreamId = mgm->streamId;
|
||||
_finishSegment(mgm);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isRecording(rr)) {
|
||||
if (state->associatedStreamId != mgm->streamId) {
|
||||
_loadStream(mgm, state->associatedStreamId);
|
||||
_incrementStream(mgm, true);
|
||||
} else {
|
||||
_finishSegment(mgm);
|
||||
}
|
||||
_markRerecord(mgm);
|
||||
} else if (rr->isPlaying(rr)) {
|
||||
_loadStream(mgm, state->associatedStreamId);
|
||||
_skipSegment(mgm);
|
||||
}
|
||||
}
|
||||
|
||||
bool _finishSegment(struct GBAMGMContext* mgm) {
|
||||
if (mgm->movieStream) {
|
||||
if (!_emitEnd(mgm, mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return _incrementStream(mgm, false);
|
||||
}
|
||||
|
||||
bool _skipSegment(struct GBAMGMContext* mgm) {
|
||||
mgm->nextTime = 0;
|
||||
while (_readTag(mgm, mgm->movieStream) != TAG_EOF);
|
||||
if (!mgm->nextTime || !_loadStream(mgm, mgm->nextTime)) {
|
||||
_streamEndReached(mgm);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _markRerecord(struct GBAMGMContext* mgm) {
|
||||
++mgm->d.rrCount;
|
||||
mgm->metadataFile->seek(mgm->metadataFile, mgm->rrCountOffset, SEEK_SET);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitMagic(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
UNUSED(mgm);
|
||||
return vf->write(vf, BINARY_MAGIC, 4) == 4;
|
||||
}
|
||||
|
||||
bool _verifyMagic(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
UNUSED(mgm);
|
||||
char buffer[4];
|
||||
if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum GBAMGMTag _readTag(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
if (!mgm || !vf) {
|
||||
return TAG_EOF;
|
||||
}
|
||||
|
||||
enum GBAMGMTag tag = mgm->peekedTag;
|
||||
switch (tag) {
|
||||
case TAG_INPUT:
|
||||
vf->read(vf, &mgm->currentInput, sizeof(uint16_t));
|
||||
break;
|
||||
case TAG_PREVIOUSLY:
|
||||
vf->read(vf, &mgm->previously, sizeof(mgm->previously));
|
||||
break;
|
||||
case TAG_NEXT_TIME:
|
||||
vf->read(vf, &mgm->nextTime, sizeof(mgm->nextTime));
|
||||
break;
|
||||
case TAG_MAX_STREAM:
|
||||
vf->read(vf, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
break;
|
||||
case TAG_FRAME_COUNT:
|
||||
vf->read(vf, &mgm->d.frames, sizeof(mgm->d.frames));
|
||||
break;
|
||||
case TAG_LAG_COUNT:
|
||||
vf->read(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames));
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
vf->read(vf, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||
break;
|
||||
|
||||
case TAG_INIT_EX_NIHILO:
|
||||
mgm->d.initFrom = INIT_EX_NIHILO;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVEGAME:
|
||||
mgm->d.initFrom = INIT_FROM_SAVEGAME;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVESTATE:
|
||||
mgm->d.initFrom = INIT_FROM_SAVESTATE;
|
||||
break;
|
||||
case TAG_INIT_FROM_BOTH:
|
||||
mgm->d.initFrom = INIT_FROM_BOTH;
|
||||
break;
|
||||
|
||||
// To be spec'd
|
||||
case TAG_AUTHOR:
|
||||
case TAG_COMMENT:
|
||||
break;
|
||||
|
||||
// Empty markers
|
||||
case TAG_FRAME:
|
||||
case TAG_LAG:
|
||||
case TAG_RESET:
|
||||
case TAG_BEGIN:
|
||||
case TAG_END:
|
||||
case TAG_INVALID:
|
||||
case TAG_EOF:
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t tagBuffer;
|
||||
if (vf->read(vf, &tagBuffer, 1) != 1) {
|
||||
mgm->peekedTag = TAG_EOF;
|
||||
} else {
|
||||
mgm->peekedTag = tagBuffer;
|
||||
}
|
||||
|
||||
if (mgm->peekedTag == TAG_END) {
|
||||
_skipSegment(mgm);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
bool _seekTag(struct GBAMGMContext* mgm, struct VFile* vf, enum GBAMGMTag tag) {
|
||||
enum GBAMGMTag readTag;
|
||||
while ((readTag = _readTag(mgm, vf)) != tag) {
|
||||
if (readTag == TAG_EOF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitTag(struct GBAMGMContext* mgm, struct VFile* vf, uint8_t tag) {
|
||||
UNUSED(mgm);
|
||||
return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
|
||||
}
|
||||
|
||||
bool _parseMetadata(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
if (!_verifyMagic(mgm, vf)) {
|
||||
return false;
|
||||
}
|
||||
while (_readTag(mgm, vf) != TAG_EOF) {
|
||||
switch (mgm->peekedTag) {
|
||||
case TAG_MAX_STREAM:
|
||||
mgm->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
case TAG_INIT_EX_NIHILO:
|
||||
case TAG_INIT_FROM_SAVEGAME:
|
||||
case TAG_INIT_FROM_SAVESTATE:
|
||||
case TAG_INIT_FROM_BOTH:
|
||||
mgm->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
mgm->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitEnd(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
// TODO: Error check
|
||||
_emitTag(mgm, vf, TAG_END);
|
||||
_emitTag(mgm, vf, TAG_FRAME_COUNT);
|
||||
vf->write(vf, &mgm->d.frames, sizeof(mgm->d.frames));
|
||||
_emitTag(mgm, vf, TAG_LAG_COUNT);
|
||||
vf->write(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames));
|
||||
_emitTag(mgm, vf, TAG_NEXT_TIME);
|
||||
|
||||
uint32_t newStreamId = 0;
|
||||
vf->write(vf, &newStreamId, sizeof(newStreamId));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _markStreamNext(struct GBAMGMContext* mgm, uint32_t newStreamId, bool recursive) {
|
||||
if (mgm->movieStream->seek(mgm->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t tagBuffer;
|
||||
if (mgm->movieStream->read(mgm->movieStream, &tagBuffer, 1) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (tagBuffer != TAG_NEXT_TIME) {
|
||||
return false;
|
||||
}
|
||||
if (mgm->movieStream->write(mgm->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
|
||||
return false;
|
||||
}
|
||||
if (recursive) {
|
||||
if (mgm->movieStream->seek(mgm->movieStream, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!_verifyMagic(mgm, mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
_readTag(mgm, mgm->movieStream);
|
||||
if (_readTag(mgm, mgm->movieStream) != TAG_PREVIOUSLY) {
|
||||
return false;
|
||||
}
|
||||
if (mgm->previously == 0) {
|
||||
return true;
|
||||
}
|
||||
uint32_t currentStreamId = mgm->streamId;
|
||||
if (!_loadStream(mgm, mgm->previously)) {
|
||||
return false;
|
||||
}
|
||||
return _markStreamNext(mgm, currentStreamId, mgm->previously);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _streamEndReached(struct GBAMGMContext* mgm) {
|
||||
if (!mgm->d.isPlaying(&mgm->d)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t endStreamId = mgm->streamId;
|
||||
mgm->d.stopPlaying(&mgm->d);
|
||||
if (mgm->autorecord) {
|
||||
mgm->isRecording = true;
|
||||
_loadStream(mgm, endStreamId);
|
||||
_incrementStream(mgm, false);
|
||||
}
|
||||
}
|
||||
|
||||
struct VFile* GBAMGMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
return mgm->streamDir->openFile(mgm->streamDir, "movie.sav", flags);
|
||||
}
|
||||
|
||||
struct VFile* GBAMGMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
return mgm->streamDir->openFile(mgm->streamDir, "movie.ssm", flags);
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/gba/rr/rr.h>
|
||||
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/serialize.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA_RR, "GBA RR", "gba.rr");
|
||||
|
||||
void GBARRInitRecord(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
||||
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata, false);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0, false);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
//GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRInitPlay(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata, false);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0, false);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
|
||||
//GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRDestroy(struct GBARRContext* rr) {
|
||||
if (rr->isPlaying(rr)) {
|
||||
rr->stopPlaying(rr);
|
||||
}
|
||||
if (rr->isRecording(rr)) {
|
||||
rr->stopRecording(rr);
|
||||
}
|
||||
if (rr->savedata) {
|
||||
rr->savedata->close(rr->savedata);
|
||||
rr->savedata = 0;
|
||||
}
|
||||
rr->destroy(rr);
|
||||
}
|
274
src/gba/rr/vbm.c
|
@ -1,274 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/gba/rr/vbm.h>
|
||||
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/serialize.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#ifdef USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
static const char VBM_MAGIC[] = "VBM\x1A";
|
||||
|
||||
static void GBAVBMContextDestroy(struct GBARRContext*);
|
||||
|
||||
static bool GBAVBMStartPlaying(struct GBARRContext*, bool autorecord);
|
||||
static void GBAVBMStopPlaying(struct GBARRContext*);
|
||||
static bool GBAVBMStartRecording(struct GBARRContext*);
|
||||
static void GBAVBMStopRecording(struct GBARRContext*);
|
||||
|
||||
static bool GBAVBMIsPlaying(const struct GBARRContext*);
|
||||
static bool GBAVBMIsRecording(const struct GBARRContext*);
|
||||
|
||||
static void GBAVBMNextFrame(struct GBARRContext*);
|
||||
static uint16_t GBAVBMQueryInput(struct GBARRContext*);
|
||||
static bool GBAVBMQueryReset(struct GBARRContext*);
|
||||
|
||||
static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||
static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||
|
||||
static struct VFile* GBAVBMOpenSavedata(struct GBARRContext*, int flags);
|
||||
static struct VFile* GBAVBMOpenSavestate(struct GBARRContext*, int flags);
|
||||
|
||||
void GBAVBMContextCreate(struct GBAVBMContext* vbm) {
|
||||
memset(vbm, 0, sizeof(*vbm));
|
||||
|
||||
vbm->d.destroy = GBAVBMContextDestroy;
|
||||
|
||||
vbm->d.startPlaying = GBAVBMStartPlaying;
|
||||
vbm->d.stopPlaying = GBAVBMStopPlaying;
|
||||
vbm->d.startRecording = GBAVBMStartRecording;
|
||||
vbm->d.stopRecording = GBAVBMStopRecording;
|
||||
|
||||
vbm->d.isPlaying = GBAVBMIsPlaying;
|
||||
vbm->d.isRecording = GBAVBMIsRecording;
|
||||
|
||||
vbm->d.nextFrame = GBAVBMNextFrame;
|
||||
vbm->d.logInput = 0;
|
||||
vbm->d.queryInput = GBAVBMQueryInput;
|
||||
vbm->d.queryReset = GBAVBMQueryReset;
|
||||
|
||||
vbm->d.stateSaved = GBAVBMStateSaved;
|
||||
vbm->d.stateLoaded = GBAVBMStateLoaded;
|
||||
|
||||
vbm->d.openSavedata = GBAVBMOpenSavedata;
|
||||
vbm->d.openSavestate = GBAVBMOpenSavestate;
|
||||
}
|
||||
|
||||
bool GBAVBMStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||
if (rr->isRecording(rr) || rr->isPlaying(rr) || autorecord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->isPlaying = true;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, vbm->inputOffset, SEEK_SET);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAVBMStopPlaying(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->isPlaying = false;
|
||||
}
|
||||
|
||||
bool GBAVBMStartRecording(struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void GBAVBMStopRecording(struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
}
|
||||
|
||||
bool GBAVBMIsPlaying(const struct GBARRContext* rr) {
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
return vbm->isPlaying;
|
||||
}
|
||||
|
||||
bool GBAVBMIsRecording(const struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void GBAVBMNextFrame(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, sizeof(uint16_t), SEEK_CUR);
|
||||
}
|
||||
|
||||
uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
uint16_t input;
|
||||
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
|
||||
return input & 0x3FF;
|
||||
}
|
||||
|
||||
bool GBAVBMQueryReset(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
uint16_t input;
|
||||
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
|
||||
return input & 0x800;
|
||||
}
|
||||
|
||||
void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||
UNUSED(rr);
|
||||
UNUSED(state);
|
||||
}
|
||||
|
||||
void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
|
||||
UNUSED(rr);
|
||||
UNUSED(state);
|
||||
}
|
||||
|
||||
struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||
UNUSED(flags);
|
||||
#ifndef USE_ZLIB
|
||||
UNUSED(rr);
|
||||
return 0;
|
||||
#else
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
off_t pos = vbm->vbmFile->seek(vbm->vbmFile, 0, SEEK_CUR);
|
||||
uint32_t saveType, flashSize, sramOffset;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, 0x18, SEEK_SET);
|
||||
vbm->vbmFile->read(vbm->vbmFile, &saveType, sizeof(saveType));
|
||||
vbm->vbmFile->read(vbm->vbmFile, &flashSize, sizeof(flashSize));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, 0x38, SEEK_SET);
|
||||
vbm->vbmFile->read(vbm->vbmFile, &sramOffset, sizeof(sramOffset));
|
||||
if (!sramOffset) {
|
||||
vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
|
||||
return 0;
|
||||
}
|
||||
vbm->vbmFile->seek(vbm->vbmFile, sramOffset, SEEK_SET);
|
||||
struct VFile* save = VFileMemChunk(0, 0);
|
||||
size_t size;
|
||||
switch (saveType) {
|
||||
case 1:
|
||||
size = SIZE_CART_SRAM;
|
||||
break;
|
||||
case 2:
|
||||
size = flashSize;
|
||||
if (size > SIZE_CART_FLASH1M) {
|
||||
size = SIZE_CART_FLASH1M;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
size = SIZE_CART_EEPROM;
|
||||
break;
|
||||
default:
|
||||
size = SIZE_CART_FLASH1M;
|
||||
break;
|
||||
}
|
||||
uLong zlen = vbm->inputOffset - sramOffset;
|
||||
char buffer[8761];
|
||||
char* zbuffer = malloc(zlen);
|
||||
vbm->vbmFile->read(vbm->vbmFile, zbuffer, zlen);
|
||||
z_stream zstr;
|
||||
zstr.zalloc = Z_NULL;
|
||||
zstr.zfree = Z_NULL;
|
||||
zstr.opaque = Z_NULL;
|
||||
zstr.avail_in = zlen;
|
||||
zstr.next_in = (Bytef*) zbuffer;
|
||||
zstr.avail_out = 0;
|
||||
inflateInit2(&zstr, 31);
|
||||
// Skip header, we know where the save file is
|
||||
zstr.avail_out = sizeof(buffer);
|
||||
zstr.next_out = (Bytef*) &buffer;
|
||||
int err = inflate(&zstr, 0);
|
||||
while (err != Z_STREAM_END && !zstr.avail_out) {
|
||||
zstr.avail_out = sizeof(buffer);
|
||||
zstr.next_out = (Bytef*) &buffer;
|
||||
int err = inflate(&zstr, 0);
|
||||
if (err < 0) {
|
||||
break;
|
||||
}
|
||||
save->write(save, buffer, sizeof(buffer) - zstr.avail_out);
|
||||
}
|
||||
inflateEnd(&zstr);
|
||||
vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
|
||||
return save;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||
UNUSED(rr);
|
||||
UNUSED(flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GBAVBMContextDestroy(struct GBARRContext* rr) {
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
if (vbm->vbmFile) {
|
||||
vbm->vbmFile->close(vbm->vbmFile);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) {
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
char magic[4];
|
||||
vf->read(vf, magic, sizeof(magic));
|
||||
if (memcmp(magic, VBM_MAGIC, sizeof(magic)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
vf->read(vf, &id, sizeof(id));
|
||||
if (id != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vf->seek(vf, 4, SEEK_CUR);
|
||||
vf->read(vf, &vbm->d.frames, sizeof(vbm->d.frames));
|
||||
vf->read(vf, &vbm->d.rrCount, sizeof(vbm->d.rrCount));
|
||||
|
||||
uint8_t flags;
|
||||
vf->read(vf, &flags, sizeof(flags));
|
||||
if (flags & 2) {
|
||||
#ifdef USE_ZLIB
|
||||
vbm->d.initFrom = INIT_FROM_SAVEGAME;
|
||||
#else
|
||||
// zlib is needed to parse the savegame
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
if (flags & 1) {
|
||||
// Incompatible savestate format
|
||||
return false;
|
||||
}
|
||||
|
||||
vf->seek(vf, 1, SEEK_CUR);
|
||||
vf->read(vf, &flags, sizeof(flags));
|
||||
if ((flags & 0x7) != 1) {
|
||||
// Non-GBA movie
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: parse more flags
|
||||
|
||||
vf->seek(vf, 0x3C, SEEK_SET);
|
||||
vf->read(vf, &vbm->inputOffset, sizeof(vbm->inputOffset));
|
||||
vf->seek(vf, vbm->inputOffset, SEEK_SET);
|
||||
vbm->vbmFile = vf;
|
||||
return true;
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include <mgba/internal/arm/macros.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
#include <mgba/internal/gba/rr/rr.h>
|
||||
|
||||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
@ -73,11 +72,6 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state);
|
||||
|
||||
state->associatedStreamId = 0;
|
||||
if (gba->rr) {
|
||||
gba->rr->stateSaved(gba->rr, state);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||
|
@ -195,10 +189,6 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
GBAAudioDeserialize(&gba->audio, state);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state);
|
||||
|
||||
if (gba->rr) {
|
||||
gba->rr->stateLoaded(gba->rr, state);
|
||||
}
|
||||
|
||||
gba->timing.reroot = gba->timing.root;
|
||||
gba->timing.root = NULL;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, si
|
|||
static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
|
||||
|
||||
static void _startHblank(struct mTiming*, void* context, uint32_t cyclesLate);
|
||||
static void _midHblank(struct mTiming*, void* context, uint32_t cyclesLate);
|
||||
static void _startHdraw(struct mTiming*, void* context, uint32_t cyclesLate);
|
||||
|
||||
static uint16_t _zeroes[0x2000] = {};
|
||||
|
@ -137,10 +138,17 @@ void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer*
|
|||
video->renderer->init(video->renderer);
|
||||
}
|
||||
|
||||
void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
void _midHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBAVideo* video = context;
|
||||
GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
|
||||
dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
|
||||
video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
|
||||
video->event.callback = _startHdraw;
|
||||
mTimingSchedule(timing, &video->event, VIDEO_HBLANK_FLIP - cyclesLate);
|
||||
}
|
||||
|
||||
void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBAVideo* video = context;
|
||||
video->event.callback = _startHblank;
|
||||
mTimingSchedule(timing, &video->event, VIDEO_HDRAW_LENGTH - cyclesLate);
|
||||
|
||||
|
@ -150,6 +158,11 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
}
|
||||
video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
|
||||
|
||||
if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) {
|
||||
video->renderer->drawScanline(video->renderer, video->vcount);
|
||||
}
|
||||
|
||||
GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
|
||||
if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
|
||||
dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
|
||||
if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
|
||||
|
@ -190,16 +203,12 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
|
||||
void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBAVideo* video = context;
|
||||
GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
|
||||
dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
|
||||
video->event.callback = _startHdraw;
|
||||
mTimingSchedule(timing, &video->event, VIDEO_HBLANK_LENGTH - cyclesLate);
|
||||
video->event.callback = _midHblank;
|
||||
mTimingSchedule(timing, &video->event, VIDEO_HBLANK_LENGTH - VIDEO_HBLANK_FLIP - cyclesLate);
|
||||
|
||||
// Begin Hblank
|
||||
GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
|
||||
dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
|
||||
if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) {
|
||||
video->renderer->drawScanline(video->renderer, video->vcount);
|
||||
}
|
||||
|
||||
if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS) {
|
||||
GBADMARunHblank(video->p, -cyclesLate);
|
||||
|
@ -324,6 +333,15 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState*
|
|||
memcpy(state->oam, video->oam.raw, SIZE_OAM);
|
||||
memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
|
||||
STORE_32(video->event.when - mTimingCurrentTime(&video->p->timing), 0, &state->video.nextEvent);
|
||||
int32_t flags = 0;
|
||||
if (video->event.callback == _startHdraw) {
|
||||
flags = GBASerializedVideoFlagsSetMode(flags, 1);
|
||||
} else if (video->event.callback == _startHblank) {
|
||||
flags = GBASerializedVideoFlagsSetMode(flags, 2);
|
||||
} else if (video->event.callback == _midHblank) {
|
||||
flags = GBASerializedVideoFlagsSetMode(flags, 3);
|
||||
}
|
||||
STORE_32(flags, 0, &state->video.flags);
|
||||
STORE_32(video->frameCounter, 0, &state->video.frameCounter);
|
||||
}
|
||||
|
||||
|
@ -341,14 +359,28 @@ void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState
|
|||
}
|
||||
LOAD_32(video->frameCounter, 0, &state->video.frameCounter);
|
||||
|
||||
int32_t flags;
|
||||
LOAD_32(flags, 0, &state->video.flags);
|
||||
GBARegisterDISPSTAT dispstat = state->io[REG_DISPSTAT >> 1];
|
||||
switch (GBASerializedVideoFlagsGetMode(flags)) {
|
||||
case 0:
|
||||
if (GBARegisterDISPSTATIsInHblank(dispstat)) {
|
||||
video->event.callback = _startHdraw;
|
||||
} else {
|
||||
video->event.callback = _startHblank;
|
||||
}
|
||||
case 1:
|
||||
video->event.callback = _startHdraw;
|
||||
break;
|
||||
case 2:
|
||||
video->event.callback = _startHblank;
|
||||
break;
|
||||
case 3:
|
||||
video->event.callback = _midHblank;
|
||||
break;
|
||||
}
|
||||
uint32_t when;
|
||||
LOAD_32(when, 0, &state->video.nextEvent);
|
||||
GBARegisterDISPSTAT dispstat = state->io[REG_DISPSTAT >> 1];
|
||||
if (GBARegisterDISPSTATIsInHblank(dispstat)) {
|
||||
video->event.callback = _startHdraw;
|
||||
} else {
|
||||
video->event.callback = _startHblank;
|
||||
}
|
||||
mTimingSchedule(&video->p->timing, &video->event, when);
|
||||
|
||||
LOAD_16(video->vcount, REG_VCOUNT, state->io);
|
||||
|
|
|
@ -137,9 +137,9 @@ void AssetTile::selectColor(int index) {
|
|||
m_ui.color->setColor(0, color);
|
||||
m_ui.color->update();
|
||||
|
||||
uint32_t r = M_R8(color);
|
||||
uint32_t g = M_G8(color);
|
||||
uint32_t b = M_B8(color);
|
||||
uint32_t r = ((color & 0xF8) * 0x21) >> 5;
|
||||
uint32_t g = (((color >> 8) & 0xF8) * 0x21) >> 5;
|
||||
uint32_t b = (((color >> 16) & 0xF8) * 0x21) >> 5;
|
||||
m_ui.r->setText(tr("0x%0 (%1)").arg(r, 2, 16, QChar('0')).arg(r, 2, 10, QChar('0')));
|
||||
m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0')));
|
||||
m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0')));
|
||||
|
|
|
@ -81,9 +81,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
|||
|
||||
controller->m_resetActions.clear();
|
||||
|
||||
if (!controller->m_hwaccel) {
|
||||
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
|
||||
}
|
||||
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
|
||||
|
||||
QMetaObject::invokeMethod(controller, "didReset");
|
||||
controller->finishFrame();
|
||||
|
@ -363,14 +361,12 @@ void CoreController::setLogger(LogController* logger) {
|
|||
}
|
||||
|
||||
void CoreController::start() {
|
||||
if (!m_hwaccel) {
|
||||
QSize size(256, 384);
|
||||
m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
|
||||
m_activeBuffer.fill(0xFF);
|
||||
m_completeBuffer = m_activeBuffer;
|
||||
QSize size(256, 384);
|
||||
m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
|
||||
m_activeBuffer.fill(0xFF);
|
||||
m_completeBuffer = m_activeBuffer;
|
||||
|
||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width());
|
||||
}
|
||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width());
|
||||
|
||||
if (!m_patched) {
|
||||
mCoreAutoloadPatch(m_threadContext.core);
|
||||
|
@ -848,11 +844,23 @@ void CoreController::endVideoLog(bool closeVf) {
|
|||
void CoreController::setFramebufferHandle(int fb) {
|
||||
Interrupter interrupter(this);
|
||||
if (fb < 0) {
|
||||
if (!m_hwaccel) {
|
||||
return;
|
||||
}
|
||||
mCoreConfigSetIntValue(&m_threadContext.core->config, "hwaccelVideo", 0);
|
||||
m_threadContext.core->setVideoGLTex(m_threadContext.core, -1);
|
||||
m_hwaccel = false;
|
||||
} else {
|
||||
mCoreConfigSetIntValue(&m_threadContext.core->config, "hwaccelVideo", 1);
|
||||
m_threadContext.core->setVideoGLTex(m_threadContext.core, fb);
|
||||
if (m_hwaccel) {
|
||||
return;
|
||||
}
|
||||
m_hwaccel = true;
|
||||
}
|
||||
if (hasStarted()) {
|
||||
m_threadContext.core->reloadConfigOption(m_threadContext.core, "hwaccelVideo", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::updateKeys() {
|
||||
|
|
|
@ -83,6 +83,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr
|
|||
|
||||
mCore* core = mCoreFindVF(vf);
|
||||
if (!core) {
|
||||
LOG(QT, ERROR) << tr("Could not load game. Are you sure it's in the correct format?");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -578,10 +578,19 @@ void PainterGL::clearShaders() {
|
|||
return;
|
||||
}
|
||||
#ifdef BUILD_GLES2
|
||||
if (!m_started) {
|
||||
m_gl->makeCurrent(m_surface);
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
}
|
||||
if (m_shader.passes) {
|
||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||
mGLES2ShaderFree(&m_shader);
|
||||
}
|
||||
if (!m_started) {
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,12 @@ LogController::Stream LogController::operator()(int category, int level) {
|
|||
|
||||
void LogController::load(const ConfigController* config) {
|
||||
mLogFilterLoad(&m_filter, config->config());
|
||||
if (!levels(mLogCategoryById("gba.bios"))) {
|
||||
mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB | mLOG_FATAL);
|
||||
}
|
||||
if (!levels(mLogCategoryById("core.status"))) {
|
||||
mLogFilterSet(&m_filter, "core.status", mLOG_ALL & ~mLOG_DEBUG);
|
||||
}
|
||||
setLogFile(config->getOption("logFile"));
|
||||
logToStdout(config->getOption("logToStdout").toInt());
|
||||
logToFile(config->getOption("logToFile").toInt());
|
||||
|
@ -114,7 +120,7 @@ void LogController::disableLevels(int levels, int category) {
|
|||
|
||||
void LogController::clearLevels(int category) {
|
||||
auto id = mLogCategoryId(category);
|
||||
mLogFilterReset (&m_filter, id);
|
||||
mLogFilterReset(&m_filter, id);
|
||||
}
|
||||
|
||||
void LogController::logToFile(bool log) {
|
||||
|
|
|
@ -487,12 +487,13 @@ void SettingsView::updateConfig() {
|
|||
}
|
||||
|
||||
int videoScale = m_controller->getOption("videoScale", 1).toInt();
|
||||
saveSetting("videoScale", m_ui.videoScale);
|
||||
|
||||
int hwaccelVideo = m_controller->getOption("hwaccelVideo").toInt();
|
||||
saveSetting("hwaccelVideo", m_ui.hwaccelVideo->currentIndex());
|
||||
if (hwaccelVideo != m_ui.hwaccelVideo->currentIndex()) {
|
||||
emit videoRendererChanged();
|
||||
}
|
||||
saveSetting("videoScale", m_ui.videoScale);
|
||||
saveSetting("hwaccelVideo", m_ui.hwaccelVideo->currentIndex());
|
||||
|
||||
m_logModel.save(m_controller);
|
||||
m_logModel.logger()->setLogFile(m_ui.logFile->text());
|
||||
|
|
|
@ -159,7 +159,11 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
|
|||
}
|
||||
Action* action = item.value()->action();
|
||||
if (action) {
|
||||
action->trigger();
|
||||
if (m_actions->isHeld(action->name())) {
|
||||
action->trigger(true);
|
||||
} else {
|
||||
action->trigger(!action->isActive());
|
||||
}
|
||||
}
|
||||
event->accept();
|
||||
return true;
|
||||
|
@ -170,7 +174,7 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
|
|||
return false;
|
||||
}
|
||||
Action* action = item.value()->action();
|
||||
if (action) {
|
||||
if (action && m_actions->isHeld(action->name())) {
|
||||
action->trigger(false);
|
||||
}
|
||||
event->accept();
|
||||
|
@ -184,7 +188,15 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
|
|||
}
|
||||
Action* action = item.value()->action();
|
||||
if (action) {
|
||||
action->trigger(gae->isNew());
|
||||
if (gae->isNew()) {
|
||||
if (m_actions->isHeld(action->name())) {
|
||||
action->trigger(true);
|
||||
} else {
|
||||
action->trigger(!action->isActive());
|
||||
}
|
||||
} else if (m_actions->isHeld(action->name())) {
|
||||
action->trigger(false);
|
||||
}
|
||||
}
|
||||
event->accept();
|
||||
return true;
|
||||
|
|
|
@ -510,7 +510,7 @@ void Window::openSettingsWindow() {
|
|||
connect(settingsWindow, &SettingsView::audioDriverChanged, this, &Window::reloadAudioDriver);
|
||||
connect(settingsWindow, &SettingsView::cameraDriverChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::cameraChanged, &m_inputController, &InputController::setCamera);
|
||||
connect(settingsWindow, &SettingsView::videoRendererChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::videoRendererChanged, this, &Window::changeRenderer);
|
||||
connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig);
|
||||
#ifdef USE_SQLITE3
|
||||
|
@ -926,8 +926,8 @@ void Window::gameCrashed(const QString& errorMessage) {
|
|||
}
|
||||
|
||||
void Window::gameFailed() {
|
||||
QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Couldn't Load"),
|
||||
tr("Could not load game. Are you sure it's in the correct format?"),
|
||||
QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Couldn't Start"),
|
||||
tr("Could not start game."),
|
||||
QMessageBox::Ok, this, Qt::Sheet);
|
||||
fail->setAttribute(Qt::WA_DeleteOnClose);
|
||||
fail->show();
|
||||
|
@ -949,10 +949,6 @@ void Window::unimplementedBiosCall(int call) {
|
|||
|
||||
void Window::reloadDisplayDriver() {
|
||||
if (m_controller) {
|
||||
if (m_controller->hardwareAccelerated()) {
|
||||
mustRestart();
|
||||
return;
|
||||
}
|
||||
m_display->stopDrawing();
|
||||
detachWidget(m_display.get());
|
||||
}
|
||||
|
@ -990,14 +986,7 @@ void Window::reloadDisplayDriver() {
|
|||
#endif
|
||||
|
||||
if (m_controller) {
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext);
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);
|
||||
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
||||
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||
attachDisplay();
|
||||
|
||||
attachWidget(m_display.get());
|
||||
m_display->startDrawing(m_controller);
|
||||
|
@ -1028,6 +1017,27 @@ void Window::reloadAudioDriver() {
|
|||
connect(m_controller.get(), &CoreController::fastForwardChanged, m_audioProcessor.get(), &AudioProcessor::inputParametersChanged);
|
||||
}
|
||||
|
||||
void Window::changeRenderer() {
|
||||
if (!m_controller) {
|
||||
return;
|
||||
}
|
||||
if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) {
|
||||
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
||||
if (!proxy) {
|
||||
proxy = std::make_shared<VideoProxy>();
|
||||
}
|
||||
m_display->setVideoProxy(proxy);
|
||||
proxy->attach(m_controller.get());
|
||||
|
||||
int fb = m_display->framebufferHandle();
|
||||
if (fb >= 0) {
|
||||
m_controller->setFramebufferHandle(fb);
|
||||
}
|
||||
} else {
|
||||
m_controller->setFramebufferHandle(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::tryMakePortable() {
|
||||
QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"),
|
||||
tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"),
|
||||
|
@ -1267,10 +1277,10 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
#ifdef M_CORE_GBA
|
||||
m_actions.addSeparator("file");
|
||||
Action* importShark = addGameAction(tr("Import GameShark Save"), "importShark", this, &Window::importSharkport, "file");
|
||||
Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "file");
|
||||
m_platformActions.insert(PLATFORM_GBA, importShark);
|
||||
|
||||
Action* exportShark = addGameAction(tr("Export GameShark Save"), "exportShark", this, &Window::exportSharkport, "file");
|
||||
Action* exportShark = addGameAction(tr("Export GameShark Save..."), "exportShark", this, &Window::exportSharkport, "file");
|
||||
m_platformActions.insert(PLATFORM_GBA, exportShark);
|
||||
#endif
|
||||
|
||||
|
@ -1828,7 +1838,6 @@ void Window::updateFrame() {
|
|||
|
||||
void Window::setController(CoreController* controller, const QString& fname) {
|
||||
if (!controller) {
|
||||
gameFailed();
|
||||
return;
|
||||
}
|
||||
if (m_pendingClose) {
|
||||
|
@ -1851,17 +1860,6 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
reloadDisplayDriver();
|
||||
}
|
||||
|
||||
if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && controller->supportsFeature(CoreController::Feature::OPENGL)) {
|
||||
std::shared_ptr<VideoProxy> proxy = std::make_shared<VideoProxy>();
|
||||
m_display->setVideoProxy(proxy);
|
||||
proxy->attach(controller);
|
||||
|
||||
int fb = m_display->framebufferHandle();
|
||||
if (fb >= 0) {
|
||||
controller->setFramebufferHandle(fb);
|
||||
}
|
||||
}
|
||||
|
||||
m_controller = std::shared_ptr<CoreController>(controller);
|
||||
m_inputController.recalibrateAxes();
|
||||
m_controller->setInputController(&m_inputController);
|
||||
|
@ -1901,14 +1899,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
emit paused(false);
|
||||
});
|
||||
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext);
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);
|
||||
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
||||
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||
attachDisplay();
|
||||
|
||||
connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver);
|
||||
connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);
|
||||
|
@ -1965,6 +1956,18 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
}
|
||||
}
|
||||
|
||||
void Window::attachDisplay() {
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext);
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);
|
||||
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
||||
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||
changeRenderer();
|
||||
}
|
||||
|
||||
WindowBackground::WindowBackground(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
|
|
|
@ -130,6 +130,7 @@ private slots:
|
|||
|
||||
void reloadAudioDriver();
|
||||
void reloadDisplayDriver();
|
||||
void changeRenderer();
|
||||
|
||||
void tryMakePortable();
|
||||
void mustRestart();
|
||||
|
@ -155,6 +156,7 @@ private:
|
|||
void updateMRU();
|
||||
|
||||
void openView(QWidget* widget);
|
||||
void attachDisplay();
|
||||
|
||||
template <typename T, typename... A> std::function<void()> openTView(A... arg);
|
||||
template <typename T, typename... A> std::function<void()> openControllerTView(A... arg);
|
||||
|
|
|
@ -387,9 +387,21 @@ void InputController::setPreferredGamepad(uint32_t type, int index) {
|
|||
if (!m_config) {
|
||||
return;
|
||||
}
|
||||
#ifdef BUILD_SDL
|
||||
char name[34] = {0};
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, index)->joystick), name, sizeof(name));
|
||||
#else
|
||||
const char* name = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, index)->joystick));
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mInputSetPreferredDevice(m_config->input(), "gba", type, m_playerId, name);
|
||||
#else
|
||||
UNUSED(type);
|
||||
UNUSED(index);
|
||||
#endif
|
||||
}
|
||||
|
||||
mRumble* InputController::rumble() {
|
||||
|
|
|
@ -320,6 +320,11 @@ static void _gameLoaded(struct mGUIRunner* runner) {
|
|||
}
|
||||
}
|
||||
|
||||
int scale;
|
||||
if (mCoreConfigGetUIntValue(&runner->config, "videoScale", &scale)) {
|
||||
runner->core->reloadConfigOption(runner->core, "videoScale", &runner->config);
|
||||
}
|
||||
|
||||
rumble.up = 0;
|
||||
rumble.down = 0;
|
||||
}
|
||||
|
@ -857,7 +862,7 @@ int main(int argc, char* argv[]) {
|
|||
.nStates = 16
|
||||
},
|
||||
{
|
||||
.title = "GPU-accelerated renderer (experimental, requires game reload)",
|
||||
.title = "GPU-accelerated renderer (requires game reload)",
|
||||
.data = "hwaccelVideo",
|
||||
.submenu = 0,
|
||||
.state = 0,
|
||||
|
|
|
@ -84,6 +84,9 @@ void ELFGetProgramHeaders(struct ELF* elf, struct ELFProgramHeaders* ph) {
|
|||
ELFProgramHeadersClear(ph);
|
||||
Elf32_Ehdr* hdr = elf32_getehdr(elf->e);
|
||||
Elf32_Phdr* phdr = elf32_getphdr(elf->e);
|
||||
if (!hdr || !phdr) {
|
||||
return;
|
||||
}
|
||||
ELFProgramHeadersResize(ph, hdr->e_phnum);
|
||||
memcpy(ELFProgramHeadersGetPointer(ph, 0), phdr, sizeof(*phdr) * hdr->e_phnum);
|
||||
}
|
||||
|
|