diff --git a/CHANGES b/CHANGES index 46b765d88..0bd2f039c 100644 --- a/CHANGES +++ b/CHANGES @@ -27,10 +27,14 @@ Features: - GB: Expose platform information to CLI debugger - Support Discord Rich Presence - Debugger: Add tracing to file - - Map viewer supports bitmapped GBA modes + - Enhanced map viewer, supporting bitmapped GBA modes and more displayed info - OpenGL renderer with high-resolution upscaling support - Experimental high level "XQ" audio for most GBA games - Interframe blending for games that use flicker effects + - Frame inspector for dissecting and debugging rendering + - Switch: Option to use built-in brightness sensor for Boktai + - Ports: Ability to enable or disable all SGB features (closes mgba.io/i/1205) + - Ports: Ability to crop SGB borders off screen (closes mgba.io/i/1204) Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs @@ -38,11 +42,16 @@ Emulation fixes: - GB Video: Delay LYC STAT check (fixes mgba.io/i/1331) - GB Video: Fix window being enabled mid-scanline (fixes mgba.io/i/1328) - GB I/O: Filter IE top bits properly (fixes mgba.io/i/1329) + - GBA Video: Fix wrapped sprite mosaic clamping (fixes mgba.io/i/1432) + - GBA Memory: Fix STM to VRAM (fixes mgba.io/i/1430) Other fixes: - Qt: Fix some Qt display driver race conditions - Core: Improved lockstep driver reliability (Le Hoang Quyen) - Switch: Fix threading-related crash on second launch - Qt: Fix FPS target maxing out at 59.727 (fixes mgba.io/i/1421) + - Core: Fix crashes if core directories aren't set + - Qt: Cap audio buffer size to 8192 (fixes mgba.io/i/1433) + - GB Serialize: Fix loading non-BIOS state from BIOS (fixes mgba.io/i/1280) Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash @@ -64,6 +73,8 @@ Misc: - Qt: Improve sync code - Switch: Dynamic display resizing - Qt: Make mute menu option also toggle fast-forward mute (fixes mgba.io/i/1424) + - Vita: L2/R2 and L3/R3 can now be mapped on PSTV (fixes mgba.io/i/1292) + - mGUI: Remember name and position of last loaded game 0.7.2: (2019-05-25) Emulation fixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index b15ecec57..790fa69d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1029,7 +1029,7 @@ endif() if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) - add_dependencies(${BINARY_NAME} version-info) + add_dependencies(${BINARY_NAME}_libretro version-info) set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2") target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) if(MSVC) diff --git a/README.md b/README.md index 921ba623f..ff1bc5363 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ This will produce a `build-win32` directory with the build products. Replace `mg - mgba/ubuntu:xenial - mgba/ubuntu:bionic - mgba/ubuntu:cosmic +- mgba/ubuntu:disco - mgba/vita - mgba/wii - mgba/windows:w32 diff --git a/README_DE.md b/README_DE.md index 172b5f826..8a1fae9ed 100644 --- a/README_DE.md +++ b/README_DE.md @@ -124,6 +124,7 @@ Dieser Befehl erzeugt ein Verzeichnis `build-win32` mit den erzeugten Programmda - mgba/ubuntu:xenial - mgba/ubuntu:bionic - mgba/ubuntu:cosmic +- mgba/ubuntu:disco - mgba/vita - mgba/wii - mgba/windows:w32 diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0000.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0000.png new file mode 100644 index 000000000..d05b02e16 Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0000.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0001.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0001.png new file mode 100644 index 000000000..bcb7a39ea Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0001.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0002.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0002.png new file mode 100644 index 000000000..92763de4a Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0002.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0003.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0003.png new file mode 100644 index 000000000..c48410ca5 Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0003.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0004.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0004.png new file mode 100644 index 000000000..11cb7a1f4 Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0004.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0005.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0005.png new file mode 100644 index 000000000..0796c9e35 Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0005.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0006.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0006.png new file mode 100644 index 000000000..8172f4670 Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0006.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/baseline_0007.png b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0007.png new file mode 100644 index 000000000..3ccd126c3 Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/baseline_0007.png differ diff --git a/cinema/gba/obj/sma2-mosaic-clamp/test.mvl b/cinema/gba/obj/sma2-mosaic-clamp/test.mvl new file mode 100644 index 000000000..9fece4fdb Binary files /dev/null and b/cinema/gba/obj/sma2-mosaic-clamp/test.mvl differ diff --git a/include/mgba-util/gui/file-select.h b/include/mgba-util/gui/file-select.h index 2b679b05a..fcbb7b789 100644 --- a/include/mgba-util/gui/file-select.h +++ b/include/mgba-util/gui/file-select.h @@ -14,7 +14,7 @@ CXX_GUARD_START struct VFile; -bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)); +bool GUISelectFile(struct GUIParams*, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect); CXX_GUARD_END diff --git a/include/mgba/feature/video-logger.h b/include/mgba/feature/video-logger.h index d33e83155..b466e4b6c 100644 --- a/include/mgba/feature/video-logger.h +++ b/include/mgba/feature/video-logger.h @@ -35,6 +35,11 @@ enum mVideoLoggerEvent { LOGGER_EVENT_GET_PIXELS, }; +enum mVideoLoggerInjectionPoint { + LOGGER_INJECTION_IMMEDIATE = 0, + LOGGER_INJECTION_FIRST_SCANLINE, +}; + struct mVideoLoggerDirtyInfo { enum mVideoLoggerDirtyType type; uint32_t address; @@ -97,6 +102,7 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger); void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger); bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block); +bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger); struct mVideoLogContext; void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId); @@ -104,6 +110,7 @@ void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogCont struct mCore; struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core); +void mVideoLogContextSetCompression(struct mVideoLogContext*, bool enable); void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*); void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core); @@ -115,6 +122,12 @@ void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size); int mVideoLoggerAddChannel(struct mVideoLogContext*); +void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint); +void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask); +void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value); +void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value); +void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value); + struct mCore* mVideoLogCoreFind(struct VFile*); CXX_GUARD_END diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h index ac9d6230d..7d007296a 100644 --- a/include/mgba/internal/gb/gb.h +++ b/include/mgba/internal/gb/gb.h @@ -150,6 +150,7 @@ void GBDestroy(struct GB* gb); void GBReset(struct LR35902Core* cpu); void GBSkipBIOS(struct GB* gb); +void GBMapBIOS(struct GB* gb); void GBUnmapBIOS(struct GB* gb); void GBDetectModel(struct GB* gb); diff --git a/include/mgba/internal/gba/hardware.h b/include/mgba/internal/gba/hardware.h index 3a1d94a62..1b2e31416 100644 --- a/include/mgba/internal/gba/hardware.h +++ b/include/mgba/internal/gba/hardware.h @@ -74,6 +74,7 @@ DECL_BITFIELD(RTCStatus2, uint8_t); DECL_BITS(RTCStatus2, INT1, 0, 4); DECL_BIT(RTCStatus2, INT2, 6); +#ifndef PYCPARSE #pragma pack(push, 1) struct GBARTC { int32_t bytesRemaining; @@ -90,6 +91,9 @@ struct GBARTC { uint8_t time[7]; }; #pragma pack(pop) +#else +struct GBATRC; +#endif struct GBAGBPKeyCallback { struct mKeyCallback d; diff --git a/include/mgba/internal/gba/renderers/common.h b/include/mgba/internal/gba/renderers/common.h index 80a618009..0bd717a8f 100644 --- a/include/mgba/internal/gba/renderers/common.h +++ b/include/mgba/internal/gba/renderers/common.h @@ -16,6 +16,7 @@ struct GBAVideoRendererSprite { struct GBAObj obj; int16_t y; int16_t endY; + int8_t index; }; int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort); diff --git a/include/mgba/internal/gba/renderers/video-software.h b/include/mgba/internal/gba/renderers/video-software.h index 0a705bf4e..98a3fbd7e 100644 --- a/include/mgba/internal/gba/renderers/video-software.h +++ b/include/mgba/internal/gba/renderers/video-software.h @@ -45,6 +45,7 @@ struct GBAVideoSoftwareBackground { color_t* variantPalette; int32_t offsetX; int32_t offsetY; + bool highlight; }; enum { @@ -117,6 +118,8 @@ struct GBAVideoSoftwareRenderer { color_t variantPalette[512]; color_t* objExtPalette; color_t* objExtVariantPalette; + color_t highlightPalette[512]; + color_t highlightVariantPalette[512]; uint16_t blda; uint16_t bldb; @@ -165,6 +168,8 @@ struct GBAVideoSoftwareRenderer { int masterBright; int masterBrightY; + + uint8_t lastHighlightAmount; }; void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer); diff --git a/include/mgba/internal/gba/sio.h b/include/mgba/internal/gba/sio.h index 07ede7009..df4cd2f7a 100644 --- a/include/mgba/internal/gba/sio.h +++ b/include/mgba/internal/gba/sio.h @@ -33,6 +33,23 @@ enum { JOYSTAT_RECV_BIT = 2, }; +DECL_BITFIELD(GBASIONormal, uint16_t); +DECL_BIT(GBASIONormal, Sc, 0); +DECL_BIT(GBASIONormal, InternalSc, 1); +DECL_BIT(GBASIONormal, Si, 2); +DECL_BIT(GBASIONormal, IdleSo, 3); +DECL_BIT(GBASIONormal, Start, 7); +DECL_BIT(GBASIONormal, Length, 12); +DECL_BIT(GBASIONormal, Irq, 14); +DECL_BITFIELD(GBASIOMultiplayer, uint16_t); +DECL_BITS(GBASIOMultiplayer, Baud, 0, 2); +DECL_BIT(GBASIOMultiplayer, Slave, 2); +DECL_BIT(GBASIOMultiplayer, Ready, 3); +DECL_BITS(GBASIOMultiplayer, Id, 4, 2); +DECL_BIT(GBASIOMultiplayer, Error, 6); +DECL_BIT(GBASIOMultiplayer, Busy, 8); +DECL_BIT(GBASIOMultiplayer, Irq, 14); + struct GBASIODriverSet { struct GBASIODriver* normal; struct GBASIODriver* multiplayer; @@ -47,36 +64,7 @@ struct GBASIO { struct GBASIODriver* activeDriver; uint16_t rcnt; - // TODO: Convert to bitfields - union { - struct { - unsigned sc : 1; - unsigned internalSc : 1; - unsigned si : 1; - unsigned idleSo : 1; - unsigned : 3; - unsigned start : 1; - unsigned : 4; - unsigned length : 1; - unsigned : 1; - unsigned irq : 1; - unsigned : 1; - } normalControl; - - struct { - unsigned baud : 2; - unsigned slave : 1; - unsigned ready : 1; - unsigned id : 2; - unsigned error : 1; - unsigned busy : 1; - unsigned : 6; - unsigned irq : 1; - unsigned : 1; - } multiplayerControl; - - uint16_t siocnt; - }; + uint16_t siocnt; }; void GBASIOInit(struct GBASIO* sio); diff --git a/include/mgba/internal/gba/video.h b/include/mgba/internal/gba/video.h index d79c5d5fe..6512dda97 100644 --- a/include/mgba/internal/gba/video.h +++ b/include/mgba/internal/gba/video.h @@ -12,6 +12,7 @@ CXX_GUARD_START #include #include +#include mLOG_DECLARE_CATEGORY(GBA_VIDEO); @@ -84,20 +85,20 @@ struct GBAObj { 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 { struct GBAObj obj[128]; - - 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]; - + struct GBAOAMMatrix mat[32]; uint16_t raw[512]; }; @@ -197,6 +198,11 @@ struct GBAVideoRenderer { bool disableBG[4]; bool disableOBJ; + + bool highlightBG[4]; + bool highlightOBJ[128]; + color_t highlightColor; + uint8_t highlightAmount; }; struct GBAVideo { diff --git a/src/core/core.c b/src/core/core.c index 11f9844cb..beba93fc9 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -164,10 +164,16 @@ bool mCorePreloadFile(struct mCore* core, const char* path) { } bool mCoreAutoloadSave(struct mCore* core) { + if (!core->dirs.save) { + return false; + } return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR)); } bool mCoreAutoloadPatch(struct mCore* core) { + if (!core->dirs.patch) { + return false; + } return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) || core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) || core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY)); @@ -230,6 +236,9 @@ bool mCoreLoadState(struct mCore* core, int slot, int flags) { } struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) { + if (!core->dirs.state) { + return NULL; + } char name[PATH_MAX + 14]; // Quash warning snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot); return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY); diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index 484465b43..ee14a019c 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -118,6 +118,20 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t }, .nStates = 2 }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Enable SGB features", + .data = "sgb.model", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .stateMappings = (const struct GUIVariant[]) { + GUI_V_S("DMG"), + GUI_V_S("SGB"), + }, + .nStates = 2 + }; *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { .title = "Enable SGB borders", .data = "sgb.borders", @@ -128,6 +142,16 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t }, .nStates = 2 }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Crop SGB borders", + .data = "sgb.borderCrop", + .submenu = 0, + .state = false, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; #endif size_t i; const char* mapNames[GUI_MAX_INPUTS + 1]; @@ -173,8 +197,6 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t continue; } if (item->stateMappings) { - item->state = 0; - size_t j; for (j = 0; j < item->nStates; ++j) { const struct GUIVariant* v = &item->stateMappings[j]; @@ -281,7 +303,7 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t } if (!strcmp(item->data, "gba.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS)) { + if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), _biosNamed, GBAIsBIOS, NULL)) { gbaBiosPath[0] = '\0'; } continue; @@ -289,21 +311,21 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t #ifdef M_CORE_GB if (!strcmp(item->data, "gb.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS)) { + if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), _biosNamed, GBIsBIOS, NULL)) { gbBiosPath[0] = '\0'; } continue; } if (!strcmp(item->data, "gbc.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS)) { + if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), _biosNamed, GBIsBIOS, NULL)) { gbcBiosPath[0] = '\0'; } continue; } if (!strcmp(item->data, "sgb.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS)) { + if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), _biosNamed, GBIsBIOS, NULL)) { sgbBiosPath[0] = '\0'; } continue; diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 60a34d79e..038d30a3c 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -628,10 +628,18 @@ void mGUIRunloop(struct mGUIRunner* runner) { } while (true) { char path[PATH_MAX]; - if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL)) { + const char* preselect = mCoreConfigGetValue(&runner->config, "lastGame"); + if (preselect) { + preselect = strrchr(preselect, '/'); + } + if (preselect) { + ++preselect; + } + if (!GUISelectFile(&runner->params, path, sizeof(path), _testExtensions, NULL, preselect)) { break; } mCoreConfigSetValue(&runner->config, "lastDirectory", runner->params.currentPath); + mCoreConfigSetValue(&runner->config, "lastGame", path); mCoreConfigSave(&runner->config); mGUIRun(runner, path); } diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index be1c7b3f7..a240d2c33 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -84,6 +84,11 @@ struct mVideoLogChannel { z_stream inflateStream; #endif + bool injecting; + enum mVideoLoggerInjectionPoint injectionPoint; + uint32_t ignorePackets; + + struct CircleBuffer injectedBuffer; struct CircleBuffer buffer; }; @@ -94,6 +99,7 @@ struct mVideoLogContext { struct mVideoLogChannel channels[mVL_MAX_CHANNELS]; bool write; + bool compression; uint32_t activeChannel; struct VFile* backing; }; @@ -285,14 +291,28 @@ void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uin } bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) { + struct mVideoLogChannel* channel = logger->dataContext; + uint32_t ignorePackets = 0; + if (channel && channel->injectionPoint == LOGGER_INJECTION_IMMEDIATE && !channel->injecting) { + mVideoLoggerRendererRunInjected(logger); + ignorePackets = channel->ignorePackets; + } struct mVideoLoggerDirtyInfo item = {0}; while (logger->readData(logger, &item, sizeof(item), block)) { + if (ignorePackets & (1 << item.type)) { + continue; + } switch (item.type) { + case DIRTY_SCANLINE: + if (channel && channel->injectionPoint == LOGGER_INJECTION_FIRST_SCANLINE && !channel->injecting && item.address == 0) { + mVideoLoggerRendererRunInjected(logger); + ignorePackets = channel->ignorePackets; + } + // Fall through case DIRTY_REGISTER: case DIRTY_PALETTE: case DIRTY_OAM: case DIRTY_VRAM: - case DIRTY_SCANLINE: case DIRTY_FLUSH: case DIRTY_FRAME: case DIRTY_RANGE: @@ -308,15 +328,34 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) { return !block; } +bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + bool res = mVideoLoggerRendererRun(logger, false); + channel->injecting = false; + return res; +} + +void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint injectionPoint) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injectionPoint = injectionPoint; +} + +void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->ignorePackets = mask; +} + static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { struct mVideoLogChannel* channel = logger->dataContext; return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; } static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) { - UNUSED(logger); - UNUSED(data); - UNUSED(length); + struct mVideoLogChannel* channel = logger->dataContext; + if (channel->injecting) { + return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; + } return false; } @@ -465,6 +504,12 @@ struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) { context->initialStateSize = 0; context->initialState = NULL; +#ifdef USE_ZLIB + context->compression = true; +#else + context->compression = false; +#endif + if (core) { context->initialStateSize = core->stateSize(core); context->initialState = anonymousMemoryMap(context->initialStateSize); @@ -482,6 +527,10 @@ void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* v vf->seek(vf, 0, SEEK_SET); } +void mVideoLogContextSetCompression(struct mVideoLogContext* context, bool compression) { + context->compression = compression; +} + void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) { struct mVideoLogHeader header = { { 0 } }; memcpy(header.magic, mVL_MAGIC, sizeof(header.magic)); @@ -499,21 +548,24 @@ void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* struct mVLBlockHeader chheader = { 0 }; STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType); #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* src = VFileFromConstMemory(context->initialState, context->initialStateSize); - _compress(vfm, src); - src->close(src); - STORE_32LE(vfm->size(vfm), 0, &chheader.length); - context->backing->write(context->backing, &chheader, sizeof(chheader)); - _copyVf(context->backing, vfm); - vfm->close(vfm); -#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); + struct VFile* vfm = VFileMemChunk(NULL, 0); + struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize); + _compress(vfm, src); + src->close(src); + STORE_32LE(vfm->size(vfm), 0, &chheader.length); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + _copyVf(context->backing, vfm); + vfm->close(vfm); + } else #endif + { + STORE_32LE(context->initialStateSize, 0, &chheader.length); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + context->backing->write(context->backing, context->initialState, context->initialStateSize); + } } size_t i; @@ -609,6 +661,7 @@ bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) { size_t i; for (i = 0; i < context->nChannels; ++i) { + CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE); CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE); context->channels[i].bufferRemaining = 0; context->channels[i].currentPointer = pointer; @@ -647,9 +700,10 @@ static void _flushBufferCompressed(struct mVideoLogContext* context) { static void _flushBuffer(struct mVideoLogContext* context) { #ifdef USE_ZLIB - // TODO: Make optional - _flushBufferCompressed(context); - return; + if (context->compression) { + _flushBufferCompressed(context); + return; + } #endif struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer; @@ -688,6 +742,7 @@ void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* contex size_t i; for (i = 0; i < context->nChannels; ++i) { + CircleBufferDeinit(&context->channels[i].injectedBuffer); CircleBufferDeinit(&context->channels[i].buffer); #ifdef USE_ZLIB if (context->channels[i].inflating) { @@ -718,6 +773,7 @@ void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core size_t i; for (i = 0; i < context->nChannels; ++i) { + CircleBufferClear(&context->channels[i].injectedBuffer); CircleBufferClear(&context->channels[i].buffer); context->channels[i].bufferRemaining = 0; context->channels[i].currentPointer = pointer; @@ -744,10 +800,35 @@ int mVideoLoggerAddChannel(struct mVideoLogContext* context) { int chid = context->nChannels; ++context->nChannels; context->channels[chid].p = context; + CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE); CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE); + context->channels[chid].injecting = false; + context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE; + context->channels[chid].ignorePackets = 0; return chid; } +void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + mVideoLoggerRendererWriteVideoRegister(logger, address, value); + channel->injecting = false; +} + +void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + mVideoLoggerRendererWritePalette(logger, address, value); + channel->injecting = false; +} + +void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLogChannel* channel = logger->dataContext; + channel->injecting = true; + mVideoLoggerRendererWriteOAM(logger, address, value); + channel->injecting = false; +} + #ifdef USE_ZLIB static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { uint8_t fbuffer[0x400]; @@ -900,12 +981,16 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d if (channelId >= mVL_MAX_CHANNELS) { return 0; } - if (CircleBufferSize(&channel->buffer) >= length) { - return CircleBufferRead(&channel->buffer, data, length); + struct CircleBuffer* buffer = &channel->buffer; + if (channel->injecting) { + buffer = &channel->injectedBuffer; + } + if (CircleBufferSize(buffer) >= length) { + return CircleBufferRead(buffer, data, length); } ssize_t size = 0; - if (CircleBufferSize(&channel->buffer)) { - size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer)); + if (CircleBufferSize(buffer)) { + size = CircleBufferRead(buffer, data, CircleBufferSize(buffer)); if (size <= 0) { return size; } @@ -915,7 +1000,7 @@ static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* d if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) { return size; } - size += CircleBufferRead(&channel->buffer, data, length); + size += CircleBufferRead(buffer, data, length); return size; } @@ -929,16 +1014,20 @@ static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const _flushBuffer(context); context->activeChannel = channelId; } - if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) { + struct CircleBuffer* buffer = &channel->buffer; + if (channel->injecting) { + buffer = &channel->injectedBuffer; + } + if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) { _flushBuffer(context); - if (CircleBufferCapacity(&channel->buffer) < length) { - CircleBufferDeinit(&channel->buffer); - CircleBufferInit(&channel->buffer, toPow2(length << 1)); + if (CircleBufferCapacity(buffer) < length) { + CircleBufferDeinit(buffer); + CircleBufferInit(buffer, toPow2(length << 1)); } } - ssize_t read = CircleBufferWrite(&channel->buffer, data, length); - if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) { + ssize_t read = CircleBufferWrite(buffer, data, length); + if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) { _flushBuffer(context); } return read; diff --git a/src/gb/core.c b/src/gb/core.c index d0b592ff5..75ee8cc7b 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -902,9 +902,11 @@ static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* co static void _GBCoreEndVideoLog(struct mCore* core) { struct GBCore* gbcore = (struct GBCore*) core; struct GB* gb = core->board; - GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); - free(gbcore->proxyRenderer.logger); - gbcore->proxyRenderer.logger = NULL; + if (gbcore->proxyRenderer.logger) { + GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); + free(gbcore->proxyRenderer.logger); + gbcore->proxyRenderer.logger = NULL; + } } #endif @@ -1007,6 +1009,7 @@ static void _GBVLPStartFrameCallback(void *context) { GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); mVideoLogContextRewind(gbcore->logContext, core); GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer); + gb->earlyExit = true; } } @@ -1022,6 +1025,7 @@ static bool _GBVLPInit(struct mCore* core) { gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback; gbcore->logCallbacks.context = core; core->addCoreCallbacks(core, &gbcore->logCallbacks); + core->videoLogger = gbcore->proxyRenderer.logger; return true; } diff --git a/src/gb/gb.c b/src/gb/gb.c index 5f5178a90..a03eb36ff 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -419,14 +419,7 @@ void GBReset(struct LR35902Core* cpu) { gb->biosVf->close(gb->biosVf); gb->biosVf = NULL; } else { - gb->biosVf->seek(gb->biosVf, 0, SEEK_SET); - gb->memory.romBase = malloc(GB_SIZE_CART_BANK0); - ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0); - memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size); - if (size > 0x100) { - memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge)); - } - + GBMapBIOS(gb); cpu->a = 0; cpu->f.packed = 0; cpu->c = 0; @@ -563,6 +556,16 @@ void GBSkipBIOS(struct GB* gb) { } } +void GBMapBIOS(struct GB* gb) { + gb->biosVf->seek(gb->biosVf, 0, SEEK_SET); + gb->memory.romBase = malloc(GB_SIZE_CART_BANK0); + ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0); + memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size); + if (size > 0x100) { + memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge)); + } +} + void GBUnmapBIOS(struct GB* gb) { if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { free(gb->memory.romBase); diff --git a/src/gb/io.c b/src/gb/io.c index beecce12a..c7b08dd03 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -185,8 +185,10 @@ void GBIOReset(struct GB* gb) { GBIOWrite(gb, REG_NR51, 0xF3); if (!gb->biosVf) { GBIOWrite(gb, REG_LCDC, 0x91); + gb->memory.io[0x50] = 1; } else { GBIOWrite(gb, REG_LCDC, 0x00); + gb->memory.io[0x50] = 0xFF; } GBIOWrite(gb, REG_SCY, 0x00); GBIOWrite(gb, REG_SCX, 0x00); diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 7a4f5de4b..78d455300 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -135,6 +135,16 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { if (ucheck16 >= 0x40) { mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range"); } + bool differentBios = !gb->biosVf || gb->model != state->model; + if (state->io[0x50] == 0xFF) { + if (differentBios) { + mLOG(GB_STATE, WARN, "Incompatible savestate, please restart with correct BIOS in %s mode", GBModelToName(state->model)); + error = true; + } else { + // TODO: Make it work correctly + mLOG(GB_STATE, WARN, "Loading savestate in BIOS. This may not work correctly"); + } + } if (error) { return false; } @@ -187,6 +197,12 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state); + if (gb->memory.io[0x50] == 0xFF) { + GBMapBIOS(gb); + } else { + GBUnmapBIOS(gb); + } + if (gb->model & GB_MODEL_SGB && canSgb) { GBSGBDeserialize(gb, state); } diff --git a/src/gb/video.c b/src/gb/video.c index 8aa978292..cfe80f913 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -343,19 +343,19 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3)); return; } + if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { + mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); + } - GBFrameEnded(video->p); - mCoreSyncPostFrame(video->p->sync); --video->frameskipCounter; if (video->frameskipCounter < 0) { video->renderer->finishFrame(video->renderer); video->frameskipCounter = video->frameskip; } + GBFrameEnded(video->p); + mCoreSyncPostFrame(video->p->sync); ++video->frameCounter; - if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { - mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); - } GBFrameStarted(video->p); } diff --git a/src/gba/core.c b/src/gba/core.c index 99a0a3bd6..1b59583fd 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -137,8 +137,11 @@ struct GBACore { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) struct GBAVideoGLRenderer glRenderer; #endif +#ifndef MINIMAL_CORE + struct GBAVideoProxyRenderer vlProxy; struct GBAVideoProxyRenderer proxyRenderer; struct mVideoLogContext* logContext; +#endif struct mCoreCallbacks logCallbacks; #ifndef DISABLE_THREADING struct mVideoThreadProxy threadProxy; @@ -170,7 +173,9 @@ static bool _GBACoreInit(struct mCore* core) { gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; +#ifndef MINIMAL_CORE gbacore->logContext = NULL; +#endif gbacore->audioMixer = NULL; GBACreate(gba); @@ -192,7 +197,10 @@ static bool _GBACoreInit(struct mCore* core) { #ifndef DISABLE_THREADING mVideoThreadProxyCreate(&gbacore->threadProxy); #endif +#ifndef MINIMAL_CORE + gbacore->vlProxy.logger = NULL; gbacore->proxyRenderer.logger = NULL; +#endif gbacore->keys = 0; gba->keySource = &gbacore->keys; @@ -475,11 +483,13 @@ static void _GBACoreReset(struct mCore* core) { } } #endif +#ifndef MINIMAL_CORE if (core->videoLogger) { gbacore->proxyRenderer.logger = core->videoLogger; GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer); renderer = &gbacore->proxyRenderer.d; } +#endif GBAVideoAssociateRenderer(&gba->video, renderer); } @@ -1000,21 +1010,23 @@ static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* c state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM; int channelId = mVideoLoggerAddChannel(context); - gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); - mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, false); - mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, context, channelId); - gbacore->proxyRenderer.logger->block = false; + gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbacore->vlProxy.logger, false); + mVideoLoggerAttachChannel(gbacore->vlProxy.logger, context, channelId); + gbacore->vlProxy.logger->block = false; - GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, &gbacore->renderer.d); - GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + GBAVideoProxyRendererCreate(&gbacore->vlProxy, gba->video.renderer); + GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy); } static void _GBACoreEndVideoLog(struct mCore* core) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = core->board; - GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); - free(gbacore->proxyRenderer.logger); - gbacore->proxyRenderer.logger = NULL; + if (gbacore->vlProxy.logger) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy); + free(gbacore->vlProxy.logger); + gbacore->vlProxy.logger = NULL; + } } #endif @@ -1112,10 +1124,11 @@ static void _GBAVLPStartFrameCallback(void *context) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = core->board; - if (!mVideoLoggerRendererRun(gbacore->proxyRenderer.logger, true)) { - GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + if (!mVideoLoggerRendererRun(gbacore->vlProxy.logger, true)) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy); mVideoLogContextRewind(gbacore->logContext, core); - GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy); + gba->earlyExit = true; } } @@ -1124,13 +1137,14 @@ static bool _GBAVLPInit(struct mCore* core) { if (!_GBACoreInit(core)) { return false; } - gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); - mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, true); - GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, NULL); + gbacore->vlProxy.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbacore->vlProxy.logger, true); + GBAVideoProxyRendererCreate(&gbacore->vlProxy, NULL); memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks)); gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback; gbacore->logCallbacks.context = core; core->addCoreCallbacks(core, &gbacore->logCallbacks); + core->videoLogger = gbacore->vlProxy.logger; return true; } @@ -1145,8 +1159,8 @@ static void _GBAVLPDeinit(struct mCore* core) { static void _GBAVLPReset(struct mCore* core) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = (struct GBA*) core->board; - if (gba->video.renderer == &gbacore->proxyRenderer.d) { - GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + if (gba->video.renderer == &gbacore->vlProxy.d) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->vlProxy); } else if (gbacore->renderer.outputBuffer) { struct GBAVideoRenderer* renderer = &gbacore->renderer.d; GBAVideoAssociateRenderer(&gba->video, renderer); @@ -1154,7 +1168,7 @@ static void _GBAVLPReset(struct mCore* core) { ARMReset(core->cpu); mVideoLogContextRewind(gbacore->logContext, core); - GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + GBAVideoProxyRendererShim(&gba->video, &gbacore->vlProxy); // Make sure CPU loop never spins GBAHalt(gba); @@ -1170,7 +1184,7 @@ static bool _GBAVLPLoadROM(struct mCore* core, struct VFile* vf) { gbacore->logContext = NULL; return false; } - mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, gbacore->logContext, 0); + mVideoLoggerAttachChannel(gbacore->vlProxy.logger, gbacore->logContext, 0); return true; } diff --git a/src/gba/extra/battlechip.c b/src/gba/extra/battlechip.c index fb411eef5..ae8aedff0 100644 --- a/src/gba/extra/battlechip.c +++ b/src/gba/extra/battlechip.c @@ -87,7 +87,7 @@ void _battlechipTransfer(struct GBASIOBattlechipGate* gate) { if (gate->d.p->mode == SIO_NORMAL_32) { cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000; } else { - cycles = GBASIOCyclesPerTransfer[gate->d.p->multiplayerControl.baud][1]; + cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1]; } mTimingDeschedule(&gate->d.p->p->timing, &gate->event); mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles); @@ -100,8 +100,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle if (gate->d.p->mode == SIO_NORMAL_32) { gate->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0; gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0; - gate->d.p->normalControl.start = 0; - if (gate->d.p->normalControl.irq) { + gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt); + if (GBASIONormalIsIrq(gate->d.p->siocnt)) { GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); } return; @@ -112,8 +112,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle gate->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = cmd; gate->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; gate->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; - gate->d.p->multiplayerControl.busy = 0; - gate->d.p->multiplayerControl.id = 0; + gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt); + gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0); mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state); @@ -193,7 +193,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply; - if (gate->d.p->multiplayerControl.irq) { + if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) { GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate); } } diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c index ae16d184a..fd773a1d8 100644 --- a/src/gba/extra/proxy.c +++ b/src/gba/extra/proxy.c @@ -44,6 +44,17 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct renderer->d.disableBG[3] = false; renderer->d.disableOBJ = false; + renderer->d.highlightBG[0] = false; + renderer->d.highlightBG[1] = false; + renderer->d.highlightBG[2] = false; + renderer->d.highlightBG[3] = false; + int i; + for (i = 0; i < 128; ++i) { + renderer->d.highlightOBJ[i] = false; + } + renderer->d.highlightColor = 0xFFFFFF; + renderer->d.highlightAmount = 0; + renderer->logger->context = renderer; renderer->logger->parsePacket = _parsePacket; renderer->logger->handleEvent = _handleEvent; @@ -205,6 +216,12 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2]; proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3]; proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ; + proxyRenderer->backend->highlightBG[0] = proxyRenderer->d.highlightBG[0]; + proxyRenderer->backend->highlightBG[1] = proxyRenderer->d.highlightBG[1]; + proxyRenderer->backend->highlightBG[2] = proxyRenderer->d.highlightBG[2]; + proxyRenderer->backend->highlightBG[3] = proxyRenderer->d.highlightBG[3]; + memcpy(proxyRenderer->backend->highlightOBJ, proxyRenderer->d.highlightOBJ, sizeof(proxyRenderer->backend->highlightOBJ)); + proxyRenderer->backend->highlightAmount = proxyRenderer->d.highlightAmount; if (item->address < GBA_VIDEO_VERTICAL_PIXELS) { proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address); } diff --git a/src/gba/hardware.c b/src/gba/hardware.c index 42a0fd950..cadba8924 100644 --- a/src/gba/hardware.c +++ b/src/gba/hardware.c @@ -577,10 +577,10 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat ++gbp->p->gbpTxPosition; gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx; gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16; - if (gbp->d.p->normalControl.irq) { + if (GBASIONormalIsIrq(gbp->d.p->siocnt)) { GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate); } - gbp->d.p->normalControl.start = 0; + gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt); gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080; } diff --git a/src/gba/memory.c b/src/gba/memory.c index e866d15de..1d7767020 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -396,9 +396,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { value = 0; \ break; \ } \ - address &= 0x00017FFC; \ + LOAD_32(value, address & 0x00017FFC, gba->video.vram); \ + } else { \ + LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ } \ - LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ wait += waitstatesRegion[REGION_VRAM]; #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); @@ -530,9 +531,10 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = 0; break; } - address &= 0x00017FFE; + LOAD_16(value, address & 0x00017FFE, gba->video.vram); + } else { + LOAD_16(value, address & 0x0001FFFE, gba->video.vram); } - LOAD_16(value, address & 0x0001FFFE, gba->video.vram); break; case REGION_OAM: LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw); @@ -645,9 +647,10 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = 0; break; } - address &= 0x00017FFF; + value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF]; + } else { + value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; } - value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; break; case REGION_OAM: 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); \ break; \ } \ - address &= 0x00017FFC; \ - } \ - 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)); \ + LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram); \ + if (oldValue != value) { \ + STORE_32(value, address & 0x00017FFC, gba->video.vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \ + } \ + } else { \ + LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); \ + if (oldValue != value) { \ + STORE_32(value, address & 0x0001FFFC, gba->video.vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ + } \ } \ wait += waitstatesRegion[REGION_VRAM]; @@ -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); break; } - address &= 0x00017FFE; - } - 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); + LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram); + if (value != oldValue) { + STORE_16(value, address & 0x00017FFE, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE); + } + } else { + LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram); + if (value != oldValue) { + STORE_16(value, address & 0x0001FFFE, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); + } } break; case REGION_OAM: diff --git a/src/gba/renderers/common.c b/src/gba/renderers/common.c index ceb51e42b..3668edf31 100644 --- a/src/gba/renderers/common.c +++ b/src/gba/renderers/common.c @@ -34,6 +34,7 @@ int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites[oamMax].y = y; sprites[oamMax].endY = y + height; sprites[oamMax].obj = obj; + sprites[oamMax].index = i; ++oamMax; } } diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 2ae610825..1b16da002 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -155,7 +155,7 @@ static const char* const _renderMode0 = " if ((size & 1) == 1) {\n" " coord.y += coord.x & 256;\n" " }\n" - " coord &= ivec2(255, 511);\n" + " coord &= ivec2(255, 1023);\n" " int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n" " vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n" " int tileFlags = int(map.g * 15.9);\n" @@ -442,7 +442,7 @@ static const char* const _renderObj = "uniform ivec4 mosaic;\n" "OUT(0) out vec4 color;\n" "OUT(1) out ivec4 flags;\n" - "OUT(2) out ivec3 window;\n" + "OUT(2) out ivec4 window;\n" "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" @@ -467,7 +467,7 @@ static const char* const _renderObj = " color = pix;\n" " flags = inflags;\n" " gl_FragDepth = float(flags.x) / 16.;\n" - " window = objwin.yzw;\n" + " window = ivec4(objwin.yzw, 0);\n" "}"; static const struct GBAVideoGLUniform _uniformsWindow[] = { @@ -488,7 +488,7 @@ static const char* const _renderWindow = "uniform ivec3 flags;\n" "uniform ivec4 win0[160];\n" "uniform ivec4 win1[160];\n" - "OUT(0) out ivec3 window;\n" + "OUT(0) out ivec4 window;\n" "void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n" " bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n" @@ -526,7 +526,7 @@ static const char* const _renderWindow = "void main() {\n" " int dispflags = (dispcnt & 0x1F) | 0x20;\n" " if ((dispcnt & 0xE0) == 0) {\n" - " window = ivec3(dispflags, blend);\n" + " window = ivec4(dispflags, blend, 0);\n" " } else {\n" " ivec3 windowFlags = ivec3(flags.z, blend);\n" " if ((dispcnt & 0x40) != 0) { \n" @@ -535,7 +535,7 @@ static const char* const _renderWindow = " if ((dispcnt & 0x20) != 0) { \n" " crop(interpolate(win0), flags.x, windowFlags);\n" " }\n" - " window = windowFlags;\n" + " window = ivec4(windowFlags, 0);\n" " }\n" "}\n"; @@ -651,6 +651,17 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) { renderer->d.disableBG[3] = false; renderer->d.disableOBJ = false; + renderer->d.highlightBG[0] = false; + renderer->d.highlightBG[1] = false; + renderer->d.highlightBG[2] = false; + renderer->d.highlightBG[3] = false; + int i; + for (i = 0; i < 128; ++i) { + renderer->d.highlightOBJ[i] = false; + } + renderer->d.highlightColor = 0xFFFFFF; + renderer->d.highlightAmount = 0; + renderer->scale = 1; } @@ -1369,7 +1380,7 @@ void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) { glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); - glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 1.f }); + glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xF8) / 248., ((backdrop >> 8) & 0xF8) / 248., (backdrop & 0xF8) / 248., 1.f }); glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 }); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index e578a0359..01a1d0fcf 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -613,16 +613,21 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer uint32_t screenBase; uint32_t charBase; int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - color_t* mainPalette; + color_t* mainPalette = renderer->normalPalette; if (background->multipalette && background->extPalette) { mainPalette = background->extPalette; if (variant) { mainPalette = background->variantPalette; } } else { - mainPalette = renderer->normalPalette; + if (renderer->d.highlightAmount && background->highlight) { + mainPalette = renderer->highlightPalette; + } if (variant) { mainPalette = renderer->variantPalette; + if (renderer->d.highlightAmount && background->highlight) { + mainPalette = renderer->highlightVariantPalette; + } } } color_t* palette = mainPalette; diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 01a08cb4e..dc8630648 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -212,7 +212,7 @@ renderer->row[outX] |= FLAG_OBJWIN; \ } -int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y) { int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0]; int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1]; int start = renderer->start; @@ -255,6 +255,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } color_t* palette = &renderer->normalPalette[0x100]; + if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) { + palette = &renderer->highlightPalette[0x100]; + } color_t* objwinPalette = palette; if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) { @@ -269,6 +272,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } } else if (variant) { palette = &renderer->variantPalette[0x100]; + if (renderer->d.highlightAmount && renderer->d.highlightOBJ[index]) { + palette = &renderer->highlightVariantPalette[0x100]; + } if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) { objwinPalette = palette; } diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index fc2d2f795..79228a233 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -26,7 +26,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int index, int y); void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* renderer, int y); @@ -145,11 +145,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re int objwinForceEnable = 0; \ UNUSED(objwinForceEnable); \ color_t* objwinPalette = renderer->normalPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + objwinPalette = renderer->highlightPalette; \ + } \ UNUSED(objwinPalette); \ if (objwinSlowPath) { \ if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ objwinPalette = renderer->variantPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + palette = renderer->highlightVariantPalette; \ + } \ } \ switch (background->index) { \ case 0: \ @@ -204,8 +210,14 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ color_t* palette = renderer->normalPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + palette = renderer->highlightPalette; \ + } \ if (variant) { \ palette = renderer->variantPalette; \ + if (renderer->d.highlightAmount && background->highlight) { \ + palette = renderer->highlightVariantPalette; \ + } \ } \ UNUSED(palette); \ PREPARE_OBJWIN; diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index acb05e52c..f87f1edae 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -68,6 +68,17 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) { renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS; renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS; + renderer->d.highlightBG[0] = false; + renderer->d.highlightBG[1] = false; + renderer->d.highlightBG[2] = false; + renderer->d.highlightBG[3] = false; + int i; + for (i = 0; i < 128; ++i) { + renderer->d.highlightOBJ[i] = false; + } + renderer->d.highlightColor = GBA_COLOR_WHITE; + renderer->d.highlightAmount = 0; + renderer->temporaryBuffer = 0; } @@ -573,11 +584,23 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y); int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y); softwareRenderer->d.vramOBJ[0] = objVramBase; + if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) { + softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount; + if (softwareRenderer->lastHighlightAmount) { + softwareRenderer->blendDirty = true; + } + } + if (softwareRenderer->blendDirty) { _updatePalettes(softwareRenderer); softwareRenderer->blendDirty = false; } + softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0]; + softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1]; + softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2]; + softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3]; + int w; unsigned priority; for (priority = 0; priority < 4; ++priority) { @@ -945,12 +968,12 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { continue; } - if (GBAObjAttributesAIsMosaic(sprite->obj.a)) { + if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) { localY = mosaicY; - if (localY < sprite->y) { + if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) { localY = sprite->y; } - if (localY >= sprite->endY) { + if (localY >= (sprite->endY & 0xFF)) { localY = sprite->endY - 1; } } @@ -965,7 +988,7 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere continue; } - int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY); + int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY); spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c); } if (renderer->spriteCyclesRemaining <= 0) { @@ -993,4 +1016,12 @@ static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) { renderer->variantPalette[i] = renderer->normalPalette[i]; } } + unsigned highlightAmount = renderer->d.highlightAmount >> 4; + + if (highlightAmount) { + for (i = 0; i < 512; ++i) { + renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor); + renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor); + } + } } diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index 356fa130a..506854a7e 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -71,7 +71,7 @@ void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLock bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - node->d.p->multiplayerControl.slave = node->id > 0; + node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0); mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id); node->event.context = node; node->event.name = "GBA SIO Lockstep"; @@ -99,10 +99,10 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister; node->d.p->rcnt |= 3; ATOMIC_ADD(node->p->attachedMulti, 1); - node->d.p->multiplayerControl.ready = node->p->attachedMulti == node->p->d.attached; + node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached); if (node->id) { node->d.p->rcnt |= 4; - node->d.p->multiplayerControl.slave = 1; + node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt); } break; case SIO_NORMAL_32: @@ -178,10 +178,10 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver ATOMIC_LOAD(transferActive, node->p->d.transferActive); if (value & 0x0080 && transferActive == TRANSFER_IDLE) { - if (!node->id && node->d.p->multiplayerControl.ready) { + if (!node->id && GBASIOMultiplayerIsReady(node->d.p->siocnt)) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); - ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->d.attached - 1]); + ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]); bool scheduled = mTimingIsScheduled(&driver->p->p->timing, &node->event); int oldWhen = node->event.when; @@ -220,37 +220,37 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) { sio->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2]; sio->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3]; sio->rcnt |= 1; - sio->multiplayerControl.busy = 0; - sio->multiplayerControl.id = node->id; - if (sio->multiplayerControl.irq) { + sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt); + sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id); + if (GBASIOMultiplayerIsIrq(sio->siocnt)) { GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; case SIO_NORMAL_8: // TODO - sio->normalControl.start = 0; + sio->siocnt = GBASIONormalClearStart(sio->siocnt); if (node->id) { - sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo; + sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); node->d.p->p->memory.io[REG_SIODATA8 >> 1] = node->p->normalRecv[node->id - 1] & 0xFF; } else { node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF; } - if (sio->multiplayerControl.irq) { + if (GBASIONormalIsIrq(sio->siocnt)) { GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; case SIO_NORMAL_32: // TODO - sio->normalControl.start = 0; + sio->siocnt = GBASIONormalClearStart(sio->siocnt); if (node->id) { - sio->normalControl.si = node->p->players[node->id - 1]->d.p->normalControl.idleSo; + sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = node->p->normalRecv[node->id - 1]; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] |= node->p->normalRecv[node->id - 1] >> 16; } else { node->d.p->p->memory.io[REG_SIODATA32_LO >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF; } - if (sio->multiplayerControl.irq) { + if (GBASIONormalIsIrq(sio->siocnt)) { GBARaiseIRQ(sio->p, IRQ_SIO, 0); } break; @@ -278,7 +278,7 @@ static int32_t _masterUpdate(struct GBASIOLockstepNode* node) { case TRANSFER_IDLE: // If the master hasn't initiated a transfer, it can keep going. node->nextEvent += LOCKSTEP_INCREMENT; - node->d.p->multiplayerControl.ready = attachedMulti == attached; + node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached); break; case TRANSFER_STARTING: // Start the transfer, but wait for the other GBAs to catch up @@ -352,11 +352,11 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) { ATOMIC_LOAD(attachedMulti, node->p->attachedMulti); ATOMIC_LOAD(attached, node->p->d.attached); - node->d.p->multiplayerControl.ready = attachedMulti == attached; + node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached); bool signal = false; switch (transferActive) { case TRANSFER_IDLE: - if (!node->d.p->multiplayerControl.ready) { + if (!GBASIOMultiplayerIsReady(node->d.p->siocnt)) { node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT); } break; @@ -376,7 +376,7 @@ static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) { node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; - node->d.p->multiplayerControl.busy = 1; + node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt); break; case SIO_NORMAL_8: node->p->multiRecv[node->id] = 0xFFFF; @@ -455,7 +455,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04x", node->id, value); value &= 0xFF8B; if (!node->id) { - driver->p->normalControl.si = 1; + driver->p->siocnt = GBASIONormalFillSi(driver->p->siocnt); } if (value & 0x0080 && !node->id) { // Internal shift clock diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 4ea3038c0..e3f9bcf51 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -104,6 +104,7 @@ static bool frameStarted = false; static C3D_RenderTarget* upscaleBuffer; static C3D_Tex upscaleBufferTex; static bool interframeBlending = false; +static bool sgbCrop = false; static aptHookCookie cookie; static bool core2; @@ -382,6 +383,10 @@ static void _gameLoaded(struct mGUIRunner* runner) { if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } + + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } } static void _gameUnloaded(struct mGUIRunner* runner) { @@ -437,6 +442,12 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { int w = corew; int h = coreh; + if (sgbCrop && w == 256 && h == 224) { + w = GB_VIDEO_HORIZONTAL_PIXELS; + h = GB_VIDEO_VERTICAL_PIXELS; + } + int innerw = w; + int innerh = h; // Get greatest common divisor while (w != 0) { int temp = h % w; @@ -444,8 +455,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { w = temp; } int gcd = h; - unsigned aspectw = corew / gcd; - unsigned aspecth = coreh / gcd; + unsigned aspectw = innerw / gcd; + unsigned aspecth = innerh / gcd; int x = 0; int y = 0; @@ -517,6 +528,8 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { } ctrFlushBatch(); + innerw = corew; + innerh = coreh; corew = w; coreh = h; screen_h = 240; @@ -529,19 +542,20 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { } ctrSetViewportSize(screen_w, screen_h, true); + float afw, afh; switch (screenMode) { default: return; case SM_AF_TOP: case SM_AF_BOTTOM: - w = screen_w / aspectw; - h = screen_h / aspecth; - if (w * aspecth > screen_h) { - w = aspectw * h; - h = aspecth * h; + afw = screen_w / (float) aspectw; + afh = screen_h / (float) aspecth; + if (afw * aspecth > screen_h) { + w = innerw * afh / gcd; + h = innerh * afh / gcd; } else { - h = aspecth * w; - w = aspectw * w; + h = innerh * afw / gcd; + w = innerw * afw / gcd; } break; case SM_SF_TOP: diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 4dad587d8..594d9942f 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -41,7 +41,7 @@ static void _drawEnd(void) { static uint32_t _pollInput(const struct mInputMap* map) { SceCtrlData pad; - sceCtrlPeekBufferPositive(0, &pad, 1); + sceCtrlPeekBufferPositiveExt2(0, &pad, 1); int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0); if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) { @@ -127,17 +127,17 @@ int main() { .id = PSP2_INPUT, .keyNames = (const char*[]) { "Select", - 0, - 0, + "L3", + "R3", "Start", "Up", "Right", "Down", "Left", - "L", - "R", - 0, // L2? - 0, // R2? + "L2", + "R2", + "L1", + "R1", "\1\xC", "\1\xA", "\1\xB", diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index f33738e65..7e96f24fb 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -56,6 +56,7 @@ static vita2d_texture* oldTex; static vita2d_texture* screenshot; static Thread audioThread; static bool interframeBlending = false; +static bool sgbCrop = false; static struct mSceRotationSource { struct mRotationSource d; @@ -267,7 +268,7 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig uint16_t mPSP2PollInput(struct mGUIRunner* runner) { SceCtrlData pad; - sceCtrlPeekBufferPositive(0, &pad, 1); + sceCtrlPeekBufferPositiveExt2(0, &pad, 1); int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0); int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly); @@ -313,8 +314,8 @@ void mPSP2Setup(struct mGUIRunner* runner) { mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT); - mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LTRIGGER, GBA_KEY_L); - mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RTRIGGER, GBA_KEY_R); + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, GBA_KEY_L); + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, GBA_KEY_R); struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 }; mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc); @@ -365,6 +366,10 @@ void mPSP2Setup(struct mGUIRunner* runner) { if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) { camera.cam = mode; } + int fakeBool; + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } } void mPSP2LoadROM(struct mGUIRunner* runner) { @@ -398,6 +403,18 @@ void mPSP2LoadROM(struct mGUIRunner* runner) { interframeBlending = fakeBool; } + // Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R + if (!sceKernelIsPSVitaTV()) { + int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2)); + if (key >= 0) { + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key); + } + key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2)); + if (key >= 0) { + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key); + } + } + MutexInit(&audioContext.mutex); ConditionInit(&audioContext.cond); memset(audioContext.buffer, 0, sizeof(audioContext.buffer)); @@ -461,8 +478,13 @@ void mPSP2Unpaused(struct mGUIRunner* runner) { } int fakeBool; - mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool); - interframeBlending = fakeBool; + if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { + interframeBlending = fakeBool; + } + + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } } void mPSP2Teardown(struct mGUIRunner* runner) { @@ -507,6 +529,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo vita2d_draw_texture_tint(backdrop, 0, 0, tint); // Fall through case SM_PLAIN: + if (sgbCrop && width == 256 && height == 224) { + w = 768; + h = 672; + scalex = 3; + scaley = 3; + break; + } w = 960 / width; h = 544 / height; if (w * height > 544) { @@ -521,6 +550,13 @@ void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bo scaley = scalex; break; case SM_ASPECT: + if (sgbCrop && width == 256 && height == 224) { + w = 967; + h = 846; + scalex = 34.0f / 9.0f; + scaley = scalex; + break; + } w = 960 / aspectw; h = 544 / aspecth; if (w * aspecth > 544) { diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index 23c58287e..e8a95af7b 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -67,6 +67,18 @@ for line in preprocessed.splitlines(): lines.append(line) ffi.cdef('\n'.join(lines)) +ffi.cdef(""" +struct GBARTC { + int32_t bytesRemaining; + int32_t transferStep; + int32_t bitsRead; + int32_t bits; + int32_t commandActive; + RTCCommandData command; + RTCControl control; + uint8_t time[7]; +};""", packed=True) + preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True) lines = [] diff --git a/src/platform/qt/AssetInfo.cpp b/src/platform/qt/AssetInfo.cpp new file mode 100644 index 000000000..f70d8ea0d --- /dev/null +++ b/src/platform/qt/AssetInfo.cpp @@ -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 +#include + +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(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(); +} \ No newline at end of file diff --git a/src/platform/qt/AssetInfo.h b/src/platform/qt/AssetInfo.h new file mode 100644 index 000000000..c30a4f4c5 --- /dev/null +++ b/src/platform/qt/AssetInfo.h @@ -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 +#include +#include +#include + +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 m_customProperties; +}; + +} diff --git a/src/platform/qt/AssetTile.cpp b/src/platform/qt/AssetTile.cpp index ad16d47a3..ea3bcb28c 100644 --- a/src/platform/qt/AssetTile.cpp +++ b/src/platform/qt/AssetTile.cpp @@ -22,7 +22,7 @@ using namespace QGBA; AssetTile::AssetTile(QWidget* parent) - : QGroupBox(parent) + : AssetInfo(parent) { m_ui.setupUi(this); @@ -42,16 +42,8 @@ AssetTile::AssetTile(QWidget* parent) m_ui.b->setFont(font); } -void AssetTile::addCustomProperty(const QString& id, const QString& visibleName) { - QHBoxLayout* newLayout = new QHBoxLayout; - newLayout->addWidget(new QLabel(visibleName)); - QLabel* value = new QLabel; - value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - value->setAlignment(Qt::AlignRight); - newLayout->addWidget(value); - m_customProperties[id] = value; - int index = layout()->indexOf(m_ui.line); - static_cast(layout())->insertLayout(index, newLayout); +int AssetTile::customLocation(const QString&) { + return layout()->indexOf(m_ui.line); } void AssetTile::setController(std::shared_ptr controller) { @@ -149,11 +141,3 @@ void AssetTile::selectColor(int index) { m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0'))); m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0'))); } - -void AssetTile::setCustomProperty(const QString& id, const QVariant& value) { - QLabel* label = m_customProperties[id]; - if (!label) { - return; - } - label->setText(value.toString()); -} diff --git a/src/platform/qt/AssetTile.h b/src/platform/qt/AssetTile.h index 35dd14e5b..676582387 100644 --- a/src/platform/qt/AssetTile.h +++ b/src/platform/qt/AssetTile.h @@ -15,13 +15,12 @@ namespace QGBA { class CoreController; -class AssetTile : public QGroupBox { +class AssetTile : public AssetInfo { Q_OBJECT public: AssetTile(QWidget* parent = nullptr); void setController(std::shared_ptr); - void addCustomProperty(const QString& id, const QString& visibleName); public slots: void setPalette(int); @@ -29,7 +28,9 @@ public slots: void selectIndex(int); void setFlip(bool h, bool v); void selectColor(int); - void setCustomProperty(const QString& id, const QVariant& value); + +protected: + int customLocation(const QString& id = {}) override; private: Ui::AssetTile m_ui; diff --git a/src/platform/qt/AssetTile.ui b/src/platform/qt/AssetTile.ui index e5557506b..403e26d60 100644 --- a/src/platform/qt/AssetTile.ui +++ b/src/platform/qt/AssetTile.ui @@ -1,13 +1,13 @@ AssetTile - + 0 0 - 171 - 355 + 241 + 406 @@ -185,6 +185,12 @@ + + QGBA::AssetInfo + QGroupBox +
AssetInfo.h
+ 1 +
QGBA::Swatch QWidget diff --git a/src/platform/qt/AssetView.cpp b/src/platform/qt/AssetView.cpp index 2f48cce32..f77b779bb 100644 --- a/src/platform/qt/AssetView.cpp +++ b/src/platform/qt/AssetView.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2019 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -9,6 +9,16 @@ #include +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#include +#endif + +#include + using namespace QGBA; AssetView::AssetView(std::shared_ptr controller, QWidget* parent) @@ -98,3 +108,175 @@ void AssetView::compositeTile(const void* tBuffer, void* buffer, size_t stride, break; } } + +QImage AssetView::compositeMap(int map, mMapCacheEntry* mapStatus) { + mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, map); + int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig); + int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig); + QImage rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32); + uchar* bgBits = rawMap.bits(); + for (int j = 0; j < tilesH; ++j) { + for (int i = 0; i < tilesW; ++i) { + mMapCacheCleanTile(mapCache, mapStatus, i, j); + } + for (int i = 0; i < 8; ++i) { + memcpy(static_cast(&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 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(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(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(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; +} \ No newline at end of file diff --git a/src/platform/qt/AssetView.h b/src/platform/qt/AssetView.h index a95483c5e..c3d3a92b2 100644 --- a/src/platform/qt/AssetView.h +++ b/src/platform/qt/AssetView.h @@ -6,12 +6,15 @@ #pragma once #include +#include #include #include #include +struct mMapCacheEntry; + namespace QGBA { class CoreController; @@ -22,8 +25,6 @@ Q_OBJECT public: AssetView(std::shared_ptr controller, QWidget* parent = nullptr); - static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8); - protected slots: void updateTiles(); void updateTiles(bool force); @@ -40,9 +41,43 @@ protected: void showEvent(QShowEvent*) override; mCacheSet* const m_cacheSet; + std::shared_ptr m_controller; + +protected: + struct ObjInfo { + unsigned tile; + unsigned width; + unsigned height; + unsigned stride; + unsigned paletteId; + unsigned paletteSet; + unsigned bits; + + bool enabled : 1; + unsigned priority : 2; + int x : 10; + int y : 10; + bool hflip : 1; + bool vflip : 1; + QTransform xform; + + bool operator!=(const ObjInfo&) const; + }; + + static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8); + QImage compositeMap(int map, mMapCacheEntry*); + QImage compositeObj(const ObjInfo&); + + bool lookupObj(int id, struct ObjInfo*); private: - std::shared_ptr 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; }; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index ba2bf9e1f..874d6c9de 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -60,6 +60,7 @@ set(SOURCE_FILES AbstractUpdater.cpp Action.cpp ActionMapper.cpp + AssetInfo.cpp AssetTile.cpp AssetView.cpp AudioProcessor.cpp @@ -72,6 +73,7 @@ set(SOURCE_FILES Display.cpp DisplayGL.cpp DisplayQt.cpp + FrameView.cpp GBAApp.cpp GIFView.cpp GamepadAxisEvent.cpp @@ -124,6 +126,7 @@ set(UI_FILES BattleChipView.ui CheatsView.ui DebuggerConsole.ui + FrameView.ui GIFView.ui IOViewer.ui LoadSaveState.ui diff --git a/src/platform/qt/ColorPicker.cpp b/src/platform/qt/ColorPicker.cpp index 18782a5dc..818a25fd4 100644 --- a/src/platform/qt/ColorPicker.cpp +++ b/src/platform/qt/ColorPicker.cpp @@ -34,6 +34,14 @@ ColorPicker& ColorPicker::operator=(const ColorPicker& other) { return *this; } +void ColorPicker::setColor(const QColor& color) { + m_defaultColor = color; + + QPalette palette = m_parent->palette(); + palette.setColor(m_parent->backgroundRole(), color); + m_parent->setPalette(palette); +} + bool ColorPicker::eventFilter(QObject* obj, QEvent* event) { if (event->type() != QEvent::MouseButtonRelease) { return false; diff --git a/src/platform/qt/ColorPicker.h b/src/platform/qt/ColorPicker.h index 1e94933c8..bf50c5528 100644 --- a/src/platform/qt/ColorPicker.h +++ b/src/platform/qt/ColorPicker.h @@ -24,6 +24,9 @@ public: signals: void colorChanged(const QColor&); +public slots: + void setColor(const QColor&); + protected: bool eventFilter(QObject* obj, QEvent* event) override; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 6083cf723..b1563256c 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -82,8 +82,7 @@ CoreController::CoreController(mCore* core, QObject* parent) controller->m_resetActions.clear(); if (!controller->m_hwaccel) { - controller->m_activeBuffer = &controller->m_buffers[0]; - context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer->data()), controller->screenDimensions().width()); + context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer.data()), controller->screenDimensions().width()); } QMetaObject::invokeMethod(controller, "didReset"); @@ -203,13 +202,33 @@ CoreController::~CoreController() { } const color_t* CoreController::drawContext() { - QMutexLocker locker(&m_mutex); if (m_hwaccel) { return nullptr; } + QMutexLocker locker(&m_bufferMutex); return reinterpret_cast(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(buffer.constData()), + size.width(), size.height(), stride, QImage::Format_RGBX8888); +} + bool CoreController::isPaused() { return mCoreThreadIsPaused(&m_threadContext); } @@ -342,15 +361,12 @@ void CoreController::setLogger(LogController* logger) { void CoreController::start() { if (!m_hwaccel) { - QSize size(1024, 2048); - m_buffers[0].resize(size.width() * size.height() * sizeof(color_t)); - m_buffers[1].resize(size.width() * size.height() * sizeof(color_t)); - m_buffers[0].fill(0xFF); - m_buffers[1].fill(0xFF); - m_activeBuffer = &m_buffers[0]; - m_completeBuffer = m_buffers[0]; + QSize size(256, 384); + m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t)); + m_activeBuffer.fill(0xFF); + m_completeBuffer = m_activeBuffer; - m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), size.width()); + m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer.data()), size.width()); } if (!m_patched) { @@ -386,8 +402,7 @@ void CoreController::setPaused(bool paused) { return; } if (paused) { - QMutexLocker locker(&m_mutex); - m_frameActions.append([this]() { + addFrameAction([this]() { mCoreThreadPauseFromThread(&m_threadContext); }); } else { @@ -396,13 +411,17 @@ void CoreController::setPaused(bool paused) { } void CoreController::frameAdvance() { - QMutexLocker locker(&m_mutex); - m_frameActions.append([this]() { + addFrameAction([this]() { mCoreThreadPauseFromThread(&m_threadContext); }); setPaused(false); } +void CoreController::addFrameAction(std::function action) { + QMutexLocker locker(&m_actionMutex); + m_frameActions.append(action); +} + void CoreController::setSync(bool sync) { if (sync) { m_threadContext.impl->sync.audioWait = m_audioSync; @@ -777,26 +796,39 @@ void CoreController::clearOverride() { m_override.reset(); } -void CoreController::startVideoLog(const QString& path) { +void CoreController::startVideoLog(const QString& path, bool compression) { if (m_vl) { return; } + VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + startVideoLog(vf); +} + +void CoreController::startVideoLog(VFile* vf, bool compression) { + if (m_vl || !vf) { + return; + } + Interrupter interrupter(this); m_vl = mVideoLogContextCreate(m_threadContext.core); - m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + m_vlVf = vf; mVideoLogContextSetOutput(m_vl, m_vlVf); + mVideoLogContextSetCompression(m_vl, compression); mVideoLogContextWriteHeader(m_vl, m_threadContext.core); } -void CoreController::endVideoLog() { +void CoreController::endVideoLog(bool closeVf) { if (!m_vl) { return; } Interrupter interrupter(this); mVideoLogContextDestroy(m_threadContext.core, m_vl); - if (m_vlVf) { + if (m_vlVf && closeVf) { m_vlVf->close(m_vlVf); m_vlVf = nullptr; } @@ -819,23 +851,20 @@ void CoreController::updateKeys() { } void CoreController::finishFrame() { - QMutexLocker locker(&m_mutex); if (!m_hwaccel) { - memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size()); + unsigned width, height; + m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); - // TODO: Generalize this to triple buffering? - m_activeBuffer = &m_buffers[0]; - if (m_activeBuffer == m_completeBuffer) { - m_activeBuffer = &m_buffers[1]; - } - // 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(m_activeBuffer->data()), screenDimensions().width()); + QMutexLocker locker(&m_bufferMutex); + memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), 256 * height * BYTES_PER_PIXEL); } - for (auto& action : m_frameActions) { + + QMutexLocker locker(&m_actionMutex); + QList> frameActions(m_frameActions); + m_frameActions.clear(); + for (auto& action : frameActions) { action(); } - m_frameActions.clear(); updateKeys(); QMetaObject::invokeMethod(this, "frameAvailable"); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index d6c7609ee..e2ecdfc45 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -67,6 +67,7 @@ public: mCoreThread* thread() { return &m_threadContext; } const color_t* drawContext(); + QImage getPixels(); bool isPaused(); bool hasStarted(); @@ -102,6 +103,8 @@ public: bool audioSync() const { return m_audioSync; } bool videoSync() const { return m_videoSync; } + void addFrameAction(std::function callback); + public slots: void start(); void stop(); @@ -157,8 +160,9 @@ public slots: void clearOverride(); - void startVideoLog(const QString& path); - void endVideoLog(); + void startVideoLog(const QString& path, bool compression = true); + void startVideoLog(VFile* vf, bool compression = true); + void endVideoLog(bool closeVf = true); void setFramebufferHandle(int fb); @@ -193,8 +197,7 @@ private: bool m_patched = false; - QByteArray m_buffers[2]; - QByteArray* m_activeBuffer; + QByteArray m_activeBuffer; QByteArray m_completeBuffer; bool m_hwaccel = false; @@ -203,7 +206,8 @@ private: QList> m_resetActions; QList> m_frameActions; - QMutex m_mutex; + QMutex m_actionMutex{QMutex::Recursive}; + QMutex m_bufferMutex; int m_activeKeys = 0; bool m_autofire[32] = {}; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 42e814254..80849061e 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -114,7 +114,6 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); #endif resizePainter(); - connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext); } void DisplayGL::stopDrawing() { diff --git a/src/platform/qt/FrameView.cpp b/src/platform/qt/FrameView.cpp new file mode 100644 index 000000000..5c13f2b77 --- /dev/null +++ b/src/platform/qt/FrameView.cpp @@ -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 +#include + +#include +#include + +#include "CoreController.h" +#include "GBAApp.h" + +#include +#include +#ifdef M_CORE_GBA +#include +#include +#include +#include +#endif +#ifdef M_CORE_GB +#include +#include +#endif + +using namespace QGBA; + +FrameView::FrameView(std::shared_ptr 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(&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(m_controller->thread()->core->board)->memory.io; + QRgb backdrop = M_RGB5_TO_RGB8(static_cast(m_controller->thread()->core->board)->video.palette[0]); + m_gbaDispcnt = io[REG_DISPCNT >> 1]; + int mode = GBARegisterDISPCNTGetMode(m_gbaDispcnt); + + std::array 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(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(event)->localPos(); + pos /= m_ui.magnification->value(); + selectLayer(pos); + return true; + case QEvent::MouseButtonDblClick: + pos = static_cast(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(m_framebuffer.bits()), width); + m_vl->reset(m_vl); +} + +void FrameView::frameCallback(FrameView* viewer, std::shared_ptr 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); +} \ No newline at end of file diff --git a/src/platform/qt/FrameView.h b/src/platform/qt/FrameView.h new file mode 100644 index 000000000..e47e82054 --- /dev/null +++ b/src/platform/qt/FrameView.h @@ -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 +#include +#include +#include +#include +#include +#include + +#include "AssetView.h" +#include "ColorPicker.h" + +#include + +#include + +struct VFile; + +namespace QGBA { + +class CoreController; + +class FrameView : public AssetView { +Q_OBJECT + +public: + FrameView(std::shared_ptr 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); + + 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 m_queue; + QSet 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 m_callbackLocker{std::make_shared(true)}; +}; + +} \ No newline at end of file diff --git a/src/platform/qt/FrameView.ui b/src/platform/qt/FrameView.ui new file mode 100644 index 000000000..80b184561 --- /dev/null +++ b/src/platform/qt/FrameView.ui @@ -0,0 +1,160 @@ + + + FrameView + + + + 0 + 0 + 869 + 875 + + + + Inspect frame + + + + + + + + + 0 + 0 + + + + × + + + 1 + + + 8 + + + + + + + Magnification + + + + + + + + + Freeze frame + + + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Backdrop color + + + + + + + + + true + + + + + 0 + 0 + 567 + 382 + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Disable scanline effects + + + + + + + + + + false + + + Export + + + + + + + Reset + + + + + + + + diff --git a/src/platform/qt/MapView.cpp b/src/platform/qt/MapView.cpp index a0232c50b..0fe80c2c3 100644 --- a/src/platform/qt/MapView.cpp +++ b/src/platform/qt/MapView.cpp @@ -18,6 +18,7 @@ #include #endif #ifdef M_CORE_GB +#include #include #endif @@ -42,6 +43,12 @@ MapView::MapView(std::shared_ptr controller, QWidget* parent) m_boundary = 2048; m_addressBase = BASE_VRAM; m_addressWidth = 8; + m_ui.bgInfo->addCustomProperty("priority", tr("Priority")); + m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base")); + m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base")); + m_ui.bgInfo->addCustomProperty("size", tr("Size")); + m_ui.bgInfo->addCustomProperty("offset", tr("Offset")); + m_ui.bgInfo->addCustomProperty("transform", tr("Xform")); break; #endif #ifdef M_CORE_GB @@ -49,6 +56,9 @@ MapView::MapView(std::shared_ptr controller, QWidget* parent) m_boundary = 1024; m_addressBase = GB_BASE_VRAM; m_addressWidth = 4; + m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base")); + m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base")); + m_ui.bgInfo->addCustomProperty("offset", tr("Offset")); break; #endif default: @@ -143,39 +153,82 @@ void MapView::updateTilesGBA(bool force) { { CoreController::Interrupter interrupter(m_controller); int bitmap = -1; + int priority = -1; + int frame = 0; + QString offset(tr("N/A")); + QString transform(tr("N/A")); if (m_controller->platform() == PLATFORM_GBA) { - int mode = GBARegisterDISPCNTGetMode(static_cast(m_controller->thread()->core->board)->memory.io[REG_DISPCNT]); + uint16_t* io = static_cast(m_controller->thread()->core->board)->memory.io; + int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]); if (m_map == 2 && mode > 2) { bitmap = mode == 4 ? 1 : 0; + if (mode != 3) { + frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]); + } } + priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]); + if (mode == 0 || (mode == 1 && m_map != 2)) { + offset = QString("%1, %2") + .arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)]) + .arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]); + } else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) { + int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)]; + refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16; + int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)]; + refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16; + refX <<= 4; + refY <<= 4; + refX >>= 4; + refY >>= 4; + offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3); + transform = QString("%1 %2\n%3 %4") + .arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2) + .arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2) + .arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2) + .arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2); + + } + } + if (m_controller->platform() == PLATFORM_GB) { + uint8_t* io = static_cast(m_controller->thread()->core->board)->memory.io; + int x = io[m_map == 0 ? 0x42 : 0x4A]; + int y = io[m_map == 0 ? 0x43 : 0x4B]; + offset = QString("%1, %2").arg(x).arg(y); } if (bitmap >= 0) { mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap); int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig); int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig); + m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0'))); + m_ui.bgInfo->setCustomProperty("charBase", tr("N/A")); + m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height)); + m_ui.bgInfo->setCustomProperty("priority", priority); + m_ui.bgInfo->setCustomProperty("offset", offset); + m_ui.bgInfo->setCustomProperty("transform", transform); m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32); uchar* bgBits = m_rawMap.bits(); for (int j = 0; j < height; ++j) { mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j); memcpy(static_cast(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4); } + m_rawMap = m_rawMap.rgbSwapped(); } else { mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map); int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig); int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig); - m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32); - uchar* bgBits = m_rawMap.bits(); - for (int j = 0; j < tilesH; ++j) { - for (int i = 0; i < tilesW; ++i) { - mMapCacheCleanTile(mapCache, m_mapStatus, i, j); - } - for (int i = 0; i < 8; ++i) { - memcpy(static_cast(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32); - } - } + m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1") + .arg(m_addressWidth == 8 ? "0x" : "") + .arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0'))); + m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1") + .arg(m_addressWidth == 8 ? "0x" : "") + .arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0'))); + m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8)); + m_ui.bgInfo->setCustomProperty("priority", priority); + m_ui.bgInfo->setCustomProperty("offset", offset); + m_ui.bgInfo->setCustomProperty("transform", transform); + m_rawMap = compositeMap(m_map, m_mapStatus); } } - m_rawMap = m_rawMap.rgbSwapped(); QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32)); if (m_ui.magnification->value() > 1) { map = map.scaled(map.size() * m_ui.magnification->value()); diff --git a/src/platform/qt/MapView.ui b/src/platform/qt/MapView.ui index d07a832b5..d21cce841 100644 --- a/src/platform/qt/MapView.ui +++ b/src/platform/qt/MapView.ui @@ -6,18 +6,80 @@ 0 0 - 641 - 489 + 941 + 617 Maps - - - + + + - + + + + + + + + + + + + Export + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + + + + × + + + 1 + + + 8 + + + + + + + Magnification + + + + + + + + true @@ -30,8 +92,8 @@ 0 0 - 457 - 463 + 613 + 601 @@ -71,62 +133,15 @@
- - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - - - 0 - 0 - - - - × - - - 1 - - - 8 - - - - - - - Magnification - - - - - - - - - Export - - - + + QGBA::AssetInfo + QGroupBox +
AssetInfo.h
+ 1 +
QGBA::AssetTile QGroupBox diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 64b689640..e482f023b 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -19,9 +19,7 @@ #endif #ifdef M_CORE_GB #include -#include #endif -#include #include using namespace QGBA; @@ -53,11 +51,7 @@ ObjView::ObjView(std::shared_ptr controller, QWidget* parent) connect(m_ui.magnification, static_cast(&QSpinBox::valueChanged), [this]() { updateTiles(true); }); -#ifdef USE_PNG connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj); -#else - m_ui.exportButton->setVisible(false); -#endif } void ObjView::selectObj(int obj) { @@ -77,79 +71,56 @@ void ObjView::updateTilesGBA(bool force) { const GBA* gba = static_cast(m_controller->thread()->core->board); const GBAObj* obj = &gba->video.oam.obj[m_objId]; - unsigned shape = GBAObjAttributesAGetShape(obj->a); - unsigned size = GBAObjAttributesBGetSize(obj->b); - unsigned width = GBAVideoObjSizes[shape * 4 + size][0]; - unsigned height = GBAVideoObjSizes[shape * 4 + size][1]; - unsigned tile = GBAObjAttributesCGetTile(obj->c); - m_ui.tiles->setTileCount(width * height / 64); - m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value()); - m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value()); - unsigned palette = GBAObjAttributesCGetPalette(obj->c); - unsigned tileBase = tile; - unsigned paletteSet; - unsigned bits; + ObjInfo newInfo; + lookupObj(m_objId, &newInfo); + + m_ui.tiles->setTileCount(newInfo.width * newInfo.height); + m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value()); + m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value()); + unsigned tileBase = newInfo.tile; + unsigned tile = newInfo.tile; if (GBAObjAttributesAIs256Color(obj->a)) { m_ui.palette->setText("256-color"); - paletteSet = 3; m_ui.tile->setBoundary(1024, 1, 3); m_ui.tile->setPalette(0); m_boundary = 1024; - palette = 0; - tile /= 2; - bits = 8; + tileBase *= 2; } else { - m_ui.palette->setText(QString::number(palette)); - paletteSet = 2; + m_ui.palette->setText(QString::number(newInfo.paletteId)); m_ui.tile->setBoundary(2048, 0, 2); - m_ui.tile->setPalette(palette); - m_boundary = 2048; - bits = 4; + m_ui.tile->setPalette(newInfo.paletteId); } - ObjInfo newInfo{ - tile, - width / 8, - height / 8, - width / 8, - palette, - paletteSet, - bits - }; if (newInfo != m_objInfo) { force = true; } - GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues - if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) { - newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a)); - }; m_objInfo = newInfo; - m_tileOffset = tile; - mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet); + m_tileOffset = newInfo.tile; + mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet); int i = 0; - for (int y = 0; y < height / 8; ++y) { - for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) { - const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette); + for (int y = 0; y < newInfo.height; ++y) { + for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) { + const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId)); } } - tile += newInfo.stride - width / 8; - tileBase += newInfo.stride - width / 8; + tile += newInfo.stride - newInfo.width; + tileBase += newInfo.stride - newInfo.width; } - m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b))); - m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a))); - m_ui.w->setText(QString::number(width)); - m_ui.h->setText(QString::number(height)); + m_ui.x->setText(QString::number(newInfo.x)); + m_ui.y->setText(QString::number(newInfo.y)); + m_ui.w->setText(QString::number(newInfo.width * 8)); + m_ui.h->setText(QString::number(newInfo.height * 8)); m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0'))); - m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c))); - m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b)); - m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b)); - m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a)); + m_ui.priority->setText(QString::number(newInfo.priority)); + m_ui.flippedH->setChecked(newInfo.hflip); + m_ui.flippedV->setChecked(newInfo.vflip); + m_ui.enabled->setChecked(newInfo.enabled); m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a)); m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a)); @@ -182,39 +153,17 @@ void ObjView::updateTilesGB(bool force) { const GB* gb = static_cast(m_controller->thread()->core->board); const GBObj* obj = &gb->video.oam.obj[m_objId]; - mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0); - unsigned width = 8; - unsigned height = 8; - GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC]; - if (GBRegisterLCDCIsObjSize(lcdc)) { - height = 16; - } - unsigned tile = obj->tile; - m_ui.tiles->setTileCount(width * height / 64); - m_ui.tile->setBoundary(1024, 0, 0); - m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value()); - m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value()); - unsigned palette = 0; - if (gb->model >= GB_MODEL_CGB) { - if (GBObjAttributesIsBank(obj->attr)) { - tile += 512; - } - palette = GBObjAttributesGetCGBPalette(obj->attr); - } else { - palette = GBObjAttributesGetPalette(obj->attr); - } - m_ui.palette->setText(QString::number(palette)); - palette += 8; + ObjInfo newInfo; + lookupObj(m_objId, &newInfo); + + mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0); + unsigned tile = newInfo.tile; + m_ui.tiles->setTileCount(newInfo.height); + m_ui.tile->setBoundary(1024, 0, 0); + m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value()); + m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value()); + m_ui.palette->setText(QString::number(newInfo.paletteId - 8)); - ObjInfo newInfo{ - tile, - 1, - height / 8, - 1, - palette, - 0, - 2 - }; if (newInfo != m_objInfo) { force = true; } @@ -223,27 +172,27 @@ void ObjView::updateTilesGB(bool force) { m_boundary = 1024; int i = 0; - m_ui.tile->setPalette(palette); - for (int y = 0; y < height / 8; ++y, ++i) { + m_ui.tile->setPalette(newInfo.paletteId); + for (int y = 0; y < newInfo.height; ++y, ++i) { unsigned t = tile + i; - const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, palette); + const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId)); } } - m_ui.x->setText(QString::number(obj->x)); - m_ui.y->setText(QString::number(obj->y)); - m_ui.w->setText(QString::number(width)); - m_ui.h->setText(QString::number(height)); + m_ui.x->setText(QString::number(newInfo.x)); + m_ui.y->setText(QString::number(newInfo.y)); + m_ui.w->setText(QString::number(8)); + m_ui.h->setText(QString::number(newInfo.height * 8)); m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0'))); - m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr))); - m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr)); - m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr)); - m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160); + m_ui.priority->setText(QString::number(newInfo.priority)); + m_ui.flippedH->setChecked(newInfo.hflip); + m_ui.flippedV->setChecked(newInfo.vflip); + m_ui.enabled->setChecked(newInfo.enabled); m_ui.doubleSize->setChecked(false); m_ui.mosaic->setChecked(false); m_ui.transform->setText(tr("N/A")); @@ -251,51 +200,10 @@ void ObjView::updateTilesGB(bool force) { } #endif -#ifdef USE_PNG void ObjView::exportObj() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), tr("Portable Network Graphics (*.png)")); - VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename); - return; - } - CoreController::Interrupter interrupter(m_controller); - png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8); - - mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, m_objInfo.paletteSet); - const color_t* rawPalette = mTileCacheGetPalette(tileCache, m_objInfo.paletteId); - unsigned colors = 1 << m_objInfo.bits; - uint32_t palette[256]; - - palette[0] = rawPalette[0]; - for (unsigned c = 1; c < colors && c < 256; ++c) { - palette[c] = rawPalette[c] | 0xFF000000; - } - PNGWritePalette(png, info, palette, colors); - - uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8]; - unsigned t = m_objInfo.tile; - for (int y = 0; y < m_objInfo.height; ++y) { - for (int x = 0; x < m_objInfo.width; ++x, ++t) { - compositeTile(static_cast(mTileCacheGetVRAM(tileCache, t)), reinterpret_cast(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(buffer)); - PNGWriteClose(png, info); - delete[] buffer; - vf->close(vf); -} -#endif - -bool ObjView::ObjInfo::operator!=(const ObjInfo& other) { - return other.tile != tile || - other.width != width || - other.height != height || - other.stride != stride || - other.paletteId != paletteId || - other.paletteSet != paletteSet; + QImage obj = compositeObj(m_objInfo); + obj.save(filename, "PNG"); } diff --git a/src/platform/qt/ObjView.h b/src/platform/qt/ObjView.h index 41677ca92..42cd3f65e 100644 --- a/src/platform/qt/ObjView.h +++ b/src/platform/qt/ObjView.h @@ -21,10 +21,8 @@ Q_OBJECT public: ObjView(std::shared_ptr controller, QWidget* parent = nullptr); -#ifdef USE_PNG public slots: void exportObj(); -#endif private slots: void selectObj(int); @@ -43,17 +41,7 @@ private: std::shared_ptr m_controller; mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size int m_objId = 0; - struct ObjInfo { - unsigned tile; - unsigned width; - unsigned height; - unsigned stride; - unsigned paletteId; - unsigned paletteSet; - unsigned bits; - - bool operator!=(const ObjInfo&); - } m_objInfo = {}; + ObjInfo m_objInfo = {}; int m_tileOffset; int m_boundary; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 1d5f181ae..ee24e06f1 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -363,7 +363,6 @@ void SettingsView::updateConfig() { saveSetting("useCgbColors", m_ui.useCgbColors); saveSetting("useBios", m_ui.useBios); saveSetting("skipBios", m_ui.skipBios); - saveSetting("audioBuffers", m_ui.audioBufferSize); saveSetting("sampleRate", m_ui.sampleRate); saveSetting("videoSync", m_ui.videoSync); saveSetting("audioSync", m_ui.audioSync); @@ -401,6 +400,11 @@ void SettingsView::updateConfig() { saveSetting("useDiscordPresence", m_ui.useDiscordPresence); saveSetting("gba.audioHle", m_ui.audioHle); + if (m_ui.audioBufferSize->currentText().toInt() > 8192) { + m_ui.audioBufferSize->setCurrentText("8192"); + } + saveSetting("audioBuffers", m_ui.audioBufferSize); + if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); } else { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index cd2ff4e5b..9b08bc586 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -30,6 +30,7 @@ #include "DebuggerConsoleController.h" #include "Display.h" #include "CoreController.h" +#include "FrameView.h" #include "GBAApp.h" #include "GDBController.h" #include "GDBWindow.h" @@ -971,6 +972,7 @@ void Window::reloadDisplayDriver() { connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext); attachWidget(m_display.get()); m_display->startDrawing(m_controller); @@ -1527,7 +1529,7 @@ void Window::setupMenu(QMenuBar* menubar) { m_overrideView->recheck(); }, "tools"); - m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() { + m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() { if (!m_sensorView) { m_sensorView = std::move(std::make_unique(&m_inputController)); if (m_controller) { @@ -1557,6 +1559,26 @@ void Window::setupMenu(QMenuBar* menubar) { addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView(), "tools"); addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView(), "tools"); addGameAction(tr("View &map..."), "mapWindow", openControllerTView(), "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(), "tools"); addGameAction(tr("Search memory..."), "memorySearch", openControllerTView(), "tools"); @@ -1744,11 +1766,8 @@ void Window::focusCheck() { } void Window::updateFrame() { - QSize size = m_controller->screenDimensions(); - QImage currentImage(reinterpret_cast(m_controller->drawContext()), size.width(), size.height(), - size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888); QPixmap pixmap; - pixmap.convertFromImage(currentImage); + pixmap.convertFromImage(m_controller->getPixels()); m_screenWidget->setPixmap(pixmap); emit paused(true); } @@ -1834,6 +1853,7 @@ void Window::setController(CoreController* controller, const QString& fname) { connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + connect(m_controller.get(), &CoreController::didReset, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver); connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 4980a9314..f134ca83b 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -32,6 +32,7 @@ class CoreController; class CoreManager; class DebuggerConsoleController; class Display; +class FrameView; class GDBController; class GIFView; class LibraryController; @@ -213,6 +214,7 @@ private: std::unique_ptr m_overrideView; std::unique_ptr m_sensorView; + FrameView* m_frameView = nullptr; #ifdef USE_FFMPEG VideoView* m_videoView = nullptr; diff --git a/src/platform/qt/ts/medusa-emu-de.ts b/src/platform/qt/ts/medusa-emu-de.ts index 36d084f66..b8380ed53 100644 --- a/src/platform/qt/ts/medusa-emu-de.ts +++ b/src/platform/qt/ts/medusa-emu-de.ts @@ -254,6 +254,49 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Unterbrechen + + FrameView + + + Inspect frame + Bild beobachten + + + + × + × + + + + Magnification + Vergrößerung + + + + Freeze frame + Bild einfrieren + + + + Backdrop color + Hintergrundfarbe + + + + Disable scanline effects + Scanline-Effekte deaktivieren + + + + Export + Exportieren + + + + Reset + Zurücksetzen + + GIFView @@ -546,17 +589,17 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Maps - + × × - + Magnification Vergrößerung - + Export Exportieren @@ -1231,14 +1274,14 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::AssetTile - + %0%1%2 %0%1%2 - - - + + + 0x%0 (%1) 0x%0 (%1) @@ -1289,22 +1332,22 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Fehler beim Öffnen der Speicherdatei: %1 - + Failed to open game file: %1 Fehler beim Öffnen der Spieldatei: %1 - + Failed to open snapshot file for reading: %1 Konnte Snapshot-Datei %1 nicht zum Lesen öffnen - + Failed to open snapshot file for writing: %1 Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen @@ -1317,6 +1360,49 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Fehler beim Öffnen der Spieldatei: %1 + + QGBA::FrameView + + + Export frame + Bild exportieren + + + + Portable Network Graphics (*.png) + Portable Network Graphics (*.png) + + + + None + Keine + + + + Background + Hintergrund + + + + Window + Fenster + + + + Sprite + Sprite + + + + Backdrop + Hintergrund + + + + %1 %2 + %1 %2 + + QGBA::GBAApp @@ -2940,47 +3026,87 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::MapView - + + Priority + Priorität + + + + + Map base + Map-Basis + + + + + Tile base + Tile-Basis + + + + Size + Größe + + + + + Offset + Versatz + + + + Xform + Xform + + + Map Addr. Map-Addr. - + Mirror Spiegel - + None Keiner - + Both Beidseitig - + Horizontal Horizontal - + Vertical Vertikal - + + + + N/A + N/A + + + Export map Map exportieren - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - + Failed to open output PNG file: %1 Fehler beim Öffnen der Ausgabe-PNG-Datei: %1 @@ -3074,57 +3200,52 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::ObjView - - + + 0x%0 0x%0 - + Off Aus - + Normal Normal - + Trans Trans - + OBJWIN OBJWIN - + Invalid Ungültig - - + + N/A N/A - + Export sprite Sprite exportieren - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - - - Failed to open output PNG file: %1 - Fehler beim Öffnen der Ausgabe-PNG-Datei: %1 - QGBA::PaletteView @@ -3321,7 +3442,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::Window - + Game Boy Advance ROMs (%1) Game Boy Advance-ROMs (%1) @@ -3331,78 +3452,78 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.DS-ROMs (%1) - + Game Boy ROMs (%1) Game Boy-ROMs (%1) - + All ROMs (%1) Alle ROMs (%1) - + %1 Video Logs (*.mvl) %1 Video-Logs (*.mvl) - + Archives (%1) Archive (%1) - - - + + + Select ROM ROM auswählen - + Game Boy Advance save files (%1) Game Boy Advance-Speicherdateien (%1) - - - + + + Select save Speicherdatei wählen - + mGBA savestate files (%1) mGBA Savestate-Dateien (%1) - - + + Select savestate Savestate auswählen - + Select patch Patch wählen - + Patches (*.ips *.ups *.bps) Patches (*.ips *.ups *.bps) - + Select image Bild auswählen - + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) Bild-Datei (*.png *.gif *.jpg *.jpeg);;Alle Dateien (*) - - + + GameShark saves (*.sps *.xps) GameShark-Speicherdaten (*.sps *.xps) @@ -3417,22 +3538,22 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.DS-Unterstützung erfordert ein Abbild des BIOS und der Firmware. - + Select video log Video-Log auswählen - + Video logs (*.mvl) Video-Logs (*.mvl) - + Crash Absturz - + The game has crashed with the following error: %1 @@ -3441,428 +3562,428 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load alternate save... Alternative Speicherdatei laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state Savestate (aktueller Zustand) &laden - + Load state file... Ssavestate-Datei laden... - + &Save state Savestate (aktueller Zustand) &speichern - + Save state file... Savestate-Datei speichern... - + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent Speichere aktuellen Zustand - + Undo load state Laden des Savestate rückgängig machen - + Undo save state Speichern des Savestate rückgängig machen - - + + State &%1 Savestate &%1 - + Load camera image... Lade Kamerabild... - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Sh&utdown Schli&eßen - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + &Next frame &Nächstes Bild - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + Step backwards Schrittweiser Rücklauf - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor - Solar-Sensor + Sonnen-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + BattleChip Gate... BattleChip Gate... - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Pixelgenaue Skalierung (Integer scaling) - + + Interframe blending + Interframe-Überblendung + + + Frame&skip Frame&skip - + Mute Stummschalten - + FPS target Bildwiederholrate - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record GIF... GIF aufzeichen... - + Game Boy Printer... Game Boy Printer... - + Video layers Video-Ebenen - + Audio channels Audio-Kanäle - + Adjust layer placement... Lage der Bildebenen anpassen... - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - - Game &Pak sensors... - Game &Pak-Sensoren... - - - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole öffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... @@ -3882,62 +4003,72 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.DS - + Select folder Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + About... Über... - + %1× %1x - + Bilinear filtering Bilineare Filterung - + Native (59.7275) Nativ (59.7275) - + Record A/V... Audio/Video aufzeichnen... - + + Game Pak sensors... + Spielmodul-Sensoren... + + + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View &map... &Map betrachten... - + + &Frame inspector... + &Bildbetrachter... + + + View memory... Speicher betrachten... @@ -3947,87 +4078,87 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.&I/O-Register betrachten... - + Search memory... Speicher durchsuchen... - + View &I/O registers... &I/O-Register betrachten... - + Record debug video log... Video-Protokoll aufzeichnen... - + Stop debug video log Aufzeichnen des Video-Protokolls beenden - + Exit fullscreen Vollbildmodus beenden - + GameShark Button (held) GameShark-Taste (gehalten) - + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links @@ -4340,7 +4471,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + frames Bild(er) @@ -4386,226 +4517,231 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Nativ (59.7275) - + + Interframe blending + Interframe-Überblendung + + + Language Sprache - + English Englisch - + List view Listenansicht - + Tree view Baumansicht - + Show FPS in title bar Bildwiederholrate in der Titelleiste anzeigen - + Automatically save cheats Cheats automatisch speichern - + Automatically load cheats Cheats automatisch laden - + Automatically save state Zustand (Savestate) automatisch speichern - + Automatically load state Zustand (Savestate) automatisch laden - + Enable Discord Rich Presence Discord-Integration aktivieren - + Video renderer: Video-Renderer: - + Software Software - + OpenGL OpenGL - + OpenGL enhancements OpenGL-Verbesserungen - + High-resolution scale: Hochauflösende Skalierung: - + XQ GBA audio (experimental) XQ GBA-Audio (experimentell) - + Cheats Cheats - + Log to file In Datei protokollieren - + Log to console Auf die Konsole protokollieren - + Select Log File Protokoll-Datei auswählen - + Camera: Kamera: - - - + + + Autodetect Automatisch erkennen - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Default BG colors: Standard-Hintergrundfarben: - + Default sprite colors 1: Standard-Sprite-Farben 1: - + Default sprite colors 2: Standard-Sprite-Farben 2: - + Use GBC colors in GB games Verwende GBC-Farben in GB-Spielen - + Super Game Boy borders Super Game Boy-Rahmen - + Game Boy model: Game Boy-Modell: - + Super Game Boy model: Super Game Boy-Modell: - + Game Boy Color model: Game Boy Color-Modell: - + Camera driver: Kamera-Treiber: - + Library: Bibliothek: - + Show when no game open Anzeigen, wenn kein Spiel geöffnet ist - + Clear cache Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - + Preload entire ROM into memory ROM-Datei vollständig in Arbeitsspeicher vorladen - - - - - - - - - + + + + + + + + + Browse Durchsuchen @@ -4620,29 +4756,29 @@ in Arbeitsspeicher vorladen DS-BIOS 9: - + Use BIOS file if found BIOS-Datei verwenden, wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - - + + × × - + Unbounded unbegrenzt - + Suspend screensaver Bildschirmschoner deaktivieren @@ -4652,50 +4788,50 @@ wenn vorhanden BIOS - + Pause when inactive Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen - + Allow opposing input directions Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren @@ -4705,42 +4841,42 @@ wenn vorhanden Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + Autofire interval: Autofeuer-Intervall: - + GB BIOS file: Datei mit GB-BIOS: - + GBA BIOS file: Datei mit GBA-BIOS: - + GBC BIOS file: Datei mit GBC-BIOS: @@ -4750,36 +4886,36 @@ wenn vorhanden DS-Firmware: - + SGB BIOS file: Datei mit SGB-BIOS: - + Save games Spielstände - - - - - + + + + + Same directory as the ROM Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 86d6abdfb..c0624423d 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -6,6 +6,7 @@ #include "feature/gui/gui-runner.h" #include #include +#include #include #include #include @@ -99,6 +100,9 @@ static u8 vmode; static u32 vwidth; static u32 vheight; static bool interframeBlending = false; +static bool sgbCrop = false; +static bool useLightSensor = true; +static struct mGUIRunnerLux lightSensor; static enum ScreenMode { SM_PA, @@ -268,6 +272,10 @@ static void _setup(struct mGUIRunner* runner) { runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); runner->core->setAVStream(runner->core, &stream); + if (runner->core->platform(runner->core) == PLATFORM_GBA && useLightSensor) { + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d); + } + unsigned mode; if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) { screenMode = mode; @@ -292,6 +300,22 @@ static void _gameLoaded(struct mGUIRunner* runner) { if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } + if (mCoreConfigGetIntValue(&runner->config, "useLightSensor", &fakeBool)) { + if (useLightSensor != fakeBool) { + useLightSensor = fakeBool; + + if (runner->core->platform(runner->core) == PLATFORM_GBA) { + if (useLightSensor) { + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d); + } else { + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d); + } + } + } + } rumble.up = 0; rumble.down = 0; @@ -313,8 +337,14 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, glUseProgram(program); glBindVertexArray(vao); - float aspectX = width / (float) vwidth; - float aspectY = height / (float) vheight; + float inwidth = width; + float inheight = height; + if (sgbCrop && width == 256 && height == 224) { + inwidth = GB_VIDEO_HORIZONTAL_PIXELS; + inheight = GB_VIDEO_VERTICAL_PIXELS; + } + float aspectX = inwidth / vwidth; + float aspectY = inheight / vheight; float max = 1.f; switch (screenMode) { case SM_PA: @@ -340,6 +370,11 @@ static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, break; } + if (screenMode != SM_SF) { + aspectX = width / (float) vwidth; + aspectY = height / (float) vheight; + } + aspectX *= max; aspectY *= max; @@ -543,6 +578,18 @@ int32_t _readGyroZ(struct mRotationSource* source) { return sixaxis.gyroscope.z * -1.1e9f; } +static void _lightSensorSample(struct GBALuminanceSource* lux) { + struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux; + float luxLevel = 0; + appletGetCurrentIlluminance(&luxLevel); + runnerLux->luxLevel = cbrtf(luxLevel) * 8; +} + +static uint8_t _lightSensorRead(struct GBALuminanceSource* lux) { + struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux; + return 0xFF - runnerLux->luxLevel; +} + static int _batteryState(void) { u32 charge; int state = 0; @@ -690,6 +737,9 @@ int main(int argc, char* argv[]) { rotation.readTiltY = _readTiltY; rotation.readGyroZ = _readGyroZ; + lightSensor.d.readLuminance = _lightSensorRead; + lightSensor.d.sample = _lightSensorSample; + stream.videoDimensionsChanged = NULL; stream.postVideoFrame = NULL; stream.postAudioFrame = NULL; @@ -707,6 +757,9 @@ int main(int argc, char* argv[]) { audoutBuffer[i].data_offset = 0; } + bool illuminanceAvailable = false; + appletIsIlluminanceAvailable(&illuminanceAvailable); + struct mGUIRunner runner = { .params = { 1280, 720, @@ -829,8 +882,19 @@ int main(int argc, char* argv[]) { }, .nStates = 6 }, + { + .title = "Use built-in brightness sensor for Boktai", + .data = "useLightSensor", + .submenu = 0, + .state = illuminanceAvailable, + .validStates = (const char*[]) { + "Off", + "On", + }, + .nStates = 2 + }, }, - .nConfigExtra = 4, + .nConfigExtra = 5, .setup = _setup, .teardown = NULL, .gameLoaded = _gameLoaded, diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index eea6b17d6..20cd2445b 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -17,6 +17,7 @@ #include #include #include "feature/gui/gui-runner.h" +#include #include #include #include @@ -113,6 +114,7 @@ static uint16_t* rescaleTexmem; static GXTexObj rescaleTex; static uint16_t* interframeTexmem; static GXTexObj interframeTex; +static bool sgbCrop = false; static int32_t tiltX; static int32_t tiltY; static int32_t gyroZ; @@ -862,6 +864,9 @@ void _unpaused(struct mGUIRunner* runner) { if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } + if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { + sgbCrop = fakeBool; + } float stretch; if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) { @@ -952,20 +957,25 @@ void _drawFrame(struct mGUIRunner* runner, bool faded) { } } - int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust); - int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust); - if (hfactor > vfactor) { - scaleFactor = vfactor; - } else { - scaleFactor = hfactor; - } - 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; vertHeight *= scaleFactor; - } - if (screenMode == SM_PA) { _reproj(corew * scaleFactor, coreh * scaleFactor); } else { _reproj2(corew, coreh); diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index 5043a860c..2e6abaed5 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -47,7 +47,7 @@ static int _strpcmp(const void* a, const void* b) { return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); } -static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) { +static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) { _cleanFiles(currentFiles); struct VDir* dir = VDirOpen(currentPath); @@ -144,6 +144,9 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, free((char*) testItem->title); GUIMenuItemListShift(currentFiles, item, 1); } else { + if (preselect && strncmp(testItem->title, preselect, PATH_MAX) == 0) { + params->fileIndex = item; + } ++item; } } @@ -152,14 +155,14 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, return true; } -bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*)) { +bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filterName)(const char* name), bool (*filterContents)(struct VFile*), const char* preselect) { struct GUIMenu menu = { .title = "Select file", .subtitle = params->currentPath, - .index = params->fileIndex, }; GUIMenuItemListInit(&menu.items, 0); - _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents); + _refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, preselect); + menu.index = params->fileIndex; while (true) { struct GUIMenuItem* item; @@ -174,7 +177,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool continue; } _upDirectory(params->currentPath); - if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) { + if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) { break; } } else { @@ -187,7 +190,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool struct GUIMenuItemList newFiles; GUIMenuItemListInit(&newFiles, 0); - if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents)) { + if (!_refreshDirectory(params, outPath, &newFiles, filterName, filterContents, NULL)) { _cleanFiles(&newFiles); GUIMenuItemListDeinit(&newFiles); _cleanFiles(&menu.items); @@ -208,7 +211,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool break; } _upDirectory(params->currentPath); - if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents)) { + if (!_refreshDirectory(params, params->currentPath, &menu.items, filterName, filterContents, NULL)) { break; } params->fileIndex = 0;