Merge branch 'master' (early part) into medusa
13
CHANGES
|
@ -27,10 +27,14 @@ Features:
|
|||
- GB: Expose platform information to CLI debugger
|
||||
- Support Discord Rich Presence
|
||||
- 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
|
||||
- Experimental high level "XQ" audio for most GBA games
|
||||
- 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:
|
||||
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
||||
- 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: Fix window being enabled mid-scanline (fixes mgba.io/i/1328)
|
||||
- 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:
|
||||
- Qt: Fix some Qt display driver race conditions
|
||||
- Core: Improved lockstep driver reliability (Le Hoang Quyen)
|
||||
- Switch: Fix threading-related crash on second launch
|
||||
- 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:
|
||||
- GBA Savedata: EEPROM performance fixes
|
||||
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
|
||||
|
@ -64,6 +73,8 @@ Misc:
|
|||
- Qt: Improve sync code
|
||||
- Switch: Dynamic display resizing
|
||||
- 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)
|
||||
Emulation fixes:
|
||||
|
|
|
@ -1029,7 +1029,7 @@ endif()
|
|||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
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")
|
||||
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
||||
if(MSVC)
|
||||
|
|
|
@ -142,6 +142,7 @@ This will produce a `build-win32` directory with the build products. Replace `mg
|
|||
- mgba/ubuntu:xenial
|
||||
- mgba/ubuntu:bionic
|
||||
- mgba/ubuntu:cosmic
|
||||
- mgba/ubuntu:disco
|
||||
- mgba/vita
|
||||
- mgba/wii
|
||||
- mgba/windows:w32
|
||||
|
|
|
@ -124,6 +124,7 @@ Dieser Befehl erzeugt ein Verzeichnis `build-win32` mit den erzeugten Programmda
|
|||
- mgba/ubuntu:xenial
|
||||
- mgba/ubuntu:bionic
|
||||
- mgba/ubuntu:cosmic
|
||||
- mgba/ubuntu:disco
|
||||
- mgba/vita
|
||||
- mgba/wii
|
||||
- mgba/windows:w32
|
||||
|
|
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.8 KiB |
|
@ -14,7 +14,7 @@ CXX_GUARD_START
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ enum mVideoLoggerEvent {
|
|||
LOGGER_EVENT_GET_PIXELS,
|
||||
};
|
||||
|
||||
enum mVideoLoggerInjectionPoint {
|
||||
LOGGER_INJECTION_IMMEDIATE = 0,
|
||||
LOGGER_INJECTION_FIRST_SCANLINE,
|
||||
};
|
||||
|
||||
struct mVideoLoggerDirtyInfo {
|
||||
enum mVideoLoggerDirtyType type;
|
||||
uint32_t address;
|
||||
|
@ -97,6 +102,7 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);
|
|||
void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger);
|
||||
|
||||
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block);
|
||||
bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger);
|
||||
|
||||
struct mVideoLogContext;
|
||||
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 mVideoLogContext* mVideoLogContextCreate(struct mCore* core);
|
||||
|
||||
void mVideoLogContextSetCompression(struct mVideoLogContext*, bool enable);
|
||||
void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*);
|
||||
void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core);
|
||||
|
||||
|
@ -115,6 +122,12 @@ void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size);
|
|||
|
||||
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*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
|
|
@ -150,6 +150,7 @@ void GBDestroy(struct GB* gb);
|
|||
|
||||
void GBReset(struct LR35902Core* cpu);
|
||||
void GBSkipBIOS(struct GB* gb);
|
||||
void GBMapBIOS(struct GB* gb);
|
||||
void GBUnmapBIOS(struct GB* gb);
|
||||
void GBDetectModel(struct GB* gb);
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ DECL_BITFIELD(RTCStatus2, uint8_t);
|
|||
DECL_BITS(RTCStatus2, INT1, 0, 4);
|
||||
DECL_BIT(RTCStatus2, INT2, 6);
|
||||
|
||||
#ifndef PYCPARSE
|
||||
#pragma pack(push, 1)
|
||||
struct GBARTC {
|
||||
int32_t bytesRemaining;
|
||||
|
@ -90,6 +91,9 @@ struct GBARTC {
|
|||
uint8_t time[7];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#else
|
||||
struct GBATRC;
|
||||
#endif
|
||||
|
||||
struct GBAGBPKeyCallback {
|
||||
struct mKeyCallback d;
|
||||
|
|
|
@ -16,6 +16,7 @@ struct GBAVideoRendererSprite {
|
|||
struct GBAObj obj;
|
||||
int16_t y;
|
||||
int16_t endY;
|
||||
int8_t index;
|
||||
};
|
||||
|
||||
int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort);
|
||||
|
|
|
@ -45,6 +45,7 @@ struct GBAVideoSoftwareBackground {
|
|||
color_t* variantPalette;
|
||||
int32_t offsetX;
|
||||
int32_t offsetY;
|
||||
bool highlight;
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -117,6 +118,8 @@ struct GBAVideoSoftwareRenderer {
|
|||
color_t variantPalette[512];
|
||||
color_t* objExtPalette;
|
||||
color_t* objExtVariantPalette;
|
||||
color_t highlightPalette[512];
|
||||
color_t highlightVariantPalette[512];
|
||||
|
||||
uint16_t blda;
|
||||
uint16_t bldb;
|
||||
|
@ -165,6 +168,8 @@ struct GBAVideoSoftwareRenderer {
|
|||
|
||||
int masterBright;
|
||||
int masterBrightY;
|
||||
|
||||
uint8_t lastHighlightAmount;
|
||||
};
|
||||
|
||||
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer);
|
||||
|
|
|
@ -33,6 +33,23 @@ enum {
|
|||
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 GBASIODriver* normal;
|
||||
struct GBASIODriver* multiplayer;
|
||||
|
@ -47,36 +64,7 @@ struct GBASIO {
|
|||
struct GBASIODriver* activeDriver;
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
void GBASIOInit(struct GBASIO* sio);
|
||||
|
|
|
@ -12,6 +12,7 @@ CXX_GUARD_START
|
|||
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/timing.h>
|
||||
#include <mgba/gba/interface.h>
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GBA_VIDEO);
|
||||
|
||||
|
@ -84,10 +85,7 @@ struct GBAObj {
|
|||
uint16_t d;
|
||||
};
|
||||
|
||||
union GBAOAM {
|
||||
struct GBAObj obj[128];
|
||||
|
||||
struct GBAOAMMatrix {
|
||||
struct GBAOAMMatrix {
|
||||
int16_t padding0[3];
|
||||
int16_t a;
|
||||
int16_t padding1[3];
|
||||
|
@ -96,8 +94,11 @@ union GBAOAM {
|
|||
int16_t c;
|
||||
int16_t padding3[3];
|
||||
int16_t d;
|
||||
} mat[32];
|
||||
};
|
||||
|
||||
union GBAOAM {
|
||||
struct GBAObj obj[128];
|
||||
struct GBAOAMMatrix mat[32];
|
||||
uint16_t raw[512];
|
||||
};
|
||||
|
||||
|
@ -197,6 +198,11 @@ struct GBAVideoRenderer {
|
|||
|
||||
bool disableBG[4];
|
||||
bool disableOBJ;
|
||||
|
||||
bool highlightBG[4];
|
||||
bool highlightOBJ[128];
|
||||
color_t highlightColor;
|
||||
uint8_t highlightAmount;
|
||||
};
|
||||
|
||||
struct GBAVideo {
|
||||
|
|
|
@ -164,10 +164,16 @@ bool mCorePreloadFile(struct mCore* core, const char* path) {
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
bool mCoreAutoloadPatch(struct mCore* core) {
|
||||
if (!core->dirs.patch) {
|
||||
return false;
|
||||
}
|
||||
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, ".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) {
|
||||
if (!core->dirs.state) {
|
||||
return NULL;
|
||||
}
|
||||
char name[PATH_MAX + 14]; // Quash warning
|
||||
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);
|
||||
|
|
|
@ -118,6 +118,20 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
|||
},
|
||||
.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) {
|
||||
.title = "Enable SGB borders",
|
||||
.data = "sgb.borders",
|
||||
|
@ -128,6 +142,16 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
|||
},
|
||||
.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
|
||||
size_t i;
|
||||
const char* mapNames[GUI_MAX_INPUTS + 1];
|
||||
|
@ -173,8 +197,6 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
|||
continue;
|
||||
}
|
||||
if (item->stateMappings) {
|
||||
item->state = 0;
|
||||
|
||||
size_t j;
|
||||
for (j = 0; j < item->nStates; ++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")) {
|
||||
// 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';
|
||||
}
|
||||
continue;
|
||||
|
@ -289,21 +311,21 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
|||
#ifdef M_CORE_GB
|
||||
if (!strcmp(item->data, "gb.bios")) {
|
||||
// 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';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(item->data, "gbc.bios")) {
|
||||
// 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';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(item->data, "sgb.bios")) {
|
||||
// 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';
|
||||
}
|
||||
continue;
|
||||
|
|
|
@ -628,10 +628,18 @@ void mGUIRunloop(struct mGUIRunner* runner) {
|
|||
}
|
||||
while (true) {
|
||||
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;
|
||||
}
|
||||
mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath);
|
||||
mCoreConfigSetValue(&runner->config, "lastGame", path);
|
||||
mCoreConfigSave(&runner->config);
|
||||
mGUIRun(runner, path);
|
||||
}
|
||||
|
|
|
@ -84,6 +84,11 @@ struct mVideoLogChannel {
|
|||
z_stream inflateStream;
|
||||
#endif
|
||||
|
||||
bool injecting;
|
||||
enum mVideoLoggerInjectionPoint injectionPoint;
|
||||
uint32_t ignorePackets;
|
||||
|
||||
struct CircleBuffer injectedBuffer;
|
||||
struct CircleBuffer buffer;
|
||||
};
|
||||
|
||||
|
@ -94,6 +99,7 @@ struct mVideoLogContext {
|
|||
struct mVideoLogChannel channels[mVL_MAX_CHANNELS];
|
||||
|
||||
bool write;
|
||||
bool compression;
|
||||
uint32_t activeChannel;
|
||||
struct VFile* backing;
|
||||
};
|
||||
|
@ -285,14 +291,28 @@ void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uin
|
|||
}
|
||||
|
||||
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};
|
||||
while (logger->readData(logger, &item, sizeof(item), block)) {
|
||||
if (ignorePackets & (1 << item.type)) {
|
||||
continue;
|
||||
}
|
||||
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_PALETTE:
|
||||
case DIRTY_OAM:
|
||||
case DIRTY_VRAM:
|
||||
case DIRTY_SCANLINE:
|
||||
case DIRTY_FLUSH:
|
||||
case DIRTY_FRAME:
|
||||
case DIRTY_RANGE:
|
||||
|
@ -308,15 +328,34 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool 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) {
|
||||
struct mVideoLogChannel* channel = logger->dataContext;
|
||||
return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
|
||||
}
|
||||
|
||||
static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
|
||||
UNUSED(logger);
|
||||
UNUSED(data);
|
||||
UNUSED(length);
|
||||
struct mVideoLogChannel* channel = logger->dataContext;
|
||||
if (channel->injecting) {
|
||||
return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -465,6 +504,12 @@ struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
|
|||
context->initialStateSize = 0;
|
||||
context->initialState = NULL;
|
||||
|
||||
#ifdef USE_ZLIB
|
||||
context->compression = true;
|
||||
#else
|
||||
context->compression = false;
|
||||
#endif
|
||||
|
||||
if (core) {
|
||||
context->initialStateSize = core->stateSize(core);
|
||||
context->initialState = anonymousMemoryMap(context->initialStateSize);
|
||||
|
@ -482,6 +527,10 @@ void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* v
|
|||
vf->seek(vf, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
void mVideoLogContextSetCompression(struct mVideoLogContext* context, bool compression) {
|
||||
context->compression = compression;
|
||||
}
|
||||
|
||||
void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
|
||||
struct mVideoLogHeader header = { { 0 } };
|
||||
memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
|
||||
|
@ -499,6 +548,7 @@ void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore*
|
|||
struct mVLBlockHeader chheader = { 0 };
|
||||
STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
|
||||
#ifdef USE_ZLIB
|
||||
if (context->compression) {
|
||||
STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
|
||||
|
||||
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));
|
||||
_copyVf(context->backing, vfm);
|
||||
vfm->close(vfm);
|
||||
#else
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
STORE_32LE(context->initialStateSize, 0, &chheader.length);
|
||||
context->backing->write(context->backing, &chheader, sizeof(chheader));
|
||||
context->backing->write(context->backing, context->initialState, context->initialStateSize);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
size_t i;
|
||||
|
@ -609,6 +661,7 @@ bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
|
|||
|
||||
size_t i;
|
||||
for (i = 0; i < context->nChannels; ++i) {
|
||||
CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE);
|
||||
CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
|
||||
context->channels[i].bufferRemaining = 0;
|
||||
context->channels[i].currentPointer = pointer;
|
||||
|
@ -647,9 +700,10 @@ static void _flushBufferCompressed(struct mVideoLogContext* context) {
|
|||
|
||||
static void _flushBuffer(struct mVideoLogContext* context) {
|
||||
#ifdef USE_ZLIB
|
||||
// TODO: Make optional
|
||||
if (context->compression) {
|
||||
_flushBufferCompressed(context);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
|
||||
|
@ -688,6 +742,7 @@ void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* contex
|
|||
|
||||
size_t i;
|
||||
for (i = 0; i < context->nChannels; ++i) {
|
||||
CircleBufferDeinit(&context->channels[i].injectedBuffer);
|
||||
CircleBufferDeinit(&context->channels[i].buffer);
|
||||
#ifdef USE_ZLIB
|
||||
if (context->channels[i].inflating) {
|
||||
|
@ -718,6 +773,7 @@ void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core
|
|||
|
||||
size_t i;
|
||||
for (i = 0; i < context->nChannels; ++i) {
|
||||
CircleBufferClear(&context->channels[i].injectedBuffer);
|
||||
CircleBufferClear(&context->channels[i].buffer);
|
||||
context->channels[i].bufferRemaining = 0;
|
||||
context->channels[i].currentPointer = pointer;
|
||||
|
@ -744,10 +800,35 @@ int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
|
|||
int chid = context->nChannels;
|
||||
++context->nChannels;
|
||||
context->channels[chid].p = context;
|
||||
CircleBufferInit(&context->channels[chid].injectedBuffer, 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;
|
||||
}
|
||||
|
||||
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
|
||||
static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
|
||||
uint8_t fbuffer[0x400];
|
||||
|
@ -900,12 +981,16 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d
|
|||
if (channelId >= mVL_MAX_CHANNELS) {
|
||||
return 0;
|
||||
}
|
||||
if (CircleBufferSize(&channel->buffer) >= length) {
|
||||
return CircleBufferRead(&channel->buffer, data, length);
|
||||
struct CircleBuffer* buffer = &channel->buffer;
|
||||
if (channel->injecting) {
|
||||
buffer = &channel->injectedBuffer;
|
||||
}
|
||||
if (CircleBufferSize(buffer) >= length) {
|
||||
return CircleBufferRead(buffer, data, length);
|
||||
}
|
||||
ssize_t size = 0;
|
||||
if (CircleBufferSize(&channel->buffer)) {
|
||||
size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer));
|
||||
if (CircleBufferSize(buffer)) {
|
||||
size = CircleBufferRead(buffer, data, CircleBufferSize(buffer));
|
||||
if (size <= 0) {
|
||||
return size;
|
||||
}
|
||||
|
@ -915,7 +1000,7 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d
|
|||
if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
|
||||
return size;
|
||||
}
|
||||
size += CircleBufferRead(&channel->buffer, data, length);
|
||||
size += CircleBufferRead(buffer, data, length);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -929,16 +1014,20 @@ static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const
|
|||
_flushBuffer(context);
|
||||
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);
|
||||
if (CircleBufferCapacity(&channel->buffer) < length) {
|
||||
CircleBufferDeinit(&channel->buffer);
|
||||
CircleBufferInit(&channel->buffer, toPow2(length << 1));
|
||||
if (CircleBufferCapacity(buffer) < length) {
|
||||
CircleBufferDeinit(buffer);
|
||||
CircleBufferInit(buffer, toPow2(length << 1));
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t read = CircleBufferWrite(&channel->buffer, data, length);
|
||||
if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) {
|
||||
ssize_t read = CircleBufferWrite(buffer, data, length);
|
||||
if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) {
|
||||
_flushBuffer(context);
|
||||
}
|
||||
return read;
|
||||
|
|
|
@ -902,9 +902,11 @@ static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* co
|
|||
static void _GBCoreEndVideoLog(struct mCore* core) {
|
||||
struct GBCore* gbcore = (struct GBCore*) core;
|
||||
struct GB* gb = core->board;
|
||||
if (gbcore->proxyRenderer.logger) {
|
||||
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
|
||||
free(gbcore->proxyRenderer.logger);
|
||||
gbcore->proxyRenderer.logger = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1007,6 +1009,7 @@ static void _GBVLPStartFrameCallback(void *context) {
|
|||
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
|
||||
mVideoLogContextRewind(gbcore->logContext, core);
|
||||
GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
|
||||
gb->earlyExit = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1022,6 +1025,7 @@ static bool _GBVLPInit(struct mCore* core) {
|
|||
gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback;
|
||||
gbcore->logCallbacks.context = core;
|
||||
core->addCoreCallbacks(core, &gbcore->logCallbacks);
|
||||
core->videoLogger = gbcore->proxyRenderer.logger;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
19
src/gb/gb.c
|
@ -419,14 +419,7 @@ void GBReset(struct LR35902Core* cpu) {
|
|||
gb->biosVf->close(gb->biosVf);
|
||||
gb->biosVf = NULL;
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
GBMapBIOS(gb);
|
||||
cpu->a = 0;
|
||||
cpu->f.packed = 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) {
|
||||
if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) {
|
||||
free(gb->memory.romBase);
|
||||
|
|
|
@ -185,8 +185,10 @@ void GBIOReset(struct GB* gb) {
|
|||
GBIOWrite(gb, REG_NR51, 0xF3);
|
||||
if (!gb->biosVf) {
|
||||
GBIOWrite(gb, REG_LCDC, 0x91);
|
||||
gb->memory.io[0x50] = 1;
|
||||
} else {
|
||||
GBIOWrite(gb, REG_LCDC, 0x00);
|
||||
gb->memory.io[0x50] = 0xFF;
|
||||
}
|
||||
GBIOWrite(gb, REG_SCY, 0x00);
|
||||
GBIOWrite(gb, REG_SCX, 0x00);
|
||||
|
|
|
@ -135,6 +135,16 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
if (ucheck16 >= 0x40) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -187,6 +197,12 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
GBTimerDeserialize(&gb->timer, state);
|
||||
GBAudioDeserialize(&gb->audio, state);
|
||||
|
||||
if (gb->memory.io[0x50] == 0xFF) {
|
||||
GBMapBIOS(gb);
|
||||
} else {
|
||||
GBUnmapBIOS(gb);
|
||||
}
|
||||
|
||||
if (gb->model & GB_MODEL_SGB && canSgb) {
|
||||
GBSGBDeserialize(gb, state);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
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;
|
||||
if (video->frameskipCounter < 0) {
|
||||
video->renderer->finishFrame(video->renderer);
|
||||
video->frameskipCounter = video->frameskip;
|
||||
}
|
||||
GBFrameEnded(video->p);
|
||||
mCoreSyncPostFrame(video->p->sync);
|
||||
++video->frameCounter;
|
||||
|
||||
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) {
|
||||
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
|
||||
}
|
||||
GBFrameStarted(video->p);
|
||||
}
|
||||
|
||||
|
|
|
@ -137,8 +137,11 @@ struct GBACore {
|
|||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
struct GBAVideoGLRenderer glRenderer;
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
struct GBAVideoProxyRenderer vlProxy;
|
||||
struct GBAVideoProxyRenderer proxyRenderer;
|
||||
struct mVideoLogContext* logContext;
|
||||
#endif
|
||||
struct mCoreCallbacks logCallbacks;
|
||||
#ifndef DISABLE_THREADING
|
||||
struct mVideoThreadProxy threadProxy;
|
||||
|
@ -170,7 +173,9 @@ static bool _GBACoreInit(struct mCore* core) {
|
|||
gbacore->overrides = NULL;
|
||||
gbacore->debuggerPlatform = NULL;
|
||||
gbacore->cheatDevice = NULL;
|
||||
#ifndef MINIMAL_CORE
|
||||
gbacore->logContext = NULL;
|
||||
#endif
|
||||
gbacore->audioMixer = NULL;
|
||||
|
||||
GBACreate(gba);
|
||||
|
@ -192,7 +197,10 @@ static bool _GBACoreInit(struct mCore* core) {
|
|||
#ifndef DISABLE_THREADING
|
||||
mVideoThreadProxyCreate(&gbacore->threadProxy);
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
gbacore->vlProxy.logger = NULL;
|
||||
gbacore->proxyRenderer.logger = NULL;
|
||||
#endif
|
||||
|
||||
gbacore->keys = 0;
|
||||
gba->keySource = &gbacore->keys;
|
||||
|
@ -475,11 +483,13 @@ static void _GBACoreReset(struct mCore* core) {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
if (core->videoLogger) {
|
||||
gbacore->proxyRenderer.logger = core->videoLogger;
|
||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
|
||||
renderer = &gbacore->proxyRenderer.d;
|
||||
}
|
||||
#endif
|
||||
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;
|
||||
|
||||
int channelId = mVideoLoggerAddChannel(context);
|
||||
gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger));
|
||||
mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, false);
|
||||
mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, context, channelId);
|
||||
gbacore->proxyRenderer.logger->block = false;
|
||||
gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger));
|
||||
mVideoLoggerRendererCreate(gbacore->vlProxy.logger, false);
|
||||
mVideoLoggerAttachChannel(gbacore->vlProxy.logger, context, channelId);
|
||||
gbacore->vlProxy.logger->block = false;
|
||||
|
||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, &gbacore->renderer.d);
|
||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer);
|
||||
GBAVideoProxyRendererCreate(&gbacore->vlProxy, gba->video.renderer);
|
||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
|
||||
}
|
||||
|
||||
static void _GBACoreEndVideoLog(struct mCore* core) {
|
||||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
struct GBA* gba = core->board;
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);
|
||||
free(gbacore->proxyRenderer.logger);
|
||||
gbacore->proxyRenderer.logger = NULL;
|
||||
if (gbacore->vlProxy.logger) {
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
|
||||
free(gbacore->vlProxy.logger);
|
||||
gbacore->vlProxy.logger = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1112,10 +1124,11 @@ static void _GBAVLPStartFrameCallback(void *context) {
|
|||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
struct GBA* gba = core->board;
|
||||
|
||||
if (!mVideoLoggerRendererRun(gbacore->proxyRenderer.logger, true)) {
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);
|
||||
if (!mVideoLoggerRendererRun(gbacore->vlProxy.logger, true)) {
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger));
|
||||
mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, true);
|
||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, NULL);
|
||||
gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger));
|
||||
mVideoLoggerRendererCreate(gbacore->vlProxy.logger, true);
|
||||
GBAVideoProxyRendererCreate(&gbacore->vlProxy, NULL);
|
||||
memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks));
|
||||
gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback;
|
||||
gbacore->logCallbacks.context = core;
|
||||
core->addCoreCallbacks(core, &gbacore->logCallbacks);
|
||||
core->videoLogger = gbacore->vlProxy.logger;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1145,8 +1159,8 @@ static void _GBAVLPDeinit(struct mCore* core) {
|
|||
static void _GBAVLPReset(struct mCore* core) {
|
||||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
struct GBA* gba = (struct GBA*) core->board;
|
||||
if (gba->video.renderer == &gbacore->proxyRenderer.d) {
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);
|
||||
if (gba->video.renderer == &gbacore->vlProxy.d) {
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
|
||||
} else if (gbacore->renderer.outputBuffer) {
|
||||
struct GBAVideoRenderer* renderer = &gbacore->renderer.d;
|
||||
GBAVideoAssociateRenderer(&gba->video, renderer);
|
||||
|
@ -1154,7 +1168,7 @@ static void _GBAVLPReset(struct mCore* core) {
|
|||
|
||||
ARMReset(core->cpu);
|
||||
mVideoLogContextRewind(gbacore->logContext, core);
|
||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer);
|
||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
|
||||
|
||||
// Make sure CPU loop never spins
|
||||
GBAHalt(gba);
|
||||
|
@ -1170,7 +1184,7 @@ static bool _GBAVLPLoadROM(struct mCore* core, struct VFile* vf) {
|
|||
gbacore->logContext = NULL;
|
||||
return false;
|
||||
}
|
||||
mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, gbacore->logContext, 0);
|
||||
mVideoLoggerAttachChannel(gbacore->vlProxy.logger, gbacore->logContext, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ void _battlechipTransfer(struct GBASIOBattlechipGate* gate) {
|
|||
if (gate->d.p->mode == SIO_NORMAL_32) {
|
||||
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000;
|
||||
} 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);
|
||||
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) {
|
||||
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->normalControl.start = 0;
|
||||
if (gate->d.p->normalControl.irq) {
|
||||
gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt);
|
||||
if (GBASIONormalIsIrq(gate->d.p->siocnt)) {
|
||||
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
|
||||
}
|
||||
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_SIOMULTI2 >> 1] = 0xFFFF;
|
||||
gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
||||
gate->d.p->multiplayerControl.busy = 0;
|
||||
gate->d.p->multiplayerControl.id = 0;
|
||||
gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt);
|
||||
gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0);
|
||||
|
||||
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;
|
||||
|
||||
if (gate->d.p->multiplayerControl.irq) {
|
||||
if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) {
|
||||
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,17 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
|
|||
renderer->d.disableBG[3] = 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->parsePacket = _parsePacket;
|
||||
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[3] = proxyRenderer->d.disableBG[3];
|
||||
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) {
|
||||
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address);
|
||||
}
|
||||
|
|
|
@ -577,10 +577,10 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
|
|||
++gbp->p->gbpTxPosition;
|
||||
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -396,9 +396,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
value = 0; \
|
||||
break; \
|
||||
} \
|
||||
address &= 0x00017FFC; \
|
||||
} \
|
||||
LOAD_32(value, address & 0x00017FFC, gba->video.vram); \
|
||||
} else { \
|
||||
LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \
|
||||
} \
|
||||
wait += waitstatesRegion[REGION_VRAM];
|
||||
|
||||
#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;
|
||||
break;
|
||||
}
|
||||
address &= 0x00017FFE;
|
||||
}
|
||||
LOAD_16(value, address & 0x00017FFE, gba->video.vram);
|
||||
} else {
|
||||
LOAD_16(value, address & 0x0001FFFE, gba->video.vram);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
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;
|
||||
break;
|
||||
}
|
||||
address &= 0x00017FFF;
|
||||
}
|
||||
value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF];
|
||||
} else {
|
||||
value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF];
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
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); \
|
||||
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); \
|
||||
if (oldValue != value) { \
|
||||
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)); \
|
||||
} \
|
||||
} \
|
||||
wait += waitstatesRegion[REGION_VRAM];
|
||||
|
||||
#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);
|
||||
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);
|
||||
if (value != oldValue) {
|
||||
STORE_16(value, address & 0x0001FFFE, gba->video.vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
LOAD_16(oldValue, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
||||
|
|
|
@ -34,6 +34,7 @@ int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite*
|
|||
sprites[oamMax].y = y;
|
||||
sprites[oamMax].endY = y + height;
|
||||
sprites[oamMax].obj = obj;
|
||||
sprites[oamMax].index = i;
|
||||
++oamMax;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ static const char* const _renderMode0 =
|
|||
" if ((size & 1) == 1) {\n"
|
||||
" coord.y += coord.x & 256;\n"
|
||||
" }\n"
|
||||
" coord &= ivec2(255, 511);\n"
|
||||
" coord &= ivec2(255, 1023);\n"
|
||||
" int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
|
||||
" vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
|
||||
" int tileFlags = int(map.g * 15.9);\n"
|
||||
|
@ -442,7 +442,7 @@ static const char* const _renderObj =
|
|||
"uniform ivec4 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\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"
|
||||
|
||||
|
@ -467,7 +467,7 @@ static const char* const _renderObj =
|
|||
" color = pix;\n"
|
||||
" flags = inflags;\n"
|
||||
" gl_FragDepth = float(flags.x) / 16.;\n"
|
||||
" window = objwin.yzw;\n"
|
||||
" window = ivec4(objwin.yzw, 0);\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsWindow[] = {
|
||||
|
@ -488,7 +488,7 @@ static const char* const _renderWindow =
|
|||
"uniform ivec3 flags;\n"
|
||||
"uniform ivec4 win0[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"
|
||||
" bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"
|
||||
|
@ -526,7 +526,7 @@ static const char* const _renderWindow =
|
|||
"void main() {\n"
|
||||
" int dispflags = (dispcnt & 0x1F) | 0x20;\n"
|
||||
" if ((dispcnt & 0xE0) == 0) {\n"
|
||||
" window = ivec3(dispflags, blend);\n"
|
||||
" window = ivec4(dispflags, blend, 0);\n"
|
||||
" } else {\n"
|
||||
" ivec3 windowFlags = ivec3(flags.z, blend);\n"
|
||||
" if ((dispcnt & 0x40) != 0) { \n"
|
||||
|
@ -535,7 +535,7 @@ static const char* const _renderWindow =
|
|||
" if ((dispcnt & 0x20) != 0) { \n"
|
||||
" crop(interpolate(win0), flags.x, windowFlags);\n"
|
||||
" }\n"
|
||||
" window = windowFlags;\n"
|
||||
" window = ivec4(windowFlags, 0);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
|
@ -651,6 +651,17 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) {
|
|||
renderer->d.disableBG[3] = 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;
|
||||
}
|
||||
|
||||
|
@ -1369,7 +1380,7 @@ void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) {
|
|||
glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
|
||||
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 });
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
|
||||
|
|
|
@ -613,16 +613,21 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer
|
|||
uint32_t screenBase;
|
||||
uint32_t charBase;
|
||||
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) {
|
||||
mainPalette = background->extPalette;
|
||||
if (variant) {
|
||||
mainPalette = background->variantPalette;
|
||||
}
|
||||
} else {
|
||||
mainPalette = renderer->normalPalette;
|
||||
if (renderer->d.highlightAmount && background->highlight) {
|
||||
mainPalette = renderer->highlightPalette;
|
||||
}
|
||||
if (variant) {
|
||||
mainPalette = renderer->variantPalette;
|
||||
if (renderer->d.highlightAmount && background->highlight) {
|
||||
mainPalette = renderer->highlightVariantPalette;
|
||||
}
|
||||
}
|
||||
}
|
||||
color_t* palette = mainPalette;
|
||||
|
|
|
@ -212,7 +212,7 @@
|
|||
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 height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
|
||||
int start = renderer->start;
|
||||
|
@ -255,6 +255,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
}
|
||||
|
||||
color_t* palette = &renderer->normalPalette[0x100];
|
||||
if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
|
||||
palette = &renderer->highlightPalette[0x100];
|
||||
}
|
||||
color_t* objwinPalette = palette;
|
||||
|
||||
if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {
|
||||
|
@ -269,6 +272,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
}
|
||||
} else if (variant) {
|
||||
palette = &renderer->variantPalette[0x100];
|
||||
if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
|
||||
palette = &renderer->highlightVariantPalette[0x100];
|
||||
}
|
||||
if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
|
||||
objwinPalette = palette;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer
|
|||
void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer,
|
||||
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 GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y);
|
||||
|
@ -145,11 +145,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
int objwinForceEnable = 0; \
|
||||
UNUSED(objwinForceEnable); \
|
||||
color_t* objwinPalette = renderer->normalPalette; \
|
||||
if (renderer->d.highlightAmount && background->highlight) { \
|
||||
objwinPalette = renderer->highlightPalette; \
|
||||
} \
|
||||
UNUSED(objwinPalette); \
|
||||
if (objwinSlowPath) { \
|
||||
if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \
|
||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
|
||||
objwinPalette = renderer->variantPalette; \
|
||||
if (renderer->d.highlightAmount && background->highlight) { \
|
||||
palette = renderer->highlightVariantPalette; \
|
||||
} \
|
||||
} \
|
||||
switch (background->index) { \
|
||||
case 0: \
|
||||
|
@ -204,8 +210,14 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \
|
||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
|
||||
color_t* palette = renderer->normalPalette; \
|
||||
if (renderer->d.highlightAmount && background->highlight) { \
|
||||
palette = renderer->highlightPalette; \
|
||||
} \
|
||||
if (variant) { \
|
||||
palette = renderer->variantPalette; \
|
||||
if (renderer->d.highlightAmount && background->highlight) { \
|
||||
palette = renderer->highlightVariantPalette; \
|
||||
} \
|
||||
} \
|
||||
UNUSED(palette); \
|
||||
PREPARE_OBJWIN;
|
||||
|
|
|
@ -68,6 +68,17 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
|||
renderer->masterHeight = GBA_VIDEO_VERTICAL_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;
|
||||
}
|
||||
|
||||
|
@ -573,11 +584,23 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
|
||||
int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
|
||||
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) {
|
||||
_updatePalettes(softwareRenderer);
|
||||
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;
|
||||
unsigned 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) {
|
||||
continue;
|
||||
}
|
||||
if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
|
||||
if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
|
||||
localY = mosaicY;
|
||||
if (localY < sprite->y) {
|
||||
if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
|
||||
localY = sprite->y;
|
||||
}
|
||||
if (localY >= sprite->endY) {
|
||||
if (localY >= (sprite->endY & 0xFF)) {
|
||||
localY = sprite->endY - 1;
|
||||
}
|
||||
}
|
||||
|
@ -965,7 +988,7 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
|
|||
continue;
|
||||
}
|
||||
|
||||
int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY);
|
||||
int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY);
|
||||
spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
|
||||
}
|
||||
if (renderer->spriteCyclesRemaining <= 0) {
|
||||
|
@ -993,4 +1016,12 @@ static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
|
|||
|
||||
bool GBASIOLockstepNodeInit(struct GBASIODriver* 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);
|
||||
node->event.context = node;
|
||||
node->event.name = "GBA SIO Lockstep";
|
||||
|
@ -99,10 +99,10 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
|||
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
||||
node->d.p->rcnt |= 3;
|
||||
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) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
|
||||
}
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
|
@ -178,10 +178,10 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
|
|||
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
||||
|
||||
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);
|
||||
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);
|
||||
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_SIOMULTI3 >> 1] = node->p->multiRecv[3];
|
||||
sio->rcnt |= 1;
|
||||
sio->multiplayerControl.busy = 0;
|
||||
sio->multiplayerControl.id = node->id;
|
||||
if (sio->multiplayerControl.irq) {
|
||||
sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
|
||||
sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
|
||||
if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
|
||||
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
||||
}
|
||||
break;
|
||||
case SIO_NORMAL_8:
|
||||
// TODO
|
||||
sio->normalControl.start = 0;
|
||||
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
|
||||
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;
|
||||
} else {
|
||||
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
|
||||
}
|
||||
if (sio->multiplayerControl.irq) {
|
||||
if (GBASIONormalIsIrq(sio->siocnt)) {
|
||||
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
||||
}
|
||||
break;
|
||||
case SIO_NORMAL_32:
|
||||
// TODO
|
||||
sio->normalControl.start = 0;
|
||||
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
|
||||
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_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
|
||||
} else {
|
||||
node->d.p->p->memory.io[REG_SIODATA32_LO >> 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);
|
||||
}
|
||||
break;
|
||||
|
@ -278,7 +278,7 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
|||
case TRANSFER_IDLE:
|
||||
// If the master hasn't initiated a transfer, it can keep going.
|
||||
node->nextEvent += LOCKSTEP_INCREMENT;
|
||||
node->d.p->multiplayerControl.ready = attachedMulti == attached;
|
||||
node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
// 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(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;
|
||||
switch (transferActive) {
|
||||
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);
|
||||
}
|
||||
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_SIOMULTI2 >> 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;
|
||||
case SIO_NORMAL_8:
|
||||
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);
|
||||
value &= 0xFF8B;
|
||||
if (!node->id) {
|
||||
driver->p->normalControl.si = 1;
|
||||
driver->p->siocnt = GBASIONormalFillSi(driver->p->siocnt);
|
||||
}
|
||||
if (value & 0x0080 && !node->id) {
|
||||
// Internal shift clock
|
||||
|
|
|
@ -104,6 +104,7 @@ static bool frameStarted = false;
|
|||
static C3D_RenderTarget* upscaleBuffer;
|
||||
static C3D_Tex upscaleBufferTex;
|
||||
static bool interframeBlending = false;
|
||||
static bool sgbCrop = false;
|
||||
|
||||
static aptHookCookie cookie;
|
||||
static bool core2;
|
||||
|
@ -382,6 +383,10 @@ static void _gameLoaded(struct mGUIRunner* runner) {
|
|||
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||
interframeBlending = fakeBool;
|
||||
}
|
||||
|
||||
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||
sgbCrop = fakeBool;
|
||||
}
|
||||
}
|
||||
|
||||
static void _gameUnloaded(struct mGUIRunner* runner) {
|
||||
|
@ -437,6 +442,12 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
|||
|
||||
int w = corew;
|
||||
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
|
||||
while (w != 0) {
|
||||
int temp = h % w;
|
||||
|
@ -444,8 +455,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
|||
w = temp;
|
||||
}
|
||||
int gcd = h;
|
||||
unsigned aspectw = corew / gcd;
|
||||
unsigned aspecth = coreh / gcd;
|
||||
unsigned aspectw = innerw / gcd;
|
||||
unsigned aspecth = innerh / gcd;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
|
@ -517,6 +528,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
|||
}
|
||||
ctrFlushBatch();
|
||||
|
||||
innerw = corew;
|
||||
innerh = coreh;
|
||||
corew = w;
|
||||
coreh = h;
|
||||
screen_h = 240;
|
||||
|
@ -529,19 +542,20 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
|||
}
|
||||
ctrSetViewportSize(screen_w, screen_h, true);
|
||||
|
||||
float afw, afh;
|
||||
switch (screenMode) {
|
||||
default:
|
||||
return;
|
||||
case SM_AF_TOP:
|
||||
case SM_AF_BOTTOM:
|
||||
w = screen_w / aspectw;
|
||||
h = screen_h / aspecth;
|
||||
if (w * aspecth > screen_h) {
|
||||
w = aspectw * h;
|
||||
h = aspecth * h;
|
||||
afw = screen_w / (float) aspectw;
|
||||
afh = screen_h / (float) aspecth;
|
||||
if (afw * aspecth > screen_h) {
|
||||
w = innerw * afh / gcd;
|
||||
h = innerh * afh / gcd;
|
||||
} else {
|
||||
h = aspecth * w;
|
||||
w = aspectw * w;
|
||||
h = innerh * afw / gcd;
|
||||
w = innerw * afw / gcd;
|
||||
}
|
||||
break;
|
||||
case SM_SF_TOP:
|
||||
|
|
|
@ -41,7 +41,7 @@ static void _drawEnd(void) {
|
|||
|
||||
static uint32_t _pollInput(const struct mInputMap* map) {
|
||||
SceCtrlData pad;
|
||||
sceCtrlPeekBufferPositive(0, &pad, 1);
|
||||
sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
|
||||
int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0);
|
||||
|
||||
if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) {
|
||||
|
@ -127,17 +127,17 @@ int main() {
|
|||
.id = PSP2_INPUT,
|
||||
.keyNames = (const char*[]) {
|
||||
"Select",
|
||||
0,
|
||||
0,
|
||||
"L3",
|
||||
"R3",
|
||||
"Start",
|
||||
"Up",
|
||||
"Right",
|
||||
"Down",
|
||||
"Left",
|
||||
"L",
|
||||
"R",
|
||||
0, // L2?
|
||||
0, // R2?
|
||||
"L2",
|
||||
"R2",
|
||||
"L1",
|
||||
"R1",
|
||||
"\1\xC",
|
||||
"\1\xA",
|
||||
"\1\xB",
|
||||
|
|
|
@ -56,6 +56,7 @@ static vita2d_texture* oldTex;
|
|||
static vita2d_texture* screenshot;
|
||||
static Thread audioThread;
|
||||
static bool interframeBlending = false;
|
||||
static bool sgbCrop = false;
|
||||
|
||||
static struct mSceRotationSource {
|
||||
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) {
|
||||
SceCtrlData pad;
|
||||
sceCtrlPeekBufferPositive(0, &pad, 1);
|
||||
sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
|
||||
|
||||
int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0);
|
||||
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_LEFT, GBA_KEY_LEFT);
|
||||
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_RTRIGGER, GBA_KEY_R);
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, GBA_KEY_L);
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, GBA_KEY_R);
|
||||
|
||||
struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 };
|
||||
mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);
|
||||
|
@ -365,6 +366,10 @@ void mPSP2Setup(struct mGUIRunner* runner) {
|
|||
if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
|
||||
camera.cam = mode;
|
||||
}
|
||||
int fakeBool;
|
||||
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||
sgbCrop = fakeBool;
|
||||
}
|
||||
}
|
||||
|
||||
void mPSP2LoadROM(struct mGUIRunner* runner) {
|
||||
|
@ -398,6 +403,18 @@ void mPSP2LoadROM(struct mGUIRunner* runner) {
|
|||
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);
|
||||
ConditionInit(&audioContext.cond);
|
||||
memset(audioContext.buffer, 0, sizeof(audioContext.buffer));
|
||||
|
@ -461,8 +478,13 @@ void mPSP2Unpaused(struct mGUIRunner* runner) {
|
|||
}
|
||||
|
||||
int fakeBool;
|
||||
mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool);
|
||||
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||
interframeBlending = fakeBool;
|
||||
}
|
||||
|
||||
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||
sgbCrop = fakeBool;
|
||||
}
|
||||
}
|
||||
|
||||
void mPSP2Teardown(struct mGUIRunner* runner) {
|
||||
|
@ -507,6 +529,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo
|
|||
vita2d_draw_texture_tint(backdrop, 0, 0, tint);
|
||||
// Fall through
|
||||
case SM_PLAIN:
|
||||
if (sgbCrop && width == 256 && height == 224) {
|
||||
w = 768;
|
||||
h = 672;
|
||||
scalex = 3;
|
||||
scaley = 3;
|
||||
break;
|
||||
}
|
||||
w = 960 / width;
|
||||
h = 544 / height;
|
||||
if (w * height > 544) {
|
||||
|
@ -521,6 +550,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo
|
|||
scaley = scalex;
|
||||
break;
|
||||
case SM_ASPECT:
|
||||
if (sgbCrop && width == 256 && height == 224) {
|
||||
w = 967;
|
||||
h = 846;
|
||||
scalex = 34.0f / 9.0f;
|
||||
scaley = scalex;
|
||||
break;
|
||||
}
|
||||
w = 960 / aspectw;
|
||||
h = 544 / aspecth;
|
||||
if (w * aspecth > 544) {
|
||||
|
|
|
@ -67,6 +67,18 @@ for line in preprocessed.splitlines():
|
|||
lines.append(line)
|
||||
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)
|
||||
|
||||
lines = []
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -22,7 +22,7 @@
|
|||
using namespace QGBA;
|
||||
|
||||
AssetTile::AssetTile(QWidget* parent)
|
||||
: QGroupBox(parent)
|
||||
: AssetInfo(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
|
@ -42,16 +42,8 @@ AssetTile::AssetTile(QWidget* parent)
|
|||
m_ui.b->setFont(font);
|
||||
}
|
||||
|
||||
void AssetTile::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 = layout()->indexOf(m_ui.line);
|
||||
static_cast<QBoxLayout*>(layout())->insertLayout(index, newLayout);
|
||||
int AssetTile::customLocation(const QString&) {
|
||||
return layout()->indexOf(m_ui.line);
|
||||
}
|
||||
|
||||
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.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());
|
||||
}
|
||||
|
|
|
@ -15,13 +15,12 @@ namespace QGBA {
|
|||
|
||||
class CoreController;
|
||||
|
||||
class AssetTile : public QGroupBox {
|
||||
class AssetTile : public AssetInfo {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AssetTile(QWidget* parent = nullptr);
|
||||
void setController(std::shared_ptr<CoreController>);
|
||||
void addCustomProperty(const QString& id, const QString& visibleName);
|
||||
|
||||
public slots:
|
||||
void setPalette(int);
|
||||
|
@ -29,7 +28,9 @@ public slots:
|
|||
void selectIndex(int);
|
||||
void setFlip(bool h, bool v);
|
||||
void selectColor(int);
|
||||
void setCustomProperty(const QString& id, const QVariant& value);
|
||||
|
||||
protected:
|
||||
int customLocation(const QString& id = {}) override;
|
||||
|
||||
private:
|
||||
Ui::AssetTile m_ui;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AssetTile</class>
|
||||
<widget class="QGroupBox" name="AssetTile">
|
||||
<widget class="QGBA::AssetInfo" name="AssetTile">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>171</width>
|
||||
<height>355</height>
|
||||
<width>241</width>
|
||||
<height>406</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -185,6 +185,12 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetInfo</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetInfo.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::Swatch</class>
|
||||
<extends>QWidget</extends>
|
||||
|
|
|
@ -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
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -9,6 +9,16 @@
|
|||
|
||||
#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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -6,12 +6,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <QTimer>
|
||||
#include <QTransform>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mgba/core/cache-set.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct mMapCacheEntry;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
@ -22,8 +25,6 @@ Q_OBJECT
|
|||
public:
|
||||
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:
|
||||
void updateTiles();
|
||||
void updateTiles(bool force);
|
||||
|
@ -40,9 +41,43 @@ protected:
|
|||
void showEvent(QShowEvent*) override;
|
||||
|
||||
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:
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ set(SOURCE_FILES
|
|||
AbstractUpdater.cpp
|
||||
Action.cpp
|
||||
ActionMapper.cpp
|
||||
AssetInfo.cpp
|
||||
AssetTile.cpp
|
||||
AssetView.cpp
|
||||
AudioProcessor.cpp
|
||||
|
@ -72,6 +73,7 @@ set(SOURCE_FILES
|
|||
Display.cpp
|
||||
DisplayGL.cpp
|
||||
DisplayQt.cpp
|
||||
FrameView.cpp
|
||||
GBAApp.cpp
|
||||
GIFView.cpp
|
||||
GamepadAxisEvent.cpp
|
||||
|
@ -124,6 +126,7 @@ set(UI_FILES
|
|||
BattleChipView.ui
|
||||
CheatsView.ui
|
||||
DebuggerConsole.ui
|
||||
FrameView.ui
|
||||
GIFView.ui
|
||||
IOViewer.ui
|
||||
LoadSaveState.ui
|
||||
|
|
|
@ -34,6 +34,14 @@ ColorPicker& ColorPicker::operator=(const ColorPicker& other) {
|
|||
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) {
|
||||
if (event->type() != QEvent::MouseButtonRelease) {
|
||||
return false;
|
||||
|
|
|
@ -24,6 +24,9 @@ public:
|
|||
signals:
|
||||
void colorChanged(const QColor&);
|
||||
|
||||
public slots:
|
||||
void setColor(const QColor&);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
|
|
|
@ -82,8 +82,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
|||
controller->m_resetActions.clear();
|
||||
|
||||
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");
|
||||
|
@ -203,13 +202,33 @@ CoreController::~CoreController() {
|
|||
}
|
||||
|
||||
const color_t* CoreController::drawContext() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_hwaccel) {
|
||||
return nullptr;
|
||||
}
|
||||
QMutexLocker locker(&m_bufferMutex);
|
||||
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() {
|
||||
return mCoreThreadIsPaused(&m_threadContext);
|
||||
}
|
||||
|
@ -342,15 +361,12 @@ void CoreController::setLogger(LogController* logger) {
|
|||
|
||||
void CoreController::start() {
|
||||
if (!m_hwaccel) {
|
||||
QSize size(1024, 2048);
|
||||
m_buffers[0].resize(size.width() * size.height() * sizeof(color_t));
|
||||
m_buffers[1].resize(size.width() * size.height() * sizeof(color_t));
|
||||
m_buffers[0].fill(0xFF);
|
||||
m_buffers[1].fill(0xFF);
|
||||
m_activeBuffer = &m_buffers[0];
|
||||
m_completeBuffer = m_buffers[0];
|
||||
QSize size(256, 384);
|
||||
m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
|
||||
m_activeBuffer.fill(0xFF);
|
||||
m_completeBuffer = m_activeBuffer;
|
||||
|
||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());
|
||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width());
|
||||
}
|
||||
|
||||
if (!m_patched) {
|
||||
|
@ -386,8 +402,7 @@ void CoreController::setPaused(bool paused) {
|
|||
return;
|
||||
}
|
||||
if (paused) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_frameActions.append([this]() {
|
||||
addFrameAction([this]() {
|
||||
mCoreThreadPauseFromThread(&m_threadContext);
|
||||
});
|
||||
} else {
|
||||
|
@ -396,13 +411,17 @@ void CoreController::setPaused(bool paused) {
|
|||
}
|
||||
|
||||
void CoreController::frameAdvance() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_frameActions.append([this]() {
|
||||
addFrameAction([this]() {
|
||||
mCoreThreadPauseFromThread(&m_threadContext);
|
||||
});
|
||||
setPaused(false);
|
||||
}
|
||||
|
||||
void CoreController::addFrameAction(std::function<void ()> action) {
|
||||
QMutexLocker locker(&m_actionMutex);
|
||||
m_frameActions.append(action);
|
||||
}
|
||||
|
||||
void CoreController::setSync(bool sync) {
|
||||
if (sync) {
|
||||
m_threadContext.impl->sync.audioWait = m_audioSync;
|
||||
|
@ -777,26 +796,39 @@ void CoreController::clearOverride() {
|
|||
m_override.reset();
|
||||
}
|
||||
|
||||
void CoreController::startVideoLog(const QString& path) {
|
||||
void CoreController::startVideoLog(const QString& path, bool compression) {
|
||||
if (m_vl) {
|
||||
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);
|
||||
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);
|
||||
mVideoLogContextSetCompression(m_vl, compression);
|
||||
mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
|
||||
}
|
||||
|
||||
void CoreController::endVideoLog() {
|
||||
void CoreController::endVideoLog(bool closeVf) {
|
||||
if (!m_vl) {
|
||||
return;
|
||||
}
|
||||
|
||||
Interrupter interrupter(this);
|
||||
mVideoLogContextDestroy(m_threadContext.core, m_vl);
|
||||
if (m_vlVf) {
|
||||
if (m_vlVf && closeVf) {
|
||||
m_vlVf->close(m_vlVf);
|
||||
m_vlVf = nullptr;
|
||||
}
|
||||
|
@ -819,23 +851,20 @@ void CoreController::updateKeys() {
|
|||
}
|
||||
|
||||
void CoreController::finishFrame() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
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?
|
||||
m_activeBuffer = &m_buffers[0];
|
||||
if (m_activeBuffer == m_completeBuffer) {
|
||||
m_activeBuffer = &m_buffers[1];
|
||||
QMutexLocker locker(&m_bufferMutex);
|
||||
memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), 256 * height * BYTES_PER_PIXEL);
|
||||
}
|
||||
// Copy contents to avoid issues when doing frameskip
|
||||
memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size());
|
||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), screenDimensions().width());
|
||||
}
|
||||
for (auto& action : m_frameActions) {
|
||||
|
||||
QMutexLocker locker(&m_actionMutex);
|
||||
QList<std::function<void ()>> frameActions(m_frameActions);
|
||||
m_frameActions.clear();
|
||||
for (auto& action : frameActions) {
|
||||
action();
|
||||
}
|
||||
m_frameActions.clear();
|
||||
updateKeys();
|
||||
|
||||
QMetaObject::invokeMethod(this, "frameAvailable");
|
||||
|
|
|
@ -67,6 +67,7 @@ public:
|
|||
mCoreThread* thread() { return &m_threadContext; }
|
||||
|
||||
const color_t* drawContext();
|
||||
QImage getPixels();
|
||||
|
||||
bool isPaused();
|
||||
bool hasStarted();
|
||||
|
@ -102,6 +103,8 @@ public:
|
|||
bool audioSync() const { return m_audioSync; }
|
||||
bool videoSync() const { return m_videoSync; }
|
||||
|
||||
void addFrameAction(std::function<void ()> callback);
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
@ -157,8 +160,9 @@ public slots:
|
|||
|
||||
void clearOverride();
|
||||
|
||||
void startVideoLog(const QString& path);
|
||||
void endVideoLog();
|
||||
void startVideoLog(const QString& path, bool compression = true);
|
||||
void startVideoLog(VFile* vf, bool compression = true);
|
||||
void endVideoLog(bool closeVf = true);
|
||||
|
||||
void setFramebufferHandle(int fb);
|
||||
|
||||
|
@ -193,8 +197,7 @@ private:
|
|||
|
||||
bool m_patched = false;
|
||||
|
||||
QByteArray m_buffers[2];
|
||||
QByteArray* m_activeBuffer;
|
||||
QByteArray m_activeBuffer;
|
||||
QByteArray m_completeBuffer;
|
||||
bool m_hwaccel = false;
|
||||
|
||||
|
@ -203,7 +206,8 @@ private:
|
|||
|
||||
QList<std::function<void()>> m_resetActions;
|
||||
QList<std::function<void()>> m_frameActions;
|
||||
QMutex m_mutex;
|
||||
QMutex m_actionMutex{QMutex::Recursive};
|
||||
QMutex m_bufferMutex;
|
||||
|
||||
int m_activeKeys = 0;
|
||||
bool m_autofire[32] = {};
|
||||
|
|
|
@ -114,7 +114,6 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
|
||||
#endif
|
||||
resizePainter();
|
||||
connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext);
|
||||
}
|
||||
|
||||
void DisplayGL::stopDrawing() {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)};
|
||||
};
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -18,6 +18,7 @@
|
|||
#include <mgba/internal/gba/video.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/memory.h>
|
||||
#endif
|
||||
|
||||
|
@ -42,6 +43,12 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_boundary = 2048;
|
||||
m_addressBase = BASE_VRAM;
|
||||
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;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
@ -49,6 +56,9 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_boundary = 1024;
|
||||
m_addressBase = GB_BASE_VRAM;
|
||||
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;
|
||||
#endif
|
||||
default:
|
||||
|
@ -143,39 +153,82 @@ void MapView::updateTilesGBA(bool force) {
|
|||
{
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
|
||||
int width = mBitmapCacheSystemInfoGetWidth(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);
|
||||
uchar* bgBits = m_rawMap.bits();
|
||||
for (int j = 0; j < height; ++j) {
|
||||
mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
|
||||
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
|
||||
}
|
||||
m_rawMap = m_rawMap.rgbSwapped();
|
||||
} else {
|
||||
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
|
||||
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
||||
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
|
||||
m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
|
||||
uchar* bgBits = m_rawMap.bits();
|
||||
for (int j = 0; j < tilesH; ++j) {
|
||||
for (int i = 0; i < tilesW; ++i) {
|
||||
mMapCacheCleanTile(mapCache, m_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);
|
||||
m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
|
||||
.arg(m_addressWidth == 8 ? "0x" : "")
|
||||
.arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
|
||||
m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
|
||||
.arg(m_addressWidth == 8 ? "0x" : "")
|
||||
.arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
|
||||
m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
|
||||
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));
|
||||
if (m_ui.magnification->value() > 1) {
|
||||
map = map.scaled(map.size() * m_ui.magnification->value());
|
||||
|
|
|
@ -6,18 +6,80 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>641</width>
|
||||
<height>489</height>
|
||||
<width>941</width>
|
||||
<height>617</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Maps</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="bgLayout"/>
|
||||
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QGBA::AssetTile" name="tile"/>
|
||||
</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">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
|
@ -30,8 +92,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>457</width>
|
||||
<height>463</height>
|
||||
<width>613</width>
|
||||
<height>601</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -71,62 +133,15 @@
|
|||
</widget>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetInfo</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetInfo.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
|
|
|
@ -19,9 +19,7 @@
|
|||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#endif
|
||||
#include <mgba-util/png-io.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
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]() {
|
||||
updateTiles(true);
|
||||
});
|
||||
#ifdef USE_PNG
|
||||
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
|
||||
#else
|
||||
m_ui.exportButton->setVisible(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
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 GBAObj* obj = &gba->video.oam.obj[m_objId];
|
||||
|
||||
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);
|
||||
m_ui.tiles->setTileCount(width * height / 64);
|
||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
||||
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
||||
unsigned tileBase = tile;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
ObjInfo newInfo;
|
||||
lookupObj(m_objId, &newInfo);
|
||||
|
||||
m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
|
||||
m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
unsigned tileBase = newInfo.tile;
|
||||
unsigned tile = newInfo.tile;
|
||||
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||
m_ui.palette->setText("256-color");
|
||||
paletteSet = 3;
|
||||
m_ui.tile->setBoundary(1024, 1, 3);
|
||||
m_ui.tile->setPalette(0);
|
||||
m_boundary = 1024;
|
||||
palette = 0;
|
||||
tile /= 2;
|
||||
bits = 8;
|
||||
tileBase *= 2;
|
||||
} else {
|
||||
m_ui.palette->setText(QString::number(palette));
|
||||
paletteSet = 2;
|
||||
m_ui.palette->setText(QString::number(newInfo.paletteId));
|
||||
m_ui.tile->setBoundary(2048, 0, 2);
|
||||
m_ui.tile->setPalette(palette);
|
||||
m_boundary = 2048;
|
||||
bits = 4;
|
||||
m_ui.tile->setPalette(newInfo.paletteId);
|
||||
}
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
width / 8,
|
||||
height / 8,
|
||||
width / 8,
|
||||
palette,
|
||||
paletteSet,
|
||||
bits
|
||||
};
|
||||
if (newInfo != m_objInfo) {
|
||||
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_tileOffset = tile;
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet);
|
||||
m_tileOffset = newInfo.tile;
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
|
||||
|
||||
int i = 0;
|
||||
for (int y = 0; y < height / 8; ++y) {
|
||||
for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) {
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette);
|
||||
for (int y = 0; y < newInfo.height; ++y) {
|
||||
for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
|
||||
if (data) {
|
||||
m_ui.tiles->setTile(i, data);
|
||||
} 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;
|
||||
tileBase += newInfo.stride - width / 8;
|
||||
tile += newInfo.stride - newInfo.width;
|
||||
tileBase += newInfo.stride - newInfo.width;
|
||||
}
|
||||
|
||||
m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
|
||||
m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
|
||||
m_ui.w->setText(QString::number(width));
|
||||
m_ui.h->setText(QString::number(height));
|
||||
m_ui.x->setText(QString::number(newInfo.x));
|
||||
m_ui.y->setText(QString::number(newInfo.y));
|
||||
m_ui.w->setText(QString::number(newInfo.width * 8));
|
||||
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.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
|
||||
m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
|
||||
m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
|
||||
m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
|
||||
m_ui.priority->setText(QString::number(newInfo.priority));
|
||||
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||
m_ui.enabled->setChecked(newInfo.enabled);
|
||||
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(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 GBObj* obj = &gb->video.oam.obj[m_objId];
|
||||
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||
unsigned width = 8;
|
||||
unsigned height = 8;
|
||||
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
|
||||
if (GBRegisterLCDCIsObjSize(lcdc)) {
|
||||
height = 16;
|
||||
}
|
||||
unsigned tile = obj->tile;
|
||||
m_ui.tiles->setTileCount(width * height / 64);
|
||||
m_ui.tile->setBoundary(1024, 0, 0);
|
||||
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;
|
||||
lookupObj(m_objId, &newInfo);
|
||||
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||
unsigned tile = newInfo.tile;
|
||||
m_ui.tiles->setTileCount(newInfo.height);
|
||||
m_ui.tile->setBoundary(1024, 0, 0);
|
||||
m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
|
||||
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
1,
|
||||
height / 8,
|
||||
1,
|
||||
palette,
|
||||
0,
|
||||
2
|
||||
};
|
||||
if (newInfo != m_objInfo) {
|
||||
force = true;
|
||||
}
|
||||
|
@ -223,27 +172,27 @@ void ObjView::updateTilesGB(bool force) {
|
|||
m_boundary = 1024;
|
||||
|
||||
int i = 0;
|
||||
m_ui.tile->setPalette(palette);
|
||||
for (int y = 0; y < height / 8; ++y, ++i) {
|
||||
m_ui.tile->setPalette(newInfo.paletteId);
|
||||
for (int y = 0; y < newInfo.height; ++y, ++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) {
|
||||
m_ui.tiles->setTile(i, data);
|
||||
} 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.y->setText(QString::number(obj->y));
|
||||
m_ui.w->setText(QString::number(width));
|
||||
m_ui.h->setText(QString::number(height));
|
||||
m_ui.x->setText(QString::number(newInfo.x));
|
||||
m_ui.y->setText(QString::number(newInfo.y));
|
||||
m_ui.w->setText(QString::number(8));
|
||||
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.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
|
||||
m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
|
||||
m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
|
||||
m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
|
||||
m_ui.priority->setText(QString::number(newInfo.priority));
|
||||
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||
m_ui.enabled->setChecked(newInfo.enabled);
|
||||
m_ui.doubleSize->setChecked(false);
|
||||
m_ui.mosaic->setChecked(false);
|
||||
m_ui.transform->setText(tr("N/A"));
|
||||
|
@ -251,51 +200,10 @@ void ObjView::updateTilesGB(bool force) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
void ObjView::exportObj() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
|
||||
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);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
|
||||
|
||||
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;
|
||||
QImage obj = compositeObj(m_objInfo);
|
||||
obj.save(filename, "PNG");
|
||||
}
|
||||
|
|
|
@ -21,10 +21,8 @@ Q_OBJECT
|
|||
public:
|
||||
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
|
||||
#ifdef USE_PNG
|
||||
public slots:
|
||||
void exportObj();
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void selectObj(int);
|
||||
|
@ -43,17 +41,7 @@ private:
|
|||
std::shared_ptr<CoreController> m_controller;
|
||||
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
|
||||
int m_objId = 0;
|
||||
struct ObjInfo {
|
||||
unsigned tile;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned stride;
|
||||
unsigned paletteId;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
|
||||
bool operator!=(const ObjInfo&);
|
||||
} m_objInfo = {};
|
||||
ObjInfo m_objInfo = {};
|
||||
|
||||
int m_tileOffset;
|
||||
int m_boundary;
|
||||
|
|
|
@ -363,7 +363,6 @@ void SettingsView::updateConfig() {
|
|||
saveSetting("useCgbColors", m_ui.useCgbColors);
|
||||
saveSetting("useBios", m_ui.useBios);
|
||||
saveSetting("skipBios", m_ui.skipBios);
|
||||
saveSetting("audioBuffers", m_ui.audioBufferSize);
|
||||
saveSetting("sampleRate", m_ui.sampleRate);
|
||||
saveSetting("videoSync", m_ui.videoSync);
|
||||
saveSetting("audioSync", m_ui.audioSync);
|
||||
|
@ -401,6 +400,11 @@ void SettingsView::updateConfig() {
|
|||
saveSetting("useDiscordPresence", m_ui.useDiscordPresence);
|
||||
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()) {
|
||||
saveSetting("fastForwardRatio", "-1");
|
||||
} else {
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "DebuggerConsoleController.h"
|
||||
#include "Display.h"
|
||||
#include "CoreController.h"
|
||||
#include "FrameView.h"
|
||||
#include "GBAApp.h"
|
||||
#include "GDBController.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::frameAvailable, m_display.get(), &Display::framePosted);
|
||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||
|
||||
attachWidget(m_display.get());
|
||||
m_display->startDrawing(m_controller);
|
||||
|
@ -1527,7 +1529,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_overrideView->recheck();
|
||||
}, "tools");
|
||||
|
||||
m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() {
|
||||
m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() {
|
||||
if (!m_sensorView) {
|
||||
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
|
||||
if (m_controller) {
|
||||
|
@ -1557,6 +1559,26 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
|
||||
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "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("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
|
||||
|
||||
|
@ -1744,11 +1766,8 @@ void Window::focusCheck() {
|
|||
}
|
||||
|
||||
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;
|
||||
pixmap.convertFromImage(currentImage);
|
||||
pixmap.convertFromImage(m_controller->getPixels());
|
||||
m_screenWidget->setPixmap(pixmap);
|
||||
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::frameAvailable, m_display.get(), &Display::framePosted);
|
||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||
|
||||
connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver);
|
||||
connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);
|
||||
|
|
|
@ -32,6 +32,7 @@ class CoreController;
|
|||
class CoreManager;
|
||||
class DebuggerConsoleController;
|
||||
class Display;
|
||||
class FrameView;
|
||||
class GDBController;
|
||||
class GIFView;
|
||||
class LibraryController;
|
||||
|
@ -213,6 +214,7 @@ private:
|
|||
|
||||
std::unique_ptr<OverrideView> m_overrideView;
|
||||
std::unique_ptr<SensorView> m_sensorView;
|
||||
FrameView* m_frameView = nullptr;
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
VideoView* m_videoView = nullptr;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "feature/gui/gui-runner.h"
|
||||
#include <mgba/core/blip_buf.h>
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/internal/gb/video.h>
|
||||
#include <mgba/internal/gba/audio.h>
|
||||
#include <mgba/internal/gba/input.h>
|
||||
#include <mgba-util/gui.h>
|
||||
|
@ -99,6 +100,9 @@ static u8 vmode;
|
|||
static u32 vwidth;
|
||||
static u32 vheight;
|
||||
static bool interframeBlending = false;
|
||||
static bool sgbCrop = false;
|
||||
static bool useLightSensor = true;
|
||||
static struct mGUIRunnerLux lightSensor;
|
||||
|
||||
static enum ScreenMode {
|
||||
SM_PA,
|
||||
|
@ -268,6 +272,10 @@ static void _setup(struct mGUIRunner* runner) {
|
|||
runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
|
||||
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;
|
||||
if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
|
||||
screenMode = mode;
|
||||
|
@ -292,6 +300,22 @@ static void _gameLoaded(struct mGUIRunner* runner) {
|
|||
if (mCoreConfigGetIntValue(&runner->config, "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.down = 0;
|
||||
|
@ -313,8 +337,14 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height,
|
|||
|
||||
glUseProgram(program);
|
||||
glBindVertexArray(vao);
|
||||
float aspectX = width / (float) vwidth;
|
||||
float aspectY = height / (float) vheight;
|
||||
float inwidth = width;
|
||||
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;
|
||||
switch (screenMode) {
|
||||
case SM_PA:
|
||||
|
@ -340,6 +370,11 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height,
|
|||
break;
|
||||
}
|
||||
|
||||
if (screenMode != SM_SF) {
|
||||
aspectX = width / (float) vwidth;
|
||||
aspectY = height / (float) vheight;
|
||||
}
|
||||
|
||||
aspectX *= max;
|
||||
aspectY *= max;
|
||||
|
||||
|
@ -543,6 +578,18 @@ int32_t _readGyroZ(struct mRotationSource* source) {
|
|||
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) {
|
||||
u32 charge;
|
||||
int state = 0;
|
||||
|
@ -690,6 +737,9 @@ int main(int argc, char* argv[]) {
|
|||
rotation.readTiltY = _readTiltY;
|
||||
rotation.readGyroZ = _readGyroZ;
|
||||
|
||||
lightSensor.d.readLuminance = _lightSensorRead;
|
||||
lightSensor.d.sample = _lightSensorSample;
|
||||
|
||||
stream.videoDimensionsChanged = NULL;
|
||||
stream.postVideoFrame = NULL;
|
||||
stream.postAudioFrame = NULL;
|
||||
|
@ -707,6 +757,9 @@ int main(int argc, char* argv[]) {
|
|||
audoutBuffer[i].data_offset = 0;
|
||||
}
|
||||
|
||||
bool illuminanceAvailable = false;
|
||||
appletIsIlluminanceAvailable(&illuminanceAvailable);
|
||||
|
||||
struct mGUIRunner runner = {
|
||||
.params = {
|
||||
1280, 720,
|
||||
|
@ -829,8 +882,19 @@ int main(int argc, char* argv[]) {
|
|||
},
|
||||
.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,
|
||||
.teardown = NULL,
|
||||
.gameLoaded = _gameLoaded,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <mgba/core/blip_buf.h>
|
||||
#include <mgba/core/core.h>
|
||||
#include "feature/gui/gui-runner.h"
|
||||
#include <mgba/internal/gb/video.h>
|
||||
#include <mgba/internal/gba/audio.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/input.h>
|
||||
|
@ -113,6 +114,7 @@ static uint16_t* rescaleTexmem;
|
|||
static GXTexObj rescaleTex;
|
||||
static uint16_t* interframeTexmem;
|
||||
static GXTexObj interframeTex;
|
||||
static bool sgbCrop = false;
|
||||
static int32_t tiltX;
|
||||
static int32_t tiltY;
|
||||
static int32_t gyroZ;
|
||||
|
@ -862,6 +864,9 @@ void _unpaused(struct mGUIRunner* runner) {
|
|||
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||
interframeBlending = fakeBool;
|
||||
}
|
||||
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||
sgbCrop = fakeBool;
|
||||
}
|
||||
|
||||
float 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);
|
||||
int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust);
|
||||
if (screenMode == SM_PA) {
|
||||
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) {
|
||||
scaleFactor = vfactor;
|
||||
} else {
|
||||
scaleFactor = hfactor;
|
||||
}
|
||||
|
||||
if (screenMode == SM_PA) {
|
||||
vertWidth *= scaleFactor;
|
||||
vertHeight *= scaleFactor;
|
||||
}
|
||||
|
||||
if (screenMode == SM_PA) {
|
||||
_reproj(corew * scaleFactor, coreh * scaleFactor);
|
||||
} else {
|
||||
_reproj2(corew, coreh);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
struct VDir* dir = VDirOpen(currentPath);
|
||||
|
@ -144,6 +144,9 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
|
|||
free((char*) testItem->title);
|
||||
GUIMenuItemListShift(currentFiles, item, 1);
|
||||
} else {
|
||||
if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) {
|
||||
params->fileIndex = item;
|
||||
}
|
||||
++item;
|
||||
}
|
||||
}
|
||||
|
@ -152,14 +155,14 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
|
|||
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 = {
|
||||
.title = "Select file",
|
||||
.subtitle = params->currentPath,
|
||||
.index = params->fileIndex,
|
||||
};
|
||||
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) {
|
||||
struct GUIMenuItem* item;
|
||||
|
@ -174,7 +177,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
|
|||
continue;
|
||||
}
|
||||
_upDirectory(params->currentPath);
|
||||
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) {
|
||||
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
@ -187,7 +190,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
|
|||
|
||||
struct GUIMenuItemList newFiles;
|
||||
GUIMenuItemListInit(&newFiles, 0);
|
||||
if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents)) {
|
||||
if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
|
||||
_cleanFiles(&newFiles);
|
||||
GUIMenuItemListDeinit(&newFiles);
|
||||
_cleanFiles(&menu.items);
|
||||
|
@ -208,7 +211,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
|
|||
break;
|
||||
}
|
||||
_upDirectory(params->currentPath);
|
||||
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) {
|
||||
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
|
||||
break;
|
||||
}
|
||||
params->fileIndex = 0;
|
||||
|
|