Merge branch 'master' (early part) into medusa
13
CHANGES
|
@ -27,10 +27,14 @@ Features:
|
||||||
- GB: Expose platform information to CLI debugger
|
- GB: Expose platform information to CLI debugger
|
||||||
- Support Discord Rich Presence
|
- Support Discord Rich Presence
|
||||||
- Debugger: Add tracing to file
|
- Debugger: Add tracing to file
|
||||||
- Map viewer supports bitmapped GBA modes
|
- Enhanced map viewer, supporting bitmapped GBA modes and more displayed info
|
||||||
- OpenGL renderer with high-resolution upscaling support
|
- OpenGL renderer with high-resolution upscaling support
|
||||||
- Experimental high level "XQ" audio for most GBA games
|
- Experimental high level "XQ" audio for most GBA games
|
||||||
- Interframe blending for games that use flicker effects
|
- Interframe blending for games that use flicker effects
|
||||||
|
- Frame inspector for dissecting and debugging rendering
|
||||||
|
- Switch: Option to use built-in brightness sensor for Boktai
|
||||||
|
- Ports: Ability to enable or disable all SGB features (closes mgba.io/i/1205)
|
||||||
|
- Ports: Ability to crop SGB borders off screen (closes mgba.io/i/1204)
|
||||||
Emulation fixes:
|
Emulation fixes:
|
||||||
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
||||||
- GBA: Reset now reloads multiboot ROMs
|
- GBA: Reset now reloads multiboot ROMs
|
||||||
|
@ -38,11 +42,16 @@ Emulation fixes:
|
||||||
- GB Video: Delay LYC STAT check (fixes mgba.io/i/1331)
|
- GB Video: Delay LYC STAT check (fixes mgba.io/i/1331)
|
||||||
- GB Video: Fix window being enabled mid-scanline (fixes mgba.io/i/1328)
|
- GB Video: Fix window being enabled mid-scanline (fixes mgba.io/i/1328)
|
||||||
- GB I/O: Filter IE top bits properly (fixes mgba.io/i/1329)
|
- GB I/O: Filter IE top bits properly (fixes mgba.io/i/1329)
|
||||||
|
- GBA Video: Fix wrapped sprite mosaic clamping (fixes mgba.io/i/1432)
|
||||||
|
- GBA Memory: Fix STM to VRAM (fixes mgba.io/i/1430)
|
||||||
Other fixes:
|
Other fixes:
|
||||||
- Qt: Fix some Qt display driver race conditions
|
- Qt: Fix some Qt display driver race conditions
|
||||||
- Core: Improved lockstep driver reliability (Le Hoang Quyen)
|
- Core: Improved lockstep driver reliability (Le Hoang Quyen)
|
||||||
- Switch: Fix threading-related crash on second launch
|
- Switch: Fix threading-related crash on second launch
|
||||||
- Qt: Fix FPS target maxing out at 59.727 (fixes mgba.io/i/1421)
|
- Qt: Fix FPS target maxing out at 59.727 (fixes mgba.io/i/1421)
|
||||||
|
- Core: Fix crashes if core directories aren't set
|
||||||
|
- Qt: Cap audio buffer size to 8192 (fixes mgba.io/i/1433)
|
||||||
|
- GB Serialize: Fix loading non-BIOS state from BIOS (fixes mgba.io/i/1280)
|
||||||
Misc:
|
Misc:
|
||||||
- GBA Savedata: EEPROM performance fixes
|
- GBA Savedata: EEPROM performance fixes
|
||||||
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
|
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
|
||||||
|
@ -64,6 +73,8 @@ Misc:
|
||||||
- Qt: Improve sync code
|
- Qt: Improve sync code
|
||||||
- Switch: Dynamic display resizing
|
- Switch: Dynamic display resizing
|
||||||
- Qt: Make mute menu option also toggle fast-forward mute (fixes mgba.io/i/1424)
|
- Qt: Make mute menu option also toggle fast-forward mute (fixes mgba.io/i/1424)
|
||||||
|
- Vita: L2/R2 and L3/R3 can now be mapped on PSTV (fixes mgba.io/i/1292)
|
||||||
|
- mGUI: Remember name and position of last loaded game
|
||||||
|
|
||||||
0.7.2: (2019-05-25)
|
0.7.2: (2019-05-25)
|
||||||
Emulation fixes:
|
Emulation fixes:
|
||||||
|
|
|
@ -1029,7 +1029,7 @@ endif()
|
||||||
if(BUILD_LIBRETRO)
|
if(BUILD_LIBRETRO)
|
||||||
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
|
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
||||||
add_dependencies(${BINARY_NAME} version-info)
|
add_dependencies(${BINARY_NAME}_libretro version-info)
|
||||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2")
|
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2")
|
||||||
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
|
|
|
@ -142,6 +142,7 @@ This will produce a `build-win32` directory with the build products. Replace `mg
|
||||||
- mgba/ubuntu:xenial
|
- mgba/ubuntu:xenial
|
||||||
- mgba/ubuntu:bionic
|
- mgba/ubuntu:bionic
|
||||||
- mgba/ubuntu:cosmic
|
- mgba/ubuntu:cosmic
|
||||||
|
- mgba/ubuntu:disco
|
||||||
- mgba/vita
|
- mgba/vita
|
||||||
- mgba/wii
|
- mgba/wii
|
||||||
- mgba/windows:w32
|
- mgba/windows:w32
|
||||||
|
|
|
@ -124,6 +124,7 @@ Dieser Befehl erzeugt ein Verzeichnis `build-win32` mit den erzeugten Programmda
|
||||||
- mgba/ubuntu:xenial
|
- mgba/ubuntu:xenial
|
||||||
- mgba/ubuntu:bionic
|
- mgba/ubuntu:bionic
|
||||||
- mgba/ubuntu:cosmic
|
- mgba/ubuntu:cosmic
|
||||||
|
- mgba/ubuntu:disco
|
||||||
- mgba/vita
|
- mgba/vita
|
||||||
- mgba/wii
|
- mgba/wii
|
||||||
- mgba/windows:w32
|
- mgba/windows:w32
|
||||||
|
|
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;
|
struct VFile;
|
||||||
|
|
||||||
bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*));
|
bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect);
|
||||||
|
|
||||||
CXX_GUARD_END
|
CXX_GUARD_END
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,11 @@ enum mVideoLoggerEvent {
|
||||||
LOGGER_EVENT_GET_PIXELS,
|
LOGGER_EVENT_GET_PIXELS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum mVideoLoggerInjectionPoint {
|
||||||
|
LOGGER_INJECTION_IMMEDIATE = 0,
|
||||||
|
LOGGER_INJECTION_FIRST_SCANLINE,
|
||||||
|
};
|
||||||
|
|
||||||
struct mVideoLoggerDirtyInfo {
|
struct mVideoLoggerDirtyInfo {
|
||||||
enum mVideoLoggerDirtyType type;
|
enum mVideoLoggerDirtyType type;
|
||||||
uint32_t address;
|
uint32_t address;
|
||||||
|
@ -97,6 +102,7 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);
|
||||||
void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger);
|
void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger);
|
||||||
|
|
||||||
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block);
|
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block);
|
||||||
|
bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger);
|
||||||
|
|
||||||
struct mVideoLogContext;
|
struct mVideoLogContext;
|
||||||
void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId);
|
void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId);
|
||||||
|
@ -104,6 +110,7 @@ void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogCont
|
||||||
struct mCore;
|
struct mCore;
|
||||||
struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core);
|
struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core);
|
||||||
|
|
||||||
|
void mVideoLogContextSetCompression(struct mVideoLogContext*, bool enable);
|
||||||
void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*);
|
void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*);
|
||||||
void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core);
|
void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core);
|
||||||
|
|
||||||
|
@ -115,6 +122,12 @@ void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size);
|
||||||
|
|
||||||
int mVideoLoggerAddChannel(struct mVideoLogContext*);
|
int mVideoLoggerAddChannel(struct mVideoLogContext*);
|
||||||
|
|
||||||
|
void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint);
|
||||||
|
void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask);
|
||||||
|
void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value);
|
||||||
|
void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value);
|
||||||
|
void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value);
|
||||||
|
|
||||||
struct mCore* mVideoLogCoreFind(struct VFile*);
|
struct mCore* mVideoLogCoreFind(struct VFile*);
|
||||||
|
|
||||||
CXX_GUARD_END
|
CXX_GUARD_END
|
||||||
|
|
|
@ -150,6 +150,7 @@ void GBDestroy(struct GB* gb);
|
||||||
|
|
||||||
void GBReset(struct LR35902Core* cpu);
|
void GBReset(struct LR35902Core* cpu);
|
||||||
void GBSkipBIOS(struct GB* gb);
|
void GBSkipBIOS(struct GB* gb);
|
||||||
|
void GBMapBIOS(struct GB* gb);
|
||||||
void GBUnmapBIOS(struct GB* gb);
|
void GBUnmapBIOS(struct GB* gb);
|
||||||
void GBDetectModel(struct GB* gb);
|
void GBDetectModel(struct GB* gb);
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ DECL_BITFIELD(RTCStatus2, uint8_t);
|
||||||
DECL_BITS(RTCStatus2, INT1, 0, 4);
|
DECL_BITS(RTCStatus2, INT1, 0, 4);
|
||||||
DECL_BIT(RTCStatus2, INT2, 6);
|
DECL_BIT(RTCStatus2, INT2, 6);
|
||||||
|
|
||||||
|
#ifndef PYCPARSE
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct GBARTC {
|
struct GBARTC {
|
||||||
int32_t bytesRemaining;
|
int32_t bytesRemaining;
|
||||||
|
@ -90,6 +91,9 @@ struct GBARTC {
|
||||||
uint8_t time[7];
|
uint8_t time[7];
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
#else
|
||||||
|
struct GBATRC;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct GBAGBPKeyCallback {
|
struct GBAGBPKeyCallback {
|
||||||
struct mKeyCallback d;
|
struct mKeyCallback d;
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct GBAVideoRendererSprite {
|
||||||
struct GBAObj obj;
|
struct GBAObj obj;
|
||||||
int16_t y;
|
int16_t y;
|
||||||
int16_t endY;
|
int16_t endY;
|
||||||
|
int8_t index;
|
||||||
};
|
};
|
||||||
|
|
||||||
int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort);
|
int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort);
|
||||||
|
|
|
@ -45,6 +45,7 @@ struct GBAVideoSoftwareBackground {
|
||||||
color_t* variantPalette;
|
color_t* variantPalette;
|
||||||
int32_t offsetX;
|
int32_t offsetX;
|
||||||
int32_t offsetY;
|
int32_t offsetY;
|
||||||
|
bool highlight;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -117,6 +118,8 @@ struct GBAVideoSoftwareRenderer {
|
||||||
color_t variantPalette[512];
|
color_t variantPalette[512];
|
||||||
color_t* objExtPalette;
|
color_t* objExtPalette;
|
||||||
color_t* objExtVariantPalette;
|
color_t* objExtVariantPalette;
|
||||||
|
color_t highlightPalette[512];
|
||||||
|
color_t highlightVariantPalette[512];
|
||||||
|
|
||||||
uint16_t blda;
|
uint16_t blda;
|
||||||
uint16_t bldb;
|
uint16_t bldb;
|
||||||
|
@ -165,6 +168,8 @@ struct GBAVideoSoftwareRenderer {
|
||||||
|
|
||||||
int masterBright;
|
int masterBright;
|
||||||
int masterBrightY;
|
int masterBrightY;
|
||||||
|
|
||||||
|
uint8_t lastHighlightAmount;
|
||||||
};
|
};
|
||||||
|
|
||||||
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer);
|
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer);
|
||||||
|
|
|
@ -33,6 +33,23 @@ enum {
|
||||||
JOYSTAT_RECV_BIT = 2,
|
JOYSTAT_RECV_BIT = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DECL_BITFIELD(GBASIONormal, uint16_t);
|
||||||
|
DECL_BIT(GBASIONormal, Sc, 0);
|
||||||
|
DECL_BIT(GBASIONormal, InternalSc, 1);
|
||||||
|
DECL_BIT(GBASIONormal, Si, 2);
|
||||||
|
DECL_BIT(GBASIONormal, IdleSo, 3);
|
||||||
|
DECL_BIT(GBASIONormal, Start, 7);
|
||||||
|
DECL_BIT(GBASIONormal, Length, 12);
|
||||||
|
DECL_BIT(GBASIONormal, Irq, 14);
|
||||||
|
DECL_BITFIELD(GBASIOMultiplayer, uint16_t);
|
||||||
|
DECL_BITS(GBASIOMultiplayer, Baud, 0, 2);
|
||||||
|
DECL_BIT(GBASIOMultiplayer, Slave, 2);
|
||||||
|
DECL_BIT(GBASIOMultiplayer, Ready, 3);
|
||||||
|
DECL_BITS(GBASIOMultiplayer, Id, 4, 2);
|
||||||
|
DECL_BIT(GBASIOMultiplayer, Error, 6);
|
||||||
|
DECL_BIT(GBASIOMultiplayer, Busy, 8);
|
||||||
|
DECL_BIT(GBASIOMultiplayer, Irq, 14);
|
||||||
|
|
||||||
struct GBASIODriverSet {
|
struct GBASIODriverSet {
|
||||||
struct GBASIODriver* normal;
|
struct GBASIODriver* normal;
|
||||||
struct GBASIODriver* multiplayer;
|
struct GBASIODriver* multiplayer;
|
||||||
|
@ -47,36 +64,7 @@ struct GBASIO {
|
||||||
struct GBASIODriver* activeDriver;
|
struct GBASIODriver* activeDriver;
|
||||||
|
|
||||||
uint16_t rcnt;
|
uint16_t rcnt;
|
||||||
// TODO: Convert to bitfields
|
uint16_t siocnt;
|
||||||
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);
|
void GBASIOInit(struct GBASIO* sio);
|
||||||
|
|
|
@ -12,6 +12,7 @@ CXX_GUARD_START
|
||||||
|
|
||||||
#include <mgba/core/log.h>
|
#include <mgba/core/log.h>
|
||||||
#include <mgba/core/timing.h>
|
#include <mgba/core/timing.h>
|
||||||
|
#include <mgba/gba/interface.h>
|
||||||
|
|
||||||
mLOG_DECLARE_CATEGORY(GBA_VIDEO);
|
mLOG_DECLARE_CATEGORY(GBA_VIDEO);
|
||||||
|
|
||||||
|
@ -84,20 +85,20 @@ struct GBAObj {
|
||||||
uint16_t d;
|
uint16_t d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GBAOAMMatrix {
|
||||||
|
int16_t padding0[3];
|
||||||
|
int16_t a;
|
||||||
|
int16_t padding1[3];
|
||||||
|
int16_t b;
|
||||||
|
int16_t padding2[3];
|
||||||
|
int16_t c;
|
||||||
|
int16_t padding3[3];
|
||||||
|
int16_t d;
|
||||||
|
};
|
||||||
|
|
||||||
union GBAOAM {
|
union GBAOAM {
|
||||||
struct GBAObj obj[128];
|
struct GBAObj obj[128];
|
||||||
|
struct GBAOAMMatrix mat[32];
|
||||||
struct GBAOAMMatrix {
|
|
||||||
int16_t padding0[3];
|
|
||||||
int16_t a;
|
|
||||||
int16_t padding1[3];
|
|
||||||
int16_t b;
|
|
||||||
int16_t padding2[3];
|
|
||||||
int16_t c;
|
|
||||||
int16_t padding3[3];
|
|
||||||
int16_t d;
|
|
||||||
} mat[32];
|
|
||||||
|
|
||||||
uint16_t raw[512];
|
uint16_t raw[512];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -197,6 +198,11 @@ struct GBAVideoRenderer {
|
||||||
|
|
||||||
bool disableBG[4];
|
bool disableBG[4];
|
||||||
bool disableOBJ;
|
bool disableOBJ;
|
||||||
|
|
||||||
|
bool highlightBG[4];
|
||||||
|
bool highlightOBJ[128];
|
||||||
|
color_t highlightColor;
|
||||||
|
uint8_t highlightAmount;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBAVideo {
|
struct GBAVideo {
|
||||||
|
|
|
@ -164,10 +164,16 @@ bool mCorePreloadFile(struct mCore* core, const char* path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mCoreAutoloadSave(struct mCore* core) {
|
bool mCoreAutoloadSave(struct mCore* core) {
|
||||||
|
if (!core->dirs.save) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR));
|
return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mCoreAutoloadPatch(struct mCore* core) {
|
bool mCoreAutoloadPatch(struct mCore* core) {
|
||||||
|
if (!core->dirs.patch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) ||
|
return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) ||
|
||||||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) ||
|
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) ||
|
||||||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));
|
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));
|
||||||
|
@ -230,6 +236,9 @@ bool mCoreLoadState(struct mCore* core, int slot, int flags) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) {
|
struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) {
|
||||||
|
if (!core->dirs.state) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
char name[PATH_MAX + 14]; // Quash warning
|
char name[PATH_MAX + 14]; // Quash warning
|
||||||
snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot);
|
snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot);
|
||||||
return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||||
|
|
|
@ -118,6 +118,20 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
||||||
},
|
},
|
||||||
.nStates = 2
|
.nStates = 2
|
||||||
};
|
};
|
||||||
|
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||||
|
.title = "Enable SGB features",
|
||||||
|
.data = "sgb.model",
|
||||||
|
.submenu = 0,
|
||||||
|
.state = true,
|
||||||
|
.validStates = (const char*[]) {
|
||||||
|
"Off", "On"
|
||||||
|
},
|
||||||
|
.stateMappings = (const struct GUIVariant[]) {
|
||||||
|
GUI_V_S("DMG"),
|
||||||
|
GUI_V_S("SGB"),
|
||||||
|
},
|
||||||
|
.nStates = 2
|
||||||
|
};
|
||||||
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||||
.title = "Enable SGB borders",
|
.title = "Enable SGB borders",
|
||||||
.data = "sgb.borders",
|
.data = "sgb.borders",
|
||||||
|
@ -128,6 +142,16 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
||||||
},
|
},
|
||||||
.nStates = 2
|
.nStates = 2
|
||||||
};
|
};
|
||||||
|
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
|
||||||
|
.title = "Crop SGB borders",
|
||||||
|
.data = "sgb.borderCrop",
|
||||||
|
.submenu = 0,
|
||||||
|
.state = false,
|
||||||
|
.validStates = (const char*[]) {
|
||||||
|
"Off", "On"
|
||||||
|
},
|
||||||
|
.nStates = 2
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
size_t i;
|
size_t i;
|
||||||
const char* mapNames[GUI_MAX_INPUTS + 1];
|
const char* mapNames[GUI_MAX_INPUTS + 1];
|
||||||
|
@ -173,8 +197,6 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (item->stateMappings) {
|
if (item->stateMappings) {
|
||||||
item->state = 0;
|
|
||||||
|
|
||||||
size_t j;
|
size_t j;
|
||||||
for (j = 0; j < item->nStates; ++j) {
|
for (j = 0; j < item->nStates; ++j) {
|
||||||
const struct GUIVariant* v = &item->stateMappings[j];
|
const struct GUIVariant* v = &item->stateMappings[j];
|
||||||
|
@ -281,7 +303,7 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
||||||
}
|
}
|
||||||
if (!strcmp(item->data, "gba.bios")) {
|
if (!strcmp(item->data, "gba.bios")) {
|
||||||
// TODO: show box if failed
|
// TODO: show box if failed
|
||||||
if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS)) {
|
if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS, NULL)) {
|
||||||
gbaBiosPath[0] = '\0';
|
gbaBiosPath[0] = '\0';
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -289,21 +311,21 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
if (!strcmp(item->data, "gb.bios")) {
|
if (!strcmp(item->data, "gb.bios")) {
|
||||||
// TODO: show box if failed
|
// TODO: show box if failed
|
||||||
if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS)) {
|
if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS, NULL)) {
|
||||||
gbBiosPath[0] = '\0';
|
gbBiosPath[0] = '\0';
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!strcmp(item->data, "gbc.bios")) {
|
if (!strcmp(item->data, "gbc.bios")) {
|
||||||
// TODO: show box if failed
|
// TODO: show box if failed
|
||||||
if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS)) {
|
if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS, NULL)) {
|
||||||
gbcBiosPath[0] = '\0';
|
gbcBiosPath[0] = '\0';
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!strcmp(item->data, "sgb.bios")) {
|
if (!strcmp(item->data, "sgb.bios")) {
|
||||||
// TODO: show box if failed
|
// TODO: show box if failed
|
||||||
if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS)) {
|
if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS, NULL)) {
|
||||||
sgbBiosPath[0] = '\0';
|
sgbBiosPath[0] = '\0';
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -628,10 +628,18 @@ void mGUIRunloop(struct mGUIRunner* runner) {
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL)) {
|
const char* preselect = mCoreConfigGetValue(&runner->config, "lastGame");
|
||||||
|
if (preselect) {
|
||||||
|
preselect = strrchr(preselect, '/');
|
||||||
|
}
|
||||||
|
if (preselect) {
|
||||||
|
++preselect;
|
||||||
|
}
|
||||||
|
if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL, preselect)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath);
|
mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath);
|
||||||
|
mCoreConfigSetValue(&runner->config, "lastGame", path);
|
||||||
mCoreConfigSave(&runner->config);
|
mCoreConfigSave(&runner->config);
|
||||||
mGUIRun(runner, path);
|
mGUIRun(runner, path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,11 @@ struct mVideoLogChannel {
|
||||||
z_stream inflateStream;
|
z_stream inflateStream;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool injecting;
|
||||||
|
enum mVideoLoggerInjectionPoint injectionPoint;
|
||||||
|
uint32_t ignorePackets;
|
||||||
|
|
||||||
|
struct CircleBuffer injectedBuffer;
|
||||||
struct CircleBuffer buffer;
|
struct CircleBuffer buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,6 +99,7 @@ struct mVideoLogContext {
|
||||||
struct mVideoLogChannel channels[mVL_MAX_CHANNELS];
|
struct mVideoLogChannel channels[mVL_MAX_CHANNELS];
|
||||||
|
|
||||||
bool write;
|
bool write;
|
||||||
|
bool compression;
|
||||||
uint32_t activeChannel;
|
uint32_t activeChannel;
|
||||||
struct VFile* backing;
|
struct VFile* backing;
|
||||||
};
|
};
|
||||||
|
@ -285,14 +291,28 @@ void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uin
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
|
bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
uint32_t ignorePackets = 0;
|
||||||
|
if (channel && channel->injectionPoint == LOGGER_INJECTION_IMMEDIATE && !channel->injecting) {
|
||||||
|
mVideoLoggerRendererRunInjected(logger);
|
||||||
|
ignorePackets = channel->ignorePackets;
|
||||||
|
}
|
||||||
struct mVideoLoggerDirtyInfo item = {0};
|
struct mVideoLoggerDirtyInfo item = {0};
|
||||||
while (logger->readData(logger, &item, sizeof(item), block)) {
|
while (logger->readData(logger, &item, sizeof(item), block)) {
|
||||||
|
if (ignorePackets & (1 << item.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
|
case DIRTY_SCANLINE:
|
||||||
|
if (channel && channel->injectionPoint == LOGGER_INJECTION_FIRST_SCANLINE && !channel->injecting && item.address == 0) {
|
||||||
|
mVideoLoggerRendererRunInjected(logger);
|
||||||
|
ignorePackets = channel->ignorePackets;
|
||||||
|
}
|
||||||
|
// Fall through
|
||||||
case DIRTY_REGISTER:
|
case DIRTY_REGISTER:
|
||||||
case DIRTY_PALETTE:
|
case DIRTY_PALETTE:
|
||||||
case DIRTY_OAM:
|
case DIRTY_OAM:
|
||||||
case DIRTY_VRAM:
|
case DIRTY_VRAM:
|
||||||
case DIRTY_SCANLINE:
|
|
||||||
case DIRTY_FLUSH:
|
case DIRTY_FLUSH:
|
||||||
case DIRTY_FRAME:
|
case DIRTY_FRAME:
|
||||||
case DIRTY_RANGE:
|
case DIRTY_RANGE:
|
||||||
|
@ -308,15 +328,34 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
|
||||||
return !block;
|
return !block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
channel->injecting = true;
|
||||||
|
bool res = mVideoLoggerRendererRun(logger, false);
|
||||||
|
channel->injecting = false;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint injectionPoint) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
channel->injectionPoint = injectionPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
channel->ignorePackets = mask;
|
||||||
|
}
|
||||||
|
|
||||||
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
|
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
|
||||||
struct mVideoLogChannel* channel = logger->dataContext;
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
|
return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
|
static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
|
||||||
UNUSED(logger);
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
UNUSED(data);
|
if (channel->injecting) {
|
||||||
UNUSED(length);
|
return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,6 +504,12 @@ struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
|
||||||
context->initialStateSize = 0;
|
context->initialStateSize = 0;
|
||||||
context->initialState = NULL;
|
context->initialState = NULL;
|
||||||
|
|
||||||
|
#ifdef USE_ZLIB
|
||||||
|
context->compression = true;
|
||||||
|
#else
|
||||||
|
context->compression = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (core) {
|
if (core) {
|
||||||
context->initialStateSize = core->stateSize(core);
|
context->initialStateSize = core->stateSize(core);
|
||||||
context->initialState = anonymousMemoryMap(context->initialStateSize);
|
context->initialState = anonymousMemoryMap(context->initialStateSize);
|
||||||
|
@ -482,6 +527,10 @@ void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* v
|
||||||
vf->seek(vf, 0, SEEK_SET);
|
vf->seek(vf, 0, SEEK_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mVideoLogContextSetCompression(struct mVideoLogContext* context, bool compression) {
|
||||||
|
context->compression = compression;
|
||||||
|
}
|
||||||
|
|
||||||
void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
|
void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
|
||||||
struct mVideoLogHeader header = { { 0 } };
|
struct mVideoLogHeader header = { { 0 } };
|
||||||
memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
|
memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
|
||||||
|
@ -499,21 +548,24 @@ void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore*
|
||||||
struct mVLBlockHeader chheader = { 0 };
|
struct mVLBlockHeader chheader = { 0 };
|
||||||
STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
|
STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
|
||||||
#ifdef USE_ZLIB
|
#ifdef USE_ZLIB
|
||||||
STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
|
if (context->compression) {
|
||||||
|
STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
|
||||||
|
|
||||||
struct VFile* vfm = VFileMemChunk(NULL, 0);
|
struct VFile* vfm = VFileMemChunk(NULL, 0);
|
||||||
struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize);
|
struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize);
|
||||||
_compress(vfm, src);
|
_compress(vfm, src);
|
||||||
src->close(src);
|
src->close(src);
|
||||||
STORE_32LE(vfm->size(vfm), 0, &chheader.length);
|
STORE_32LE(vfm->size(vfm), 0, &chheader.length);
|
||||||
context->backing->write(context->backing, &chheader, sizeof(chheader));
|
context->backing->write(context->backing, &chheader, sizeof(chheader));
|
||||||
_copyVf(context->backing, vfm);
|
_copyVf(context->backing, vfm);
|
||||||
vfm->close(vfm);
|
vfm->close(vfm);
|
||||||
#else
|
} else
|
||||||
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
|
#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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
|
@ -609,6 +661,7 @@ bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < context->nChannels; ++i) {
|
for (i = 0; i < context->nChannels; ++i) {
|
||||||
|
CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE);
|
||||||
CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
|
CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
|
||||||
context->channels[i].bufferRemaining = 0;
|
context->channels[i].bufferRemaining = 0;
|
||||||
context->channels[i].currentPointer = pointer;
|
context->channels[i].currentPointer = pointer;
|
||||||
|
@ -647,9 +700,10 @@ static void _flushBufferCompressed(struct mVideoLogContext* context) {
|
||||||
|
|
||||||
static void _flushBuffer(struct mVideoLogContext* context) {
|
static void _flushBuffer(struct mVideoLogContext* context) {
|
||||||
#ifdef USE_ZLIB
|
#ifdef USE_ZLIB
|
||||||
// TODO: Make optional
|
if (context->compression) {
|
||||||
_flushBufferCompressed(context);
|
_flushBufferCompressed(context);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
|
struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
|
||||||
|
@ -688,6 +742,7 @@ void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* contex
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < context->nChannels; ++i) {
|
for (i = 0; i < context->nChannels; ++i) {
|
||||||
|
CircleBufferDeinit(&context->channels[i].injectedBuffer);
|
||||||
CircleBufferDeinit(&context->channels[i].buffer);
|
CircleBufferDeinit(&context->channels[i].buffer);
|
||||||
#ifdef USE_ZLIB
|
#ifdef USE_ZLIB
|
||||||
if (context->channels[i].inflating) {
|
if (context->channels[i].inflating) {
|
||||||
|
@ -718,6 +773,7 @@ void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < context->nChannels; ++i) {
|
for (i = 0; i < context->nChannels; ++i) {
|
||||||
|
CircleBufferClear(&context->channels[i].injectedBuffer);
|
||||||
CircleBufferClear(&context->channels[i].buffer);
|
CircleBufferClear(&context->channels[i].buffer);
|
||||||
context->channels[i].bufferRemaining = 0;
|
context->channels[i].bufferRemaining = 0;
|
||||||
context->channels[i].currentPointer = pointer;
|
context->channels[i].currentPointer = pointer;
|
||||||
|
@ -744,10 +800,35 @@ int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
|
||||||
int chid = context->nChannels;
|
int chid = context->nChannels;
|
||||||
++context->nChannels;
|
++context->nChannels;
|
||||||
context->channels[chid].p = context;
|
context->channels[chid].p = context;
|
||||||
|
CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE);
|
||||||
CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
|
CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
|
||||||
|
context->channels[chid].injecting = false;
|
||||||
|
context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE;
|
||||||
|
context->channels[chid].ignorePackets = 0;
|
||||||
return chid;
|
return chid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
channel->injecting = true;
|
||||||
|
mVideoLoggerRendererWriteVideoRegister(logger, address, value);
|
||||||
|
channel->injecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
channel->injecting = true;
|
||||||
|
mVideoLoggerRendererWritePalette(logger, address, value);
|
||||||
|
channel->injecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
|
||||||
|
struct mVideoLogChannel* channel = logger->dataContext;
|
||||||
|
channel->injecting = true;
|
||||||
|
mVideoLoggerRendererWriteOAM(logger, address, value);
|
||||||
|
channel->injecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_ZLIB
|
#ifdef USE_ZLIB
|
||||||
static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
|
static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
|
||||||
uint8_t fbuffer[0x400];
|
uint8_t fbuffer[0x400];
|
||||||
|
@ -900,12 +981,16 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d
|
||||||
if (channelId >= mVL_MAX_CHANNELS) {
|
if (channelId >= mVL_MAX_CHANNELS) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (CircleBufferSize(&channel->buffer) >= length) {
|
struct CircleBuffer* buffer = &channel->buffer;
|
||||||
return CircleBufferRead(&channel->buffer, data, length);
|
if (channel->injecting) {
|
||||||
|
buffer = &channel->injectedBuffer;
|
||||||
|
}
|
||||||
|
if (CircleBufferSize(buffer) >= length) {
|
||||||
|
return CircleBufferRead(buffer, data, length);
|
||||||
}
|
}
|
||||||
ssize_t size = 0;
|
ssize_t size = 0;
|
||||||
if (CircleBufferSize(&channel->buffer)) {
|
if (CircleBufferSize(buffer)) {
|
||||||
size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer));
|
size = CircleBufferRead(buffer, data, CircleBufferSize(buffer));
|
||||||
if (size <= 0) {
|
if (size <= 0) {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
@ -915,7 +1000,7 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d
|
||||||
if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
|
if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
size += CircleBufferRead(&channel->buffer, data, length);
|
size += CircleBufferRead(buffer, data, length);
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,16 +1014,20 @@ static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const
|
||||||
_flushBuffer(context);
|
_flushBuffer(context);
|
||||||
context->activeChannel = channelId;
|
context->activeChannel = channelId;
|
||||||
}
|
}
|
||||||
if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) {
|
struct CircleBuffer* buffer = &channel->buffer;
|
||||||
|
if (channel->injecting) {
|
||||||
|
buffer = &channel->injectedBuffer;
|
||||||
|
}
|
||||||
|
if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) {
|
||||||
_flushBuffer(context);
|
_flushBuffer(context);
|
||||||
if (CircleBufferCapacity(&channel->buffer) < length) {
|
if (CircleBufferCapacity(buffer) < length) {
|
||||||
CircleBufferDeinit(&channel->buffer);
|
CircleBufferDeinit(buffer);
|
||||||
CircleBufferInit(&channel->buffer, toPow2(length << 1));
|
CircleBufferInit(buffer, toPow2(length << 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t read = CircleBufferWrite(&channel->buffer, data, length);
|
ssize_t read = CircleBufferWrite(buffer, data, length);
|
||||||
if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) {
|
if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) {
|
||||||
_flushBuffer(context);
|
_flushBuffer(context);
|
||||||
}
|
}
|
||||||
return read;
|
return read;
|
||||||
|
|
|
@ -902,9 +902,11 @@ static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* co
|
||||||
static void _GBCoreEndVideoLog(struct mCore* core) {
|
static void _GBCoreEndVideoLog(struct mCore* core) {
|
||||||
struct GBCore* gbcore = (struct GBCore*) core;
|
struct GBCore* gbcore = (struct GBCore*) core;
|
||||||
struct GB* gb = core->board;
|
struct GB* gb = core->board;
|
||||||
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
|
if (gbcore->proxyRenderer.logger) {
|
||||||
free(gbcore->proxyRenderer.logger);
|
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
|
||||||
gbcore->proxyRenderer.logger = NULL;
|
free(gbcore->proxyRenderer.logger);
|
||||||
|
gbcore->proxyRenderer.logger = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1007,6 +1009,7 @@ static void _GBVLPStartFrameCallback(void *context) {
|
||||||
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
|
GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
|
||||||
mVideoLogContextRewind(gbcore->logContext, core);
|
mVideoLogContextRewind(gbcore->logContext, core);
|
||||||
GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
|
GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
|
||||||
|
gb->earlyExit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1022,6 +1025,7 @@ static bool _GBVLPInit(struct mCore* core) {
|
||||||
gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback;
|
gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback;
|
||||||
gbcore->logCallbacks.context = core;
|
gbcore->logCallbacks.context = core;
|
||||||
core->addCoreCallbacks(core, &gbcore->logCallbacks);
|
core->addCoreCallbacks(core, &gbcore->logCallbacks);
|
||||||
|
core->videoLogger = gbcore->proxyRenderer.logger;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
src/gb/gb.c
|
@ -419,14 +419,7 @@ void GBReset(struct LR35902Core* cpu) {
|
||||||
gb->biosVf->close(gb->biosVf);
|
gb->biosVf->close(gb->biosVf);
|
||||||
gb->biosVf = NULL;
|
gb->biosVf = NULL;
|
||||||
} else {
|
} else {
|
||||||
gb->biosVf->seek(gb->biosVf, 0, SEEK_SET);
|
GBMapBIOS(gb);
|
||||||
gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
|
|
||||||
ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
|
|
||||||
memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
|
|
||||||
if (size > 0x100) {
|
|
||||||
memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu->a = 0;
|
cpu->a = 0;
|
||||||
cpu->f.packed = 0;
|
cpu->f.packed = 0;
|
||||||
cpu->c = 0;
|
cpu->c = 0;
|
||||||
|
@ -563,6 +556,16 @@ void GBSkipBIOS(struct GB* gb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBMapBIOS(struct GB* gb) {
|
||||||
|
gb->biosVf->seek(gb->biosVf, 0, SEEK_SET);
|
||||||
|
gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
|
||||||
|
ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
|
||||||
|
memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
|
||||||
|
if (size > 0x100) {
|
||||||
|
memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GBUnmapBIOS(struct GB* gb) {
|
void GBUnmapBIOS(struct GB* gb) {
|
||||||
if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) {
|
if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) {
|
||||||
free(gb->memory.romBase);
|
free(gb->memory.romBase);
|
||||||
|
|
|
@ -185,8 +185,10 @@ void GBIOReset(struct GB* gb) {
|
||||||
GBIOWrite(gb, REG_NR51, 0xF3);
|
GBIOWrite(gb, REG_NR51, 0xF3);
|
||||||
if (!gb->biosVf) {
|
if (!gb->biosVf) {
|
||||||
GBIOWrite(gb, REG_LCDC, 0x91);
|
GBIOWrite(gb, REG_LCDC, 0x91);
|
||||||
|
gb->memory.io[0x50] = 1;
|
||||||
} else {
|
} else {
|
||||||
GBIOWrite(gb, REG_LCDC, 0x00);
|
GBIOWrite(gb, REG_LCDC, 0x00);
|
||||||
|
gb->memory.io[0x50] = 0xFF;
|
||||||
}
|
}
|
||||||
GBIOWrite(gb, REG_SCY, 0x00);
|
GBIOWrite(gb, REG_SCY, 0x00);
|
||||||
GBIOWrite(gb, REG_SCX, 0x00);
|
GBIOWrite(gb, REG_SCX, 0x00);
|
||||||
|
|
|
@ -135,6 +135,16 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||||
if (ucheck16 >= 0x40) {
|
if (ucheck16 >= 0x40) {
|
||||||
mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range");
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range");
|
||||||
}
|
}
|
||||||
|
bool differentBios = !gb->biosVf || gb->model != state->model;
|
||||||
|
if (state->io[0x50] == 0xFF) {
|
||||||
|
if (differentBios) {
|
||||||
|
mLOG(GB_STATE, WARN, "Incompatible savestate, please restart with correct BIOS in %s mode", GBModelToName(state->model));
|
||||||
|
error = true;
|
||||||
|
} else {
|
||||||
|
// TODO: Make it work correctly
|
||||||
|
mLOG(GB_STATE, WARN, "Loading savestate in BIOS. This may not work correctly");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -187,6 +197,12 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||||
GBTimerDeserialize(&gb->timer, state);
|
GBTimerDeserialize(&gb->timer, state);
|
||||||
GBAudioDeserialize(&gb->audio, state);
|
GBAudioDeserialize(&gb->audio, state);
|
||||||
|
|
||||||
|
if (gb->memory.io[0x50] == 0xFF) {
|
||||||
|
GBMapBIOS(gb);
|
||||||
|
} else {
|
||||||
|
GBUnmapBIOS(gb);
|
||||||
|
}
|
||||||
|
|
||||||
if (gb->model & GB_MODEL_SGB && canSgb) {
|
if (gb->model & GB_MODEL_SGB && canSgb) {
|
||||||
GBSGBDeserialize(gb, state);
|
GBSGBDeserialize(gb, state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -343,19 +343,19 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat
|
||||||
mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3));
|
mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) {
|
||||||
|
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
GBFrameEnded(video->p);
|
|
||||||
mCoreSyncPostFrame(video->p->sync);
|
|
||||||
--video->frameskipCounter;
|
--video->frameskipCounter;
|
||||||
if (video->frameskipCounter < 0) {
|
if (video->frameskipCounter < 0) {
|
||||||
video->renderer->finishFrame(video->renderer);
|
video->renderer->finishFrame(video->renderer);
|
||||||
video->frameskipCounter = video->frameskip;
|
video->frameskipCounter = video->frameskip;
|
||||||
}
|
}
|
||||||
|
GBFrameEnded(video->p);
|
||||||
|
mCoreSyncPostFrame(video->p->sync);
|
||||||
++video->frameCounter;
|
++video->frameCounter;
|
||||||
|
|
||||||
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) {
|
|
||||||
mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH);
|
|
||||||
}
|
|
||||||
GBFrameStarted(video->p);
|
GBFrameStarted(video->p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,8 +137,11 @@ struct GBACore {
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
struct GBAVideoGLRenderer glRenderer;
|
struct GBAVideoGLRenderer glRenderer;
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef MINIMAL_CORE
|
||||||
|
struct GBAVideoProxyRenderer vlProxy;
|
||||||
struct GBAVideoProxyRenderer proxyRenderer;
|
struct GBAVideoProxyRenderer proxyRenderer;
|
||||||
struct mVideoLogContext* logContext;
|
struct mVideoLogContext* logContext;
|
||||||
|
#endif
|
||||||
struct mCoreCallbacks logCallbacks;
|
struct mCoreCallbacks logCallbacks;
|
||||||
#ifndef DISABLE_THREADING
|
#ifndef DISABLE_THREADING
|
||||||
struct mVideoThreadProxy threadProxy;
|
struct mVideoThreadProxy threadProxy;
|
||||||
|
@ -170,7 +173,9 @@ static bool _GBACoreInit(struct mCore* core) {
|
||||||
gbacore->overrides = NULL;
|
gbacore->overrides = NULL;
|
||||||
gbacore->debuggerPlatform = NULL;
|
gbacore->debuggerPlatform = NULL;
|
||||||
gbacore->cheatDevice = NULL;
|
gbacore->cheatDevice = NULL;
|
||||||
|
#ifndef MINIMAL_CORE
|
||||||
gbacore->logContext = NULL;
|
gbacore->logContext = NULL;
|
||||||
|
#endif
|
||||||
gbacore->audioMixer = NULL;
|
gbacore->audioMixer = NULL;
|
||||||
|
|
||||||
GBACreate(gba);
|
GBACreate(gba);
|
||||||
|
@ -192,7 +197,10 @@ static bool _GBACoreInit(struct mCore* core) {
|
||||||
#ifndef DISABLE_THREADING
|
#ifndef DISABLE_THREADING
|
||||||
mVideoThreadProxyCreate(&gbacore->threadProxy);
|
mVideoThreadProxyCreate(&gbacore->threadProxy);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef MINIMAL_CORE
|
||||||
|
gbacore->vlProxy.logger = NULL;
|
||||||
gbacore->proxyRenderer.logger = NULL;
|
gbacore->proxyRenderer.logger = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
gbacore->keys = 0;
|
gbacore->keys = 0;
|
||||||
gba->keySource = &gbacore->keys;
|
gba->keySource = &gbacore->keys;
|
||||||
|
@ -475,11 +483,13 @@ static void _GBACoreReset(struct mCore* core) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef MINIMAL_CORE
|
||||||
if (core->videoLogger) {
|
if (core->videoLogger) {
|
||||||
gbacore->proxyRenderer.logger = core->videoLogger;
|
gbacore->proxyRenderer.logger = core->videoLogger;
|
||||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
|
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
|
||||||
renderer = &gbacore->proxyRenderer.d;
|
renderer = &gbacore->proxyRenderer.d;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
GBAVideoAssociateRenderer(&gba->video, renderer);
|
GBAVideoAssociateRenderer(&gba->video, renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1000,21 +1010,23 @@ static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* c
|
||||||
state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM;
|
state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM;
|
||||||
|
|
||||||
int channelId = mVideoLoggerAddChannel(context);
|
int channelId = mVideoLoggerAddChannel(context);
|
||||||
gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger));
|
gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger));
|
||||||
mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, false);
|
mVideoLoggerRendererCreate(gbacore->vlProxy.logger, false);
|
||||||
mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, context, channelId);
|
mVideoLoggerAttachChannel(gbacore->vlProxy.logger, context, channelId);
|
||||||
gbacore->proxyRenderer.logger->block = false;
|
gbacore->vlProxy.logger->block = false;
|
||||||
|
|
||||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, &gbacore->renderer.d);
|
GBAVideoProxyRendererCreate(&gbacore->vlProxy, gba->video.renderer);
|
||||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer);
|
GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _GBACoreEndVideoLog(struct mCore* core) {
|
static void _GBACoreEndVideoLog(struct mCore* core) {
|
||||||
struct GBACore* gbacore = (struct GBACore*) core;
|
struct GBACore* gbacore = (struct GBACore*) core;
|
||||||
struct GBA* gba = core->board;
|
struct GBA* gba = core->board;
|
||||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);
|
if (gbacore->vlProxy.logger) {
|
||||||
free(gbacore->proxyRenderer.logger);
|
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
|
||||||
gbacore->proxyRenderer.logger = NULL;
|
free(gbacore->vlProxy.logger);
|
||||||
|
gbacore->vlProxy.logger = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1112,10 +1124,11 @@ static void _GBAVLPStartFrameCallback(void *context) {
|
||||||
struct GBACore* gbacore = (struct GBACore*) core;
|
struct GBACore* gbacore = (struct GBACore*) core;
|
||||||
struct GBA* gba = core->board;
|
struct GBA* gba = core->board;
|
||||||
|
|
||||||
if (!mVideoLoggerRendererRun(gbacore->proxyRenderer.logger, true)) {
|
if (!mVideoLoggerRendererRun(gbacore->vlProxy.logger, true)) {
|
||||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);
|
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
|
||||||
mVideoLogContextRewind(gbacore->logContext, core);
|
mVideoLogContextRewind(gbacore->logContext, core);
|
||||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer);
|
GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
|
||||||
|
gba->earlyExit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1124,13 +1137,14 @@ static bool _GBAVLPInit(struct mCore* core) {
|
||||||
if (!_GBACoreInit(core)) {
|
if (!_GBACoreInit(core)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger));
|
gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger));
|
||||||
mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, true);
|
mVideoLoggerRendererCreate(gbacore->vlProxy.logger, true);
|
||||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, NULL);
|
GBAVideoProxyRendererCreate(&gbacore->vlProxy, NULL);
|
||||||
memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks));
|
memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks));
|
||||||
gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback;
|
gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback;
|
||||||
gbacore->logCallbacks.context = core;
|
gbacore->logCallbacks.context = core;
|
||||||
core->addCoreCallbacks(core, &gbacore->logCallbacks);
|
core->addCoreCallbacks(core, &gbacore->logCallbacks);
|
||||||
|
core->videoLogger = gbacore->vlProxy.logger;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,8 +1159,8 @@ static void _GBAVLPDeinit(struct mCore* core) {
|
||||||
static void _GBAVLPReset(struct mCore* core) {
|
static void _GBAVLPReset(struct mCore* core) {
|
||||||
struct GBACore* gbacore = (struct GBACore*) core;
|
struct GBACore* gbacore = (struct GBACore*) core;
|
||||||
struct GBA* gba = (struct GBA*) core->board;
|
struct GBA* gba = (struct GBA*) core->board;
|
||||||
if (gba->video.renderer == &gbacore->proxyRenderer.d) {
|
if (gba->video.renderer == &gbacore->vlProxy.d) {
|
||||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);
|
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy);
|
||||||
} else if (gbacore->renderer.outputBuffer) {
|
} else if (gbacore->renderer.outputBuffer) {
|
||||||
struct GBAVideoRenderer* renderer = &gbacore->renderer.d;
|
struct GBAVideoRenderer* renderer = &gbacore->renderer.d;
|
||||||
GBAVideoAssociateRenderer(&gba->video, renderer);
|
GBAVideoAssociateRenderer(&gba->video, renderer);
|
||||||
|
@ -1154,7 +1168,7 @@ static void _GBAVLPReset(struct mCore* core) {
|
||||||
|
|
||||||
ARMReset(core->cpu);
|
ARMReset(core->cpu);
|
||||||
mVideoLogContextRewind(gbacore->logContext, core);
|
mVideoLogContextRewind(gbacore->logContext, core);
|
||||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer);
|
GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy);
|
||||||
|
|
||||||
// Make sure CPU loop never spins
|
// Make sure CPU loop never spins
|
||||||
GBAHalt(gba);
|
GBAHalt(gba);
|
||||||
|
@ -1170,7 +1184,7 @@ static bool _GBAVLPLoadROM(struct mCore* core, struct VFile* vf) {
|
||||||
gbacore->logContext = NULL;
|
gbacore->logContext = NULL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, gbacore->logContext, 0);
|
mVideoLoggerAttachChannel(gbacore->vlProxy.logger, gbacore->logContext, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ void _battlechipTransfer(struct GBASIOBattlechipGate* gate) {
|
||||||
if (gate->d.p->mode == SIO_NORMAL_32) {
|
if (gate->d.p->mode == SIO_NORMAL_32) {
|
||||||
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000;
|
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000;
|
||||||
} else {
|
} else {
|
||||||
cycles = GBASIOCyclesPerTransfer[gate->d.p->multiplayerControl.baud][1];
|
cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1];
|
||||||
}
|
}
|
||||||
mTimingDeschedule(&gate->d.p->p->timing, &gate->event);
|
mTimingDeschedule(&gate->d.p->p->timing, &gate->event);
|
||||||
mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles);
|
mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles);
|
||||||
|
@ -100,8 +100,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
|
||||||
if (gate->d.p->mode == SIO_NORMAL_32) {
|
if (gate->d.p->mode == SIO_NORMAL_32) {
|
||||||
gate->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0;
|
gate->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0;
|
||||||
gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0;
|
gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0;
|
||||||
gate->d.p->normalControl.start = 0;
|
gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt);
|
||||||
if (gate->d.p->normalControl.irq) {
|
if (GBASIONormalIsIrq(gate->d.p->siocnt)) {
|
||||||
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
|
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -112,8 +112,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
|
||||||
gate->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = cmd;
|
gate->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = cmd;
|
||||||
gate->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
|
gate->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
|
||||||
gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
||||||
gate->d.p->multiplayerControl.busy = 0;
|
gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt);
|
||||||
gate->d.p->multiplayerControl.id = 0;
|
gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0);
|
||||||
|
|
||||||
mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state);
|
mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state);
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
|
||||||
|
|
||||||
gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply;
|
gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply;
|
||||||
|
|
||||||
if (gate->d.p->multiplayerControl.irq) {
|
if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) {
|
||||||
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
|
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,17 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
|
||||||
renderer->d.disableBG[3] = false;
|
renderer->d.disableBG[3] = false;
|
||||||
renderer->d.disableOBJ = false;
|
renderer->d.disableOBJ = false;
|
||||||
|
|
||||||
|
renderer->d.highlightBG[0] = false;
|
||||||
|
renderer->d.highlightBG[1] = false;
|
||||||
|
renderer->d.highlightBG[2] = false;
|
||||||
|
renderer->d.highlightBG[3] = false;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 128; ++i) {
|
||||||
|
renderer->d.highlightOBJ[i] = false;
|
||||||
|
}
|
||||||
|
renderer->d.highlightColor = 0xFFFFFF;
|
||||||
|
renderer->d.highlightAmount = 0;
|
||||||
|
|
||||||
renderer->logger->context = renderer;
|
renderer->logger->context = renderer;
|
||||||
renderer->logger->parsePacket = _parsePacket;
|
renderer->logger->parsePacket = _parsePacket;
|
||||||
renderer->logger->handleEvent = _handleEvent;
|
renderer->logger->handleEvent = _handleEvent;
|
||||||
|
@ -205,6 +216,12 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
|
||||||
proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2];
|
proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2];
|
||||||
proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3];
|
proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3];
|
||||||
proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ;
|
proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ;
|
||||||
|
proxyRenderer->backend->highlightBG[0] = proxyRenderer->d.highlightBG[0];
|
||||||
|
proxyRenderer->backend->highlightBG[1] = proxyRenderer->d.highlightBG[1];
|
||||||
|
proxyRenderer->backend->highlightBG[2] = proxyRenderer->d.highlightBG[2];
|
||||||
|
proxyRenderer->backend->highlightBG[3] = proxyRenderer->d.highlightBG[3];
|
||||||
|
memcpy(proxyRenderer->backend->highlightOBJ, proxyRenderer->d.highlightOBJ, sizeof(proxyRenderer->backend->highlightOBJ));
|
||||||
|
proxyRenderer->backend->highlightAmount = proxyRenderer->d.highlightAmount;
|
||||||
if (item->address < GBA_VIDEO_VERTICAL_PIXELS) {
|
if (item->address < GBA_VIDEO_VERTICAL_PIXELS) {
|
||||||
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address);
|
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address);
|
||||||
}
|
}
|
||||||
|
|
|
@ -577,10 +577,10 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
|
||||||
++gbp->p->gbpTxPosition;
|
++gbp->p->gbpTxPosition;
|
||||||
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
||||||
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
||||||
if (gbp->d.p->normalControl.irq) {
|
if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
|
||||||
GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate);
|
GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate);
|
||||||
}
|
}
|
||||||
gbp->d.p->normalControl.start = 0;
|
gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
|
||||||
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;
|
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -396,9 +396,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
||||||
value = 0; \
|
value = 0; \
|
||||||
break; \
|
break; \
|
||||||
} \
|
} \
|
||||||
address &= 0x00017FFC; \
|
LOAD_32(value, address & 0x00017FFC, gba->video.vram); \
|
||||||
|
} else { \
|
||||||
|
LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \
|
||||||
} \
|
} \
|
||||||
LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \
|
|
||||||
wait += waitstatesRegion[REGION_VRAM];
|
wait += waitstatesRegion[REGION_VRAM];
|
||||||
|
|
||||||
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);
|
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);
|
||||||
|
@ -530,9 +531,10 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||||
value = 0;
|
value = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
address &= 0x00017FFE;
|
LOAD_16(value, address & 0x00017FFE, gba->video.vram);
|
||||||
|
} else {
|
||||||
|
LOAD_16(value, address & 0x0001FFFE, gba->video.vram);
|
||||||
}
|
}
|
||||||
LOAD_16(value, address & 0x0001FFFE, gba->video.vram);
|
|
||||||
break;
|
break;
|
||||||
case REGION_OAM:
|
case REGION_OAM:
|
||||||
LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);
|
||||||
|
@ -645,9 +647,10 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||||
value = 0;
|
value = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
address &= 0x00017FFF;
|
value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF];
|
||||||
|
} else {
|
||||||
|
value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF];
|
||||||
}
|
}
|
||||||
value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF];
|
|
||||||
break;
|
break;
|
||||||
case REGION_OAM:
|
case REGION_OAM:
|
||||||
value = ((uint8_t*) gba->video.oam.raw)[address & (SIZE_OAM - 1)];
|
value = ((uint8_t*) gba->video.oam.raw)[address & (SIZE_OAM - 1)];
|
||||||
|
@ -734,13 +737,19 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||||
mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store32: 0x%08X", address); \
|
mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store32: 0x%08X", address); \
|
||||||
break; \
|
break; \
|
||||||
} \
|
} \
|
||||||
address &= 0x00017FFC; \
|
LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram); \
|
||||||
} \
|
if (oldValue != value) { \
|
||||||
LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); \
|
STORE_32(value, address & 0x00017FFC, gba->video.vram); \
|
||||||
if (oldValue != value) { \
|
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \
|
||||||
STORE_32(value, address & 0x0001FFFC, gba->video.vram); \
|
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \
|
||||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \
|
} \
|
||||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \
|
} 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];
|
wait += waitstatesRegion[REGION_VRAM];
|
||||||
|
|
||||||
|
@ -855,12 +864,17 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
|
||||||
mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store16: 0x%08X", address);
|
mLOG(GBA_MEM, GAME_ERROR, "Bad VRAM Store16: 0x%08X", address);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
address &= 0x00017FFE;
|
LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram);
|
||||||
}
|
if (value != oldValue) {
|
||||||
LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram);
|
STORE_16(value, address & 0x00017FFE, gba->video.vram);
|
||||||
if (value != oldValue) {
|
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
|
||||||
STORE_16(value, address & 0x0001FFFE, gba->video.vram);
|
}
|
||||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
|
} 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;
|
break;
|
||||||
case REGION_OAM:
|
case REGION_OAM:
|
||||||
|
|
|
@ -34,6 +34,7 @@ int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite*
|
||||||
sprites[oamMax].y = y;
|
sprites[oamMax].y = y;
|
||||||
sprites[oamMax].endY = y + height;
|
sprites[oamMax].endY = y + height;
|
||||||
sprites[oamMax].obj = obj;
|
sprites[oamMax].obj = obj;
|
||||||
|
sprites[oamMax].index = i;
|
||||||
++oamMax;
|
++oamMax;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ static const char* const _renderMode0 =
|
||||||
" if ((size & 1) == 1) {\n"
|
" if ((size & 1) == 1) {\n"
|
||||||
" coord.y += coord.x & 256;\n"
|
" coord.y += coord.x & 256;\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" coord &= ivec2(255, 511);\n"
|
" coord &= ivec2(255, 1023);\n"
|
||||||
" int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
|
" int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
|
||||||
" vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
|
" vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
|
||||||
" int tileFlags = int(map.g * 15.9);\n"
|
" int tileFlags = int(map.g * 15.9);\n"
|
||||||
|
@ -442,7 +442,7 @@ static const char* const _renderObj =
|
||||||
"uniform ivec4 mosaic;\n"
|
"uniform ivec4 mosaic;\n"
|
||||||
"OUT(0) out vec4 color;\n"
|
"OUT(0) out vec4 color;\n"
|
||||||
"OUT(1) out ivec4 flags;\n"
|
"OUT(1) out ivec4 flags;\n"
|
||||||
"OUT(2) out ivec3 window;\n"
|
"OUT(2) out ivec4 window;\n"
|
||||||
|
|
||||||
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||||
|
|
||||||
|
@ -467,7 +467,7 @@ static const char* const _renderObj =
|
||||||
" color = pix;\n"
|
" color = pix;\n"
|
||||||
" flags = inflags;\n"
|
" flags = inflags;\n"
|
||||||
" gl_FragDepth = float(flags.x) / 16.;\n"
|
" gl_FragDepth = float(flags.x) / 16.;\n"
|
||||||
" window = objwin.yzw;\n"
|
" window = ivec4(objwin.yzw, 0);\n"
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
static const struct GBAVideoGLUniform _uniformsWindow[] = {
|
static const struct GBAVideoGLUniform _uniformsWindow[] = {
|
||||||
|
@ -488,7 +488,7 @@ static const char* const _renderWindow =
|
||||||
"uniform ivec3 flags;\n"
|
"uniform ivec3 flags;\n"
|
||||||
"uniform ivec4 win0[160];\n"
|
"uniform ivec4 win0[160];\n"
|
||||||
"uniform ivec4 win1[160];\n"
|
"uniform ivec4 win1[160];\n"
|
||||||
"OUT(0) out ivec3 window;\n"
|
"OUT(0) out ivec4 window;\n"
|
||||||
|
|
||||||
"void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n"
|
"void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n"
|
||||||
" bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"
|
" bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"
|
||||||
|
@ -526,7 +526,7 @@ static const char* const _renderWindow =
|
||||||
"void main() {\n"
|
"void main() {\n"
|
||||||
" int dispflags = (dispcnt & 0x1F) | 0x20;\n"
|
" int dispflags = (dispcnt & 0x1F) | 0x20;\n"
|
||||||
" if ((dispcnt & 0xE0) == 0) {\n"
|
" if ((dispcnt & 0xE0) == 0) {\n"
|
||||||
" window = ivec3(dispflags, blend);\n"
|
" window = ivec4(dispflags, blend, 0);\n"
|
||||||
" } else {\n"
|
" } else {\n"
|
||||||
" ivec3 windowFlags = ivec3(flags.z, blend);\n"
|
" ivec3 windowFlags = ivec3(flags.z, blend);\n"
|
||||||
" if ((dispcnt & 0x40) != 0) { \n"
|
" if ((dispcnt & 0x40) != 0) { \n"
|
||||||
|
@ -535,7 +535,7 @@ static const char* const _renderWindow =
|
||||||
" if ((dispcnt & 0x20) != 0) { \n"
|
" if ((dispcnt & 0x20) != 0) { \n"
|
||||||
" crop(interpolate(win0), flags.x, windowFlags);\n"
|
" crop(interpolate(win0), flags.x, windowFlags);\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" window = windowFlags;\n"
|
" window = ivec4(windowFlags, 0);\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"}\n";
|
"}\n";
|
||||||
|
|
||||||
|
@ -651,6 +651,17 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) {
|
||||||
renderer->d.disableBG[3] = false;
|
renderer->d.disableBG[3] = false;
|
||||||
renderer->d.disableOBJ = false;
|
renderer->d.disableOBJ = false;
|
||||||
|
|
||||||
|
renderer->d.highlightBG[0] = false;
|
||||||
|
renderer->d.highlightBG[1] = false;
|
||||||
|
renderer->d.highlightBG[2] = false;
|
||||||
|
renderer->d.highlightBG[3] = false;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 128; ++i) {
|
||||||
|
renderer->d.highlightOBJ[i] = false;
|
||||||
|
}
|
||||||
|
renderer->d.highlightColor = 0xFFFFFF;
|
||||||
|
renderer->d.highlightAmount = 0;
|
||||||
|
|
||||||
renderer->scale = 1;
|
renderer->scale = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1369,7 +1380,7 @@ void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) {
|
||||||
glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1);
|
glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
|
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
|
||||||
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
||||||
glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 1.f });
|
glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xF8) / 248., ((backdrop >> 8) & 0xF8) / 248., (backdrop & 0xF8) / 248., 1.f });
|
||||||
glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 });
|
glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 });
|
||||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||||
|
|
||||||
|
|
|
@ -613,16 +613,21 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer
|
||||||
uint32_t screenBase;
|
uint32_t screenBase;
|
||||||
uint32_t charBase;
|
uint32_t charBase;
|
||||||
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||||
color_t* mainPalette;
|
color_t* mainPalette = renderer->normalPalette;
|
||||||
if (background->multipalette && background->extPalette) {
|
if (background->multipalette && background->extPalette) {
|
||||||
mainPalette = background->extPalette;
|
mainPalette = background->extPalette;
|
||||||
if (variant) {
|
if (variant) {
|
||||||
mainPalette = background->variantPalette;
|
mainPalette = background->variantPalette;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mainPalette = renderer->normalPalette;
|
if (renderer->d.highlightAmount && background->highlight) {
|
||||||
|
mainPalette = renderer->highlightPalette;
|
||||||
|
}
|
||||||
if (variant) {
|
if (variant) {
|
||||||
mainPalette = renderer->variantPalette;
|
mainPalette = renderer->variantPalette;
|
||||||
|
if (renderer->d.highlightAmount && background->highlight) {
|
||||||
|
mainPalette = renderer->highlightVariantPalette;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
color_t* palette = mainPalette;
|
color_t* palette = mainPalette;
|
||||||
|
|
|
@ -212,7 +212,7 @@
|
||||||
renderer->row[outX] |= FLAG_OBJWIN; \
|
renderer->row[outX] |= FLAG_OBJWIN; \
|
||||||
}
|
}
|
||||||
|
|
||||||
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
|
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y) {
|
||||||
int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
|
int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
|
||||||
int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
|
int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
|
||||||
int start = renderer->start;
|
int start = renderer->start;
|
||||||
|
@ -255,6 +255,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
}
|
}
|
||||||
|
|
||||||
color_t* palette = &renderer->normalPalette[0x100];
|
color_t* palette = &renderer->normalPalette[0x100];
|
||||||
|
if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
|
||||||
|
palette = &renderer->highlightPalette[0x100];
|
||||||
|
}
|
||||||
color_t* objwinPalette = palette;
|
color_t* objwinPalette = palette;
|
||||||
|
|
||||||
if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {
|
if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) {
|
||||||
|
@ -269,6 +272,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
}
|
}
|
||||||
} else if (variant) {
|
} else if (variant) {
|
||||||
palette = &renderer->variantPalette[0x100];
|
palette = &renderer->variantPalette[0x100];
|
||||||
|
if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) {
|
||||||
|
palette = &renderer->highlightVariantPalette[0x100];
|
||||||
|
}
|
||||||
if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
|
if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) {
|
||||||
objwinPalette = palette;
|
objwinPalette = palette;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer
|
||||||
void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer,
|
void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer,
|
||||||
struct GBAVideoSoftwareBackground* background, int y);
|
struct GBAVideoSoftwareBackground* background, int y);
|
||||||
|
|
||||||
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y);
|
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y);
|
||||||
void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority);
|
void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority);
|
||||||
|
|
||||||
void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y);
|
void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y);
|
||||||
|
@ -145,11 +145,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
||||||
int objwinForceEnable = 0; \
|
int objwinForceEnable = 0; \
|
||||||
UNUSED(objwinForceEnable); \
|
UNUSED(objwinForceEnable); \
|
||||||
color_t* objwinPalette = renderer->normalPalette; \
|
color_t* objwinPalette = renderer->normalPalette; \
|
||||||
|
if (renderer->d.highlightAmount && background->highlight) { \
|
||||||
|
objwinPalette = renderer->highlightPalette; \
|
||||||
|
} \
|
||||||
UNUSED(objwinPalette); \
|
UNUSED(objwinPalette); \
|
||||||
if (objwinSlowPath) { \
|
if (objwinSlowPath) { \
|
||||||
if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \
|
if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \
|
||||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
|
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
|
||||||
objwinPalette = renderer->variantPalette; \
|
objwinPalette = renderer->variantPalette; \
|
||||||
|
if (renderer->d.highlightAmount && background->highlight) { \
|
||||||
|
palette = renderer->highlightVariantPalette; \
|
||||||
|
} \
|
||||||
} \
|
} \
|
||||||
switch (background->index) { \
|
switch (background->index) { \
|
||||||
case 0: \
|
case 0: \
|
||||||
|
@ -204,8 +210,14 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
||||||
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \
|
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \
|
||||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
|
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
|
||||||
color_t* palette = renderer->normalPalette; \
|
color_t* palette = renderer->normalPalette; \
|
||||||
|
if (renderer->d.highlightAmount && background->highlight) { \
|
||||||
|
palette = renderer->highlightPalette; \
|
||||||
|
} \
|
||||||
if (variant) { \
|
if (variant) { \
|
||||||
palette = renderer->variantPalette; \
|
palette = renderer->variantPalette; \
|
||||||
|
if (renderer->d.highlightAmount && background->highlight) { \
|
||||||
|
palette = renderer->highlightVariantPalette; \
|
||||||
|
} \
|
||||||
} \
|
} \
|
||||||
UNUSED(palette); \
|
UNUSED(palette); \
|
||||||
PREPARE_OBJWIN;
|
PREPARE_OBJWIN;
|
||||||
|
|
|
@ -68,6 +68,17 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
||||||
renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS;
|
renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS;
|
||||||
renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS;
|
renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS;
|
||||||
|
|
||||||
|
renderer->d.highlightBG[0] = false;
|
||||||
|
renderer->d.highlightBG[1] = false;
|
||||||
|
renderer->d.highlightBG[2] = false;
|
||||||
|
renderer->d.highlightBG[3] = false;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 128; ++i) {
|
||||||
|
renderer->d.highlightOBJ[i] = false;
|
||||||
|
}
|
||||||
|
renderer->d.highlightColor = GBA_COLOR_WHITE;
|
||||||
|
renderer->d.highlightAmount = 0;
|
||||||
|
|
||||||
renderer->temporaryBuffer = 0;
|
renderer->temporaryBuffer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,11 +584,23 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
||||||
GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
|
GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
|
||||||
int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
|
int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
|
||||||
softwareRenderer->d.vramOBJ[0] = objVramBase;
|
softwareRenderer->d.vramOBJ[0] = objVramBase;
|
||||||
|
if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) {
|
||||||
|
softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount;
|
||||||
|
if (softwareRenderer->lastHighlightAmount) {
|
||||||
|
softwareRenderer->blendDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (softwareRenderer->blendDirty) {
|
if (softwareRenderer->blendDirty) {
|
||||||
_updatePalettes(softwareRenderer);
|
_updatePalettes(softwareRenderer);
|
||||||
softwareRenderer->blendDirty = false;
|
softwareRenderer->blendDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0];
|
||||||
|
softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1];
|
||||||
|
softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2];
|
||||||
|
softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3];
|
||||||
|
|
||||||
int w;
|
int w;
|
||||||
unsigned priority;
|
unsigned priority;
|
||||||
for (priority = 0; priority < 4; ++priority) {
|
for (priority = 0; priority < 4; ++priority) {
|
||||||
|
@ -945,12 +968,12 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
|
||||||
if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
|
if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
|
if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
|
||||||
localY = mosaicY;
|
localY = mosaicY;
|
||||||
if (localY < sprite->y) {
|
if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
|
||||||
localY = sprite->y;
|
localY = sprite->y;
|
||||||
}
|
}
|
||||||
if (localY >= sprite->endY) {
|
if (localY >= (sprite->endY & 0xFF)) {
|
||||||
localY = sprite->endY - 1;
|
localY = sprite->endY - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -965,7 +988,7 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY);
|
int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY);
|
||||||
spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
|
spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
|
||||||
}
|
}
|
||||||
if (renderer->spriteCyclesRemaining <= 0) {
|
if (renderer->spriteCyclesRemaining <= 0) {
|
||||||
|
@ -993,4 +1016,12 @@ static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
|
||||||
renderer->variantPalette[i] = renderer->normalPalette[i];
|
renderer->variantPalette[i] = renderer->normalPalette[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
unsigned highlightAmount = renderer->d.highlightAmount >> 4;
|
||||||
|
|
||||||
|
if (highlightAmount) {
|
||||||
|
for (i = 0; i < 512; ++i) {
|
||||||
|
renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor);
|
||||||
|
renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock
|
||||||
|
|
||||||
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
|
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
|
||||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||||
node->d.p->multiplayerControl.slave = node->id > 0;
|
node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0);
|
||||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
|
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id);
|
||||||
node->event.context = node;
|
node->event.context = node;
|
||||||
node->event.name = "GBA SIO Lockstep";
|
node->event.name = "GBA SIO Lockstep";
|
||||||
|
@ -99,10 +99,10 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
||||||
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
||||||
node->d.p->rcnt |= 3;
|
node->d.p->rcnt |= 3;
|
||||||
ATOMIC_ADD(node->p->attachedMulti, 1);
|
ATOMIC_ADD(node->p->attachedMulti, 1);
|
||||||
node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached;
|
node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
|
||||||
if (node->id) {
|
if (node->id) {
|
||||||
node->d.p->rcnt |= 4;
|
node->d.p->rcnt |= 4;
|
||||||
node->d.p->multiplayerControl.slave = 1;
|
node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SIO_NORMAL_32:
|
case SIO_NORMAL_32:
|
||||||
|
@ -178,10 +178,10 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
|
||||||
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
||||||
|
|
||||||
if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
|
if (value & 0x0080 && transferActive == TRANSFER_IDLE) {
|
||||||
if (!node->id && node->d.p->multiplayerControl.ready) {
|
if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
|
||||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
|
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
|
||||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
||||||
ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1]);
|
ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]);
|
||||||
|
|
||||||
bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
|
bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event);
|
||||||
int oldWhen = node->event.when;
|
int oldWhen = node->event.when;
|
||||||
|
@ -220,37 +220,37 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
|
||||||
sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
|
sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
|
||||||
sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
|
sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
|
||||||
sio->rcnt |= 1;
|
sio->rcnt |= 1;
|
||||||
sio->multiplayerControl.busy = 0;
|
sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
|
||||||
sio->multiplayerControl.id = node->id;
|
sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
|
||||||
if (sio->multiplayerControl.irq) {
|
if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
|
||||||
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SIO_NORMAL_8:
|
case SIO_NORMAL_8:
|
||||||
// TODO
|
// TODO
|
||||||
sio->normalControl.start = 0;
|
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
|
||||||
if (node->id) {
|
if (node->id) {
|
||||||
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
|
sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
|
||||||
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
|
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF;
|
||||||
} else {
|
} else {
|
||||||
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
|
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
|
||||||
}
|
}
|
||||||
if (sio->multiplayerControl.irq) {
|
if (GBASIONormalIsIrq(sio->siocnt)) {
|
||||||
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SIO_NORMAL_32:
|
case SIO_NORMAL_32:
|
||||||
// TODO
|
// TODO
|
||||||
sio->normalControl.start = 0;
|
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
|
||||||
if (node->id) {
|
if (node->id) {
|
||||||
sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo;
|
sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
|
||||||
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
|
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1];
|
||||||
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
|
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16;
|
||||||
} else {
|
} else {
|
||||||
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
|
node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF;
|
||||||
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
|
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
|
||||||
}
|
}
|
||||||
if (sio->multiplayerControl.irq) {
|
if (GBASIONormalIsIrq(sio->siocnt)) {
|
||||||
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -278,7 +278,7 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) {
|
||||||
case TRANSFER_IDLE:
|
case TRANSFER_IDLE:
|
||||||
// If the master hasn't initiated a transfer, it can keep going.
|
// If the master hasn't initiated a transfer, it can keep going.
|
||||||
node->nextEvent += LOCKSTEP_INCREMENT;
|
node->nextEvent += LOCKSTEP_INCREMENT;
|
||||||
node->d.p->multiplayerControl.ready = attachedMulti == attached;
|
node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
|
||||||
break;
|
break;
|
||||||
case TRANSFER_STARTING:
|
case TRANSFER_STARTING:
|
||||||
// Start the transfer, but wait for the other GBAs to catch up
|
// Start the transfer, but wait for the other GBAs to catch up
|
||||||
|
@ -352,11 +352,11 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
||||||
ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
|
ATOMIC_LOAD(attachedMulti, node->p->attachedMulti);
|
||||||
ATOMIC_LOAD(attached, node->p->d.attached);
|
ATOMIC_LOAD(attached, node->p->d.attached);
|
||||||
|
|
||||||
node->d.p->multiplayerControl.ready = attachedMulti == attached;
|
node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached);
|
||||||
bool signal = false;
|
bool signal = false;
|
||||||
switch (transferActive) {
|
switch (transferActive) {
|
||||||
case TRANSFER_IDLE:
|
case TRANSFER_IDLE:
|
||||||
if (!node->d.p->multiplayerControl.ready) {
|
if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) {
|
||||||
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
|
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -376,7 +376,7 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) {
|
||||||
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
|
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
|
||||||
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
|
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
|
||||||
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
||||||
node->d.p->multiplayerControl.busy = 1;
|
node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt);
|
||||||
break;
|
break;
|
||||||
case SIO_NORMAL_8:
|
case SIO_NORMAL_8:
|
||||||
node->p->multiRecv[node->id] = 0xFFFF;
|
node->p->multiRecv[node->id] = 0xFFFF;
|
||||||
|
@ -455,7 +455,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
||||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value);
|
||||||
value &= 0xFF8B;
|
value &= 0xFF8B;
|
||||||
if (!node->id) {
|
if (!node->id) {
|
||||||
driver->p->normalControl.si = 1;
|
driver->p->siocnt = GBASIONormalFillSi(driver->p->siocnt);
|
||||||
}
|
}
|
||||||
if (value & 0x0080 && !node->id) {
|
if (value & 0x0080 && !node->id) {
|
||||||
// Internal shift clock
|
// Internal shift clock
|
||||||
|
|
|
@ -104,6 +104,7 @@ static bool frameStarted = false;
|
||||||
static C3D_RenderTarget* upscaleBuffer;
|
static C3D_RenderTarget* upscaleBuffer;
|
||||||
static C3D_Tex upscaleBufferTex;
|
static C3D_Tex upscaleBufferTex;
|
||||||
static bool interframeBlending = false;
|
static bool interframeBlending = false;
|
||||||
|
static bool sgbCrop = false;
|
||||||
|
|
||||||
static aptHookCookie cookie;
|
static aptHookCookie cookie;
|
||||||
static bool core2;
|
static bool core2;
|
||||||
|
@ -382,6 +383,10 @@ static void _gameLoaded(struct mGUIRunner* runner) {
|
||||||
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||||
interframeBlending = fakeBool;
|
interframeBlending = fakeBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||||
|
sgbCrop = fakeBool;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _gameUnloaded(struct mGUIRunner* runner) {
|
static void _gameUnloaded(struct mGUIRunner* runner) {
|
||||||
|
@ -437,6 +442,12 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
||||||
|
|
||||||
int w = corew;
|
int w = corew;
|
||||||
int h = coreh;
|
int h = coreh;
|
||||||
|
if (sgbCrop && w == 256 && h == 224) {
|
||||||
|
w = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||||
|
h = GB_VIDEO_VERTICAL_PIXELS;
|
||||||
|
}
|
||||||
|
int innerw = w;
|
||||||
|
int innerh = h;
|
||||||
// Get greatest common divisor
|
// Get greatest common divisor
|
||||||
while (w != 0) {
|
while (w != 0) {
|
||||||
int temp = h % w;
|
int temp = h % w;
|
||||||
|
@ -444,8 +455,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
||||||
w = temp;
|
w = temp;
|
||||||
}
|
}
|
||||||
int gcd = h;
|
int gcd = h;
|
||||||
unsigned aspectw = corew / gcd;
|
unsigned aspectw = innerw / gcd;
|
||||||
unsigned aspecth = coreh / gcd;
|
unsigned aspecth = innerh / gcd;
|
||||||
int x = 0;
|
int x = 0;
|
||||||
int y = 0;
|
int y = 0;
|
||||||
|
|
||||||
|
@ -517,6 +528,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
||||||
}
|
}
|
||||||
ctrFlushBatch();
|
ctrFlushBatch();
|
||||||
|
|
||||||
|
innerw = corew;
|
||||||
|
innerh = coreh;
|
||||||
corew = w;
|
corew = w;
|
||||||
coreh = h;
|
coreh = h;
|
||||||
screen_h = 240;
|
screen_h = 240;
|
||||||
|
@ -529,19 +542,20 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
||||||
}
|
}
|
||||||
ctrSetViewportSize(screen_w, screen_h, true);
|
ctrSetViewportSize(screen_w, screen_h, true);
|
||||||
|
|
||||||
|
float afw, afh;
|
||||||
switch (screenMode) {
|
switch (screenMode) {
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
case SM_AF_TOP:
|
case SM_AF_TOP:
|
||||||
case SM_AF_BOTTOM:
|
case SM_AF_BOTTOM:
|
||||||
w = screen_w / aspectw;
|
afw = screen_w / (float) aspectw;
|
||||||
h = screen_h / aspecth;
|
afh = screen_h / (float) aspecth;
|
||||||
if (w * aspecth > screen_h) {
|
if (afw * aspecth > screen_h) {
|
||||||
w = aspectw * h;
|
w = innerw * afh / gcd;
|
||||||
h = aspecth * h;
|
h = innerh * afh / gcd;
|
||||||
} else {
|
} else {
|
||||||
h = aspecth * w;
|
h = innerh * afw / gcd;
|
||||||
w = aspectw * w;
|
w = innerw * afw / gcd;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SM_SF_TOP:
|
case SM_SF_TOP:
|
||||||
|
|
|
@ -41,7 +41,7 @@ static void _drawEnd(void) {
|
||||||
|
|
||||||
static uint32_t _pollInput(const struct mInputMap* map) {
|
static uint32_t _pollInput(const struct mInputMap* map) {
|
||||||
SceCtrlData pad;
|
SceCtrlData pad;
|
||||||
sceCtrlPeekBufferPositive(0, &pad, 1);
|
sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
|
||||||
int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0);
|
int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0);
|
||||||
|
|
||||||
if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) {
|
if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) {
|
||||||
|
@ -127,17 +127,17 @@ int main() {
|
||||||
.id = PSP2_INPUT,
|
.id = PSP2_INPUT,
|
||||||
.keyNames = (const char*[]) {
|
.keyNames = (const char*[]) {
|
||||||
"Select",
|
"Select",
|
||||||
0,
|
"L3",
|
||||||
0,
|
"R3",
|
||||||
"Start",
|
"Start",
|
||||||
"Up",
|
"Up",
|
||||||
"Right",
|
"Right",
|
||||||
"Down",
|
"Down",
|
||||||
"Left",
|
"Left",
|
||||||
"L",
|
"L2",
|
||||||
"R",
|
"R2",
|
||||||
0, // L2?
|
"L1",
|
||||||
0, // R2?
|
"R1",
|
||||||
"\1\xC",
|
"\1\xC",
|
||||||
"\1\xA",
|
"\1\xA",
|
||||||
"\1\xB",
|
"\1\xB",
|
||||||
|
|
|
@ -56,6 +56,7 @@ static vita2d_texture* oldTex;
|
||||||
static vita2d_texture* screenshot;
|
static vita2d_texture* screenshot;
|
||||||
static Thread audioThread;
|
static Thread audioThread;
|
||||||
static bool interframeBlending = false;
|
static bool interframeBlending = false;
|
||||||
|
static bool sgbCrop = false;
|
||||||
|
|
||||||
static struct mSceRotationSource {
|
static struct mSceRotationSource {
|
||||||
struct mRotationSource d;
|
struct mRotationSource d;
|
||||||
|
@ -267,7 +268,7 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig
|
||||||
|
|
||||||
uint16_t mPSP2PollInput(struct mGUIRunner* runner) {
|
uint16_t mPSP2PollInput(struct mGUIRunner* runner) {
|
||||||
SceCtrlData pad;
|
SceCtrlData pad;
|
||||||
sceCtrlPeekBufferPositive(0, &pad, 1);
|
sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
|
||||||
|
|
||||||
int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0);
|
int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0);
|
||||||
int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly);
|
int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly);
|
||||||
|
@ -313,8 +314,8 @@ void mPSP2Setup(struct mGUIRunner* runner) {
|
||||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN);
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN);
|
||||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT);
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT);
|
||||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT);
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT);
|
||||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LTRIGGER, GBA_KEY_L);
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, GBA_KEY_L);
|
||||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RTRIGGER, GBA_KEY_R);
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, GBA_KEY_R);
|
||||||
|
|
||||||
struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 };
|
struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 };
|
||||||
mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);
|
mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);
|
||||||
|
@ -365,6 +366,10 @@ void mPSP2Setup(struct mGUIRunner* runner) {
|
||||||
if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
|
if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
|
||||||
camera.cam = mode;
|
camera.cam = mode;
|
||||||
}
|
}
|
||||||
|
int fakeBool;
|
||||||
|
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||||
|
sgbCrop = fakeBool;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mPSP2LoadROM(struct mGUIRunner* runner) {
|
void mPSP2LoadROM(struct mGUIRunner* runner) {
|
||||||
|
@ -398,6 +403,18 @@ void mPSP2LoadROM(struct mGUIRunner* runner) {
|
||||||
interframeBlending = fakeBool;
|
interframeBlending = fakeBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R
|
||||||
|
if (!sceKernelIsPSVitaTV()) {
|
||||||
|
int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2));
|
||||||
|
if (key >= 0) {
|
||||||
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key);
|
||||||
|
}
|
||||||
|
key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2));
|
||||||
|
if (key >= 0) {
|
||||||
|
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MutexInit(&audioContext.mutex);
|
MutexInit(&audioContext.mutex);
|
||||||
ConditionInit(&audioContext.cond);
|
ConditionInit(&audioContext.cond);
|
||||||
memset(audioContext.buffer, 0, sizeof(audioContext.buffer));
|
memset(audioContext.buffer, 0, sizeof(audioContext.buffer));
|
||||||
|
@ -461,8 +478,13 @@ void mPSP2Unpaused(struct mGUIRunner* runner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int fakeBool;
|
int fakeBool;
|
||||||
mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool);
|
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||||
interframeBlending = fakeBool;
|
interframeBlending = fakeBool;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||||
|
sgbCrop = fakeBool;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mPSP2Teardown(struct mGUIRunner* runner) {
|
void mPSP2Teardown(struct mGUIRunner* runner) {
|
||||||
|
@ -507,6 +529,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo
|
||||||
vita2d_draw_texture_tint(backdrop, 0, 0, tint);
|
vita2d_draw_texture_tint(backdrop, 0, 0, tint);
|
||||||
// Fall through
|
// Fall through
|
||||||
case SM_PLAIN:
|
case SM_PLAIN:
|
||||||
|
if (sgbCrop && width == 256 && height == 224) {
|
||||||
|
w = 768;
|
||||||
|
h = 672;
|
||||||
|
scalex = 3;
|
||||||
|
scaley = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
w = 960 / width;
|
w = 960 / width;
|
||||||
h = 544 / height;
|
h = 544 / height;
|
||||||
if (w * height > 544) {
|
if (w * height > 544) {
|
||||||
|
@ -521,6 +550,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo
|
||||||
scaley = scalex;
|
scaley = scalex;
|
||||||
break;
|
break;
|
||||||
case SM_ASPECT:
|
case SM_ASPECT:
|
||||||
|
if (sgbCrop && width == 256 && height == 224) {
|
||||||
|
w = 967;
|
||||||
|
h = 846;
|
||||||
|
scalex = 34.0f / 9.0f;
|
||||||
|
scaley = scalex;
|
||||||
|
break;
|
||||||
|
}
|
||||||
w = 960 / aspectw;
|
w = 960 / aspectw;
|
||||||
h = 544 / aspecth;
|
h = 544 / aspecth;
|
||||||
if (w * aspecth > 544) {
|
if (w * aspecth > 544) {
|
||||||
|
|
|
@ -67,6 +67,18 @@ for line in preprocessed.splitlines():
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
ffi.cdef('\n'.join(lines))
|
ffi.cdef('\n'.join(lines))
|
||||||
|
|
||||||
|
ffi.cdef("""
|
||||||
|
struct GBARTC {
|
||||||
|
int32_t bytesRemaining;
|
||||||
|
int32_t transferStep;
|
||||||
|
int32_t bitsRead;
|
||||||
|
int32_t bits;
|
||||||
|
int32_t commandActive;
|
||||||
|
RTCCommandData command;
|
||||||
|
RTCControl control;
|
||||||
|
uint8_t time[7];
|
||||||
|
};""", packed=True)
|
||||||
|
|
||||||
preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True)
|
preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True)
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
|
|
|
@ -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;
|
using namespace QGBA;
|
||||||
|
|
||||||
AssetTile::AssetTile(QWidget* parent)
|
AssetTile::AssetTile(QWidget* parent)
|
||||||
: QGroupBox(parent)
|
: AssetInfo(parent)
|
||||||
{
|
{
|
||||||
m_ui.setupUi(this);
|
m_ui.setupUi(this);
|
||||||
|
|
||||||
|
@ -42,16 +42,8 @@ AssetTile::AssetTile(QWidget* parent)
|
||||||
m_ui.b->setFont(font);
|
m_ui.b->setFont(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetTile::addCustomProperty(const QString& id, const QString& visibleName) {
|
int AssetTile::customLocation(const QString&) {
|
||||||
QHBoxLayout* newLayout = new QHBoxLayout;
|
return layout()->indexOf(m_ui.line);
|
||||||
newLayout->addWidget(new QLabel(visibleName));
|
|
||||||
QLabel* value = new QLabel;
|
|
||||||
value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
|
||||||
value->setAlignment(Qt::AlignRight);
|
|
||||||
newLayout->addWidget(value);
|
|
||||||
m_customProperties[id] = value;
|
|
||||||
int index = layout()->indexOf(m_ui.line);
|
|
||||||
static_cast<QBoxLayout*>(layout())->insertLayout(index, newLayout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetTile::setController(std::shared_ptr<CoreController> controller) {
|
void AssetTile::setController(std::shared_ptr<CoreController> controller) {
|
||||||
|
@ -149,11 +141,3 @@ void AssetTile::selectColor(int index) {
|
||||||
m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0')));
|
m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0')));
|
||||||
m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0')));
|
m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0')));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetTile::setCustomProperty(const QString& id, const QVariant& value) {
|
|
||||||
QLabel* label = m_customProperties[id];
|
|
||||||
if (!label) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
label->setText(value.toString());
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,13 +15,12 @@ namespace QGBA {
|
||||||
|
|
||||||
class CoreController;
|
class CoreController;
|
||||||
|
|
||||||
class AssetTile : public QGroupBox {
|
class AssetTile : public AssetInfo {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AssetTile(QWidget* parent = nullptr);
|
AssetTile(QWidget* parent = nullptr);
|
||||||
void setController(std::shared_ptr<CoreController>);
|
void setController(std::shared_ptr<CoreController>);
|
||||||
void addCustomProperty(const QString& id, const QString& visibleName);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setPalette(int);
|
void setPalette(int);
|
||||||
|
@ -29,7 +28,9 @@ public slots:
|
||||||
void selectIndex(int);
|
void selectIndex(int);
|
||||||
void setFlip(bool h, bool v);
|
void setFlip(bool h, bool v);
|
||||||
void selectColor(int);
|
void selectColor(int);
|
||||||
void setCustomProperty(const QString& id, const QVariant& value);
|
|
||||||
|
protected:
|
||||||
|
int customLocation(const QString& id = {}) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::AssetTile m_ui;
|
Ui::AssetTile m_ui;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>AssetTile</class>
|
<class>AssetTile</class>
|
||||||
<widget class="QGroupBox" name="AssetTile">
|
<widget class="QGBA::AssetInfo" name="AssetTile">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>171</width>
|
<width>241</width>
|
||||||
<height>355</height>
|
<height>406</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -185,6 +185,12 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>QGBA::AssetInfo</class>
|
||||||
|
<extends>QGroupBox</extends>
|
||||||
|
<header>AssetInfo.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>QGBA::Swatch</class>
|
<class>QGBA::Swatch</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
/* Copyright (c) 2013-2019 Jeffrey Pfau
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
@ -9,6 +9,16 @@
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
#include <mgba/internal/gba/gba.h>
|
||||||
|
#endif
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
#include <mgba/internal/gb/gb.h>
|
||||||
|
#include <mgba/internal/gb/io.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <mgba/core/map-cache.h>
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||||
|
@ -98,3 +108,175 @@ void AssetView::compositeTile(const void* tBuffer, void* buffer, size_t stride,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage AssetView::compositeMap(int map, mMapCacheEntry* mapStatus) {
|
||||||
|
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, map);
|
||||||
|
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
||||||
|
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
|
||||||
|
QImage rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
|
||||||
|
uchar* bgBits = rawMap.bits();
|
||||||
|
for (int j = 0; j < tilesH; ++j) {
|
||||||
|
for (int i = 0; i < tilesW; ++i) {
|
||||||
|
mMapCacheCleanTile(mapCache, mapStatus, i, j);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawMap.rgbSwapped();
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage AssetView::compositeObj(const ObjInfo& objInfo) {
|
||||||
|
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, objInfo.paletteSet);
|
||||||
|
const color_t* rawPalette = mTileCacheGetPalette(tileCache, objInfo.paletteId);
|
||||||
|
unsigned colors = 1 << objInfo.bits;
|
||||||
|
QVector<QRgb> palette;
|
||||||
|
|
||||||
|
palette.append(rawPalette[0] & 0xFFFFFF);
|
||||||
|
for (unsigned c = 1; c < colors && c < 256; ++c) {
|
||||||
|
palette.append(rawPalette[c] | 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage image = QImage(QSize(objInfo.width * 8, objInfo.height * 8), QImage::Format_Indexed8);
|
||||||
|
image.setColorTable(palette);
|
||||||
|
uchar* bits = image.bits();
|
||||||
|
unsigned t = objInfo.tile;
|
||||||
|
for (int y = 0; y < objInfo.height; ++y) {
|
||||||
|
for (int x = 0; x < objInfo.width; ++x, ++t) {
|
||||||
|
compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), bits, objInfo.width * 8, x * 8, y * 8, objInfo.bits);
|
||||||
|
}
|
||||||
|
t += objInfo.stride - objInfo.width;
|
||||||
|
}
|
||||||
|
return image.rgbSwapped();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetView::lookupObj(int id, struct ObjInfo* info) {
|
||||||
|
switch (m_controller->platform()) {
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
case PLATFORM_GBA:
|
||||||
|
return lookupObjGBA(id, info);
|
||||||
|
#endif
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
case PLATFORM_GB:
|
||||||
|
return lookupObjGB(id, info);
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
bool AssetView::lookupObjGBA(int id, struct ObjInfo* info) {
|
||||||
|
if (id > 127) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
|
||||||
|
const GBAObj* obj = &gba->video.oam.obj[id];
|
||||||
|
|
||||||
|
unsigned shape = GBAObjAttributesAGetShape(obj->a);
|
||||||
|
unsigned size = GBAObjAttributesBGetSize(obj->b);
|
||||||
|
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
|
||||||
|
unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
|
||||||
|
unsigned tile = GBAObjAttributesCGetTile(obj->c);
|
||||||
|
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
||||||
|
unsigned tileBase = tile;
|
||||||
|
unsigned paletteSet;
|
||||||
|
unsigned bits;
|
||||||
|
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||||
|
paletteSet = 3;
|
||||||
|
palette = 0;
|
||||||
|
tile /= 2;
|
||||||
|
bits = 8;
|
||||||
|
} else {
|
||||||
|
paletteSet = 2;
|
||||||
|
bits = 4;
|
||||||
|
}
|
||||||
|
ObjInfo newInfo{
|
||||||
|
tile,
|
||||||
|
width / 8,
|
||||||
|
height / 8,
|
||||||
|
width / 8,
|
||||||
|
palette,
|
||||||
|
paletteSet,
|
||||||
|
bits,
|
||||||
|
!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a),
|
||||||
|
GBAObjAttributesCGetPriority(obj->c),
|
||||||
|
GBAObjAttributesBGetX(obj->b),
|
||||||
|
GBAObjAttributesAGetY(obj->a),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
};
|
||||||
|
if (GBAObjAttributesAIsTransformed(obj->a)) {
|
||||||
|
int matIndex = GBAObjAttributesBGetMatIndex(obj->b);
|
||||||
|
const GBAOAMMatrix* mat = &gba->video.oam.mat[matIndex];
|
||||||
|
QTransform invXform(mat->a / 256., mat->c / 256., mat->b / 256., mat->d / 256., 0, 0);
|
||||||
|
newInfo.xform = invXform.inverted();
|
||||||
|
} else {
|
||||||
|
newInfo.hflip = bool(GBAObjAttributesBIsHFlip(obj->b));
|
||||||
|
newInfo.vflip = bool(GBAObjAttributesBIsVFlip(obj->b));
|
||||||
|
}
|
||||||
|
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
|
||||||
|
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
|
||||||
|
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
|
||||||
|
};
|
||||||
|
*info = newInfo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
bool AssetView::lookupObjGB(int id, struct ObjInfo* info) {
|
||||||
|
if (id > 39) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||||
|
const GBObj* obj = &gb->video.oam.obj[id];
|
||||||
|
|
||||||
|
unsigned width = 8;
|
||||||
|
unsigned height = 8;
|
||||||
|
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
|
||||||
|
if (GBRegisterLCDCIsObjSize(lcdc)) {
|
||||||
|
height = 16;
|
||||||
|
}
|
||||||
|
unsigned tile = obj->tile;
|
||||||
|
unsigned palette = 0;
|
||||||
|
if (gb->model >= GB_MODEL_CGB) {
|
||||||
|
if (GBObjAttributesIsBank(obj->attr)) {
|
||||||
|
tile += 512;
|
||||||
|
}
|
||||||
|
palette = GBObjAttributesGetCGBPalette(obj->attr);
|
||||||
|
} else {
|
||||||
|
palette = GBObjAttributesGetPalette(obj->attr);
|
||||||
|
}
|
||||||
|
palette += 8;
|
||||||
|
|
||||||
|
ObjInfo newInfo{
|
||||||
|
tile,
|
||||||
|
1,
|
||||||
|
height / 8,
|
||||||
|
1,
|
||||||
|
palette,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
obj->y != 0 && obj->y < 160 && obj->x != 0 && obj->x < 168,
|
||||||
|
GBObjAttributesGetPriority(obj->attr),
|
||||||
|
obj->x - 8,
|
||||||
|
obj->y - 16,
|
||||||
|
bool(GBObjAttributesIsXFlip(obj->attr)),
|
||||||
|
bool(GBObjAttributesIsYFlip(obj->attr)),
|
||||||
|
};
|
||||||
|
*info = newInfo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool AssetView::ObjInfo::operator!=(const ObjInfo& other) const {
|
||||||
|
return other.tile != tile ||
|
||||||
|
other.width != width ||
|
||||||
|
other.height != height ||
|
||||||
|
other.stride != stride ||
|
||||||
|
other.paletteId != paletteId ||
|
||||||
|
other.paletteSet != paletteSet;
|
||||||
|
}
|
|
@ -6,12 +6,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QTransform>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include <mgba/core/cache-set.h>
|
#include <mgba/core/cache-set.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
struct mMapCacheEntry;
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
class CoreController;
|
class CoreController;
|
||||||
|
@ -22,8 +25,6 @@ Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||||
|
|
||||||
static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8);
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void updateTiles();
|
void updateTiles();
|
||||||
void updateTiles(bool force);
|
void updateTiles(bool force);
|
||||||
|
@ -40,9 +41,43 @@ protected:
|
||||||
void showEvent(QShowEvent*) override;
|
void showEvent(QShowEvent*) override;
|
||||||
|
|
||||||
mCacheSet* const m_cacheSet;
|
mCacheSet* const m_cacheSet;
|
||||||
|
std::shared_ptr<CoreController> m_controller;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct ObjInfo {
|
||||||
|
unsigned tile;
|
||||||
|
unsigned width;
|
||||||
|
unsigned height;
|
||||||
|
unsigned stride;
|
||||||
|
unsigned paletteId;
|
||||||
|
unsigned paletteSet;
|
||||||
|
unsigned bits;
|
||||||
|
|
||||||
|
bool enabled : 1;
|
||||||
|
unsigned priority : 2;
|
||||||
|
int x : 10;
|
||||||
|
int y : 10;
|
||||||
|
bool hflip : 1;
|
||||||
|
bool vflip : 1;
|
||||||
|
QTransform xform;
|
||||||
|
|
||||||
|
bool operator!=(const ObjInfo&) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8);
|
||||||
|
QImage compositeMap(int map, mMapCacheEntry*);
|
||||||
|
QImage compositeObj(const ObjInfo&);
|
||||||
|
|
||||||
|
bool lookupObj(int id, struct ObjInfo*);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<CoreController> m_controller;
|
#ifdef M_CORE_GBA
|
||||||
|
bool lookupObjGBA(int id, struct ObjInfo*);
|
||||||
|
#endif
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
bool lookupObjGB(int id, struct ObjInfo*);
|
||||||
|
#endif
|
||||||
|
|
||||||
QTimer m_updateTimer;
|
QTimer m_updateTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ set(SOURCE_FILES
|
||||||
AbstractUpdater.cpp
|
AbstractUpdater.cpp
|
||||||
Action.cpp
|
Action.cpp
|
||||||
ActionMapper.cpp
|
ActionMapper.cpp
|
||||||
|
AssetInfo.cpp
|
||||||
AssetTile.cpp
|
AssetTile.cpp
|
||||||
AssetView.cpp
|
AssetView.cpp
|
||||||
AudioProcessor.cpp
|
AudioProcessor.cpp
|
||||||
|
@ -72,6 +73,7 @@ set(SOURCE_FILES
|
||||||
Display.cpp
|
Display.cpp
|
||||||
DisplayGL.cpp
|
DisplayGL.cpp
|
||||||
DisplayQt.cpp
|
DisplayQt.cpp
|
||||||
|
FrameView.cpp
|
||||||
GBAApp.cpp
|
GBAApp.cpp
|
||||||
GIFView.cpp
|
GIFView.cpp
|
||||||
GamepadAxisEvent.cpp
|
GamepadAxisEvent.cpp
|
||||||
|
@ -124,6 +126,7 @@ set(UI_FILES
|
||||||
BattleChipView.ui
|
BattleChipView.ui
|
||||||
CheatsView.ui
|
CheatsView.ui
|
||||||
DebuggerConsole.ui
|
DebuggerConsole.ui
|
||||||
|
FrameView.ui
|
||||||
GIFView.ui
|
GIFView.ui
|
||||||
IOViewer.ui
|
IOViewer.ui
|
||||||
LoadSaveState.ui
|
LoadSaveState.ui
|
||||||
|
|
|
@ -34,6 +34,14 @@ ColorPicker& ColorPicker::operator=(const ColorPicker& other) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ColorPicker::setColor(const QColor& color) {
|
||||||
|
m_defaultColor = color;
|
||||||
|
|
||||||
|
QPalette palette = m_parent->palette();
|
||||||
|
palette.setColor(m_parent->backgroundRole(), color);
|
||||||
|
m_parent->setPalette(palette);
|
||||||
|
}
|
||||||
|
|
||||||
bool ColorPicker::eventFilter(QObject* obj, QEvent* event) {
|
bool ColorPicker::eventFilter(QObject* obj, QEvent* event) {
|
||||||
if (event->type() != QEvent::MouseButtonRelease) {
|
if (event->type() != QEvent::MouseButtonRelease) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -24,6 +24,9 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void colorChanged(const QColor&);
|
void colorChanged(const QColor&);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setColor(const QColor&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
|
|
||||||
|
|
|
@ -82,8 +82,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
||||||
controller->m_resetActions.clear();
|
controller->m_resetActions.clear();
|
||||||
|
|
||||||
if (!controller->m_hwaccel) {
|
if (!controller->m_hwaccel) {
|
||||||
controller->m_activeBuffer = &controller->m_buffers[0];
|
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
|
||||||
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer->data()), controller->screenDimensions().width());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QMetaObject::invokeMethod(controller, "didReset");
|
QMetaObject::invokeMethod(controller, "didReset");
|
||||||
|
@ -203,13 +202,33 @@ CoreController::~CoreController() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const color_t* CoreController::drawContext() {
|
const color_t* CoreController::drawContext() {
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
if (m_hwaccel) {
|
if (m_hwaccel) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
QMutexLocker locker(&m_bufferMutex);
|
||||||
return reinterpret_cast<const color_t*>(m_completeBuffer.constData());
|
return reinterpret_cast<const color_t*>(m_completeBuffer.constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage CoreController::getPixels() {
|
||||||
|
QByteArray buffer;
|
||||||
|
QSize size = screenDimensions();
|
||||||
|
size_t stride = size.width() * BYTES_PER_PIXEL;
|
||||||
|
|
||||||
|
if (!m_hwaccel) {
|
||||||
|
buffer = m_completeBuffer;
|
||||||
|
} else {
|
||||||
|
Interrupter interrupter(this);
|
||||||
|
const void* pixels;
|
||||||
|
m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride);
|
||||||
|
stride *= BYTES_PER_PIXEL;
|
||||||
|
buffer.resize(stride * size.height());
|
||||||
|
memcpy(buffer.data(), pixels, buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return QImage(reinterpret_cast<const uchar*>(buffer.constData()),
|
||||||
|
size.width(), size.height(), stride, QImage::Format_RGBX8888);
|
||||||
|
}
|
||||||
|
|
||||||
bool CoreController::isPaused() {
|
bool CoreController::isPaused() {
|
||||||
return mCoreThreadIsPaused(&m_threadContext);
|
return mCoreThreadIsPaused(&m_threadContext);
|
||||||
}
|
}
|
||||||
|
@ -342,15 +361,12 @@ void CoreController::setLogger(LogController* logger) {
|
||||||
|
|
||||||
void CoreController::start() {
|
void CoreController::start() {
|
||||||
if (!m_hwaccel) {
|
if (!m_hwaccel) {
|
||||||
QSize size(1024, 2048);
|
QSize size(256, 384);
|
||||||
m_buffers[0].resize(size.width() * size.height() * sizeof(color_t));
|
m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
|
||||||
m_buffers[1].resize(size.width() * size.height() * sizeof(color_t));
|
m_activeBuffer.fill(0xFF);
|
||||||
m_buffers[0].fill(0xFF);
|
m_completeBuffer = m_activeBuffer;
|
||||||
m_buffers[1].fill(0xFF);
|
|
||||||
m_activeBuffer = &m_buffers[0];
|
|
||||||
m_completeBuffer = m_buffers[0];
|
|
||||||
|
|
||||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());
|
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_patched) {
|
if (!m_patched) {
|
||||||
|
@ -386,8 +402,7 @@ void CoreController::setPaused(bool paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (paused) {
|
if (paused) {
|
||||||
QMutexLocker locker(&m_mutex);
|
addFrameAction([this]() {
|
||||||
m_frameActions.append([this]() {
|
|
||||||
mCoreThreadPauseFromThread(&m_threadContext);
|
mCoreThreadPauseFromThread(&m_threadContext);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -396,13 +411,17 @@ void CoreController::setPaused(bool paused) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::frameAdvance() {
|
void CoreController::frameAdvance() {
|
||||||
QMutexLocker locker(&m_mutex);
|
addFrameAction([this]() {
|
||||||
m_frameActions.append([this]() {
|
|
||||||
mCoreThreadPauseFromThread(&m_threadContext);
|
mCoreThreadPauseFromThread(&m_threadContext);
|
||||||
});
|
});
|
||||||
setPaused(false);
|
setPaused(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::addFrameAction(std::function<void ()> action) {
|
||||||
|
QMutexLocker locker(&m_actionMutex);
|
||||||
|
m_frameActions.append(action);
|
||||||
|
}
|
||||||
|
|
||||||
void CoreController::setSync(bool sync) {
|
void CoreController::setSync(bool sync) {
|
||||||
if (sync) {
|
if (sync) {
|
||||||
m_threadContext.impl->sync.audioWait = m_audioSync;
|
m_threadContext.impl->sync.audioWait = m_audioSync;
|
||||||
|
@ -777,26 +796,39 @@ void CoreController::clearOverride() {
|
||||||
m_override.reset();
|
m_override.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::startVideoLog(const QString& path) {
|
void CoreController::startVideoLog(const QString& path, bool compression) {
|
||||||
if (m_vl) {
|
if (m_vl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
|
if (!vf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startVideoLog(vf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::startVideoLog(VFile* vf, bool compression) {
|
||||||
|
if (m_vl || !vf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Interrupter interrupter(this);
|
Interrupter interrupter(this);
|
||||||
m_vl = mVideoLogContextCreate(m_threadContext.core);
|
m_vl = mVideoLogContextCreate(m_threadContext.core);
|
||||||
m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
|
m_vlVf = vf;
|
||||||
mVideoLogContextSetOutput(m_vl, m_vlVf);
|
mVideoLogContextSetOutput(m_vl, m_vlVf);
|
||||||
|
mVideoLogContextSetCompression(m_vl, compression);
|
||||||
mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
|
mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::endVideoLog() {
|
void CoreController::endVideoLog(bool closeVf) {
|
||||||
if (!m_vl) {
|
if (!m_vl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interrupter interrupter(this);
|
Interrupter interrupter(this);
|
||||||
mVideoLogContextDestroy(m_threadContext.core, m_vl);
|
mVideoLogContextDestroy(m_threadContext.core, m_vl);
|
||||||
if (m_vlVf) {
|
if (m_vlVf && closeVf) {
|
||||||
m_vlVf->close(m_vlVf);
|
m_vlVf->close(m_vlVf);
|
||||||
m_vlVf = nullptr;
|
m_vlVf = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -819,23 +851,20 @@ void CoreController::updateKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::finishFrame() {
|
void CoreController::finishFrame() {
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
if (!m_hwaccel) {
|
if (!m_hwaccel) {
|
||||||
memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size());
|
unsigned width, height;
|
||||||
|
m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
|
||||||
|
|
||||||
// TODO: Generalize this to triple buffering?
|
QMutexLocker locker(&m_bufferMutex);
|
||||||
m_activeBuffer = &m_buffers[0];
|
memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), 256 * height * BYTES_PER_PIXEL);
|
||||||
if (m_activeBuffer == m_completeBuffer) {
|
|
||||||
m_activeBuffer = &m_buffers[1];
|
|
||||||
}
|
|
||||||
// Copy contents to avoid issues when doing frameskip
|
|
||||||
memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size());
|
|
||||||
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();
|
action();
|
||||||
}
|
}
|
||||||
m_frameActions.clear();
|
|
||||||
updateKeys();
|
updateKeys();
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "frameAvailable");
|
QMetaObject::invokeMethod(this, "frameAvailable");
|
||||||
|
|
|
@ -67,6 +67,7 @@ public:
|
||||||
mCoreThread* thread() { return &m_threadContext; }
|
mCoreThread* thread() { return &m_threadContext; }
|
||||||
|
|
||||||
const color_t* drawContext();
|
const color_t* drawContext();
|
||||||
|
QImage getPixels();
|
||||||
|
|
||||||
bool isPaused();
|
bool isPaused();
|
||||||
bool hasStarted();
|
bool hasStarted();
|
||||||
|
@ -102,6 +103,8 @@ public:
|
||||||
bool audioSync() const { return m_audioSync; }
|
bool audioSync() const { return m_audioSync; }
|
||||||
bool videoSync() const { return m_videoSync; }
|
bool videoSync() const { return m_videoSync; }
|
||||||
|
|
||||||
|
void addFrameAction(std::function<void ()> callback);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
@ -157,8 +160,9 @@ public slots:
|
||||||
|
|
||||||
void clearOverride();
|
void clearOverride();
|
||||||
|
|
||||||
void startVideoLog(const QString& path);
|
void startVideoLog(const QString& path, bool compression = true);
|
||||||
void endVideoLog();
|
void startVideoLog(VFile* vf, bool compression = true);
|
||||||
|
void endVideoLog(bool closeVf = true);
|
||||||
|
|
||||||
void setFramebufferHandle(int fb);
|
void setFramebufferHandle(int fb);
|
||||||
|
|
||||||
|
@ -193,8 +197,7 @@ private:
|
||||||
|
|
||||||
bool m_patched = false;
|
bool m_patched = false;
|
||||||
|
|
||||||
QByteArray m_buffers[2];
|
QByteArray m_activeBuffer;
|
||||||
QByteArray* m_activeBuffer;
|
|
||||||
QByteArray m_completeBuffer;
|
QByteArray m_completeBuffer;
|
||||||
bool m_hwaccel = false;
|
bool m_hwaccel = false;
|
||||||
|
|
||||||
|
@ -203,7 +206,8 @@ private:
|
||||||
|
|
||||||
QList<std::function<void()>> m_resetActions;
|
QList<std::function<void()>> m_resetActions;
|
||||||
QList<std::function<void()>> m_frameActions;
|
QList<std::function<void()>> m_frameActions;
|
||||||
QMutex m_mutex;
|
QMutex m_actionMutex{QMutex::Recursive};
|
||||||
|
QMutex m_bufferMutex;
|
||||||
|
|
||||||
int m_activeKeys = 0;
|
int m_activeKeys = 0;
|
||||||
bool m_autofire[32] = {};
|
bool m_autofire[32] = {};
|
||||||
|
|
|
@ -114,7 +114,6 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
||||||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
|
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
|
||||||
#endif
|
#endif
|
||||||
resizePainter();
|
resizePainter();
|
||||||
connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayGL::stopDrawing() {
|
void DisplayGL::stopDrawing() {
|
||||||
|
|
|
@ -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>
|
#include <mgba/internal/gba/video.h>
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
|
#include <mgba/internal/gb/gb.h>
|
||||||
#include <mgba/internal/gb/memory.h>
|
#include <mgba/internal/gb/memory.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -42,6 +43,12 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||||
m_boundary = 2048;
|
m_boundary = 2048;
|
||||||
m_addressBase = BASE_VRAM;
|
m_addressBase = BASE_VRAM;
|
||||||
m_addressWidth = 8;
|
m_addressWidth = 8;
|
||||||
|
m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("size", tr("Size"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("transform", tr("Xform"));
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
|
@ -49,6 +56,9 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||||
m_boundary = 1024;
|
m_boundary = 1024;
|
||||||
m_addressBase = GB_BASE_VRAM;
|
m_addressBase = GB_BASE_VRAM;
|
||||||
m_addressWidth = 4;
|
m_addressWidth = 4;
|
||||||
|
m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
|
||||||
|
m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
|
@ -143,39 +153,82 @@ void MapView::updateTilesGBA(bool force) {
|
||||||
{
|
{
|
||||||
CoreController::Interrupter interrupter(m_controller);
|
CoreController::Interrupter interrupter(m_controller);
|
||||||
int bitmap = -1;
|
int bitmap = -1;
|
||||||
|
int priority = -1;
|
||||||
|
int frame = 0;
|
||||||
|
QString offset(tr("N/A"));
|
||||||
|
QString transform(tr("N/A"));
|
||||||
if (m_controller->platform() == PLATFORM_GBA) {
|
if (m_controller->platform() == PLATFORM_GBA) {
|
||||||
int mode = GBARegisterDISPCNTGetMode(static_cast<GBA*>(m_controller->thread()->core->board)->memory.io[REG_DISPCNT]);
|
uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
|
||||||
|
int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
|
||||||
if (m_map == 2 && mode > 2) {
|
if (m_map == 2 && mode > 2) {
|
||||||
bitmap = mode == 4 ? 1 : 0;
|
bitmap = mode == 4 ? 1 : 0;
|
||||||
|
if (mode != 3) {
|
||||||
|
frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
|
||||||
|
if (mode == 0 || (mode == 1 && m_map != 2)) {
|
||||||
|
offset = QString("%1, %2")
|
||||||
|
.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
|
||||||
|
.arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
|
||||||
|
} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
|
||||||
|
int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
|
||||||
|
refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
|
||||||
|
int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)];
|
||||||
|
refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16;
|
||||||
|
refX <<= 4;
|
||||||
|
refY <<= 4;
|
||||||
|
refX >>= 4;
|
||||||
|
refY >>= 4;
|
||||||
|
offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3);
|
||||||
|
transform = QString("%1 %2\n%3 %4")
|
||||||
|
.arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
|
||||||
|
.arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
|
||||||
|
.arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
|
||||||
|
.arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_controller->platform() == PLATFORM_GB) {
|
||||||
|
uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io;
|
||||||
|
int x = io[m_map == 0 ? 0x42 : 0x4A];
|
||||||
|
int y = io[m_map == 0 ? 0x43 : 0x4B];
|
||||||
|
offset = QString("%1, %2").arg(x).arg(y);
|
||||||
}
|
}
|
||||||
if (bitmap >= 0) {
|
if (bitmap >= 0) {
|
||||||
mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
|
mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
|
||||||
int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
|
int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
|
||||||
int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
|
int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
|
||||||
|
m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0')));
|
||||||
|
m_ui.bgInfo->setCustomProperty("charBase", tr("N/A"));
|
||||||
|
m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height));
|
||||||
|
m_ui.bgInfo->setCustomProperty("priority", priority);
|
||||||
|
m_ui.bgInfo->setCustomProperty("offset", offset);
|
||||||
|
m_ui.bgInfo->setCustomProperty("transform", transform);
|
||||||
m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
|
m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
|
||||||
uchar* bgBits = m_rawMap.bits();
|
uchar* bgBits = m_rawMap.bits();
|
||||||
for (int j = 0; j < height; ++j) {
|
for (int j = 0; j < height; ++j) {
|
||||||
mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
|
mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
|
||||||
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
|
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
|
||||||
}
|
}
|
||||||
|
m_rawMap = m_rawMap.rgbSwapped();
|
||||||
} else {
|
} else {
|
||||||
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
|
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
|
||||||
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
||||||
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
|
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
|
||||||
m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
|
m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
|
||||||
uchar* bgBits = m_rawMap.bits();
|
.arg(m_addressWidth == 8 ? "0x" : "")
|
||||||
for (int j = 0; j < tilesH; ++j) {
|
.arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
|
||||||
for (int i = 0; i < tilesW; ++i) {
|
m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
|
||||||
mMapCacheCleanTile(mapCache, m_mapStatus, i, j);
|
.arg(m_addressWidth == 8 ? "0x" : "")
|
||||||
}
|
.arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
|
||||||
for (int i = 0; i < 8; ++i) {
|
m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
|
||||||
memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
|
m_ui.bgInfo->setCustomProperty("priority", priority);
|
||||||
}
|
m_ui.bgInfo->setCustomProperty("offset", offset);
|
||||||
}
|
m_ui.bgInfo->setCustomProperty("transform", transform);
|
||||||
|
m_rawMap = compositeMap(m_map, m_mapStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_rawMap = m_rawMap.rgbSwapped();
|
|
||||||
QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
|
QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
|
||||||
if (m_ui.magnification->value() > 1) {
|
if (m_ui.magnification->value() > 1) {
|
||||||
map = map.scaled(map.size() * m_ui.magnification->value());
|
map = map.scaled(map.size() * m_ui.magnification->value());
|
||||||
|
|
|
@ -6,18 +6,80 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>641</width>
|
<width>941</width>
|
||||||
<height>489</height>
|
<height>617</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Maps</string>
|
<string>Maps</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0">
|
||||||
<item row="0" column="0">
|
<item row="3" column="0">
|
||||||
<layout class="QVBoxLayout" name="bgLayout"/>
|
<widget class="QGBA::AssetTile" name="tile"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1" rowspan="5">
|
<item row="2" column="0">
|
||||||
|
<widget class="QGBA::AssetInfo" name="bgInfo">
|
||||||
|
<property name="title">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QPushButton" name="exportButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="bgLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="magnification">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string>×</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>8</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Magnification</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" rowspan="5" colspan="2">
|
||||||
<widget class="QScrollArea" name="scrollArea">
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
<property name="widgetResizable">
|
<property name="widgetResizable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -30,8 +92,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>457</width>
|
<width>613</width>
|
||||||
<height>463</height>
|
<height>601</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
@ -71,62 +133,15 @@
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QGBA::AssetTile" name="tile"/>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
|
||||||
<item>
|
|
||||||
<widget class="QSpinBox" name="magnification">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="suffix">
|
|
||||||
<string>×</string>
|
|
||||||
</property>
|
|
||||||
<property name="minimum">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>8</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Magnification</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QPushButton" name="exportButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Export</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>QGBA::AssetInfo</class>
|
||||||
|
<extends>QGroupBox</extends>
|
||||||
|
<header>AssetInfo.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>QGBA::AssetTile</class>
|
<class>QGBA::AssetTile</class>
|
||||||
<extends>QGroupBox</extends>
|
<extends>QGroupBox</extends>
|
||||||
|
|
|
@ -19,9 +19,7 @@
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
#include <mgba/internal/gb/gb.h>
|
#include <mgba/internal/gb/gb.h>
|
||||||
#include <mgba/internal/gb/io.h>
|
|
||||||
#endif
|
#endif
|
||||||
#include <mgba-util/png-io.h>
|
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
@ -53,11 +51,7 @@ ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||||
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
|
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
|
||||||
updateTiles(true);
|
updateTiles(true);
|
||||||
});
|
});
|
||||||
#ifdef USE_PNG
|
|
||||||
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
|
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
|
||||||
#else
|
|
||||||
m_ui.exportButton->setVisible(false);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjView::selectObj(int obj) {
|
void ObjView::selectObj(int obj) {
|
||||||
|
@ -77,79 +71,56 @@ void ObjView::updateTilesGBA(bool force) {
|
||||||
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
|
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
|
||||||
const GBAObj* obj = &gba->video.oam.obj[m_objId];
|
const GBAObj* obj = &gba->video.oam.obj[m_objId];
|
||||||
|
|
||||||
unsigned shape = GBAObjAttributesAGetShape(obj->a);
|
ObjInfo newInfo;
|
||||||
unsigned size = GBAObjAttributesBGetSize(obj->b);
|
lookupObj(m_objId, &newInfo);
|
||||||
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
|
|
||||||
unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
|
m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
|
||||||
unsigned tile = GBAObjAttributesCGetTile(obj->c);
|
m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tiles->setTileCount(width * height / 64);
|
m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
unsigned tileBase = newInfo.tile;
|
||||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
unsigned tile = newInfo.tile;
|
||||||
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
|
||||||
unsigned tileBase = tile;
|
|
||||||
unsigned paletteSet;
|
|
||||||
unsigned bits;
|
|
||||||
if (GBAObjAttributesAIs256Color(obj->a)) {
|
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||||
m_ui.palette->setText("256-color");
|
m_ui.palette->setText("256-color");
|
||||||
paletteSet = 3;
|
|
||||||
m_ui.tile->setBoundary(1024, 1, 3);
|
m_ui.tile->setBoundary(1024, 1, 3);
|
||||||
m_ui.tile->setPalette(0);
|
m_ui.tile->setPalette(0);
|
||||||
m_boundary = 1024;
|
m_boundary = 1024;
|
||||||
palette = 0;
|
tileBase *= 2;
|
||||||
tile /= 2;
|
|
||||||
bits = 8;
|
|
||||||
} else {
|
} else {
|
||||||
m_ui.palette->setText(QString::number(palette));
|
m_ui.palette->setText(QString::number(newInfo.paletteId));
|
||||||
paletteSet = 2;
|
|
||||||
m_ui.tile->setBoundary(2048, 0, 2);
|
m_ui.tile->setBoundary(2048, 0, 2);
|
||||||
m_ui.tile->setPalette(palette);
|
m_ui.tile->setPalette(newInfo.paletteId);
|
||||||
m_boundary = 2048;
|
|
||||||
bits = 4;
|
|
||||||
}
|
}
|
||||||
ObjInfo newInfo{
|
|
||||||
tile,
|
|
||||||
width / 8,
|
|
||||||
height / 8,
|
|
||||||
width / 8,
|
|
||||||
palette,
|
|
||||||
paletteSet,
|
|
||||||
bits
|
|
||||||
};
|
|
||||||
if (newInfo != m_objInfo) {
|
if (newInfo != m_objInfo) {
|
||||||
force = true;
|
force = true;
|
||||||
}
|
}
|
||||||
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
|
|
||||||
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
|
|
||||||
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
|
|
||||||
};
|
|
||||||
m_objInfo = newInfo;
|
m_objInfo = newInfo;
|
||||||
m_tileOffset = tile;
|
m_tileOffset = newInfo.tile;
|
||||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet);
|
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (int y = 0; y < height / 8; ++y) {
|
for (int y = 0; y < newInfo.height; ++y) {
|
||||||
for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) {
|
for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
|
||||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette);
|
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
|
||||||
if (data) {
|
if (data) {
|
||||||
m_ui.tiles->setTile(i, data);
|
m_ui.tiles->setTile(i, data);
|
||||||
} else if (force) {
|
} else if (force) {
|
||||||
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, palette));
|
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tile += newInfo.stride - width / 8;
|
tile += newInfo.stride - newInfo.width;
|
||||||
tileBase += newInfo.stride - width / 8;
|
tileBase += newInfo.stride - newInfo.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
|
m_ui.x->setText(QString::number(newInfo.x));
|
||||||
m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
|
m_ui.y->setText(QString::number(newInfo.y));
|
||||||
m_ui.w->setText(QString::number(width));
|
m_ui.w->setText(QString::number(newInfo.width * 8));
|
||||||
m_ui.h->setText(QString::number(height));
|
m_ui.h->setText(QString::number(newInfo.height * 8));
|
||||||
|
|
||||||
m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
|
m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
|
||||||
m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
|
m_ui.priority->setText(QString::number(newInfo.priority));
|
||||||
m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
|
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||||
m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
|
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||||
m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
|
m_ui.enabled->setChecked(newInfo.enabled);
|
||||||
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
|
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
|
||||||
m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
|
m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
|
||||||
|
|
||||||
|
@ -182,39 +153,17 @@ void ObjView::updateTilesGB(bool force) {
|
||||||
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||||
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
||||||
|
|
||||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
ObjInfo newInfo;
|
||||||
unsigned width = 8;
|
lookupObj(m_objId, &newInfo);
|
||||||
unsigned height = 8;
|
|
||||||
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
|
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||||
if (GBRegisterLCDCIsObjSize(lcdc)) {
|
unsigned tile = newInfo.tile;
|
||||||
height = 16;
|
m_ui.tiles->setTileCount(newInfo.height);
|
||||||
}
|
m_ui.tile->setBoundary(1024, 0, 0);
|
||||||
unsigned tile = obj->tile;
|
m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tiles->setTileCount(width * height / 64);
|
m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tile->setBoundary(1024, 0, 0);
|
m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
|
||||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
|
||||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
|
||||||
unsigned palette = 0;
|
|
||||||
if (gb->model >= GB_MODEL_CGB) {
|
|
||||||
if (GBObjAttributesIsBank(obj->attr)) {
|
|
||||||
tile += 512;
|
|
||||||
}
|
|
||||||
palette = GBObjAttributesGetCGBPalette(obj->attr);
|
|
||||||
} else {
|
|
||||||
palette = GBObjAttributesGetPalette(obj->attr);
|
|
||||||
}
|
|
||||||
m_ui.palette->setText(QString::number(palette));
|
|
||||||
palette += 8;
|
|
||||||
|
|
||||||
ObjInfo newInfo{
|
|
||||||
tile,
|
|
||||||
1,
|
|
||||||
height / 8,
|
|
||||||
1,
|
|
||||||
palette,
|
|
||||||
0,
|
|
||||||
2
|
|
||||||
};
|
|
||||||
if (newInfo != m_objInfo) {
|
if (newInfo != m_objInfo) {
|
||||||
force = true;
|
force = true;
|
||||||
}
|
}
|
||||||
|
@ -223,27 +172,27 @@ void ObjView::updateTilesGB(bool force) {
|
||||||
m_boundary = 1024;
|
m_boundary = 1024;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
m_ui.tile->setPalette(palette);
|
m_ui.tile->setPalette(newInfo.paletteId);
|
||||||
for (int y = 0; y < height / 8; ++y, ++i) {
|
for (int y = 0; y < newInfo.height; ++y, ++i) {
|
||||||
unsigned t = tile + i;
|
unsigned t = tile + i;
|
||||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, palette);
|
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
|
||||||
if (data) {
|
if (data) {
|
||||||
m_ui.tiles->setTile(i, data);
|
m_ui.tiles->setTile(i, data);
|
||||||
} else if (force) {
|
} else if (force) {
|
||||||
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, palette));
|
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui.x->setText(QString::number(obj->x));
|
m_ui.x->setText(QString::number(newInfo.x));
|
||||||
m_ui.y->setText(QString::number(obj->y));
|
m_ui.y->setText(QString::number(newInfo.y));
|
||||||
m_ui.w->setText(QString::number(width));
|
m_ui.w->setText(QString::number(8));
|
||||||
m_ui.h->setText(QString::number(height));
|
m_ui.h->setText(QString::number(newInfo.height * 8));
|
||||||
|
|
||||||
m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
|
m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
|
||||||
m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
|
m_ui.priority->setText(QString::number(newInfo.priority));
|
||||||
m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
|
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||||
m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
|
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||||
m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
|
m_ui.enabled->setChecked(newInfo.enabled);
|
||||||
m_ui.doubleSize->setChecked(false);
|
m_ui.doubleSize->setChecked(false);
|
||||||
m_ui.mosaic->setChecked(false);
|
m_ui.mosaic->setChecked(false);
|
||||||
m_ui.transform->setText(tr("N/A"));
|
m_ui.transform->setText(tr("N/A"));
|
||||||
|
@ -251,51 +200,10 @@ void ObjView::updateTilesGB(bool force) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_PNG
|
|
||||||
void ObjView::exportObj() {
|
void ObjView::exportObj() {
|
||||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
|
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
|
||||||
tr("Portable Network Graphics (*.png)"));
|
tr("Portable Network Graphics (*.png)"));
|
||||||
VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
|
||||||
if (!vf) {
|
|
||||||
LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreController::Interrupter interrupter(m_controller);
|
CoreController::Interrupter interrupter(m_controller);
|
||||||
png_structp png = PNGWriteOpen(vf);
|
QImage obj = compositeObj(m_objInfo);
|
||||||
png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
|
obj.save(filename, "PNG");
|
||||||
|
|
||||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, m_objInfo.paletteSet);
|
|
||||||
const color_t* rawPalette = mTileCacheGetPalette(tileCache, m_objInfo.paletteId);
|
|
||||||
unsigned colors = 1 << m_objInfo.bits;
|
|
||||||
uint32_t palette[256];
|
|
||||||
|
|
||||||
palette[0] = rawPalette[0];
|
|
||||||
for (unsigned c = 1; c < colors && c < 256; ++c) {
|
|
||||||
palette[c] = rawPalette[c] | 0xFF000000;
|
|
||||||
}
|
|
||||||
PNGWritePalette(png, info, palette, colors);
|
|
||||||
|
|
||||||
uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8];
|
|
||||||
unsigned t = m_objInfo.tile;
|
|
||||||
for (int y = 0; y < m_objInfo.height; ++y) {
|
|
||||||
for (int x = 0; x < m_objInfo.width; ++x, ++t) {
|
|
||||||
compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), reinterpret_cast<color_t*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits);
|
|
||||||
}
|
|
||||||
t += m_objInfo.stride - m_objInfo.width;
|
|
||||||
}
|
|
||||||
PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));
|
|
||||||
PNGWriteClose(png, info);
|
|
||||||
delete[] buffer;
|
|
||||||
vf->close(vf);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool ObjView::ObjInfo::operator!=(const ObjInfo& other) {
|
|
||||||
return other.tile != tile ||
|
|
||||||
other.width != width ||
|
|
||||||
other.height != height ||
|
|
||||||
other.stride != stride ||
|
|
||||||
other.paletteId != paletteId ||
|
|
||||||
other.paletteSet != paletteSet;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,8 @@ Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||||
|
|
||||||
#ifdef USE_PNG
|
|
||||||
public slots:
|
public slots:
|
||||||
void exportObj();
|
void exportObj();
|
||||||
#endif
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void selectObj(int);
|
void selectObj(int);
|
||||||
|
@ -43,17 +41,7 @@ private:
|
||||||
std::shared_ptr<CoreController> m_controller;
|
std::shared_ptr<CoreController> m_controller;
|
||||||
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
|
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
|
||||||
int m_objId = 0;
|
int m_objId = 0;
|
||||||
struct ObjInfo {
|
ObjInfo m_objInfo = {};
|
||||||
unsigned tile;
|
|
||||||
unsigned width;
|
|
||||||
unsigned height;
|
|
||||||
unsigned stride;
|
|
||||||
unsigned paletteId;
|
|
||||||
unsigned paletteSet;
|
|
||||||
unsigned bits;
|
|
||||||
|
|
||||||
bool operator!=(const ObjInfo&);
|
|
||||||
} m_objInfo = {};
|
|
||||||
|
|
||||||
int m_tileOffset;
|
int m_tileOffset;
|
||||||
int m_boundary;
|
int m_boundary;
|
||||||
|
|
|
@ -363,7 +363,6 @@ void SettingsView::updateConfig() {
|
||||||
saveSetting("useCgbColors", m_ui.useCgbColors);
|
saveSetting("useCgbColors", m_ui.useCgbColors);
|
||||||
saveSetting("useBios", m_ui.useBios);
|
saveSetting("useBios", m_ui.useBios);
|
||||||
saveSetting("skipBios", m_ui.skipBios);
|
saveSetting("skipBios", m_ui.skipBios);
|
||||||
saveSetting("audioBuffers", m_ui.audioBufferSize);
|
|
||||||
saveSetting("sampleRate", m_ui.sampleRate);
|
saveSetting("sampleRate", m_ui.sampleRate);
|
||||||
saveSetting("videoSync", m_ui.videoSync);
|
saveSetting("videoSync", m_ui.videoSync);
|
||||||
saveSetting("audioSync", m_ui.audioSync);
|
saveSetting("audioSync", m_ui.audioSync);
|
||||||
|
@ -401,6 +400,11 @@ void SettingsView::updateConfig() {
|
||||||
saveSetting("useDiscordPresence", m_ui.useDiscordPresence);
|
saveSetting("useDiscordPresence", m_ui.useDiscordPresence);
|
||||||
saveSetting("gba.audioHle", m_ui.audioHle);
|
saveSetting("gba.audioHle", m_ui.audioHle);
|
||||||
|
|
||||||
|
if (m_ui.audioBufferSize->currentText().toInt() > 8192) {
|
||||||
|
m_ui.audioBufferSize->setCurrentText("8192");
|
||||||
|
}
|
||||||
|
saveSetting("audioBuffers", m_ui.audioBufferSize);
|
||||||
|
|
||||||
if (m_ui.fastForwardUnbounded->isChecked()) {
|
if (m_ui.fastForwardUnbounded->isChecked()) {
|
||||||
saveSetting("fastForwardRatio", "-1");
|
saveSetting("fastForwardRatio", "-1");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "DebuggerConsoleController.h"
|
#include "DebuggerConsoleController.h"
|
||||||
#include "Display.h"
|
#include "Display.h"
|
||||||
#include "CoreController.h"
|
#include "CoreController.h"
|
||||||
|
#include "FrameView.h"
|
||||||
#include "GBAApp.h"
|
#include "GBAApp.h"
|
||||||
#include "GDBController.h"
|
#include "GDBController.h"
|
||||||
#include "GDBWindow.h"
|
#include "GDBWindow.h"
|
||||||
|
@ -971,6 +972,7 @@ void Window::reloadDisplayDriver() {
|
||||||
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
||||||
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
||||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||||
|
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||||
|
|
||||||
attachWidget(m_display.get());
|
attachWidget(m_display.get());
|
||||||
m_display->startDrawing(m_controller);
|
m_display->startDrawing(m_controller);
|
||||||
|
@ -1527,7 +1529,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
m_overrideView->recheck();
|
m_overrideView->recheck();
|
||||||
}, "tools");
|
}, "tools");
|
||||||
|
|
||||||
m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() {
|
m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() {
|
||||||
if (!m_sensorView) {
|
if (!m_sensorView) {
|
||||||
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
|
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
|
||||||
if (m_controller) {
|
if (m_controller) {
|
||||||
|
@ -1557,6 +1559,26 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
|
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
|
||||||
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools");
|
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools");
|
||||||
addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools");
|
addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools");
|
||||||
|
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
Action* frameWindow = addGameAction(tr("&Frame inspector..."), "frameWindow", [this]() {
|
||||||
|
if (!m_frameView) {
|
||||||
|
m_frameView = new FrameView(m_controller);
|
||||||
|
connect(this, &Window::shutdown, this, [this]() {
|
||||||
|
if (m_frameView) {
|
||||||
|
m_frameView->close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_frameView, &QObject::destroyed, this, [this]() {
|
||||||
|
m_frameView = nullptr;
|
||||||
|
});
|
||||||
|
m_frameView->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
}
|
||||||
|
m_frameView->show();
|
||||||
|
}, "tools");
|
||||||
|
m_platformActions.insert(PLATFORM_GBA, frameWindow);
|
||||||
|
#endif
|
||||||
|
|
||||||
addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools");
|
addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools");
|
||||||
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
|
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
|
||||||
|
|
||||||
|
@ -1744,11 +1766,8 @@ void Window::focusCheck() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::updateFrame() {
|
void Window::updateFrame() {
|
||||||
QSize size = m_controller->screenDimensions();
|
|
||||||
QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(),
|
|
||||||
size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
|
|
||||||
QPixmap pixmap;
|
QPixmap pixmap;
|
||||||
pixmap.convertFromImage(currentImage);
|
pixmap.convertFromImage(m_controller->getPixels());
|
||||||
m_screenWidget->setPixmap(pixmap);
|
m_screenWidget->setPixmap(pixmap);
|
||||||
emit paused(true);
|
emit paused(true);
|
||||||
}
|
}
|
||||||
|
@ -1834,6 +1853,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
||||||
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
|
||||||
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
|
||||||
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
|
||||||
|
connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext);
|
||||||
|
|
||||||
connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver);
|
connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver);
|
||||||
connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);
|
connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);
|
||||||
|
|
|
@ -32,6 +32,7 @@ class CoreController;
|
||||||
class CoreManager;
|
class CoreManager;
|
||||||
class DebuggerConsoleController;
|
class DebuggerConsoleController;
|
||||||
class Display;
|
class Display;
|
||||||
|
class FrameView;
|
||||||
class GDBController;
|
class GDBController;
|
||||||
class GIFView;
|
class GIFView;
|
||||||
class LibraryController;
|
class LibraryController;
|
||||||
|
@ -213,6 +214,7 @@ private:
|
||||||
|
|
||||||
std::unique_ptr<OverrideView> m_overrideView;
|
std::unique_ptr<OverrideView> m_overrideView;
|
||||||
std::unique_ptr<SensorView> m_sensorView;
|
std::unique_ptr<SensorView> m_sensorView;
|
||||||
|
FrameView* m_frameView = nullptr;
|
||||||
|
|
||||||
#ifdef USE_FFMPEG
|
#ifdef USE_FFMPEG
|
||||||
VideoView* m_videoView = nullptr;
|
VideoView* m_videoView = nullptr;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "feature/gui/gui-runner.h"
|
#include "feature/gui/gui-runner.h"
|
||||||
#include <mgba/core/blip_buf.h>
|
#include <mgba/core/blip_buf.h>
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
|
#include <mgba/internal/gb/video.h>
|
||||||
#include <mgba/internal/gba/audio.h>
|
#include <mgba/internal/gba/audio.h>
|
||||||
#include <mgba/internal/gba/input.h>
|
#include <mgba/internal/gba/input.h>
|
||||||
#include <mgba-util/gui.h>
|
#include <mgba-util/gui.h>
|
||||||
|
@ -99,6 +100,9 @@ static u8 vmode;
|
||||||
static u32 vwidth;
|
static u32 vwidth;
|
||||||
static u32 vheight;
|
static u32 vheight;
|
||||||
static bool interframeBlending = false;
|
static bool interframeBlending = false;
|
||||||
|
static bool sgbCrop = false;
|
||||||
|
static bool useLightSensor = true;
|
||||||
|
static struct mGUIRunnerLux lightSensor;
|
||||||
|
|
||||||
static enum ScreenMode {
|
static enum ScreenMode {
|
||||||
SM_PA,
|
SM_PA,
|
||||||
|
@ -268,6 +272,10 @@ static void _setup(struct mGUIRunner* runner) {
|
||||||
runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
|
runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
|
||||||
runner->core->setAVStream(runner->core, &stream);
|
runner->core->setAVStream(runner->core, &stream);
|
||||||
|
|
||||||
|
if (runner->core->platform(runner->core) == PLATFORM_GBA && useLightSensor) {
|
||||||
|
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
|
||||||
|
}
|
||||||
|
|
||||||
unsigned mode;
|
unsigned mode;
|
||||||
if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
|
if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
|
||||||
screenMode = mode;
|
screenMode = mode;
|
||||||
|
@ -292,6 +300,22 @@ static void _gameLoaded(struct mGUIRunner* runner) {
|
||||||
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||||
interframeBlending = fakeBool;
|
interframeBlending = fakeBool;
|
||||||
}
|
}
|
||||||
|
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||||
|
sgbCrop = fakeBool;
|
||||||
|
}
|
||||||
|
if (mCoreConfigGetIntValue(&runner->config, "useLightSensor", &fakeBool)) {
|
||||||
|
if (useLightSensor != fakeBool) {
|
||||||
|
useLightSensor = fakeBool;
|
||||||
|
|
||||||
|
if (runner->core->platform(runner->core) == PLATFORM_GBA) {
|
||||||
|
if (useLightSensor) {
|
||||||
|
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
|
||||||
|
} else {
|
||||||
|
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rumble.up = 0;
|
rumble.up = 0;
|
||||||
rumble.down = 0;
|
rumble.down = 0;
|
||||||
|
@ -313,8 +337,14 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height,
|
||||||
|
|
||||||
glUseProgram(program);
|
glUseProgram(program);
|
||||||
glBindVertexArray(vao);
|
glBindVertexArray(vao);
|
||||||
float aspectX = width / (float) vwidth;
|
float inwidth = width;
|
||||||
float aspectY = height / (float) vheight;
|
float inheight = height;
|
||||||
|
if (sgbCrop && width == 256 && height == 224) {
|
||||||
|
inwidth = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||||
|
inheight = GB_VIDEO_VERTICAL_PIXELS;
|
||||||
|
}
|
||||||
|
float aspectX = inwidth / vwidth;
|
||||||
|
float aspectY = inheight / vheight;
|
||||||
float max = 1.f;
|
float max = 1.f;
|
||||||
switch (screenMode) {
|
switch (screenMode) {
|
||||||
case SM_PA:
|
case SM_PA:
|
||||||
|
@ -340,6 +370,11 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (screenMode != SM_SF) {
|
||||||
|
aspectX = width / (float) vwidth;
|
||||||
|
aspectY = height / (float) vheight;
|
||||||
|
}
|
||||||
|
|
||||||
aspectX *= max;
|
aspectX *= max;
|
||||||
aspectY *= max;
|
aspectY *= max;
|
||||||
|
|
||||||
|
@ -543,6 +578,18 @@ int32_t _readGyroZ(struct mRotationSource* source) {
|
||||||
return sixaxis.gyroscope.z * -1.1e9f;
|
return sixaxis.gyroscope.z * -1.1e9f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _lightSensorSample(struct GBALuminanceSource* lux) {
|
||||||
|
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
|
||||||
|
float luxLevel = 0;
|
||||||
|
appletGetCurrentIlluminance(&luxLevel);
|
||||||
|
runnerLux->luxLevel = cbrtf(luxLevel) * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _lightSensorRead(struct GBALuminanceSource* lux) {
|
||||||
|
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
|
||||||
|
return 0xFF - runnerLux->luxLevel;
|
||||||
|
}
|
||||||
|
|
||||||
static int _batteryState(void) {
|
static int _batteryState(void) {
|
||||||
u32 charge;
|
u32 charge;
|
||||||
int state = 0;
|
int state = 0;
|
||||||
|
@ -690,6 +737,9 @@ int main(int argc, char* argv[]) {
|
||||||
rotation.readTiltY = _readTiltY;
|
rotation.readTiltY = _readTiltY;
|
||||||
rotation.readGyroZ = _readGyroZ;
|
rotation.readGyroZ = _readGyroZ;
|
||||||
|
|
||||||
|
lightSensor.d.readLuminance = _lightSensorRead;
|
||||||
|
lightSensor.d.sample = _lightSensorSample;
|
||||||
|
|
||||||
stream.videoDimensionsChanged = NULL;
|
stream.videoDimensionsChanged = NULL;
|
||||||
stream.postVideoFrame = NULL;
|
stream.postVideoFrame = NULL;
|
||||||
stream.postAudioFrame = NULL;
|
stream.postAudioFrame = NULL;
|
||||||
|
@ -707,6 +757,9 @@ int main(int argc, char* argv[]) {
|
||||||
audoutBuffer[i].data_offset = 0;
|
audoutBuffer[i].data_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool illuminanceAvailable = false;
|
||||||
|
appletIsIlluminanceAvailable(&illuminanceAvailable);
|
||||||
|
|
||||||
struct mGUIRunner runner = {
|
struct mGUIRunner runner = {
|
||||||
.params = {
|
.params = {
|
||||||
1280, 720,
|
1280, 720,
|
||||||
|
@ -829,8 +882,19 @@ int main(int argc, char* argv[]) {
|
||||||
},
|
},
|
||||||
.nStates = 6
|
.nStates = 6
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.title = "Use built-in brightness sensor for Boktai",
|
||||||
|
.data = "useLightSensor",
|
||||||
|
.submenu = 0,
|
||||||
|
.state = illuminanceAvailable,
|
||||||
|
.validStates = (const char*[]) {
|
||||||
|
"Off",
|
||||||
|
"On",
|
||||||
|
},
|
||||||
|
.nStates = 2
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.nConfigExtra = 4,
|
.nConfigExtra = 5,
|
||||||
.setup = _setup,
|
.setup = _setup,
|
||||||
.teardown = NULL,
|
.teardown = NULL,
|
||||||
.gameLoaded = _gameLoaded,
|
.gameLoaded = _gameLoaded,
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <mgba/core/blip_buf.h>
|
#include <mgba/core/blip_buf.h>
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
#include "feature/gui/gui-runner.h"
|
#include "feature/gui/gui-runner.h"
|
||||||
|
#include <mgba/internal/gb/video.h>
|
||||||
#include <mgba/internal/gba/audio.h>
|
#include <mgba/internal/gba/audio.h>
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba/internal/gba/input.h>
|
#include <mgba/internal/gba/input.h>
|
||||||
|
@ -113,6 +114,7 @@ static uint16_t* rescaleTexmem;
|
||||||
static GXTexObj rescaleTex;
|
static GXTexObj rescaleTex;
|
||||||
static uint16_t* interframeTexmem;
|
static uint16_t* interframeTexmem;
|
||||||
static GXTexObj interframeTex;
|
static GXTexObj interframeTex;
|
||||||
|
static bool sgbCrop = false;
|
||||||
static int32_t tiltX;
|
static int32_t tiltX;
|
||||||
static int32_t tiltY;
|
static int32_t tiltY;
|
||||||
static int32_t gyroZ;
|
static int32_t gyroZ;
|
||||||
|
@ -862,6 +864,9 @@ void _unpaused(struct mGUIRunner* runner) {
|
||||||
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
||||||
interframeBlending = fakeBool;
|
interframeBlending = fakeBool;
|
||||||
}
|
}
|
||||||
|
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
||||||
|
sgbCrop = fakeBool;
|
||||||
|
}
|
||||||
|
|
||||||
float stretch;
|
float stretch;
|
||||||
if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
|
if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
|
||||||
|
@ -952,20 +957,25 @@ void _drawFrame(struct mGUIRunner* runner, bool faded) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust);
|
|
||||||
int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust);
|
|
||||||
if (hfactor > vfactor) {
|
|
||||||
scaleFactor = vfactor;
|
|
||||||
} else {
|
|
||||||
scaleFactor = hfactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (screenMode == SM_PA) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
vertWidth *= scaleFactor;
|
vertWidth *= scaleFactor;
|
||||||
vertHeight *= scaleFactor;
|
vertHeight *= scaleFactor;
|
||||||
}
|
|
||||||
|
|
||||||
if (screenMode == SM_PA) {
|
|
||||||
_reproj(corew * scaleFactor, coreh * scaleFactor);
|
_reproj(corew * scaleFactor, coreh * scaleFactor);
|
||||||
} else {
|
} else {
|
||||||
_reproj2(corew, coreh);
|
_reproj2(corew, coreh);
|
||||||
|
|
|
@ -47,7 +47,7 @@ static int _strpcmp(const void* a, const void* b) {
|
||||||
return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
|
return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) {
|
static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
|
||||||
_cleanFiles(currentFiles);
|
_cleanFiles(currentFiles);
|
||||||
|
|
||||||
struct VDir* dir = VDirOpen(currentPath);
|
struct VDir* dir = VDirOpen(currentPath);
|
||||||
|
@ -144,6 +144,9 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
|
||||||
free((char*) testItem->title);
|
free((char*) testItem->title);
|
||||||
GUIMenuItemListShift(currentFiles, item, 1);
|
GUIMenuItemListShift(currentFiles, item, 1);
|
||||||
} else {
|
} else {
|
||||||
|
if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) {
|
||||||
|
params->fileIndex = item;
|
||||||
|
}
|
||||||
++item;
|
++item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,14 +155,14 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) {
|
bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) {
|
||||||
struct GUIMenu menu = {
|
struct GUIMenu menu = {
|
||||||
.title = "Select file",
|
.title = "Select file",
|
||||||
.subtitle = params->currentPath,
|
.subtitle = params->currentPath,
|
||||||
.index = params->fileIndex,
|
|
||||||
};
|
};
|
||||||
GUIMenuItemListInit(&menu.items, 0);
|
GUIMenuItemListInit(&menu.items, 0);
|
||||||
_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents);
|
_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect);
|
||||||
|
menu.index = params->fileIndex;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
struct GUIMenuItem* item;
|
struct GUIMenuItem* item;
|
||||||
|
@ -174,7 +177,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_upDirectory(params->currentPath);
|
_upDirectory(params->currentPath);
|
||||||
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) {
|
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -187,7 +190,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
|
||||||
|
|
||||||
struct GUIMenuItemList newFiles;
|
struct GUIMenuItemList newFiles;
|
||||||
GUIMenuItemListInit(&newFiles, 0);
|
GUIMenuItemListInit(&newFiles, 0);
|
||||||
if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents)) {
|
if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) {
|
||||||
_cleanFiles(&newFiles);
|
_cleanFiles(&newFiles);
|
||||||
GUIMenuItemListDeinit(&newFiles);
|
GUIMenuItemListDeinit(&newFiles);
|
||||||
_cleanFiles(&menu.items);
|
_cleanFiles(&menu.items);
|
||||||
|
@ -208,7 +211,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_upDirectory(params->currentPath);
|
_upDirectory(params->currentPath);
|
||||||
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) {
|
if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
params->fileIndex = 0;
|
params->fileIndex = 0;
|
||||||
|
|