Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2019-09-28 15:45:35 -07:00
commit 54579380be
73 changed files with 2433 additions and 798 deletions

13
CHANGES
View File

@ -27,10 +27,14 @@ Features:
- GB: Expose platform information to CLI debugger - GB: Expose platform information to CLI debugger
- Support Discord Rich Presence - Support Discord Rich Presence
- Debugger: Add tracing to file - Debugger: Add tracing to file
- Map viewer supports bitmapped GBA modes - Enhanced map viewer, supporting bitmapped GBA modes and more displayed info
- OpenGL renderer with high-resolution upscaling support - OpenGL renderer with high-resolution upscaling support
- Experimental high level "XQ" audio for most GBA games - Experimental high level "XQ" audio for most GBA games
- Interframe blending for games that use flicker effects - Interframe blending for games that use flicker effects
- Frame inspector for dissecting and debugging rendering
- Switch: Option to use built-in brightness sensor for Boktai
- Ports: Ability to enable or disable all SGB features (closes mgba.io/i/1205)
- Ports: Ability to crop SGB borders off screen (closes mgba.io/i/1204)
Emulation fixes: Emulation fixes:
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
- GBA: Reset now reloads multiboot ROMs - GBA: Reset now reloads multiboot ROMs
@ -38,11 +42,16 @@ Emulation fixes:
- GB Video: Delay LYC STAT check (fixes mgba.io/i/1331) - GB Video: Delay LYC STAT check (fixes mgba.io/i/1331)
- GB Video: Fix window being enabled mid-scanline (fixes mgba.io/i/1328) - GB Video: Fix window being enabled mid-scanline (fixes mgba.io/i/1328)
- GB I/O: Filter IE top bits properly (fixes mgba.io/i/1329) - GB I/O: Filter IE top bits properly (fixes mgba.io/i/1329)
- GBA Video: Fix wrapped sprite mosaic clamping (fixes mgba.io/i/1432)
- GBA Memory: Fix STM to VRAM (fixes mgba.io/i/1430)
Other fixes: Other fixes:
- Qt: Fix some Qt display driver race conditions - Qt: Fix some Qt display driver race conditions
- Core: Improved lockstep driver reliability (Le Hoang Quyen) - Core: Improved lockstep driver reliability (Le Hoang Quyen)
- Switch: Fix threading-related crash on second launch - Switch: Fix threading-related crash on second launch
- Qt: Fix FPS target maxing out at 59.727 (fixes mgba.io/i/1421) - Qt: Fix FPS target maxing out at 59.727 (fixes mgba.io/i/1421)
- Core: Fix crashes if core directories aren't set
- Qt: Cap audio buffer size to 8192 (fixes mgba.io/i/1433)
- GB Serialize: Fix loading non-BIOS state from BIOS (fixes mgba.io/i/1280)
Misc: Misc:
- GBA Savedata: EEPROM performance fixes - GBA Savedata: EEPROM performance fixes
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
@ -64,6 +73,8 @@ Misc:
- Qt: Improve sync code - Qt: Improve sync code
- Switch: Dynamic display resizing - Switch: Dynamic display resizing
- Qt: Make mute menu option also toggle fast-forward mute (fixes mgba.io/i/1424) - Qt: Make mute menu option also toggle fast-forward mute (fixes mgba.io/i/1424)
- Vita: L2/R2 and L3/R3 can now be mapped on PSTV (fixes mgba.io/i/1292)
- mGUI: Remember name and position of last loaded game
0.7.2: (2019-05-25) 0.7.2: (2019-05-25)
Emulation fixes: Emulation fixes:

View File

@ -1029,7 +1029,7 @@ endif()
if(BUILD_LIBRETRO) if(BUILD_LIBRETRO)
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c) file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
add_dependencies(${BINARY_NAME} version-info) add_dependencies(${BINARY_NAME}_libretro version-info)
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2") set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2")
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
if(MSVC) if(MSVC)

View File

@ -142,6 +142,7 @@ This will produce a `build-win32` directory with the build products. Replace `mg
- mgba/ubuntu:xenial - mgba/ubuntu:xenial
- mgba/ubuntu:bionic - mgba/ubuntu:bionic
- mgba/ubuntu:cosmic - mgba/ubuntu:cosmic
- mgba/ubuntu:disco
- mgba/vita - mgba/vita
- mgba/wii - mgba/wii
- mgba/windows:w32 - mgba/windows:w32

View File

@ -124,6 +124,7 @@ Dieser Befehl erzeugt ein Verzeichnis `build-win32` mit den erzeugten Programmda
- mgba/ubuntu:xenial - mgba/ubuntu:xenial
- mgba/ubuntu:bionic - mgba/ubuntu:bionic
- mgba/ubuntu:cosmic - mgba/ubuntu:cosmic
- mgba/ubuntu:disco
- mgba/vita - mgba/vita
- mgba/wii - mgba/wii
- mgba/windows:w32 - mgba/windows:w32

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

View File

@ -14,7 +14,7 @@ CXX_GUARD_START
struct VFile; struct VFile;
bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)); bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect);
CXX_GUARD_END CXX_GUARD_END

View File

@ -35,6 +35,11 @@ enum mVideoLoggerEvent {
LOGGER_EVENT_GET_PIXELS, LOGGER_EVENT_GET_PIXELS,
}; };
enum mVideoLoggerInjectionPoint {
LOGGER_INJECTION_IMMEDIATE = 0,
LOGGER_INJECTION_FIRST_SCANLINE,
};
struct mVideoLoggerDirtyInfo { struct mVideoLoggerDirtyInfo {
enum mVideoLoggerDirtyType type; enum mVideoLoggerDirtyType type;
uint32_t address; uint32_t address;
@ -97,6 +102,7 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);
void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger); void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger);
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block); bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block);
bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger);
struct mVideoLogContext; struct mVideoLogContext;
void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId); void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId);
@ -104,6 +110,7 @@ void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogCont
struct mCore; struct mCore;
struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core); struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core);
void mVideoLogContextSetCompression(struct mVideoLogContext*, bool enable);
void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*); void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*);
void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core); void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core);
@ -115,6 +122,12 @@ void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size);
int mVideoLoggerAddChannel(struct mVideoLogContext*); int mVideoLoggerAddChannel(struct mVideoLogContext*);
void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint);
void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask);
void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value);
void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value);
void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value);
struct mCore* mVideoLogCoreFind(struct VFile*); struct mCore* mVideoLogCoreFind(struct VFile*);
CXX_GUARD_END CXX_GUARD_END

View File

@ -150,6 +150,7 @@ void GBDestroy(struct GB* gb);
void GBReset(struct LR35902Core* cpu); void GBReset(struct LR35902Core* cpu);
void GBSkipBIOS(struct GB* gb); void GBSkipBIOS(struct GB* gb);
void GBMapBIOS(struct GB* gb);
void GBUnmapBIOS(struct GB* gb); void GBUnmapBIOS(struct GB* gb);
void GBDetectModel(struct GB* gb); void GBDetectModel(struct GB* gb);

View File

@ -74,6 +74,7 @@ DECL_BITFIELD(RTCStatus2, uint8_t);
DECL_BITS(RTCStatus2, INT1, 0, 4); DECL_BITS(RTCStatus2, INT1, 0, 4);
DECL_BIT(RTCStatus2, INT2, 6); DECL_BIT(RTCStatus2, INT2, 6);
#ifndef PYCPARSE
#pragma pack(push, 1) #pragma pack(push, 1)
struct GBARTC { struct GBARTC {
int32_t bytesRemaining; int32_t bytesRemaining;
@ -90,6 +91,9 @@ struct GBARTC {
uint8_t time[7]; uint8_t time[7];
}; };
#pragma pack(pop) #pragma pack(pop)
#else
struct GBATRC;
#endif
struct GBAGBPKeyCallback { struct GBAGBPKeyCallback {
struct mKeyCallback d; struct mKeyCallback d;

View File

@ -16,6 +16,7 @@ struct GBAVideoRendererSprite {
struct GBAObj obj; struct GBAObj obj;
int16_t y; int16_t y;
int16_t endY; int16_t endY;
int8_t index;
}; };
int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort); int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort);

View File

@ -45,6 +45,7 @@ struct GBAVideoSoftwareBackground {
color_t* variantPalette; color_t* variantPalette;
int32_t offsetX; int32_t offsetX;
int32_t offsetY; int32_t offsetY;
bool highlight;
}; };
enum { enum {
@ -117,6 +118,8 @@ struct GBAVideoSoftwareRenderer {
color_t variantPalette[512]; color_t variantPalette[512];
color_t* objExtPalette; color_t* objExtPalette;
color_t* objExtVariantPalette; color_t* objExtVariantPalette;
color_t highlightPalette[512];
color_t highlightVariantPalette[512];
uint16_t blda; uint16_t blda;
uint16_t bldb; uint16_t bldb;
@ -165,6 +168,8 @@ struct GBAVideoSoftwareRenderer {
int masterBright; int masterBright;
int masterBrightY; int masterBrightY;
uint8_t lastHighlightAmount;
}; };
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer); void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer);

View File

@ -33,6 +33,23 @@ enum {
JOYSTAT_RECV_BIT = 2, JOYSTAT_RECV_BIT = 2,
}; };
DECL_BITFIELD(GBASIONormal, uint16_t);
DECL_BIT(GBASIONormal, Sc, 0);
DECL_BIT(GBASIONormal, InternalSc, 1);
DECL_BIT(GBASIONormal, Si, 2);
DECL_BIT(GBASIONormal, IdleSo, 3);
DECL_BIT(GBASIONormal, Start, 7);
DECL_BIT(GBASIONormal, Length, 12);
DECL_BIT(GBASIONormal, Irq, 14);
DECL_BITFIELD(GBASIOMultiplayer, uint16_t);
DECL_BITS(GBASIOMultiplayer, Baud, 0, 2);
DECL_BIT(GBASIOMultiplayer, Slave, 2);
DECL_BIT(GBASIOMultiplayer, Ready, 3);
DECL_BITS(GBASIOMultiplayer, Id, 4, 2);
DECL_BIT(GBASIOMultiplayer, Error, 6);
DECL_BIT(GBASIOMultiplayer, Busy, 8);
DECL_BIT(GBASIOMultiplayer, Irq, 14);
struct GBASIODriverSet { struct GBASIODriverSet {
struct GBASIODriver* normal; struct GBASIODriver* normal;
struct GBASIODriver* multiplayer; struct GBASIODriver* multiplayer;
@ -47,37 +64,8 @@ struct GBASIO {
struct GBASIODriver* activeDriver; struct GBASIODriver* activeDriver;
uint16_t rcnt; uint16_t rcnt;
// TODO: Convert to bitfields
union {
struct {
unsigned sc : 1;
unsigned internalSc : 1;
unsigned si : 1;
unsigned idleSo : 1;
unsigned : 3;
unsigned start : 1;
unsigned : 4;
unsigned length : 1;
unsigned : 1;
unsigned irq : 1;
unsigned : 1;
} normalControl;
struct {
unsigned baud : 2;
unsigned slave : 1;
unsigned ready : 1;
unsigned id : 2;
unsigned error : 1;
unsigned busy : 1;
unsigned : 6;
unsigned irq : 1;
unsigned : 1;
} multiplayerControl;
uint16_t siocnt; uint16_t siocnt;
}; };
};
void GBASIOInit(struct GBASIO* sio); void GBASIOInit(struct GBASIO* sio);
void GBASIODeinit(struct GBASIO* sio); void GBASIODeinit(struct GBASIO* sio);

View File

@ -12,6 +12,7 @@ CXX_GUARD_START
#include <mgba/core/log.h> #include <mgba/core/log.h>
#include <mgba/core/timing.h> #include <mgba/core/timing.h>
#include <mgba/gba/interface.h>
mLOG_DECLARE_CATEGORY(GBA_VIDEO); mLOG_DECLARE_CATEGORY(GBA_VIDEO);
@ -84,9 +85,6 @@ struct GBAObj {
uint16_t d; uint16_t d;
}; };
union GBAOAM {
struct GBAObj obj[128];
struct GBAOAMMatrix { struct GBAOAMMatrix {
int16_t padding0[3]; int16_t padding0[3];
int16_t a; int16_t a;
@ -96,8 +94,11 @@ union GBAOAM {
int16_t c; int16_t c;
int16_t padding3[3]; int16_t padding3[3];
int16_t d; int16_t d;
} mat[32]; };
union GBAOAM {
struct GBAObj obj[128];
struct GBAOAMMatrix mat[32];
uint16_t raw[512]; uint16_t raw[512];
}; };
@ -197,6 +198,11 @@ struct GBAVideoRenderer {
bool disableBG[4]; bool disableBG[4];
bool disableOBJ; bool disableOBJ;
bool highlightBG[4];
bool highlightOBJ[128];
color_t highlightColor;
uint8_t highlightAmount;
}; };
struct GBAVideo { struct GBAVideo {

View File

@ -164,10 +164,16 @@ bool mCorePreloadFile(struct mCore* core, const char* path) {
} }
bool mCoreAutoloadSave(struct mCore* core) { bool mCoreAutoloadSave(struct mCore* core) {
if (!core->dirs.save) {
return false;
}
return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR)); return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR));
} }
bool mCoreAutoloadPatch(struct mCore* core) { bool mCoreAutoloadPatch(struct mCore* core) {
if (!core->dirs.patch) {
return false;
}
return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) || return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) ||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) || core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) ||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY)); core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));
@ -230,6 +236,9 @@ bool mCoreLoadState(struct mCore* core, int slot, int flags) {
} }
struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) { struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) {
if (!core->dirs.state) {
return NULL;
}
char name[PATH_MAX + 14]; // Quash warning char name[PATH_MAX + 14]; // Quash warning
snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot); snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot);
return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY); return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);

View File

@ -118,6 +118,20 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
}, },
.nStates = 2 .nStates = 2
}; };
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
.title = "Enable SGB features",
.data = "sgb.model",
.submenu = 0,
.state = true,
.validStates = (const char*[]) {
"Off", "On"
},
.stateMappings = (const struct GUIVariant[]) {
GUI_V_S("DMG"),
GUI_V_S("SGB"),
},
.nStates = 2
};
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
.title = "Enable SGB borders", .title = "Enable SGB borders",
.data = "sgb.borders", .data = "sgb.borders",
@ -128,6 +142,16 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
}, },
.nStates = 2 .nStates = 2
}; };
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
.title = "Crop SGB borders",
.data = "sgb.borderCrop",
.submenu = 0,
.state = false,
.validStates = (const char*[]) {
"Off", "On"
},
.nStates = 2
};
#endif #endif
size_t i; size_t i;
const char* mapNames[GUI_MAX_INPUTS + 1]; const char* mapNames[GUI_MAX_INPUTS + 1];
@ -173,8 +197,6 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
continue; continue;
} }
if (item->stateMappings) { if (item->stateMappings) {
item->state = 0;
size_t j; size_t j;
for (j = 0; j < item->nStates; ++j) { for (j = 0; j < item->nStates; ++j) {
const struct GUIVariant* v = &item->stateMappings[j]; const struct GUIVariant* v = &item->stateMappings[j];
@ -281,7 +303,7 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
} }
if (!strcmp(item->data, "gba.bios")) { if (!strcmp(item->data, "gba.bios")) {
// TODO: show box if failed // TODO: show box if failed
if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS)) { if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS, NULL)) {
gbaBiosPath[0] = '\0'; gbaBiosPath[0] = '\0';
} }
continue; continue;
@ -289,21 +311,21 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
#ifdef M_CORE_GB #ifdef M_CORE_GB
if (!strcmp(item->data, "gb.bios")) { if (!strcmp(item->data, "gb.bios")) {
// TODO: show box if failed // TODO: show box if failed
if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS)) { if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS, NULL)) {
gbBiosPath[0] = '\0'; gbBiosPath[0] = '\0';
} }
continue; continue;
} }
if (!strcmp(item->data, "gbc.bios")) { if (!strcmp(item->data, "gbc.bios")) {
// TODO: show box if failed // TODO: show box if failed
if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS)) { if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS, NULL)) {
gbcBiosPath[0] = '\0'; gbcBiosPath[0] = '\0';
} }
continue; continue;
} }
if (!strcmp(item->data, "sgb.bios")) { if (!strcmp(item->data, "sgb.bios")) {
// TODO: show box if failed // TODO: show box if failed
if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS)) { if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS, NULL)) {
sgbBiosPath[0] = '\0'; sgbBiosPath[0] = '\0';
} }
continue; continue;

View File

@ -628,10 +628,18 @@ void mGUIRunloop(struct mGUIRunner* runner) {
} }
while (true) { while (true) {
char path[PATH_MAX]; char path[PATH_MAX];
if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL)) { const char* preselect = mCoreConfigGetValue(&runner->config, "lastGame");
if (preselect) {
preselect = strrchr(preselect, '/');
}
if (preselect) {
++preselect;
}
if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL, preselect)) {
break; break;
} }
mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath); mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath);
mCoreConfigSetValue(&runner->config, "lastGame", path);
mCoreConfigSave(&runner->config); mCoreConfigSave(&runner->config);
mGUIRun(runner, path); mGUIRun(runner, path);
} }

View File

@ -84,6 +84,11 @@ struct mVideoLogChannel {
z_stream inflateStream; z_stream inflateStream;
#endif #endif
bool injecting;
enum mVideoLoggerInjectionPoint injectionPoint;
uint32_t ignorePackets;
struct CircleBuffer injectedBuffer;
struct CircleBuffer buffer; struct CircleBuffer buffer;
}; };
@ -94,6 +99,7 @@ struct mVideoLogContext {
struct mVideoLogChannel channels[mVL_MAX_CHANNELS]; struct mVideoLogChannel channels[mVL_MAX_CHANNELS];
bool write; bool write;
bool compression;
uint32_t activeChannel; uint32_t activeChannel;
struct VFile* backing; struct VFile* backing;
}; };
@ -285,14 +291,28 @@ void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uin
} }
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) { bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
struct mVideoLogChannel* channel = logger->dataContext;
uint32_t ignorePackets = 0;
if (channel && channel->injectionPoint == LOGGER_INJECTION_IMMEDIATE && !channel->injecting) {
mVideoLoggerRendererRunInjected(logger);
ignorePackets = channel->ignorePackets;
}
struct mVideoLoggerDirtyInfo item = {0}; struct mVideoLoggerDirtyInfo item = {0};
while (logger->readData(logger, &item, sizeof(item), block)) { while (logger->readData(logger, &item, sizeof(item), block)) {
if (ignorePackets & (1 << item.type)) {
continue;
}
switch (item.type) { switch (item.type) {
case DIRTY_SCANLINE:
if (channel && channel->injectionPoint == LOGGER_INJECTION_FIRST_SCANLINE && !channel->injecting && item.address == 0) {
mVideoLoggerRendererRunInjected(logger);
ignorePackets = channel->ignorePackets;
}
// Fall through
case DIRTY_REGISTER: case DIRTY_REGISTER:
case DIRTY_PALETTE: case DIRTY_PALETTE:
case DIRTY_OAM: case DIRTY_OAM:
case DIRTY_VRAM: case DIRTY_VRAM:
case DIRTY_SCANLINE:
case DIRTY_FLUSH: case DIRTY_FLUSH:
case DIRTY_FRAME: case DIRTY_FRAME:
case DIRTY_RANGE: case DIRTY_RANGE:
@ -308,15 +328,34 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
return !block; return !block;
} }
bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger) {
struct mVideoLogChannel* channel = logger->dataContext;
channel->injecting = true;
bool res = mVideoLoggerRendererRun(logger, false);
channel->injecting = false;
return res;
}
void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint injectionPoint) {
struct mVideoLogChannel* channel = logger->dataContext;
channel->injectionPoint = injectionPoint;
}
void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask) {
struct mVideoLogChannel* channel = logger->dataContext;
channel->ignorePackets = mask;
}
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
struct mVideoLogChannel* channel = logger->dataContext; struct mVideoLogChannel* channel = logger->dataContext;
return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
} }
static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) { static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
UNUSED(logger); struct mVideoLogChannel* channel = logger->dataContext;
UNUSED(data); if (channel->injecting) {
UNUSED(length); return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
}
return false; return false;
} }
@ -465,6 +504,12 @@ struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
context->initialStateSize = 0; context->initialStateSize = 0;
context->initialState = NULL; context->initialState = NULL;
#ifdef USE_ZLIB
context->compression = true;
#else
context->compression = false;
#endif
if (core) { if (core) {
context->initialStateSize = core->stateSize(core); context->initialStateSize = core->stateSize(core);
context->initialState = anonymousMemoryMap(context->initialStateSize); context->initialState = anonymousMemoryMap(context->initialStateSize);
@ -482,6 +527,10 @@ void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* v
vf->seek(vf, 0, SEEK_SET); vf->seek(vf, 0, SEEK_SET);
} }
void mVideoLogContextSetCompression(struct mVideoLogContext* context, bool compression) {
context->compression = compression;
}
void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) { void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
struct mVideoLogHeader header = { { 0 } }; struct mVideoLogHeader header = { { 0 } };
memcpy(header.magic, mVL_MAGIC, sizeof(header.magic)); memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
@ -499,6 +548,7 @@ void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore*
struct mVLBlockHeader chheader = { 0 }; struct mVLBlockHeader chheader = { 0 };
STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType); STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
#ifdef USE_ZLIB #ifdef USE_ZLIB
if (context->compression) {
STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags); STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
struct VFile* vfm = VFileMemChunk(NULL, 0); struct VFile* vfm = VFileMemChunk(NULL, 0);
@ -509,11 +559,13 @@ void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore*
context->backing->write(context->backing, &chheader, sizeof(chheader)); context->backing->write(context->backing, &chheader, sizeof(chheader));
_copyVf(context->backing, vfm); _copyVf(context->backing, vfm);
vfm->close(vfm); vfm->close(vfm);
#else } else
#endif
{
STORE_32LE(context->initialStateSize, 0, &chheader.length); STORE_32LE(context->initialStateSize, 0, &chheader.length);
context->backing->write(context->backing, &chheader, sizeof(chheader)); context->backing->write(context->backing, &chheader, sizeof(chheader));
context->backing->write(context->backing, context->initialState, context->initialStateSize); context->backing->write(context->backing, context->initialState, context->initialStateSize);
#endif }
} }
size_t i; size_t i;
@ -609,6 +661,7 @@ bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
size_t i; size_t i;
for (i = 0; i < context->nChannels; ++i) { for (i = 0; i < context->nChannels; ++i) {
CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE);
CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE); CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
context->channels[i].bufferRemaining = 0; context->channels[i].bufferRemaining = 0;
context->channels[i].currentPointer = pointer; context->channels[i].currentPointer = pointer;
@ -647,9 +700,10 @@ static void _flushBufferCompressed(struct mVideoLogContext* context) {
static void _flushBuffer(struct mVideoLogContext* context) { static void _flushBuffer(struct mVideoLogContext* context) {
#ifdef USE_ZLIB #ifdef USE_ZLIB
// TODO: Make optional if (context->compression) {
_flushBufferCompressed(context); _flushBufferCompressed(context);
return; return;
}
#endif #endif
struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer; struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
@ -688,6 +742,7 @@ void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* contex
size_t i; size_t i;
for (i = 0; i < context->nChannels; ++i) { for (i = 0; i < context->nChannels; ++i) {
CircleBufferDeinit(&context->channels[i].injectedBuffer);
CircleBufferDeinit(&context->channels[i].buffer); CircleBufferDeinit(&context->channels[i].buffer);
#ifdef USE_ZLIB #ifdef USE_ZLIB
if (context->channels[i].inflating) { if (context->channels[i].inflating) {
@ -718,6 +773,7 @@ void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core
size_t i; size_t i;
for (i = 0; i < context->nChannels; ++i) { for (i = 0; i < context->nChannels; ++i) {
CircleBufferClear(&context->channels[i].injectedBuffer);
CircleBufferClear(&context->channels[i].buffer); CircleBufferClear(&context->channels[i].buffer);
context->channels[i].bufferRemaining = 0; context->channels[i].bufferRemaining = 0;
context->channels[i].currentPointer = pointer; context->channels[i].currentPointer = pointer;
@ -744,10 +800,35 @@ int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
int chid = context->nChannels; int chid = context->nChannels;
++context->nChannels; ++context->nChannels;
context->channels[chid].p = context; context->channels[chid].p = context;
CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE);
CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE); CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
context->channels[chid].injecting = false;
context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE;
context->channels[chid].ignorePackets = 0;
return chid; return chid;
} }
void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
struct mVideoLogChannel* channel = logger->dataContext;
channel->injecting = true;
mVideoLoggerRendererWriteVideoRegister(logger, address, value);
channel->injecting = false;
}
void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
struct mVideoLogChannel* channel = logger->dataContext;
channel->injecting = true;
mVideoLoggerRendererWritePalette(logger, address, value);
channel->injecting = false;
}
void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
struct mVideoLogChannel* channel = logger->dataContext;
channel->injecting = true;
mVideoLoggerRendererWriteOAM(logger, address, value);
channel->injecting = false;
}
#ifdef USE_ZLIB #ifdef USE_ZLIB
static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
uint8_t fbuffer[0x400]; uint8_t fbuffer[0x400];
@ -900,12 +981,16 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d
if (channelId >= mVL_MAX_CHANNELS) { if (channelId >= mVL_MAX_CHANNELS) {
return 0; return 0;
} }
if (CircleBufferSize(&channel->buffer) >= length) { struct CircleBuffer* buffer = &channel->buffer;
return CircleBufferRead(&channel->buffer, data, length); if (channel->injecting) {
buffer = &channel->injectedBuffer;
}
if (CircleBufferSize(buffer) >= length) {
return CircleBufferRead(buffer, data, length);
} }
ssize_t size = 0; ssize_t size = 0;
if (CircleBufferSize(&channel->buffer)) { if (CircleBufferSize(buffer)) {
size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer)); size = CircleBufferRead(buffer, data, CircleBufferSize(buffer));
if (size <= 0) { if (size <= 0) {
return size; return size;
} }
@ -915,7 +1000,7 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d
if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) { if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
return size; return size;
} }
size += CircleBufferRead(&channel->buffer, data, length); size += CircleBufferRead(buffer, data, length);
return size; return size;
} }
@ -929,16 +1014,20 @@ static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const
_flushBuffer(context); _flushBuffer(context);
context->activeChannel = channelId; context->activeChannel = channelId;
} }
if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) { struct CircleBuffer* buffer = &channel->buffer;
if (channel->injecting) {
buffer = &channel->injectedBuffer;
}
if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) {
_flushBuffer(context); _flushBuffer(context);
if (CircleBufferCapacity(&channel->buffer) < length) { if (CircleBufferCapacity(buffer) < length) {
CircleBufferDeinit(&channel->buffer); CircleBufferDeinit(buffer);
CircleBufferInit(&channel->buffer, toPow2(length << 1)); CircleBufferInit(buffer, toPow2(length << 1));
} }
} }
ssize_t read = CircleBufferWrite(&channel->buffer, data, length); ssize_t read = CircleBufferWrite(buffer, data, length);
if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) { if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) {
_flushBuffer(context); _flushBuffer(context);
} }
return read; return read;

View File

@ -902,10 +902,12 @@ static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* co
static void _GBCoreEndVideoLog(struct mCore* core) { static void _GBCoreEndVideoLog(struct mCore* core) {
struct GBCore* gbcore = (struct GBCore*) core; struct GBCore* gbcore = (struct GBCore*) core;
struct GB* gb = core->board; struct GB* gb = core->board;
if (gbcore->proxyRenderer.logger) {
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
free(gbcore->proxyRenderer.logger); free(gbcore->proxyRenderer.logger);
gbcore->proxyRenderer.logger = NULL; gbcore->proxyRenderer.logger = NULL;
} }
}
#endif #endif
struct mCore* GBCoreCreate(void) { struct mCore* GBCoreCreate(void) {
@ -1007,6 +1009,7 @@ static void _GBVLPStartFrameCallback(void *context) {
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
mVideoLogContextRewind(gbcore->logContext, core); mVideoLogContextRewind(gbcore->logContext, core);
GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer); GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
gb->earlyExit = true;
} }
} }
@ -1022,6 +1025,7 @@ static bool _GBVLPInit(struct mCore* core) {
gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback; gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback;
gbcore->logCallbacks.context = core; gbcore->logCallbacks.context = core;
core->addCoreCallbacks(core, &gbcore->logCallbacks); core->addCoreCallbacks(core, &gbcore->logCallbacks);
core->videoLogger = gbcore->proxyRenderer.logger;
return true; return true;
} }

View File

@ -419,14 +419,7 @@ void GBReset(struct LR35902Core* cpu) {
gb->biosVf->close(gb->biosVf); gb->biosVf->close(gb->biosVf);
gb->biosVf = NULL; gb->biosVf = NULL;
} else { } else {
gb->biosVf->seek(gb->biosVf, 0, SEEK_SET); GBMapBIOS(gb);
gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
if (size > 0x100) {
memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
}
cpu->a = 0; cpu->a = 0;
cpu->f.packed = 0; cpu->f.packed = 0;
cpu->c = 0; cpu->c = 0;
@ -563,6 +556,16 @@ void GBSkipBIOS(struct GB* gb) {
} }
} }
void GBMapBIOS(struct GB* gb) {
gb->biosVf->seek(gb->biosVf, 0, SEEK_SET);
gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
if (size > 0x100) {
memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
}
}
void GBUnmapBIOS(struct GB* gb) { void GBUnmapBIOS(struct GB* gb) {
if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) {
free(gb->memory.romBase); free(gb->memory.romBase);

View File

@ -185,8 +185,10 @@ void GBIOReset(struct GB* gb) {
GBIOWrite(gb, REG_NR51, 0xF3); GBIOWrite(gb, REG_NR51, 0xF3);
if (!gb->biosVf) { if (!gb->biosVf) {
GBIOWrite(gb, REG_LCDC, 0x91); GBIOWrite(gb, REG_LCDC, 0x91);
gb->memory.io[0x50] = 1;
} else { } else {
GBIOWrite(gb, REG_LCDC, 0x00); GBIOWrite(gb, REG_LCDC, 0x00);
gb->memory.io[0x50] = 0xFF;
} }
GBIOWrite(gb, REG_SCY, 0x00); GBIOWrite(gb, REG_SCY, 0x00);
GBIOWrite(gb, REG_SCX, 0x00); GBIOWrite(gb, REG_SCX, 0x00);

View File

@ -135,6 +135,16 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
if (ucheck16 >= 0x40) { if (ucheck16 >= 0x40) {
mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range"); mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range");
} }
bool differentBios = !gb->biosVf || gb->model != state->model;
if (state->io[0x50] == 0xFF) {
if (differentBios) {
mLOG(GB_STATE, WARN, "Incompatible savestate, please restart with correct BIOS in %s mode", GBModelToName(state->model));
error = true;
} else {
// TODO: Make it work correctly
mLOG(GB_STATE, WARN, "Loading savestate in BIOS. This may not work correctly");
}
}
if (error) { if (error) {
return false; return false;
} }
@ -187,6 +197,12 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
GBTimerDeserialize(&gb->timer, state); GBTimerDeserialize(&gb->timer, state);
GBAudioDeserialize(&gb->audio, state); GBAudioDeserialize(&gb->audio, state);
if (gb->memory.io[0x50] == 0xFF) {
GBMapBIOS(gb);
} else {
GBUnmapBIOS(gb);
}
if (gb->model & GB_MODEL_SGB && canSgb) { if (gb->model & GB_MODEL_SGB && canSgb) {
GBSGBDeserialize(gb, state); GBSGBDeserialize(gb, state);
} }

View File

@ -343,19 +343,19 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat
mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3)); mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3));
return; return;
} }
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) {
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
}
GBFrameEnded(video->p);
mCoreSyncPostFrame(video->p->sync);
--video->frameskipCounter; --video->frameskipCounter;
if (video->frameskipCounter < 0) { if (video->frameskipCounter < 0) {
video->renderer->finishFrame(video->renderer); video->renderer->finishFrame(video->renderer);
video->frameskipCounter = video->frameskip; video->frameskipCounter = video->frameskip;
} }
GBFrameEnded(video->p);
mCoreSyncPostFrame(video->p->sync);
++video->frameCounter; ++video->frameCounter;
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) {
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
}
GBFrameStarted(video->p); GBFrameStarted(video->p);
} }

View File

@ -137,8 +137,11 @@ struct GBACore {
#if defined(BUILD_GLES2) || defined(BUILD_GLES3) #if defined(BUILD_GLES2) || defined(BUILD_GLES3)
struct GBAVideoGLRenderer glRenderer; struct GBAVideoGLRenderer glRenderer;
#endif #endif
#ifndef MINIMAL_CORE
struct GBAVideoProxyRenderer vlProxy;
struct GBAVideoProxyRenderer proxyRenderer; struct GBAVideoProxyRenderer proxyRenderer;
struct mVideoLogContext* logContext; struct mVideoLogContext* logContext;
#endif
struct mCoreCallbacks logCallbacks; struct mCoreCallbacks logCallbacks;
#ifndef DISABLE_THREADING #ifndef DISABLE_THREADING
struct mVideoThreadProxy threadProxy; struct mVideoThreadProxy threadProxy;
@ -170,7 +173,9 @@ static bool _GBACoreInit(struct mCore* core) {
gbacore->overrides = NULL; gbacore->overrides = NULL;
gbacore->debuggerPlatform = NULL; gbacore->debuggerPlatform = NULL;
gbacore->cheatDevice = NULL; gbacore->cheatDevice = NULL;
#ifndef MINIMAL_CORE
gbacore->logContext = NULL; gbacore->logContext = NULL;
#endif
gbacore->audioMixer = NULL; gbacore->audioMixer = NULL;
GBACreate(gba); GBACreate(gba);
@ -192,7 +197,10 @@ static bool _GBACoreInit(struct mCore* core) {
#ifndef DISABLE_THREADING #ifndef DISABLE_THREADING
mVideoThreadProxyCreate(&gbacore->threadProxy); mVideoThreadProxyCreate(&gbacore->threadProxy);
#endif #endif
#ifndef MINIMAL_CORE
gbacore->vlProxy.logger = NULL;
gbacore->proxyRenderer.logger = NULL; gbacore->proxyRenderer.logger = NULL;
#endif
gbacore->keys = 0; gbacore->keys = 0;
gba->keySource = &gbacore->keys; gba->keySource = &gbacore->keys;
@ -475,11 +483,13 @@ static void _GBACoreReset(struct mCore* core) {
} }
} }
#endif #endif
#ifndef MINIMAL_CORE
if (core->videoLogger) { if (core->videoLogger) {
gbacore->proxyRenderer.logger = core->videoLogger; gbacore->proxyRenderer.logger = core->videoLogger;
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer); GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
renderer = &gbacore->proxyRenderer.d; renderer = &gbacore->proxyRenderer.d;
} }
#endif
GBAVideoAssociateRenderer(&gba->video, renderer); GBAVideoAssociateRenderer(&gba->video, renderer);
} }
@ -1000,21 +1010,23 @@ static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* c
state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM; state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM;
int channelId = mVideoLoggerAddChannel(context); int channelId = mVideoLoggerAddChannel(context);
gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger));
mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, false); mVideoLoggerRendererCreate(gbacore->vlProxy.logger, false);
mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, context, channelId); mVideoLoggerAttachChannel(gbacore->vlProxy.logger, context, channelId);
gbacore->proxyRenderer.logger->block = false; gbacore->vlProxy.logger->block = false;
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, &gbacore->renderer.d); GBAVideoProxyRendererCreate(&gbacore->vlProxy, gba->video.renderer);
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
} }
static void _GBACoreEndVideoLog(struct mCore* core) { static void _GBACoreEndVideoLog(struct mCore* core) {
struct GBACore* gbacore = (struct GBACore*) core; struct GBACore* gbacore = (struct GBACore*) core;
struct GBA* gba = core->board; struct GBA* gba = core->board;
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); if (gbacore->vlProxy.logger) {
free(gbacore->proxyRenderer.logger); GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
gbacore->proxyRenderer.logger = NULL; free(gbacore->vlProxy.logger);
gbacore->vlProxy.logger = NULL;
}
} }
#endif #endif
@ -1112,10 +1124,11 @@ static void _GBAVLPStartFrameCallback(void *context) {
struct GBACore* gbacore = (struct GBACore*) core; struct GBACore* gbacore = (struct GBACore*) core;
struct GBA* gba = core->board; struct GBA* gba = core->board;
if (!mVideoLoggerRendererRun(gbacore->proxyRenderer.logger, true)) { if (!mVideoLoggerRendererRun(gbacore->vlProxy.logger, true)) {
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
mVideoLogContextRewind(gbacore->logContext, core); mVideoLogContextRewind(gbacore->logContext, core);
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
gba->earlyExit = true;
} }
} }
@ -1124,13 +1137,14 @@ static bool _GBAVLPInit(struct mCore* core) {
if (!_GBACoreInit(core)) { if (!_GBACoreInit(core)) {
return false; return false;
} }
gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger));
mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, true); mVideoLoggerRendererCreate(gbacore->vlProxy.logger, true);
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, NULL); GBAVideoProxyRendererCreate(&gbacore->vlProxy, NULL);
memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks)); memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks));
gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback; gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback;
gbacore->logCallbacks.context = core; gbacore->logCallbacks.context = core;
core->addCoreCallbacks(core, &gbacore->logCallbacks); core->addCoreCallbacks(core, &gbacore->logCallbacks);
core->videoLogger = gbacore->vlProxy.logger;
return true; return true;
} }
@ -1145,8 +1159,8 @@ static void _GBAVLPDeinit(struct mCore* core) {
static void _GBAVLPReset(struct mCore* core) { static void _GBAVLPReset(struct mCore* core) {
struct GBACore* gbacore = (struct GBACore*) core; struct GBACore* gbacore = (struct GBACore*) core;
struct GBA* gba = (struct GBA*) core->board; struct GBA* gba = (struct GBA*) core->board;
if (gba->video.renderer == &gbacore->proxyRenderer.d) { if (gba->video.renderer == &gbacore->vlProxy.d) {
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
} else if (gbacore->renderer.outputBuffer) { } else if (gbacore->renderer.outputBuffer) {
struct GBAVideoRenderer* renderer = &gbacore->renderer.d; struct GBAVideoRenderer* renderer = &gbacore->renderer.d;
GBAVideoAssociateRenderer(&gba->video, renderer); GBAVideoAssociateRenderer(&gba->video, renderer);
@ -1154,7 +1168,7 @@ static void _GBAVLPReset(struct mCore* core) {
ARMReset(core->cpu); ARMReset(core->cpu);
mVideoLogContextRewind(gbacore->logContext, core); mVideoLogContextRewind(gbacore->logContext, core);
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
// Make sure CPU loop never spins // Make sure CPU loop never spins
GBAHalt(gba); GBAHalt(gba);
@ -1170,7 +1184,7 @@ static bool _GBAVLPLoadROM(struct mCore* core, struct VFile* vf) {
gbacore->logContext = NULL; gbacore->logContext = NULL;
return false; return false;
} }
mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, gbacore->logContext, 0); mVideoLoggerAttachChannel(gbacore->vlProxy.logger, gbacore->logContext, 0);
return true; return true;
} }

View File

@ -87,7 +87,7 @@ void _battlechipTransfer(struct GBASIOBattlechipGate* gate) {
if (gate->d.p->mode == SIO_NORMAL_32) { if (gate->d.p->mode == SIO_NORMAL_32) {
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000; cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000;
} else { } else {
cycles = GBASIOCyclesPerTransfer[gate->d.p->multiplayerControl.baud][1]; cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1];
} }
mTimingDeschedule(&gate->d.p->p->timing, &gate->event); mTimingDeschedule(&gate->d.p->p->timing, &gate->event);
mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles); mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles);
@ -100,8 +100,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
if (gate->d.p->mode == SIO_NORMAL_32) { if (gate->d.p->mode == SIO_NORMAL_32) {
gate->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0; gate->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0;
gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0; gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0;
gate->d.p->normalControl.start = 0; gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt);
if (gate->d.p->normalControl.irq) { if (GBASIONormalIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
} }
return; return;
@ -112,8 +112,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
gate->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = cmd; gate->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = cmd;
gate->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; gate->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
gate->d.p->multiplayerControl.busy = 0; gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt);
gate->d.p->multiplayerControl.id = 0; gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0);
mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state); mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state);
@ -193,7 +193,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply; gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply;
if (gate->d.p->multiplayerControl.irq) { if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
} }
} }

View File

@ -44,6 +44,17 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
renderer->d.disableBG[3] = false; renderer->d.disableBG[3] = false;
renderer->d.disableOBJ = false; renderer->d.disableOBJ = false;
renderer->d.highlightBG[0] = false;
renderer->d.highlightBG[1] = false;
renderer->d.highlightBG[2] = false;
renderer->d.highlightBG[3] = false;
int i;
for (i = 0; i < 128; ++i) {
renderer->d.highlightOBJ[i] = false;
}
renderer->d.highlightColor = 0xFFFFFF;
renderer->d.highlightAmount = 0;
renderer->logger->context = renderer; renderer->logger->context = renderer;
renderer->logger->parsePacket = _parsePacket; renderer->logger->parsePacket = _parsePacket;
renderer->logger->handleEvent = _handleEvent; renderer->logger->handleEvent = _handleEvent;
@ -205,6 +216,12 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2]; proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2];
proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3]; proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3];
proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ; proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ;
proxyRenderer->backend->highlightBG[0] = proxyRenderer->d.highlightBG[0];
proxyRenderer->backend->highlightBG[1] = proxyRenderer->d.highlightBG[1];
proxyRenderer->backend->highlightBG[2] = proxyRenderer->d.highlightBG[2];
proxyRenderer->backend->highlightBG[3] = proxyRenderer->d.highlightBG[3];
memcpy(proxyRenderer->backend->highlightOBJ, proxyRenderer->d.highlightOBJ, sizeof(proxyRenderer->backend->highlightOBJ));
proxyRenderer->backend->highlightAmount = proxyRenderer->d.highlightAmount;
if (item->address < GBA_VIDEO_VERTICAL_PIXELS) { if (item->address < GBA_VIDEO_VERTICAL_PIXELS) {
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address); proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address);
} }

View File

@ -577,10 +577,10 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
++gbp->p->gbpTxPosition; ++gbp->p->gbpTxPosition;
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx; gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16; gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
if (gbp->d.p->normalControl.irq) { if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate); GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate);
} }
gbp->d.p->normalControl.start = 0; gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080; gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;
} }

View File

@ -396,9 +396,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
value = 0; \ value = 0; \
break; \ break; \
} \ } \
address &= 0x00017FFC; \ LOAD_32(value, address & 0x00017FFC, gba->video.vram); \
} \ } else { \
LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \
} \
wait += waitstatesRegion[REGION_VRAM]; wait += waitstatesRegion[REGION_VRAM];
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);
@ -530,9 +531,10 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
value = 0; value = 0;
break; break;
} }
address &= 0x00017FFE; LOAD_16(value, address & 0x00017FFE, gba->video.vram);
} } else {
LOAD_16(value, address & 0x0001FFFE, gba->video.vram); LOAD_16(value, address & 0x0001FFFE, gba->video.vram);
}
break; break;
case REGION_OAM: case REGION_OAM:
LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw); LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);
@ -645,9 +647,10 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
value = 0; value = 0;
break; break;
} }
address &= 0x00017FFF; value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF];
} } else {
value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF];
}
break; break;
case REGION_OAM: case REGION_OAM:
value = ((uint8_t*) gba->video.oam.raw)[address & (SIZE_OAM - 1)]; value = ((uint8_t*) gba->video.oam.raw)[address & (SIZE_OAM - 1)];
@ -734,14 +737,20 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store32: 0x%08X", address); \ mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store32: 0x%08X", address); \
break; \ break; \
} \ } \
address &= 0x00017FFC; \ LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram); \
if (oldValue != value) { \
STORE_32(value, address & 0x00017FFC, gba->video.vram); \
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \
} \ } \
} else { \
LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); \ LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); \
if (oldValue != value) { \ if (oldValue != value) { \
STORE_32(value, address & 0x0001FFFC, gba->video.vram); \ STORE_32(value, address & 0x0001FFFC, gba->video.vram); \
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \
} \ } \
} \
wait += waitstatesRegion[REGION_VRAM]; wait += waitstatesRegion[REGION_VRAM];
#define STORE_OAM \ #define STORE_OAM \
@ -855,13 +864,18 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store16: 0x%08X", address); mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store16: 0x%08X", address);
break; break;
} }
address &= 0x00017FFE; LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram);
if (value != oldValue) {
STORE_16(value, address & 0x00017FFE, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
} }
} else {
LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram); LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram);
if (value != oldValue) { if (value != oldValue) {
STORE_16(value, address & 0x0001FFFE, gba->video.vram); STORE_16(value, address & 0x0001FFFE, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
} }
}
break; break;
case REGION_OAM: case REGION_OAM:
LOAD_16(oldValue, address & (SIZE_OAM - 2), gba->video.oam.raw); LOAD_16(oldValue, address & (SIZE_OAM - 2), gba->video.oam.raw);

View File

@ -34,6 +34,7 @@ int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite*
sprites[oamMax].y = y; sprites[oamMax].y = y;
sprites[oamMax].endY = y + height; sprites[oamMax].endY = y + height;
sprites[oamMax].obj = obj; sprites[oamMax].obj = obj;
sprites[oamMax].index = i;
++oamMax; ++oamMax;
} }
} }

View File

@ -155,7 +155,7 @@ static const char* const _renderMode0 =
" if ((size & 1) == 1) {\n" " if ((size & 1) == 1) {\n"
" coord.y += coord.x & 256;\n" " coord.y += coord.x & 256;\n"
" }\n" " }\n"
" coord &= ivec2(255, 511);\n" " coord &= ivec2(255, 1023);\n"
" int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n" " int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
" vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n" " vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
" int tileFlags = int(map.g * 15.9);\n" " int tileFlags = int(map.g * 15.9);\n"
@ -442,7 +442,7 @@ static const char* const _renderObj =
"uniform ivec4 mosaic;\n" "uniform ivec4 mosaic;\n"
"OUT(0) out vec4 color;\n" "OUT(0) out vec4 color;\n"
"OUT(1) out ivec4 flags;\n" "OUT(1) out ivec4 flags;\n"
"OUT(2) out ivec3 window;\n" "OUT(2) out ivec4 window;\n"
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
@ -467,7 +467,7 @@ static const char* const _renderObj =
" color = pix;\n" " color = pix;\n"
" flags = inflags;\n" " flags = inflags;\n"
" gl_FragDepth = float(flags.x) / 16.;\n" " gl_FragDepth = float(flags.x) / 16.;\n"
" window = objwin.yzw;\n" " window = ivec4(objwin.yzw, 0);\n"
"}"; "}";
static const struct GBAVideoGLUniform _uniformsWindow[] = { static const struct GBAVideoGLUniform _uniformsWindow[] = {
@ -488,7 +488,7 @@ static const char* const _renderWindow =
"uniform ivec3 flags;\n" "uniform ivec3 flags;\n"
"uniform ivec4 win0[160];\n" "uniform ivec4 win0[160];\n"
"uniform ivec4 win1[160];\n" "uniform ivec4 win1[160];\n"
"OUT(0) out ivec3 window;\n" "OUT(0) out ivec4 window;\n"
"void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n" "void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n"
" bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n" " bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"
@ -526,7 +526,7 @@ static const char* const _renderWindow =
"void main() {\n" "void main() {\n"
" int dispflags = (dispcnt & 0x1F) | 0x20;\n" " int dispflags = (dispcnt & 0x1F) | 0x20;\n"
" if ((dispcnt & 0xE0) == 0) {\n" " if ((dispcnt & 0xE0) == 0) {\n"
" window = ivec3(dispflags, blend);\n" " window = ivec4(dispflags, blend, 0);\n"
" } else {\n" " } else {\n"
" ivec3 windowFlags = ivec3(flags.z, blend);\n" " ivec3 windowFlags = ivec3(flags.z, blend);\n"
" if ((dispcnt & 0x40) != 0) { \n" " if ((dispcnt & 0x40) != 0) { \n"
@ -535,7 +535,7 @@ static const char* const _renderWindow =
" if ((dispcnt & 0x20) != 0) { \n" " if ((dispcnt & 0x20) != 0) { \n"
" crop(interpolate(win0), flags.x, windowFlags);\n" " crop(interpolate(win0), flags.x, windowFlags);\n"
" }\n" " }\n"
" window = windowFlags;\n" " window = ivec4(windowFlags, 0);\n"
" }\n" " }\n"
"}\n"; "}\n";
@ -651,6 +651,17 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) {
renderer->d.disableBG[3] = false; renderer->d.disableBG[3] = false;
renderer->d.disableOBJ = false; renderer->d.disableOBJ = false;
renderer->d.highlightBG[0] = false;
renderer->d.highlightBG[1] = false;
renderer->d.highlightBG[2] = false;
renderer->d.highlightBG[3] = false;
int i;
for (i = 0; i < 128; ++i) {
renderer->d.highlightOBJ[i] = false;
}
renderer->d.highlightColor = 0xFFFFFF;
renderer->d.highlightAmount = 0;
renderer->scale = 1; renderer->scale = 1;
} }
@ -1369,7 +1380,7 @@ void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) {
glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1); glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1);
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 1.f }); 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 }); glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 });
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });

View File

@ -613,16 +613,21 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer
uint32_t screenBase; uint32_t screenBase;
uint32_t charBase; uint32_t charBase;
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
color_t* mainPalette; color_t* mainPalette = renderer->normalPalette;
if (background->multipalette && background->extPalette) { if (background->multipalette && background->extPalette) {
mainPalette = background->extPalette; mainPalette = background->extPalette;
if (variant) { if (variant) {
mainPalette = background->variantPalette; mainPalette = background->variantPalette;
} }
} else { } else {
mainPalette = renderer->normalPalette; if (renderer->d.highlightAmount && background->highlight) {
mainPalette = renderer->highlightPalette;
}
if (variant) { if (variant) {
mainPalette = renderer->variantPalette; mainPalette = renderer->variantPalette;
if (renderer->d.highlightAmount && background->highlight) {
mainPalette = renderer->highlightVariantPalette;
}
} }
} }
color_t* palette = mainPalette; color_t* palette = mainPalette;

View File

@ -212,7 +212,7 @@
renderer->row[outX] |= FLAG_OBJWIN; \ renderer->row[outX] |= FLAG_OBJWIN; \
} }
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y) {
int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0]; int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1]; int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
int start = renderer->start; int start = renderer->start;
@ -255,6 +255,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
} }
color_t* palette = &renderer->normalPalette[0x100]; color_t* palette = &renderer->normalPalette[0x100];
if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
palette = &renderer->highlightPalette[0x100];
}
color_t* objwinPalette = palette; color_t* objwinPalette = palette;
if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) { if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {
@ -269,6 +272,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
} }
} else if (variant) { } else if (variant) {
palette = &renderer->variantPalette[0x100]; palette = &renderer->variantPalette[0x100];
if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
palette = &renderer->highlightVariantPalette[0x100];
}
if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) { if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
objwinPalette = palette; objwinPalette = palette;
} }

View File

@ -26,7 +26,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer
void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer,
struct GBAVideoSoftwareBackground* background, int y); struct GBAVideoSoftwareBackground* background, int y);
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y);
void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority);
void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y); void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y);
@ -145,11 +145,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
int objwinForceEnable = 0; \ int objwinForceEnable = 0; \
UNUSED(objwinForceEnable); \ UNUSED(objwinForceEnable); \
color_t* objwinPalette = renderer->normalPalette; \ color_t* objwinPalette = renderer->normalPalette; \
if (renderer->d.highlightAmount && background->highlight) { \
objwinPalette = renderer->highlightPalette; \
} \
UNUSED(objwinPalette); \ UNUSED(objwinPalette); \
if (objwinSlowPath) { \ if (objwinSlowPath) { \
if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \ if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
objwinPalette = renderer->variantPalette; \ objwinPalette = renderer->variantPalette; \
if (renderer->d.highlightAmount && background->highlight) { \
palette = renderer->highlightVariantPalette; \
} \
} \ } \
switch (background->index) { \ switch (background->index) { \
case 0: \ case 0: \
@ -204,8 +210,14 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \ int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
color_t* palette = renderer->normalPalette; \ color_t* palette = renderer->normalPalette; \
if (renderer->d.highlightAmount && background->highlight) { \
palette = renderer->highlightPalette; \
} \
if (variant) { \ if (variant) { \
palette = renderer->variantPalette; \ palette = renderer->variantPalette; \
if (renderer->d.highlightAmount && background->highlight) { \
palette = renderer->highlightVariantPalette; \
} \
} \ } \
UNUSED(palette); \ UNUSED(palette); \
PREPARE_OBJWIN; PREPARE_OBJWIN;

View File

@ -68,6 +68,17 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS; renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS;
renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS; renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS;
renderer->d.highlightBG[0] = false;
renderer->d.highlightBG[1] = false;
renderer->d.highlightBG[2] = false;
renderer->d.highlightBG[3] = false;
int i;
for (i = 0; i < 128; ++i) {
renderer->d.highlightOBJ[i] = false;
}
renderer->d.highlightColor = GBA_COLOR_WHITE;
renderer->d.highlightAmount = 0;
renderer->temporaryBuffer = 0; renderer->temporaryBuffer = 0;
} }
@ -573,11 +584,23 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y); GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y); int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
softwareRenderer->d.vramOBJ[0] = objVramBase; softwareRenderer->d.vramOBJ[0] = objVramBase;
if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) {
softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount;
if (softwareRenderer->lastHighlightAmount) {
softwareRenderer->blendDirty = true;
}
}
if (softwareRenderer->blendDirty) { if (softwareRenderer->blendDirty) {
_updatePalettes(softwareRenderer); _updatePalettes(softwareRenderer);
softwareRenderer->blendDirty = false; softwareRenderer->blendDirty = false;
} }
softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0];
softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1];
softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2];
softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3];
int w; int w;
unsigned priority; unsigned priority;
for (priority = 0; priority < 4; ++priority) { for (priority = 0; priority < 4; ++priority) {
@ -945,12 +968,12 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
continue; continue;
} }
if (GBAObjAttributesAIsMosaic(sprite->obj.a)) { if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
localY = mosaicY; localY = mosaicY;
if (localY < sprite->y) { if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
localY = sprite->y; localY = sprite->y;
} }
if (localY >= sprite->endY) { if (localY >= (sprite->endY & 0xFF)) {
localY = sprite->endY - 1; localY = sprite->endY - 1;
} }
} }
@ -965,7 +988,7 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
continue; continue;
} }
int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY); int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY);
spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c); spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
} }
if (renderer->spriteCyclesRemaining <= 0) { if (renderer->spriteCyclesRemaining <= 0) {
@ -993,4 +1016,12 @@ static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
renderer->variantPalette[i] = renderer->normalPalette[i]; renderer->variantPalette[i] = renderer->normalPalette[i];
} }
} }
unsigned highlightAmount = renderer->d.highlightAmount >> 4;
if (highlightAmount) {
for (i = 0; i < 512; ++i) {
renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor);
renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor);
}
}
} }

View File

@ -71,7 +71,7 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
node->d.p->multiplayerControl.slave = node->id > 0; node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0);
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id); mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
node->event.context = node; node->event.context = node;
node->event.name = "GBA SIO Lockstep"; node->event.name = "GBA SIO Lockstep";
@ -99,10 +99,10 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister; node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
node->d.p->rcnt |= 3; node->d.p->rcnt |= 3;
ATOMIC_ADD(node->p->attachedMulti, 1); ATOMIC_ADD(node->p->attachedMulti, 1);
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached; node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
if (node->id) { if (node->id) {
node->d.p->rcnt |= 4; node->d.p->rcnt |= 4;
node->d.p->multiplayerControl.slave = 1; node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
} }
break; break;
case SIO_NORMAL_32: case SIO_NORMAL_32:
@ -178,10 +178,10 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
ATOMIC_LOAD(transferActive, node->p->d.transferActive); ATOMIC_LOAD(transferActive, node->p->d.transferActive);
if (value & 0x0080 && transferActive == TRANSFER_IDLE) { if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
if (!node->id && node->d.p->multiplayerControl.ready) { if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1]); ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event); bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
int oldWhen = node->event.when; int oldWhen = node->event.when;
@ -220,37 +220,37 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2]; sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3]; sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
sio->rcnt |= 1; sio->rcnt |= 1;
sio->multiplayerControl.busy = 0; sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
sio->multiplayerControl.id = node->id; sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
if (sio->multiplayerControl.irq) { if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, IRQ_SIO, 0); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
break; break;
case SIO_NORMAL_8: case SIO_NORMAL_8:
// TODO // TODO
sio->normalControl.start = 0; sio->siocnt = GBASIONormalClearStart(sio->siocnt);
if (node->id) { if (node->id) {
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo; sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF; node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
} else { } else {
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
} }
if (sio->multiplayerControl.irq) { if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, IRQ_SIO, 0); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
break; break;
case SIO_NORMAL_32: case SIO_NORMAL_32:
// TODO // TODO
sio->normalControl.start = 0; sio->siocnt = GBASIONormalClearStart(sio->siocnt);
if (node->id) { if (node->id) {
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo; sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1]; node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
} else { } else {
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
} }
if (sio->multiplayerControl.irq) { if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, IRQ_SIO, 0); GBARaiseIRQ(sio->p, IRQ_SIO, 0);
} }
break; break;
@ -278,7 +278,7 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
case TRANSFER_IDLE: case TRANSFER_IDLE:
// If the master hasn't initiated a transfer, it can keep going. // If the master hasn't initiated a transfer, it can keep going.
node->nextEvent += LOCKSTEP_INCREMENT; node->nextEvent += LOCKSTEP_INCREMENT;
node->d.p->multiplayerControl.ready = attachedMulti == attached; node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
break; break;
case TRANSFER_STARTING: case TRANSFER_STARTING:
// Start the transfer, but wait for the other GBAs to catch up // Start the transfer, but wait for the other GBAs to catch up
@ -352,11 +352,11 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
ATOMIC_LOAD(attachedMulti, node->p->attachedMulti); ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
ATOMIC_LOAD(attached, node->p->d.attached); ATOMIC_LOAD(attached, node->p->d.attached);
node->d.p->multiplayerControl.ready = attachedMulti == attached; node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
bool signal = false; bool signal = false;
switch (transferActive) { switch (transferActive) {
case TRANSFER_IDLE: case TRANSFER_IDLE:
if (!node->d.p->multiplayerControl.ready) { if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT); node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
} }
break; break;
@ -376,7 +376,7 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
node->d.p->multiplayerControl.busy = 1; node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
break; break;
case SIO_NORMAL_8: case SIO_NORMAL_8:
node->p->multiRecv[node->id] = 0xFFFF; node->p->multiRecv[node->id] = 0xFFFF;
@ -455,7 +455,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value); mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
value &= 0xFF8B; value &= 0xFF8B;
if (!node->id) { if (!node->id) {
driver->p->normalControl.si = 1; driver->p->siocnt = GBASIONormalFillSi(driver->p->siocnt);
} }
if (value & 0x0080 && !node->id) { if (value & 0x0080 && !node->id) {
// Internal shift clock // Internal shift clock

View File

@ -104,6 +104,7 @@ static bool frameStarted = false;
static C3D_RenderTarget* upscaleBuffer; static C3D_RenderTarget* upscaleBuffer;
static C3D_Tex upscaleBufferTex; static C3D_Tex upscaleBufferTex;
static bool interframeBlending = false; static bool interframeBlending = false;
static bool sgbCrop = false;
static aptHookCookie cookie; static aptHookCookie cookie;
static bool core2; static bool core2;
@ -382,6 +383,10 @@ static void _gameLoaded(struct mGUIRunner* runner) {
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
interframeBlending = fakeBool; interframeBlending = fakeBool;
} }
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
sgbCrop = fakeBool;
}
} }
static void _gameUnloaded(struct mGUIRunner* runner) { static void _gameUnloaded(struct mGUIRunner* runner) {
@ -437,6 +442,12 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
int w = corew; int w = corew;
int h = coreh; int h = coreh;
if (sgbCrop && w == 256 && h == 224) {
w = GB_VIDEO_HORIZONTAL_PIXELS;
h = GB_VIDEO_VERTICAL_PIXELS;
}
int innerw = w;
int innerh = h;
// Get greatest common divisor // Get greatest common divisor
while (w != 0) { while (w != 0) {
int temp = h % w; int temp = h % w;
@ -444,8 +455,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
w = temp; w = temp;
} }
int gcd = h; int gcd = h;
unsigned aspectw = corew / gcd; unsigned aspectw = innerw / gcd;
unsigned aspecth = coreh / gcd; unsigned aspecth = innerh / gcd;
int x = 0; int x = 0;
int y = 0; int y = 0;
@ -517,6 +528,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
} }
ctrFlushBatch(); ctrFlushBatch();
innerw = corew;
innerh = coreh;
corew = w; corew = w;
coreh = h; coreh = h;
screen_h = 240; screen_h = 240;
@ -529,19 +542,20 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
} }
ctrSetViewportSize(screen_w, screen_h, true); ctrSetViewportSize(screen_w, screen_h, true);
float afw, afh;
switch (screenMode) { switch (screenMode) {
default: default:
return; return;
case SM_AF_TOP: case SM_AF_TOP:
case SM_AF_BOTTOM: case SM_AF_BOTTOM:
w = screen_w / aspectw; afw = screen_w / (float) aspectw;
h = screen_h / aspecth; afh = screen_h / (float) aspecth;
if (w * aspecth > screen_h) { if (afw * aspecth > screen_h) {
w = aspectw * h; w = innerw * afh / gcd;
h = aspecth * h; h = innerh * afh / gcd;
} else { } else {
h = aspecth * w; h = innerh * afw / gcd;
w = aspectw * w; w = innerw * afw / gcd;
} }
break; break;
case SM_SF_TOP: case SM_SF_TOP:

View File

@ -41,7 +41,7 @@ static void _drawEnd(void) {
static uint32_t _pollInput(const struct mInputMap* map) { static uint32_t _pollInput(const struct mInputMap* map) {
SceCtrlData pad; SceCtrlData pad;
sceCtrlPeekBufferPositive(0, &pad, 1); sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0); int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0);
if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) { if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) {
@ -127,17 +127,17 @@ int main() {
.id = PSP2_INPUT, .id = PSP2_INPUT,
.keyNames = (const char*[]) { .keyNames = (const char*[]) {
"Select", "Select",
0, "L3",
0, "R3",
"Start", "Start",
"Up", "Up",
"Right", "Right",
"Down", "Down",
"Left", "Left",
"L", "L2",
"R", "R2",
0, // L2? "L1",
0, // R2? "R1",
"\1\xC", "\1\xC",
"\1\xA", "\1\xA",
"\1\xB", "\1\xB",

View File

@ -56,6 +56,7 @@ static vita2d_texture* oldTex;
static vita2d_texture* screenshot; static vita2d_texture* screenshot;
static Thread audioThread; static Thread audioThread;
static bool interframeBlending = false; static bool interframeBlending = false;
static bool sgbCrop = false;
static struct mSceRotationSource { static struct mSceRotationSource {
struct mRotationSource d; struct mRotationSource d;
@ -267,7 +268,7 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig
uint16_t mPSP2PollInput(struct mGUIRunner* runner) { uint16_t mPSP2PollInput(struct mGUIRunner* runner) {
SceCtrlData pad; SceCtrlData pad;
sceCtrlPeekBufferPositive(0, &pad, 1); sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0); int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0);
int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly); int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly);
@ -313,8 +314,8 @@ void mPSP2Setup(struct mGUIRunner* runner) {
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN);
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT);
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT);
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LTRIGGER, GBA_KEY_L); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, GBA_KEY_L);
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RTRIGGER, GBA_KEY_R); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, GBA_KEY_R);
struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 }; struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 };
mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc); mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);
@ -365,6 +366,10 @@ void mPSP2Setup(struct mGUIRunner* runner) {
if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) { if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
camera.cam = mode; camera.cam = mode;
} }
int fakeBool;
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
sgbCrop = fakeBool;
}
} }
void mPSP2LoadROM(struct mGUIRunner* runner) { void mPSP2LoadROM(struct mGUIRunner* runner) {
@ -398,6 +403,18 @@ void mPSP2LoadROM(struct mGUIRunner* runner) {
interframeBlending = fakeBool; interframeBlending = fakeBool;
} }
// Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R
if (!sceKernelIsPSVitaTV()) {
int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2));
if (key >= 0) {
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key);
}
key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2));
if (key >= 0) {
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key);
}
}
MutexInit(&audioContext.mutex); MutexInit(&audioContext.mutex);
ConditionInit(&audioContext.cond); ConditionInit(&audioContext.cond);
memset(audioContext.buffer, 0, sizeof(audioContext.buffer)); memset(audioContext.buffer, 0, sizeof(audioContext.buffer));
@ -461,10 +478,15 @@ void mPSP2Unpaused(struct mGUIRunner* runner) {
} }
int fakeBool; int fakeBool;
mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool); if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
interframeBlending = fakeBool; interframeBlending = fakeBool;
} }
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
sgbCrop = fakeBool;
}
}
void mPSP2Teardown(struct mGUIRunner* runner) { void mPSP2Teardown(struct mGUIRunner* runner) {
UNUSED(runner); UNUSED(runner);
CircleBufferDeinit(&rumble.history); CircleBufferDeinit(&rumble.history);
@ -507,6 +529,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo
vita2d_draw_texture_tint(backdrop, 0, 0, tint); vita2d_draw_texture_tint(backdrop, 0, 0, tint);
// Fall through // Fall through
case SM_PLAIN: case SM_PLAIN:
if (sgbCrop && width == 256 && height == 224) {
w = 768;
h = 672;
scalex = 3;
scaley = 3;
break;
}
w = 960 / width; w = 960 / width;
h = 544 / height; h = 544 / height;
if (w * height > 544) { if (w * height > 544) {
@ -521,6 +550,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo
scaley = scalex; scaley = scalex;
break; break;
case SM_ASPECT: case SM_ASPECT:
if (sgbCrop && width == 256 && height == 224) {
w = 967;
h = 846;
scalex = 34.0f / 9.0f;
scaley = scalex;
break;
}
w = 960 / aspectw; w = 960 / aspectw;
h = 544 / aspecth; h = 544 / aspecth;
if (w * aspecth > 544) { if (w * aspecth > 544) {

View File

@ -67,6 +67,18 @@ for line in preprocessed.splitlines():
lines.append(line) lines.append(line)
ffi.cdef('\n'.join(lines)) ffi.cdef('\n'.join(lines))
ffi.cdef("""
struct GBARTC {
int32_t bytesRemaining;
int32_t transferStep;
int32_t bitsRead;
int32_t bits;
int32_t commandActive;
RTCCommandData command;
RTCControl control;
uint8_t time[7];
};""", packed=True)
preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True) preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True)
lines = [] lines = []

View File

@ -0,0 +1,40 @@
/* Copyright (c) 2013-2019 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 "AssetInfo.h"
#include <QFontDatabase>
#include <QHBoxLayout>
using namespace QGBA;
AssetInfo::AssetInfo(QWidget* parent)
: QGroupBox(parent)
{
}
void AssetInfo::addCustomProperty(const QString& id, const QString& visibleName) {
QHBoxLayout* newLayout = new QHBoxLayout;
newLayout->addWidget(new QLabel(visibleName));
QLabel* value = new QLabel;
value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
value->setAlignment(Qt::AlignRight);
newLayout->addWidget(value);
m_customProperties[id] = value;
int index = customLocation();
static_cast<QBoxLayout*>(layout())->insertLayout(index, newLayout);
}
void AssetInfo::setCustomProperty(const QString& id, const QVariant& value) {
QLabel* label = m_customProperties[id];
if (!label) {
return;
}
label->setText(value.toString());
}
int AssetInfo::customLocation(const QString&) {
return layout()->count();
}

View File

@ -0,0 +1,34 @@
/* Copyright (c) 2013-2019 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/. */
#pragma once
#include <QGroupBox>
#include <QHash>
#include <QLabel>
#include <QVariant>
namespace QGBA {
class CoreController;
class AssetInfo : public QGroupBox {
Q_OBJECT
public:
AssetInfo(QWidget* parent = nullptr);
void addCustomProperty(const QString& id, const QString& visibleName);
public slots:
void setCustomProperty(const QString& id, const QVariant& value);
protected:
virtual int customLocation(const QString& id = {});
private:
QHash<QString, QLabel*> m_customProperties;
};
}

View File

@ -22,7 +22,7 @@
using namespace QGBA; using namespace QGBA;
AssetTile::AssetTile(QWidget* parent) AssetTile::AssetTile(QWidget* parent)
: QGroupBox(parent) : AssetInfo(parent)
{ {
m_ui.setupUi(this); m_ui.setupUi(this);
@ -42,16 +42,8 @@ AssetTile::AssetTile(QWidget* parent)
m_ui.b->setFont(font); m_ui.b->setFont(font);
} }
void AssetTile::addCustomProperty(const QString& id, const QString& visibleName) { int AssetTile::customLocation(const QString&) {
QHBoxLayout* newLayout = new QHBoxLayout; return layout()->indexOf(m_ui.line);
newLayout->addWidget(new QLabel(visibleName));
QLabel* value = new QLabel;
value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
value->setAlignment(Qt::AlignRight);
newLayout->addWidget(value);
m_customProperties[id] = value;
int index = layout()->indexOf(m_ui.line);
static_cast<QBoxLayout*>(layout())->insertLayout(index, newLayout);
} }
void AssetTile::setController(std::shared_ptr<CoreController> controller) { void AssetTile::setController(std::shared_ptr<CoreController> controller) {
@ -149,11 +141,3 @@ void AssetTile::selectColor(int index) {
m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 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'))); m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0')));
} }
void AssetTile::setCustomProperty(const QString& id, const QVariant& value) {
QLabel* label = m_customProperties[id];
if (!label) {
return;
}
label->setText(value.toString());
}

View File

@ -15,13 +15,12 @@ namespace QGBA {
class CoreController; class CoreController;
class AssetTile : public QGroupBox { class AssetTile : public AssetInfo {
Q_OBJECT Q_OBJECT
public: public:
AssetTile(QWidget* parent = nullptr); AssetTile(QWidget* parent = nullptr);
void setController(std::shared_ptr<CoreController>); void setController(std::shared_ptr<CoreController>);
void addCustomProperty(const QString& id, const QString& visibleName);
public slots: public slots:
void setPalette(int); void setPalette(int);
@ -29,7 +28,9 @@ public slots:
void selectIndex(int); void selectIndex(int);
void setFlip(bool h, bool v); void setFlip(bool h, bool v);
void selectColor(int); void selectColor(int);
void setCustomProperty(const QString& id, const QVariant& value);
protected:
int customLocation(const QString& id = {}) override;
private: private:
Ui::AssetTile m_ui; Ui::AssetTile m_ui;

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>AssetTile</class> <class>AssetTile</class>
<widget class="QGroupBox" name="AssetTile"> <widget class="QGBA::AssetInfo" name="AssetTile">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>171</width> <width>241</width>
<height>355</height> <height>406</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -185,6 +185,12 @@
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>QGBA::AssetInfo</class>
<extends>QGroupBox</extends>
<header>AssetInfo.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>QGBA::Swatch</class> <class>QGBA::Swatch</class>
<extends>QWidget</extends> <extends>QWidget</extends>

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau /* Copyright (c) 2013-2019 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,6 +9,16 @@
#include <QTimer> #include <QTimer>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/io.h>
#endif
#include <mgba/core/map-cache.h>
using namespace QGBA; using namespace QGBA;
AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent) AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent)
@ -98,3 +108,175 @@ void AssetView::compositeTile(const void* tBuffer, void* buffer, size_t stride,
break; break;
} }
} }
QImage AssetView::compositeMap(int map, mMapCacheEntry* mapStatus) {
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, map);
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
QImage rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
uchar* bgBits = rawMap.bits();
for (int j = 0; j < tilesH; ++j) {
for (int i = 0; i < tilesW; ++i) {
mMapCacheCleanTile(mapCache, mapStatus, i, j);
}
for (int i = 0; i < 8; ++i) {
memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
}
}
return rawMap.rgbSwapped();
}
QImage AssetView::compositeObj(const ObjInfo& objInfo) {
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, objInfo.paletteSet);
const color_t* rawPalette = mTileCacheGetPalette(tileCache, objInfo.paletteId);
unsigned colors = 1 << objInfo.bits;
QVector<QRgb> palette;
palette.append(rawPalette[0] & 0xFFFFFF);
for (unsigned c = 1; c < colors && c < 256; ++c) {
palette.append(rawPalette[c] | 0xFF000000);
}
QImage image = QImage(QSize(objInfo.width * 8, objInfo.height * 8), QImage::Format_Indexed8);
image.setColorTable(palette);
uchar* bits = image.bits();
unsigned t = objInfo.tile;
for (int y = 0; y < objInfo.height; ++y) {
for (int x = 0; x < objInfo.width; ++x, ++t) {
compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), bits, objInfo.width * 8, x * 8, y * 8, objInfo.bits);
}
t += objInfo.stride - objInfo.width;
}
return image.rgbSwapped();
}
bool AssetView::lookupObj(int id, struct ObjInfo* info) {
switch (m_controller->platform()) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
return lookupObjGBA(id, info);
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
return lookupObjGB(id, info);
#endif
default:
return false;
}
}
#ifdef M_CORE_GBA
bool AssetView::lookupObjGBA(int id, struct ObjInfo* info) {
if (id > 127) {
return false;
}
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
const GBAObj* obj = &gba->video.oam.obj[id];
unsigned shape = GBAObjAttributesAGetShape(obj->a);
unsigned size = GBAObjAttributesBGetSize(obj->b);
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
unsigned tile = GBAObjAttributesCGetTile(obj->c);
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
unsigned tileBase = tile;
unsigned paletteSet;
unsigned bits;
if (GBAObjAttributesAIs256Color(obj->a)) {
paletteSet = 3;
palette = 0;
tile /= 2;
bits = 8;
} else {
paletteSet = 2;
bits = 4;
}
ObjInfo newInfo{
tile,
width / 8,
height / 8,
width / 8,
palette,
paletteSet,
bits,
!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a),
GBAObjAttributesCGetPriority(obj->c),
GBAObjAttributesBGetX(obj->b),
GBAObjAttributesAGetY(obj->a),
false,
false,
};
if (GBAObjAttributesAIsTransformed(obj->a)) {
int matIndex = GBAObjAttributesBGetMatIndex(obj->b);
const GBAOAMMatrix* mat = &gba->video.oam.mat[matIndex];
QTransform invXform(mat->a / 256., mat->c / 256., mat->b / 256., mat->d / 256., 0, 0);
newInfo.xform = invXform.inverted();
} else {
newInfo.hflip = bool(GBAObjAttributesBIsHFlip(obj->b));
newInfo.vflip = bool(GBAObjAttributesBIsVFlip(obj->b));
}
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
};
*info = newInfo;
return true;
}
#endif
#ifdef M_CORE_GB
bool AssetView::lookupObjGB(int id, struct ObjInfo* info) {
if (id > 39) {
return false;
}
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
const GBObj* obj = &gb->video.oam.obj[id];
unsigned width = 8;
unsigned height = 8;
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
if (GBRegisterLCDCIsObjSize(lcdc)) {
height = 16;
}
unsigned tile = obj->tile;
unsigned palette = 0;
if (gb->model >= GB_MODEL_CGB) {
if (GBObjAttributesIsBank(obj->attr)) {
tile += 512;
}
palette = GBObjAttributesGetCGBPalette(obj->attr);
} else {
palette = GBObjAttributesGetPalette(obj->attr);
}
palette += 8;
ObjInfo newInfo{
tile,
1,
height / 8,
1,
palette,
0,
2,
obj->y != 0 && obj->y < 160 && obj->x != 0 && obj->x < 168,
GBObjAttributesGetPriority(obj->attr),
obj->x - 8,
obj->y - 16,
bool(GBObjAttributesIsXFlip(obj->attr)),
bool(GBObjAttributesIsYFlip(obj->attr)),
};
*info = newInfo;
return true;
}
#endif
bool AssetView::ObjInfo::operator!=(const ObjInfo& other) const {
return other.tile != tile ||
other.width != width ||
other.height != height ||
other.stride != stride ||
other.paletteId != paletteId ||
other.paletteSet != paletteSet;
}

View File

@ -6,12 +6,15 @@
#pragma once #pragma once
#include <QTimer> #include <QTimer>
#include <QTransform>
#include <QWidget> #include <QWidget>
#include <mgba/core/cache-set.h> #include <mgba/core/cache-set.h>
#include <memory> #include <memory>
struct mMapCacheEntry;
namespace QGBA { namespace QGBA {
class CoreController; class CoreController;
@ -22,8 +25,6 @@ Q_OBJECT
public: public:
AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8);
protected slots: protected slots:
void updateTiles(); void updateTiles();
void updateTiles(bool force); void updateTiles(bool force);
@ -40,9 +41,43 @@ protected:
void showEvent(QShowEvent*) override; void showEvent(QShowEvent*) override;
mCacheSet* const m_cacheSet; mCacheSet* const m_cacheSet;
std::shared_ptr<CoreController> m_controller;
protected:
struct ObjInfo {
unsigned tile;
unsigned width;
unsigned height;
unsigned stride;
unsigned paletteId;
unsigned paletteSet;
unsigned bits;
bool enabled : 1;
unsigned priority : 2;
int x : 10;
int y : 10;
bool hflip : 1;
bool vflip : 1;
QTransform xform;
bool operator!=(const ObjInfo&) const;
};
static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8);
QImage compositeMap(int map, mMapCacheEntry*);
QImage compositeObj(const ObjInfo&);
bool lookupObj(int id, struct ObjInfo*);
private: private:
std::shared_ptr<CoreController> m_controller; #ifdef M_CORE_GBA
bool lookupObjGBA(int id, struct ObjInfo*);
#endif
#ifdef M_CORE_GB
bool lookupObjGB(int id, struct ObjInfo*);
#endif
QTimer m_updateTimer; QTimer m_updateTimer;
}; };

View File

@ -60,6 +60,7 @@ set(SOURCE_FILES
AbstractUpdater.cpp AbstractUpdater.cpp
Action.cpp Action.cpp
ActionMapper.cpp ActionMapper.cpp
AssetInfo.cpp
AssetTile.cpp AssetTile.cpp
AssetView.cpp AssetView.cpp
AudioProcessor.cpp AudioProcessor.cpp
@ -72,6 +73,7 @@ set(SOURCE_FILES
Display.cpp Display.cpp
DisplayGL.cpp DisplayGL.cpp
DisplayQt.cpp DisplayQt.cpp
FrameView.cpp
GBAApp.cpp GBAApp.cpp
GIFView.cpp GIFView.cpp
GamepadAxisEvent.cpp GamepadAxisEvent.cpp
@ -124,6 +126,7 @@ set(UI_FILES
BattleChipView.ui BattleChipView.ui
CheatsView.ui CheatsView.ui
DebuggerConsole.ui DebuggerConsole.ui
FrameView.ui
GIFView.ui GIFView.ui
IOViewer.ui IOViewer.ui
LoadSaveState.ui LoadSaveState.ui

View File

@ -34,6 +34,14 @@ ColorPicker& ColorPicker::operator=(const ColorPicker& other) {
return *this; return *this;
} }
void ColorPicker::setColor(const QColor& color) {
m_defaultColor = color;
QPalette palette = m_parent->palette();
palette.setColor(m_parent->backgroundRole(), color);
m_parent->setPalette(palette);
}
bool ColorPicker::eventFilter(QObject* obj, QEvent* event) { bool ColorPicker::eventFilter(QObject* obj, QEvent* event) {
if (event->type() != QEvent::MouseButtonRelease) { if (event->type() != QEvent::MouseButtonRelease) {
return false; return false;

View File

@ -24,6 +24,9 @@ public:
signals: signals:
void colorChanged(const QColor&); void colorChanged(const QColor&);
public slots:
void setColor(const QColor&);
protected: protected:
bool eventFilter(QObject* obj, QEvent* event) override; bool eventFilter(QObject* obj, QEvent* event) override;

View File

@ -82,8 +82,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
controller->m_resetActions.clear(); controller->m_resetActions.clear();
if (!controller->m_hwaccel) { if (!controller->m_hwaccel) {
controller->m_activeBuffer = &controller->m_buffers[0]; 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"); QMetaObject::invokeMethod(controller, "didReset");
@ -203,13 +202,33 @@ CoreController::~CoreController() {
} }
const color_t* CoreController::drawContext() { const color_t* CoreController::drawContext() {
QMutexLocker locker(&m_mutex);
if (m_hwaccel) { if (m_hwaccel) {
return nullptr; return nullptr;
} }
QMutexLocker locker(&m_bufferMutex);
return reinterpret_cast<const color_t*>(m_completeBuffer.constData()); return reinterpret_cast<const color_t*>(m_completeBuffer.constData());
} }
QImage CoreController::getPixels() {
QByteArray buffer;
QSize size = screenDimensions();
size_t stride = size.width() * BYTES_PER_PIXEL;
if (!m_hwaccel) {
buffer = m_completeBuffer;
} else {
Interrupter interrupter(this);
const void* pixels;
m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride);
stride *= BYTES_PER_PIXEL;
buffer.resize(stride * size.height());
memcpy(buffer.data(), pixels, buffer.size());
}
return QImage(reinterpret_cast<const uchar*>(buffer.constData()),
size.width(), size.height(), stride, QImage::Format_RGBX8888);
}
bool CoreController::isPaused() { bool CoreController::isPaused() {
return mCoreThreadIsPaused(&m_threadContext); return mCoreThreadIsPaused(&m_threadContext);
} }
@ -342,15 +361,12 @@ void CoreController::setLogger(LogController* logger) {
void CoreController::start() { void CoreController::start() {
if (!m_hwaccel) { if (!m_hwaccel) {
QSize size(1024, 2048); QSize size(256, 384);
m_buffers[0].resize(size.width() * size.height() * sizeof(color_t)); m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
m_buffers[1].resize(size.width() * size.height() * sizeof(color_t)); m_activeBuffer.fill(0xFF);
m_buffers[0].fill(0xFF); m_completeBuffer = m_activeBuffer;
m_buffers[1].fill(0xFF);
m_activeBuffer = &m_buffers[0];
m_completeBuffer = m_buffers[0];
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) { if (!m_patched) {
@ -386,8 +402,7 @@ void CoreController::setPaused(bool paused) {
return; return;
} }
if (paused) { if (paused) {
QMutexLocker locker(&m_mutex); addFrameAction([this]() {
m_frameActions.append([this]() {
mCoreThreadPauseFromThread(&m_threadContext); mCoreThreadPauseFromThread(&m_threadContext);
}); });
} else { } else {
@ -396,13 +411,17 @@ void CoreController::setPaused(bool paused) {
} }
void CoreController::frameAdvance() { void CoreController::frameAdvance() {
QMutexLocker locker(&m_mutex); addFrameAction([this]() {
m_frameActions.append([this]() {
mCoreThreadPauseFromThread(&m_threadContext); mCoreThreadPauseFromThread(&m_threadContext);
}); });
setPaused(false); setPaused(false);
} }
void CoreController::addFrameAction(std::function<void ()> action) {
QMutexLocker locker(&m_actionMutex);
m_frameActions.append(action);
}
void CoreController::setSync(bool sync) { void CoreController::setSync(bool sync) {
if (sync) { if (sync) {
m_threadContext.impl->sync.audioWait = m_audioSync; m_threadContext.impl->sync.audioWait = m_audioSync;
@ -777,26 +796,39 @@ void CoreController::clearOverride() {
m_override.reset(); m_override.reset();
} }
void CoreController::startVideoLog(const QString& path) { void CoreController::startVideoLog(const QString& path, bool compression) {
if (m_vl) { if (m_vl) {
return; return;
} }
VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
if (!vf) {
return;
}
startVideoLog(vf);
}
void CoreController::startVideoLog(VFile* vf, bool compression) {
if (m_vl || !vf) {
return;
}
Interrupter interrupter(this); Interrupter interrupter(this);
m_vl = mVideoLogContextCreate(m_threadContext.core); m_vl = mVideoLogContextCreate(m_threadContext.core);
m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); m_vlVf = vf;
mVideoLogContextSetOutput(m_vl, m_vlVf); mVideoLogContextSetOutput(m_vl, m_vlVf);
mVideoLogContextSetCompression(m_vl, compression);
mVideoLogContextWriteHeader(m_vl, m_threadContext.core); mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
} }
void CoreController::endVideoLog() { void CoreController::endVideoLog(bool closeVf) {
if (!m_vl) { if (!m_vl) {
return; return;
} }
Interrupter interrupter(this); Interrupter interrupter(this);
mVideoLogContextDestroy(m_threadContext.core, m_vl); mVideoLogContextDestroy(m_threadContext.core, m_vl);
if (m_vlVf) { if (m_vlVf && closeVf) {
m_vlVf->close(m_vlVf); m_vlVf->close(m_vlVf);
m_vlVf = nullptr; m_vlVf = nullptr;
} }
@ -819,23 +851,20 @@ void CoreController::updateKeys() {
} }
void CoreController::finishFrame() { void CoreController::finishFrame() {
QMutexLocker locker(&m_mutex);
if (!m_hwaccel) { if (!m_hwaccel) {
memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size()); unsigned width, height;
m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
// TODO: Generalize this to triple buffering? QMutexLocker locker(&m_bufferMutex);
m_activeBuffer = &m_buffers[0]; memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), 256 * height * BYTES_PER_PIXEL);
if (m_activeBuffer == m_completeBuffer) {
m_activeBuffer = &m_buffers[1];
} }
// Copy contents to avoid issues when doing frameskip
memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size()); QMutexLocker locker(&m_actionMutex);
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), screenDimensions().width()); QList<std::function<void ()>> frameActions(m_frameActions);
} m_frameActions.clear();
for (auto& action : m_frameActions) { for (auto& action : frameActions) {
action(); action();
} }
m_frameActions.clear();
updateKeys(); updateKeys();
QMetaObject::invokeMethod(this, "frameAvailable"); QMetaObject::invokeMethod(this, "frameAvailable");

View File

@ -67,6 +67,7 @@ public:
mCoreThread* thread() { return &m_threadContext; } mCoreThread* thread() { return &m_threadContext; }
const color_t* drawContext(); const color_t* drawContext();
QImage getPixels();
bool isPaused(); bool isPaused();
bool hasStarted(); bool hasStarted();
@ -102,6 +103,8 @@ public:
bool audioSync() const { return m_audioSync; } bool audioSync() const { return m_audioSync; }
bool videoSync() const { return m_videoSync; } bool videoSync() const { return m_videoSync; }
void addFrameAction(std::function<void ()> callback);
public slots: public slots:
void start(); void start();
void stop(); void stop();
@ -157,8 +160,9 @@ public slots:
void clearOverride(); void clearOverride();
void startVideoLog(const QString& path); void startVideoLog(const QString& path, bool compression = true);
void endVideoLog(); void startVideoLog(VFile* vf, bool compression = true);
void endVideoLog(bool closeVf = true);
void setFramebufferHandle(int fb); void setFramebufferHandle(int fb);
@ -193,8 +197,7 @@ private:
bool m_patched = false; bool m_patched = false;
QByteArray m_buffers[2]; QByteArray m_activeBuffer;
QByteArray* m_activeBuffer;
QByteArray m_completeBuffer; QByteArray m_completeBuffer;
bool m_hwaccel = false; bool m_hwaccel = false;
@ -203,7 +206,8 @@ private:
QList<std::function<void()>> m_resetActions; QList<std::function<void()>> m_resetActions;
QList<std::function<void()>> m_frameActions; QList<std::function<void()>> m_frameActions;
QMutex m_mutex; QMutex m_actionMutex{QMutex::Recursive};
QMutex m_bufferMutex;
int m_activeKeys = 0; int m_activeKeys = 0;
bool m_autofire[32] = {}; bool m_autofire[32] = {};

View File

@ -114,7 +114,6 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
#endif #endif
resizePainter(); resizePainter();
connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext);
} }
void DisplayGL::stopDrawing() { void DisplayGL::stopDrawing() {

View File

@ -0,0 +1,467 @@
/* Copyright (c) 2013-2019 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 "FrameView.h"
#include <QMouseEvent>
#include <QPalette>
#include <array>
#include <cmath>
#include "CoreController.h"
#include "GBAApp.h"
#include <mgba/core/core.h>
#include <mgba/feature/video-logger.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.h>
#include <mgba/internal/gba/memory.h>
#include <mgba/internal/gba/video.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/memory.h>
#endif
using namespace QGBA;
FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent)
: AssetView(controller, parent)
{
m_ui.setupUi(this);
m_glowTimer.setInterval(33);
connect(&m_glowTimer, &QTimer::timeout, this, [this]() {
++m_glowFrame;
invalidateQueue();
});
m_ui.compositedView->installEventFilter(this);
connect(m_ui.queue, &QListWidget::itemChanged, this, [this](QListWidgetItem* item) {
Layer& layer = m_queue[item->data(Qt::UserRole).toInt()];
layer.enabled = item->checkState() == Qt::Checked;
if (layer.enabled) {
m_disabled.remove(layer.id);
} else {
m_disabled.insert(layer.id);
}
invalidateQueue();
});
connect(m_ui.queue, &QListWidget::currentItemChanged, this, [this](QListWidgetItem* item) {
if (item) {
m_active = m_queue[item->data(Qt::UserRole).toInt()].id;
} else {
m_active = {};
}
invalidateQueue();
});
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
invalidateQueue();
});
connect(m_ui.exportButton, &QAbstractButton::pressed, this, &FrameView::exportFrame);
connect(m_ui.reset, &QAbstractButton::pressed, this, &FrameView::reset);
m_backdropPicker = ColorPicker(m_ui.backdrop, QColor(0, 0, 0, 0));
connect(&m_backdropPicker, &ColorPicker::colorChanged, this, [this](const QColor& color) {
m_overrideBackdrop = color;
});
m_controller->addFrameAction(std::bind(&FrameView::frameCallback, this, m_callbackLocker));
{
CoreController::Interrupter interrupter(m_controller);
refreshVl();
}
m_controller->frameAdvance();
}
FrameView::~FrameView() {
QMutexLocker locker(&m_mutex);
*m_callbackLocker = false;
if (m_vl) {
m_vl->deinit(m_vl);
}
}
bool FrameView::lookupLayer(const QPointF& coord, Layer*& out) {
for (Layer& layer : m_queue) {
if (!layer.enabled || m_disabled.contains(layer.id)) {
continue;
}
QPointF location = layer.location;
QSizeF layerDims(layer.image.width(), layer.image.height());
QRegion region;
if (layer.repeats) {
if (location.x() + layerDims.width() < 0) {
location.setX(std::fmod(location.x(), layerDims.width()));
}
if (location.y() + layerDims.height() < 0) {
location.setY(std::fmod(location.y(), layerDims.height()));
}
region += layer.mask.translated(location.x(), location.y());
region += layer.mask.translated(location.x() + layerDims.width(), location.y());
region += layer.mask.translated(location.x(), location.y() + layerDims.height());
region += layer.mask.translated(location.x() + layerDims.width(), location.y() + layerDims.height());
} else {
region = layer.mask.translated(location.x(), location.y());
}
if (region.contains(QPoint(coord.x(), coord.y()))) {
out = &layer;
return true;
}
}
return false;
}
void FrameView::selectLayer(const QPointF& coord) {
Layer* layer;
if (!lookupLayer(coord, layer)) {
return;
}
if (layer->id == m_active) {
m_active = {};
} else {
m_active = layer->id;
}
m_glowFrame = 0;
}
void FrameView::disableLayer(const QPointF& coord) {
Layer* layer;
if (!lookupLayer(coord, layer)) {
return;
}
layer->enabled = false;
m_disabled.insert(layer->id);
}
#ifdef M_CORE_GBA
void FrameView::updateTilesGBA(bool force) {
if (m_ui.freeze->checkState() == Qt::Checked) {
return;
}
QMutexLocker locker(&m_mutex);
m_queue.clear();
{
CoreController::Interrupter interrupter(m_controller);
uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
QRgb backdrop = M_RGB5_TO_RGB8(static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[0]);
m_gbaDispcnt = io[REG_DISPCNT >> 1];
int mode = GBARegisterDISPCNTGetMode(m_gbaDispcnt);
std::array<bool, 4> enabled{
bool(GBARegisterDISPCNTIsBg0Enable(m_gbaDispcnt)),
bool(GBARegisterDISPCNTIsBg1Enable(m_gbaDispcnt)),
bool(GBARegisterDISPCNTIsBg2Enable(m_gbaDispcnt)),
bool(GBARegisterDISPCNTIsBg3Enable(m_gbaDispcnt)),
};
for (int priority = 0; priority < 4; ++priority) {
for (int sprite = 0; sprite < 128; ++sprite) {
ObjInfo info;
lookupObj(sprite, &info);
if (!info.enabled || info.priority != priority) {
continue;
}
QPointF offset(info.x, info.y);
QImage obj(compositeObj(info));
if (info.hflip || info.vflip) {
obj = obj.mirrored(info.hflip, info.vflip);
}
if (!info.xform.isIdentity()) {
offset += QPointF(obj.width(), obj.height()) / 2;
obj = obj.transformed(info.xform);
offset -= QPointF(obj.width() / 2, obj.height() / 2);
}
m_queue.append({
{ LayerId::SPRITE, sprite },
!m_disabled.contains({ LayerId::SPRITE, sprite }),
QPixmap::fromImage(obj),
{}, offset, false
});
if (m_queue.back().image.hasAlpha()) {
m_queue.back().mask = QRegion(m_queue.back().image.mask());
} else {
m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height());
}
}
for (int bg = 0; bg < 4; ++bg) {
if (!enabled[bg]) {
continue;
}
if (GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + bg]) != priority) {
continue;
}
QPointF offset;
if (mode == 0) {
offset.setX(-(io[(REG_BG0HOFS >> 1) + (bg << 1)] & 0x1FF));
offset.setY(-(io[(REG_BG0VOFS >> 1) + (bg << 1)] & 0x1FF));
};
m_queue.append({
{ LayerId::BACKGROUND, bg },
!m_disabled.contains({ LayerId::BACKGROUND, bg }),
QPixmap::fromImage(compositeMap(bg, m_mapStatus[bg])),
{}, offset, true
});
if (m_queue.back().image.hasAlpha()) {
m_queue.back().mask = QRegion(m_queue.back().image.mask());
} else {
m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height());
}
}
}
QImage backdropImage(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS), QImage::Format_Mono);
backdropImage.fill(1);
backdropImage.setColorTable({backdrop, backdrop | 0xFF000000 });
m_queue.append({
{ LayerId::BACKDROP },
!m_disabled.contains({ LayerId::BACKDROP }),
QPixmap::fromImage(backdropImage),
{}, {0, 0}, false
});
updateRendered();
}
invalidateQueue(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS));
}
void FrameView::injectGBA() {
mVideoLogger* logger = m_vl->videoLogger;
mVideoLoggerInjectionPoint(logger, LOGGER_INJECTION_FIRST_SCANLINE);
GBA* gba = static_cast<GBA*>(m_vl->board);
gba->video.renderer->highlightBG[0] = false;
gba->video.renderer->highlightBG[1] = false;
gba->video.renderer->highlightBG[2] = false;
gba->video.renderer->highlightBG[3] = false;
for (int i = 0; i < 128; ++i) {
gba->video.renderer->highlightOBJ[i] = false;
}
QPalette palette;
gba->video.renderer->highlightColor = palette.color(QPalette::HighlightedText).rgb();
gba->video.renderer->highlightAmount = sin(m_glowFrame * M_PI / 30) * 48 + 64;
if (!m_overrideBackdrop.isValid()) {
QRgb backdrop = M_RGB5_TO_RGB8(gba->video.palette[0]) | 0xFF000000;
m_backdropPicker.setColor(backdrop);
}
m_vl->reset(m_vl);
for (const Layer& layer : m_queue) {
switch (layer.id.type) {
case LayerId::SPRITE:
if (!layer.enabled) {
mVideoLoggerInjectOAM(logger, layer.id.index << 2, 0x200);
}
if (layer.id == m_active) {
gba->video.renderer->highlightOBJ[layer.id.index] = true;
}
break;
case LayerId::BACKGROUND:
m_vl->enableVideoLayer(m_vl, layer.id.index, layer.enabled);
if (layer.id == m_active) {
gba->video.renderer->highlightBG[layer.id.index] = true;
}
break;
}
}
if (m_overrideBackdrop.isValid()) {
mVideoLoggerInjectPalette(logger, 0, M_RGB8_TO_RGB5(m_overrideBackdrop.rgb()));
}
if (m_ui.disableScanline->checkState() == Qt::Checked) {
mVideoLoggerIgnoreAfterInjection(logger, (1 << DIRTY_PALETTE) | (1 << DIRTY_OAM) | (1 << DIRTY_REGISTER));
} else {
mVideoLoggerIgnoreAfterInjection(logger, 0);
}
}
#endif
#ifdef M_CORE_GB
void FrameView::updateTilesGB(bool force) {
if (m_ui.freeze->checkState() == Qt::Checked) {
return;
}
m_queue.clear();
{
CoreController::Interrupter interrupter(m_controller);
updateRendered();
}
invalidateQueue(m_controller->screenDimensions());
}
void FrameView::injectGB() {
for (const Layer& layer : m_queue) {
}
}
#endif
void FrameView::invalidateQueue(const QSize& dims) {
if (dims.isValid()) {
m_dims = dims;
}
bool blockSignals = m_ui.queue->blockSignals(true);
QMutexLocker locker(&m_mutex);
if (m_vl) {
switch (m_controller->platform()) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
injectGBA();
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
injectGB();
#endif
}
m_vl->runFrame(m_vl);
}
for (int i = 0; i < m_queue.count(); ++i) {
const Layer& layer = m_queue[i];
QListWidgetItem* item;
if (i >= m_ui.queue->count()) {
item = new QListWidgetItem;
m_ui.queue->addItem(item);
} else {
item = m_ui.queue->item(i);
}
item->setText(layer.id.readable());
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(layer.enabled ? Qt::Checked : Qt::Unchecked);
item->setData(Qt::UserRole, i);
item->setSelected(layer.id == m_active);
}
while (m_ui.queue->count() > m_queue.count()) {
delete m_ui.queue->takeItem(m_queue.count());
}
m_ui.queue->blockSignals(blockSignals);
QPixmap composited;
if (m_framebuffer.isNull()) {
updateRendered();
composited = m_rendered;
} else {
m_ui.exportButton->setEnabled(true);
composited.convertFromImage(m_framebuffer);
}
m_composited = composited.scaled(m_dims * m_ui.magnification->value());
m_ui.compositedView->setPixmap(m_composited);
}
void FrameView::updateRendered() {
if (m_ui.freeze->checkState() == Qt::Checked) {
return;
}
m_rendered.convertFromImage(m_controller->getPixels());
}
bool FrameView::eventFilter(QObject* obj, QEvent* event) {
QPointF pos;
switch (event->type()) {
case QEvent::MouseButtonPress:
pos = static_cast<QMouseEvent*>(event)->localPos();
pos /= m_ui.magnification->value();
selectLayer(pos);
return true;
case QEvent::MouseButtonDblClick:
pos = static_cast<QMouseEvent*>(event)->localPos();
pos /= m_ui.magnification->value();
disableLayer(pos);
return true;
}
return false;
}
void FrameView::refreshVl() {
QMutexLocker locker(&m_mutex);
m_currentFrame = m_nextFrame;
m_nextFrame = VFileMemChunk(nullptr, 0);
if (m_currentFrame) {
m_controller->endVideoLog(false);
VFile* currentFrame = VFileMemChunk(nullptr, m_currentFrame->size(m_currentFrame));
void* buffer = currentFrame->map(currentFrame, m_currentFrame->size(m_currentFrame), MAP_WRITE);
m_currentFrame->seek(m_currentFrame, 0, SEEK_SET);
m_currentFrame->read(m_currentFrame, buffer, m_currentFrame->size(m_currentFrame));
currentFrame->unmap(currentFrame, buffer, m_currentFrame->size(m_currentFrame));
m_currentFrame = currentFrame;
QMetaObject::invokeMethod(this, "newVl");
}
m_controller->endVideoLog();
m_controller->startVideoLog(m_nextFrame, false);
}
void FrameView::newVl() {
if (!m_glowTimer.isActive()) {
m_glowTimer.start();
}
QMutexLocker locker(&m_mutex);
if (m_vl) {
m_vl->deinit(m_vl);
}
m_vl = mCoreFindVF(m_currentFrame);
m_vl->init(m_vl);
m_vl->loadROM(m_vl, m_currentFrame);
mCoreInitConfig(m_vl, nullptr);
unsigned width, height;
m_vl->desiredVideoDimensions(m_vl, &width, &height);
m_framebuffer = QImage(width, height, QImage::Format_RGBX8888);
m_vl->setVideoBuffer(m_vl, reinterpret_cast<color_t*>(m_framebuffer.bits()), width);
m_vl->reset(m_vl);
}
void FrameView::frameCallback(FrameView* viewer, std::shared_ptr<bool> lock) {
if (!*lock) {
return;
}
CoreController::Interrupter interrupter(viewer->m_controller, true);
viewer->refreshVl();
viewer->m_controller->addFrameAction(std::bind(&FrameView::frameCallback, viewer, lock));
}
void FrameView::exportFrame() {
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export frame"),
tr("Portable Network Graphics (*.png)"));
CoreController::Interrupter interrupter(m_controller);
m_framebuffer.save(filename, "PNG");
}
void FrameView::reset() {
m_disabled.clear();
for (Layer& layer : m_queue) {
layer.enabled = true;
}
m_overrideBackdrop = QColor();
invalidateQueue();
}
QString FrameView::LayerId::readable() const {
QString typeStr;
switch (type) {
case NONE:
return tr("None");
case BACKGROUND:
typeStr = tr("Background");
break;
case WINDOW:
typeStr = tr("Window");
break;
case SPRITE:
typeStr = tr("Sprite");
break;
case BACKDROP:
typeStr = tr("Backdrop");
break;
}
if (index < 0) {
return typeStr;
}
return tr("%1 %2").arg(typeStr).arg(index);
}

120
src/platform/qt/FrameView.h Normal file
View File

@ -0,0 +1,120 @@
/* Copyright (c) 2013-2019 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/. */
#pragma once
#include "ui_FrameView.h"
#include <QBitmap>
#include <QImage>
#include <QList>
#include <QMutex>
#include <QPixmap>
#include <QSet>
#include <QTimer>
#include "AssetView.h"
#include "ColorPicker.h"
#include <mgba-util/vfs.h>
#include <memory>
struct VFile;
namespace QGBA {
class CoreController;
class FrameView : public AssetView {
Q_OBJECT
public:
FrameView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
~FrameView();
public slots:
void selectLayer(const QPointF& coord);
void disableLayer(const QPointF& coord);
void exportFrame();
void reset();
protected:
#ifdef M_CORE_GBA
void updateTilesGBA(bool force) override;
void injectGBA();
#endif
#ifdef M_CORE_GB
void updateTilesGB(bool force) override;
void injectGB();
#endif
bool eventFilter(QObject* obj, QEvent* event) override;
private slots:
void invalidateQueue(const QSize& = {});
void updateRendered();
void refreshVl();
void newVl();
private:
struct LayerId {
enum {
NONE = 0,
BACKGROUND,
WINDOW,
SPRITE,
BACKDROP
} type = NONE;
int index = -1;
bool operator!=(const LayerId& other) const { return other.type != type || other.index != index; }
operator uint() const { return (type << 8) | index; }
QString readable() const;
};
struct Layer {
LayerId id;
bool enabled;
QPixmap image;
QRegion mask;
QPointF location;
bool repeats;
};
bool lookupLayer(const QPointF& coord, Layer*&);
static void frameCallback(FrameView*, std::shared_ptr<bool>);
Ui::FrameView m_ui;
LayerId m_active{};
int m_glowFrame;
QTimer m_glowTimer;
QMutex m_mutex{QMutex::Recursive};
VFile* m_currentFrame = nullptr;
VFile* m_nextFrame = nullptr;
mCore* m_vl = nullptr;
QImage m_framebuffer;
QSize m_dims;
QList<Layer> m_queue;
QSet<LayerId> m_disabled;
QPixmap m_composited;
QPixmap m_rendered;
mMapCacheEntry m_mapStatus[4][128 * 128] = {}; // TODO: Correct size
ColorPicker m_backdropPicker;
QColor m_overrideBackdrop;
#ifdef M_CORE_GBA
uint16_t m_gbaDispcnt;
#endif
std::shared_ptr<bool> m_callbackLocker{std::make_shared<bool>(true)};
};
}

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FrameView</class>
<widget class="QWidget" name="FrameView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>869</width>
<height>875</height>
</rect>
</property>
<property name="windowTitle">
<string>Inspect frame</string>
</property>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,1,0,0,0" columnstretch="0,1">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSpinBox" name="magnification">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>×</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Magnification</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="freeze">
<property name="text">
<string>Freeze frame</string>
</property>
</widget>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QFrame" name="backdrop">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Backdrop color</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1" rowspan="9">
<widget class="QScrollArea" name="compositedArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>567</width>
<height>382</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="compositedView">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="disableScanline">
<property name="text">
<string>Disable scanline effects</string>
</property>
</widget>
</item>
<item row="5" column="0" rowspan="2">
<widget class="QListWidget" name="queue"/>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="exportButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QPushButton" name="reset">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -18,6 +18,7 @@
#include <mgba/internal/gba/video.h> #include <mgba/internal/gba/video.h>
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/memory.h> #include <mgba/internal/gb/memory.h>
#endif #endif
@ -42,6 +43,12 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
m_boundary = 2048; m_boundary = 2048;
m_addressBase = BASE_VRAM; m_addressBase = BASE_VRAM;
m_addressWidth = 8; m_addressWidth = 8;
m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
m_ui.bgInfo->addCustomProperty("size", tr("Size"));
m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
m_ui.bgInfo->addCustomProperty("transform", tr("Xform"));
break; break;
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
@ -49,6 +56,9 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
m_boundary = 1024; m_boundary = 1024;
m_addressBase = GB_BASE_VRAM; m_addressBase = GB_BASE_VRAM;
m_addressWidth = 4; m_addressWidth = 4;
m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
break; break;
#endif #endif
default: default:
@ -143,39 +153,82 @@ void MapView::updateTilesGBA(bool force) {
{ {
CoreController::Interrupter interrupter(m_controller); CoreController::Interrupter interrupter(m_controller);
int bitmap = -1; int bitmap = -1;
int priority = -1;
int frame = 0;
QString offset(tr("N/A"));
QString transform(tr("N/A"));
if (m_controller->platform() == PLATFORM_GBA) { if (m_controller->platform() == PLATFORM_GBA) {
int mode = GBARegisterDISPCNTGetMode(static_cast<GBA*>(m_controller->thread()->core->board)->memory.io[REG_DISPCNT]); uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
if (m_map == 2 && mode > 2) { if (m_map == 2 && mode > 2) {
bitmap = mode == 4 ? 1 : 0; bitmap = mode == 4 ? 1 : 0;
if (mode != 3) {
frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
} }
} }
priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
if (mode == 0 || (mode == 1 && m_map != 2)) {
offset = QString("%1, %2")
.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
.arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)];
refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16;
refX <<= 4;
refY <<= 4;
refX >>= 4;
refY >>= 4;
offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3);
transform = QString("%1 %2\n%3 %4")
.arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
.arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
.arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
.arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2);
}
}
if (m_controller->platform() == PLATFORM_GB) {
uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io;
int x = io[m_map == 0 ? 0x42 : 0x4A];
int y = io[m_map == 0 ? 0x43 : 0x4B];
offset = QString("%1, %2").arg(x).arg(y);
}
if (bitmap >= 0) { if (bitmap >= 0) {
mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap); mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig); int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig); int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0')));
m_ui.bgInfo->setCustomProperty("charBase", tr("N/A"));
m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height));
m_ui.bgInfo->setCustomProperty("priority", priority);
m_ui.bgInfo->setCustomProperty("offset", offset);
m_ui.bgInfo->setCustomProperty("transform", transform);
m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32); m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
uchar* bgBits = m_rawMap.bits(); uchar* bgBits = m_rawMap.bits();
for (int j = 0; j < height; ++j) { for (int j = 0; j < height; ++j) {
mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j); mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4); memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
} }
m_rawMap = m_rawMap.rgbSwapped();
} else { } else {
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map); mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig); int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig); int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32); m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
uchar* bgBits = m_rawMap.bits(); .arg(m_addressWidth == 8 ? "0x" : "")
for (int j = 0; j < tilesH; ++j) { .arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
for (int i = 0; i < tilesW; ++i) { m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
mMapCacheCleanTile(mapCache, m_mapStatus, i, j); .arg(m_addressWidth == 8 ? "0x" : "")
} .arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
for (int i = 0; i < 8; ++i) { m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32); m_ui.bgInfo->setCustomProperty("priority", priority);
m_ui.bgInfo->setCustomProperty("offset", offset);
m_ui.bgInfo->setCustomProperty("transform", transform);
m_rawMap = compositeMap(m_map, m_mapStatus);
} }
} }
}
}
m_rawMap = m_rawMap.rgbSwapped();
QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32)); QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
if (m_ui.magnification->value() > 1) { if (m_ui.magnification->value() > 1) {
map = map.scaled(map.size() * m_ui.magnification->value()); map = map.scaled(map.size() * m_ui.magnification->value());

View File

@ -6,18 +6,80 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>641</width> <width>941</width>
<height>489</height> <height>617</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Maps</string> <string>Maps</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0">
<item row="0" column="0"> <item row="3" column="0">
<layout class="QVBoxLayout" name="bgLayout"/> <widget class="QGBA::AssetTile" name="tile"/>
</item> </item>
<item row="0" column="1" rowspan="5"> <item row="2" column="0">
<widget class="QGBA::AssetInfo" name="bgInfo">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="exportButton">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="bgLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSpinBox" name="magnification">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>×</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Magnification</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="1" rowspan="5" colspan="2">
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable"> <property name="widgetResizable">
<bool>true</bool> <bool>true</bool>
@ -30,8 +92,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>457</width> <width>613</width>
<height>463</height> <height>601</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -71,62 +133,15 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QGBA::AssetTile" name="tile"/>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSpinBox" name="magnification">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>×</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Magnification</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="exportButton">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>QGBA::AssetInfo</class>
<extends>QGroupBox</extends>
<header>AssetInfo.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>QGBA::AssetTile</class> <class>QGBA::AssetTile</class>
<extends>QGroupBox</extends> <extends>QGroupBox</extends>

View File

@ -19,9 +19,7 @@
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
#include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/io.h>
#endif #endif
#include <mgba-util/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
using namespace QGBA; using namespace QGBA;
@ -53,11 +51,7 @@ ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent)
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() { connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
updateTiles(true); updateTiles(true);
}); });
#ifdef USE_PNG
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj); connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
#else
m_ui.exportButton->setVisible(false);
#endif
} }
void ObjView::selectObj(int obj) { void ObjView::selectObj(int obj) {
@ -77,79 +71,56 @@ void ObjView::updateTilesGBA(bool force) {
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board); const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
const GBAObj* obj = &gba->video.oam.obj[m_objId]; const GBAObj* obj = &gba->video.oam.obj[m_objId];
unsigned shape = GBAObjAttributesAGetShape(obj->a); ObjInfo newInfo;
unsigned size = GBAObjAttributesBGetSize(obj->b); lookupObj(m_objId, &newInfo);
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
unsigned height = GBAVideoObjSizes[shape * 4 + size][1]; m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
unsigned tile = GBAObjAttributesCGetTile(obj->c); m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
m_ui.tiles->setTileCount(width * height / 64); m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value()); unsigned tileBase = newInfo.tile;
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value()); unsigned tile = newInfo.tile;
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
unsigned tileBase = tile;
unsigned paletteSet;
unsigned bits;
if (GBAObjAttributesAIs256Color(obj->a)) { if (GBAObjAttributesAIs256Color(obj->a)) {
m_ui.palette->setText("256-color"); m_ui.palette->setText("256-color");
paletteSet = 3;
m_ui.tile->setBoundary(1024, 1, 3); m_ui.tile->setBoundary(1024, 1, 3);
m_ui.tile->setPalette(0); m_ui.tile->setPalette(0);
m_boundary = 1024; m_boundary = 1024;
palette = 0; tileBase *= 2;
tile /= 2;
bits = 8;
} else { } else {
m_ui.palette->setText(QString::number(palette)); m_ui.palette->setText(QString::number(newInfo.paletteId));
paletteSet = 2;
m_ui.tile->setBoundary(2048, 0, 2); m_ui.tile->setBoundary(2048, 0, 2);
m_ui.tile->setPalette(palette); m_ui.tile->setPalette(newInfo.paletteId);
m_boundary = 2048;
bits = 4;
} }
ObjInfo newInfo{
tile,
width / 8,
height / 8,
width / 8,
palette,
paletteSet,
bits
};
if (newInfo != m_objInfo) { if (newInfo != m_objInfo) {
force = true; force = true;
} }
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
};
m_objInfo = newInfo; m_objInfo = newInfo;
m_tileOffset = tile; m_tileOffset = newInfo.tile;
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet); mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
int i = 0; int i = 0;
for (int y = 0; y < height / 8; ++y) { for (int y = 0; y < newInfo.height; ++y) {
for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) { for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette); const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
if (data) { if (data) {
m_ui.tiles->setTile(i, data); m_ui.tiles->setTile(i, data);
} else if (force) { } else if (force) {
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, palette)); m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
} }
} }
tile += newInfo.stride - width / 8; tile += newInfo.stride - newInfo.width;
tileBase += newInfo.stride - width / 8; tileBase += newInfo.stride - newInfo.width;
} }
m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b))); m_ui.x->setText(QString::number(newInfo.x));
m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a))); m_ui.y->setText(QString::number(newInfo.y));
m_ui.w->setText(QString::number(width)); m_ui.w->setText(QString::number(newInfo.width * 8));
m_ui.h->setText(QString::number(height)); m_ui.h->setText(QString::number(newInfo.height * 8));
m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0'))); m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c))); m_ui.priority->setText(QString::number(newInfo.priority));
m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b)); m_ui.flippedH->setChecked(newInfo.hflip);
m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b)); m_ui.flippedV->setChecked(newInfo.vflip);
m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a)); m_ui.enabled->setChecked(newInfo.enabled);
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a)); m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a)); m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
@ -182,39 +153,17 @@ void ObjView::updateTilesGB(bool force) {
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board); const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
const GBObj* obj = &gb->video.oam.obj[m_objId]; const GBObj* obj = &gb->video.oam.obj[m_objId];
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0); ObjInfo newInfo;
unsigned width = 8; lookupObj(m_objId, &newInfo);
unsigned height = 8;
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC]; mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
if (GBRegisterLCDCIsObjSize(lcdc)) { unsigned tile = newInfo.tile;
height = 16; m_ui.tiles->setTileCount(newInfo.height);
} m_ui.tile->setBoundary(1024, 0, 0);
unsigned tile = obj->tile; m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
m_ui.tiles->setTileCount(width * height / 64); m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
m_ui.tile->setBoundary(1024, 0, 0); m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
unsigned palette = 0;
if (gb->model >= GB_MODEL_CGB) {
if (GBObjAttributesIsBank(obj->attr)) {
tile += 512;
}
palette = GBObjAttributesGetCGBPalette(obj->attr);
} else {
palette = GBObjAttributesGetPalette(obj->attr);
}
m_ui.palette->setText(QString::number(palette));
palette += 8;
ObjInfo newInfo{
tile,
1,
height / 8,
1,
palette,
0,
2
};
if (newInfo != m_objInfo) { if (newInfo != m_objInfo) {
force = true; force = true;
} }
@ -223,27 +172,27 @@ void ObjView::updateTilesGB(bool force) {
m_boundary = 1024; m_boundary = 1024;
int i = 0; int i = 0;
m_ui.tile->setPalette(palette); m_ui.tile->setPalette(newInfo.paletteId);
for (int y = 0; y < height / 8; ++y, ++i) { for (int y = 0; y < newInfo.height; ++y, ++i) {
unsigned t = tile + i; unsigned t = tile + i;
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, palette); const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
if (data) { if (data) {
m_ui.tiles->setTile(i, data); m_ui.tiles->setTile(i, data);
} else if (force) { } else if (force) {
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, palette)); m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
} }
} }
m_ui.x->setText(QString::number(obj->x)); m_ui.x->setText(QString::number(newInfo.x));
m_ui.y->setText(QString::number(obj->y)); m_ui.y->setText(QString::number(newInfo.y));
m_ui.w->setText(QString::number(width)); m_ui.w->setText(QString::number(8));
m_ui.h->setText(QString::number(height)); m_ui.h->setText(QString::number(newInfo.height * 8));
m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0'))); m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr))); m_ui.priority->setText(QString::number(newInfo.priority));
m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr)); m_ui.flippedH->setChecked(newInfo.hflip);
m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr)); m_ui.flippedV->setChecked(newInfo.vflip);
m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160); m_ui.enabled->setChecked(newInfo.enabled);
m_ui.doubleSize->setChecked(false); m_ui.doubleSize->setChecked(false);
m_ui.mosaic->setChecked(false); m_ui.mosaic->setChecked(false);
m_ui.transform->setText(tr("N/A")); m_ui.transform->setText(tr("N/A"));
@ -251,51 +200,10 @@ void ObjView::updateTilesGB(bool force) {
} }
#endif #endif
#ifdef USE_PNG
void ObjView::exportObj() { void ObjView::exportObj() {
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
tr("Portable Network Graphics (*.png)")); tr("Portable Network Graphics (*.png)"));
VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
if (!vf) {
LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
return;
}
CoreController::Interrupter interrupter(m_controller); CoreController::Interrupter interrupter(m_controller);
png_structp png = PNGWriteOpen(vf); QImage obj = compositeObj(m_objInfo);
png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8); obj.save(filename, "PNG");
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, m_objInfo.paletteSet);
const color_t* rawPalette = mTileCacheGetPalette(tileCache, m_objInfo.paletteId);
unsigned colors = 1 << m_objInfo.bits;
uint32_t palette[256];
palette[0] = rawPalette[0];
for (unsigned c = 1; c < colors && c < 256; ++c) {
palette[c] = rawPalette[c] | 0xFF000000;
}
PNGWritePalette(png, info, palette, colors);
uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8];
unsigned t = m_objInfo.tile;
for (int y = 0; y < m_objInfo.height; ++y) {
for (int x = 0; x < m_objInfo.width; ++x, ++t) {
compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), reinterpret_cast<color_t*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits);
}
t += m_objInfo.stride - m_objInfo.width;
}
PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));
PNGWriteClose(png, info);
delete[] buffer;
vf->close(vf);
}
#endif
bool ObjView::ObjInfo::operator!=(const ObjInfo& other) {
return other.tile != tile ||
other.width != width ||
other.height != height ||
other.stride != stride ||
other.paletteId != paletteId ||
other.paletteSet != paletteSet;
} }

View File

@ -21,10 +21,8 @@ Q_OBJECT
public: public:
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
#ifdef USE_PNG
public slots: public slots:
void exportObj(); void exportObj();
#endif
private slots: private slots:
void selectObj(int); void selectObj(int);
@ -43,17 +41,7 @@ private:
std::shared_ptr<CoreController> m_controller; std::shared_ptr<CoreController> m_controller;
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
int m_objId = 0; int m_objId = 0;
struct ObjInfo { ObjInfo m_objInfo = {};
unsigned tile;
unsigned width;
unsigned height;
unsigned stride;
unsigned paletteId;
unsigned paletteSet;
unsigned bits;
bool operator!=(const ObjInfo&);
} m_objInfo = {};
int m_tileOffset; int m_tileOffset;
int m_boundary; int m_boundary;

View File

@ -363,7 +363,6 @@ void SettingsView::updateConfig() {
saveSetting("useCgbColors", m_ui.useCgbColors); saveSetting("useCgbColors", m_ui.useCgbColors);
saveSetting("useBios", m_ui.useBios); saveSetting("useBios", m_ui.useBios);
saveSetting("skipBios", m_ui.skipBios); saveSetting("skipBios", m_ui.skipBios);
saveSetting("audioBuffers", m_ui.audioBufferSize);
saveSetting("sampleRate", m_ui.sampleRate); saveSetting("sampleRate", m_ui.sampleRate);
saveSetting("videoSync", m_ui.videoSync); saveSetting("videoSync", m_ui.videoSync);
saveSetting("audioSync", m_ui.audioSync); saveSetting("audioSync", m_ui.audioSync);
@ -401,6 +400,11 @@ void SettingsView::updateConfig() {
saveSetting("useDiscordPresence", m_ui.useDiscordPresence); saveSetting("useDiscordPresence", m_ui.useDiscordPresence);
saveSetting("gba.audioHle", m_ui.audioHle); saveSetting("gba.audioHle", m_ui.audioHle);
if (m_ui.audioBufferSize->currentText().toInt() > 8192) {
m_ui.audioBufferSize->setCurrentText("8192");
}
saveSetting("audioBuffers", m_ui.audioBufferSize);
if (m_ui.fastForwardUnbounded->isChecked()) { if (m_ui.fastForwardUnbounded->isChecked()) {
saveSetting("fastForwardRatio", "-1"); saveSetting("fastForwardRatio", "-1");
} else { } else {

View File

@ -30,6 +30,7 @@
#include "DebuggerConsoleController.h" #include "DebuggerConsoleController.h"
#include "Display.h" #include "Display.h"
#include "CoreController.h" #include "CoreController.h"
#include "FrameView.h"
#include "GBAApp.h" #include "GBAApp.h"
#include "GDBController.h" #include "GDBController.h"
#include "GDBWindow.h" #include "GDBWindow.h"
@ -971,6 +972,7 @@ void Window::reloadDisplayDriver() {
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); 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::frameAvailable, m_display.get(), &Display::framePosted);
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
attachWidget(m_display.get()); attachWidget(m_display.get());
m_display->startDrawing(m_controller); m_display->startDrawing(m_controller);
@ -1527,7 +1529,7 @@ void Window::setupMenu(QMenuBar* menubar) {
m_overrideView->recheck(); m_overrideView->recheck();
}, "tools"); }, "tools");
m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() { m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() {
if (!m_sensorView) { if (!m_sensorView) {
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController)); m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
if (m_controller) { if (m_controller) {
@ -1557,6 +1559,26 @@ void Window::setupMenu(QMenuBar* menubar) {
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools"); addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools"); addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools");
addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools"); addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools");
#ifdef M_CORE_GBA
Action* frameWindow = addGameAction(tr("&Frame inspector..."), "frameWindow", [this]() {
if (!m_frameView) {
m_frameView = new FrameView(m_controller);
connect(this, &Window::shutdown, this, [this]() {
if (m_frameView) {
m_frameView->close();
}
});
connect(m_frameView, &QObject::destroyed, this, [this]() {
m_frameView = nullptr;
});
m_frameView->setAttribute(Qt::WA_DeleteOnClose);
}
m_frameView->show();
}, "tools");
m_platformActions.insert(PLATFORM_GBA, frameWindow);
#endif
addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools"); addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools");
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools"); addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
@ -1744,11 +1766,8 @@ void Window::focusCheck() {
} }
void Window::updateFrame() { void Window::updateFrame() {
QSize size = m_controller->screenDimensions();
QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(),
size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
QPixmap pixmap; QPixmap pixmap;
pixmap.convertFromImage(currentImage); pixmap.convertFromImage(m_controller->getPixels());
m_screenWidget->setPixmap(pixmap); m_screenWidget->setPixmap(pixmap);
emit paused(true); emit paused(true);
} }
@ -1834,6 +1853,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); 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::frameAvailable, m_display.get(), &Display::framePosted);
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver); connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver);
connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame); connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);

View File

@ -32,6 +32,7 @@ class CoreController;
class CoreManager; class CoreManager;
class DebuggerConsoleController; class DebuggerConsoleController;
class Display; class Display;
class FrameView;
class GDBController; class GDBController;
class GIFView; class GIFView;
class LibraryController; class LibraryController;
@ -213,6 +214,7 @@ private:
std::unique_ptr<OverrideView> m_overrideView; std::unique_ptr<OverrideView> m_overrideView;
std::unique_ptr<SensorView> m_sensorView; std::unique_ptr<SensorView> m_sensorView;
FrameView* m_frameView = nullptr;
#ifdef USE_FFMPEG #ifdef USE_FFMPEG
VideoView* m_videoView = nullptr; VideoView* m_videoView = nullptr;

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@
#include "feature/gui/gui-runner.h" #include "feature/gui/gui-runner.h"
#include <mgba/core/blip_buf.h> #include <mgba/core/blip_buf.h>
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba/internal/gb/video.h>
#include <mgba/internal/gba/audio.h> #include <mgba/internal/gba/audio.h>
#include <mgba/internal/gba/input.h> #include <mgba/internal/gba/input.h>
#include <mgba-util/gui.h> #include <mgba-util/gui.h>
@ -99,6 +100,9 @@ static u8 vmode;
static u32 vwidth; static u32 vwidth;
static u32 vheight; static u32 vheight;
static bool interframeBlending = false; static bool interframeBlending = false;
static bool sgbCrop = false;
static bool useLightSensor = true;
static struct mGUIRunnerLux lightSensor;
static enum ScreenMode { static enum ScreenMode {
SM_PA, SM_PA,
@ -268,6 +272,10 @@ static void _setup(struct mGUIRunner* runner) {
runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
runner->core->setAVStream(runner->core, &stream); runner->core->setAVStream(runner->core, &stream);
if (runner->core->platform(runner->core) == PLATFORM_GBA && useLightSensor) {
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
}
unsigned mode; unsigned mode;
if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) { if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
screenMode = mode; screenMode = mode;
@ -292,6 +300,22 @@ static void _gameLoaded(struct mGUIRunner* runner) {
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
interframeBlending = fakeBool; interframeBlending = fakeBool;
} }
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
sgbCrop = fakeBool;
}
if (mCoreConfigGetIntValue(&runner->config, "useLightSensor", &fakeBool)) {
if (useLightSensor != fakeBool) {
useLightSensor = fakeBool;
if (runner->core->platform(runner->core) == PLATFORM_GBA) {
if (useLightSensor) {
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
} else {
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d);
}
}
}
}
rumble.up = 0; rumble.up = 0;
rumble.down = 0; rumble.down = 0;
@ -313,8 +337,14 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height,
glUseProgram(program); glUseProgram(program);
glBindVertexArray(vao); glBindVertexArray(vao);
float aspectX = width / (float) vwidth; float inwidth = width;
float aspectY = height / (float) vheight; float inheight = height;
if (sgbCrop && width == 256 && height == 224) {
inwidth = GB_VIDEO_HORIZONTAL_PIXELS;
inheight = GB_VIDEO_VERTICAL_PIXELS;
}
float aspectX = inwidth / vwidth;
float aspectY = inheight / vheight;
float max = 1.f; float max = 1.f;
switch (screenMode) { switch (screenMode) {
case SM_PA: case SM_PA:
@ -340,6 +370,11 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height,
break; break;
} }
if (screenMode != SM_SF) {
aspectX = width / (float) vwidth;
aspectY = height / (float) vheight;
}
aspectX *= max; aspectX *= max;
aspectY *= max; aspectY *= max;
@ -543,6 +578,18 @@ int32_t _readGyroZ(struct mRotationSource* source) {
return sixaxis.gyroscope.z * -1.1e9f; return sixaxis.gyroscope.z * -1.1e9f;
} }
static void _lightSensorSample(struct GBALuminanceSource* lux) {
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
float luxLevel = 0;
appletGetCurrentIlluminance(&luxLevel);
runnerLux->luxLevel = cbrtf(luxLevel) * 8;
}
static uint8_t _lightSensorRead(struct GBALuminanceSource* lux) {
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
return 0xFF - runnerLux->luxLevel;
}
static int _batteryState(void) { static int _batteryState(void) {
u32 charge; u32 charge;
int state = 0; int state = 0;
@ -690,6 +737,9 @@ int main(int argc, char* argv[]) {
rotation.readTiltY = _readTiltY; rotation.readTiltY = _readTiltY;
rotation.readGyroZ = _readGyroZ; rotation.readGyroZ = _readGyroZ;
lightSensor.d.readLuminance = _lightSensorRead;
lightSensor.d.sample = _lightSensorSample;
stream.videoDimensionsChanged = NULL; stream.videoDimensionsChanged = NULL;
stream.postVideoFrame = NULL; stream.postVideoFrame = NULL;
stream.postAudioFrame = NULL; stream.postAudioFrame = NULL;
@ -707,6 +757,9 @@ int main(int argc, char* argv[]) {
audoutBuffer[i].data_offset = 0; audoutBuffer[i].data_offset = 0;
} }
bool illuminanceAvailable = false;
appletIsIlluminanceAvailable(&illuminanceAvailable);
struct mGUIRunner runner = { struct mGUIRunner runner = {
.params = { .params = {
1280, 720, 1280, 720,
@ -829,8 +882,19 @@ int main(int argc, char* argv[]) {
}, },
.nStates = 6 .nStates = 6
}, },
{
.title = "Use built-in brightness sensor for Boktai",
.data = "useLightSensor",
.submenu = 0,
.state = illuminanceAvailable,
.validStates = (const char*[]) {
"Off",
"On",
}, },
.nConfigExtra = 4, .nStates = 2
},
},
.nConfigExtra = 5,
.setup = _setup, .setup = _setup,
.teardown = NULL, .teardown = NULL,
.gameLoaded = _gameLoaded, .gameLoaded = _gameLoaded,

View File

@ -17,6 +17,7 @@
#include <mgba/core/blip_buf.h> #include <mgba/core/blip_buf.h>
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include "feature/gui/gui-runner.h" #include "feature/gui/gui-runner.h"
#include <mgba/internal/gb/video.h>
#include <mgba/internal/gba/audio.h> #include <mgba/internal/gba/audio.h>
#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/input.h> #include <mgba/internal/gba/input.h>
@ -113,6 +114,7 @@ static uint16_t* rescaleTexmem;
static GXTexObj rescaleTex; static GXTexObj rescaleTex;
static uint16_t* interframeTexmem; static uint16_t* interframeTexmem;
static GXTexObj interframeTex; static GXTexObj interframeTex;
static bool sgbCrop = false;
static int32_t tiltX; static int32_t tiltX;
static int32_t tiltY; static int32_t tiltY;
static int32_t gyroZ; static int32_t gyroZ;
@ -862,6 +864,9 @@ void _unpaused(struct mGUIRunner* runner) {
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
interframeBlending = fakeBool; interframeBlending = fakeBool;
} }
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
sgbCrop = fakeBool;
}
float stretch; float stretch;
if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) { if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
@ -952,20 +957,25 @@ void _drawFrame(struct mGUIRunner* runner, bool faded) {
} }
} }
int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust); if (screenMode == SM_PA) {
int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust); unsigned factorWidth = corew;
unsigned factorHeight = coreh;
if (sgbCrop && factorWidth == 256 && factorHeight == 224) {
factorWidth = GB_VIDEO_HORIZONTAL_PIXELS;
factorHeight = GB_VIDEO_VERTICAL_PIXELS;
}
int hfactor = (vmode->fbWidth * wStretch) / (factorWidth * wAdjust);
int vfactor = (vmode->efbHeight * hStretch) / (factorHeight * hAdjust);
if (hfactor > vfactor) { if (hfactor > vfactor) {
scaleFactor = vfactor; scaleFactor = vfactor;
} else { } else {
scaleFactor = hfactor; scaleFactor = hfactor;
} }
if (screenMode == SM_PA) {
vertWidth *= scaleFactor; vertWidth *= scaleFactor;
vertHeight *= scaleFactor; vertHeight *= scaleFactor;
}
if (screenMode == SM_PA) {
_reproj(corew * scaleFactor, coreh * scaleFactor); _reproj(corew * scaleFactor, coreh * scaleFactor);
} else { } else {
_reproj2(corew, coreh); _reproj2(corew, coreh);

View File

@ -47,7 +47,7 @@ static int _strpcmp(const void* a, const void* b) {
return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
} }
static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) { static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
_cleanFiles(currentFiles); _cleanFiles(currentFiles);
struct VDir* dir = VDirOpen(currentPath); struct VDir* dir = VDirOpen(currentPath);
@ -144,6 +144,9 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
free((char*) testItem->title); free((char*) testItem->title);
GUIMenuItemListShift(currentFiles, item, 1); GUIMenuItemListShift(currentFiles, item, 1);
} else { } else {
if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) {
params->fileIndex = item;
}
++item; ++item;
} }
} }
@ -152,14 +155,14 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
return true; return true;
} }
bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) { bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
struct GUIMenu menu = { struct GUIMenu menu = {
.title = "Select file", .title = "Select file",
.subtitle = params->currentPath, .subtitle = params->currentPath,
.index = params->fileIndex,
}; };
GUIMenuItemListInit(&menu.items, 0); GUIMenuItemListInit(&menu.items, 0);
_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents); _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect);
menu.index = params->fileIndex;
while (true) { while (true) {
struct GUIMenuItem* item; struct GUIMenuItem* item;
@ -174,7 +177,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
continue; continue;
} }
_upDirectory(params->currentPath); _upDirectory(params->currentPath);
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) { if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
break; break;
} }
} else { } else {
@ -187,7 +190,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
struct GUIMenuItemList newFiles; struct GUIMenuItemList newFiles;
GUIMenuItemListInit(&newFiles, 0); GUIMenuItemListInit(&newFiles, 0);
if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents)) { if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
_cleanFiles(&newFiles); _cleanFiles(&newFiles);
GUIMenuItemListDeinit(&newFiles); GUIMenuItemListDeinit(&newFiles);
_cleanFiles(&menu.items); _cleanFiles(&menu.items);
@ -208,7 +211,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
break; break;
} }
_upDirectory(params->currentPath); _upDirectory(params->currentPath);
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) { if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
break; break;
} }
params->fileIndex = 0; params->fileIndex = 0;