From 2dcefe8fa5d1beb835e822ae2fc0eac216972942 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 4 Sep 2015 21:02:33 -0700 Subject: [PATCH 001/127] All: CMake cleanup part 1 (ports) --- CMakeLists.txt | 72 +++++-------------- src/platform/3ds/CMakeLists.txt | 47 ++++++++++-- src/platform/3ds/CMakeToolchain.txt | 2 + src/platform/psp2/CMakeLists.txt | 40 +++++++---- src/platform/psp2/{memory.c => psp2-memory.c} | 0 src/platform/wii/CMakeLists.txt | 32 ++++++++- src/platform/wii/CMakeToolchain.txt | 1 + version.cmake | 1 + 8 files changed, 120 insertions(+), 75 deletions(-) rename src/platform/psp2/{memory.c => psp2-memory.c} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 672bad8a9..b926a19f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,32 +147,6 @@ elseif(UNIX) list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c) source_group("POSIX-specific code" FILES ${OS_SRC}) -elseif(WII) - set(M_LIBRARY m) - list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) - add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5 -DUSE_VFS_FILE) - include_directories(${CMAKE_BINARY_DIR}) - file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/wii/*.c) - list(APPEND OS_LIB wiiuse bte fat ogc) - source_group("Wii-specific code" FILES ${OS_SRC}) -elseif(3DS) - set(M_LIBRARY m) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format") - add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5) - if (${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw IS_NEWER_THAN ${CMAKE_BINARY_DIR}/font.c) - execute_process(COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) - endif() - include_directories(${CMAKE_BINARY_DIR}) - list(APPEND OS_LIB sf2d ctru) - file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/3ds/*.c ${CMAKE_BINARY_DIR}/font.c) - set(USE_VFS_3DS ON) - if(USE_VFS_3DS) - add_definitions(-DUSE_VFS_3DS) - else() - add_definitions(-DUSE_VFS_FILE) - list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) - endif() - source_group("3DS-specific code" FILES ${OS_SRC}) endif() if(APPLE) @@ -181,8 +155,9 @@ if(APPLE) endif() if(NOT HAIKU AND NOT MSVC AND NOT PSP2) - list(APPEND OS_LIB m) + set(M_LIBRARY m) endif() +list(APPEND OS_LIB ${M_LIBRARY}) if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto") @@ -196,13 +171,6 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA) endif() endif() -if(WII) - add_definitions(-U__STRICT_ANSI__) - if (${CMAKE_SOURCE_DIR}/src/platform/wii/font.tpl IS_NEWER_THAN ${CMAKE_BINARY_DIR}/font.c) - execute_process(COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/wii/font.tpl OUTPUT_QUIET ERROR_QUIET) - endif() -endif() - if(BUILD_RASPI) set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE) endif() @@ -262,9 +230,6 @@ if(HAVE_SETLOCALE) add_definitions(-DHAVE_SETLOCALE) endif() -if(DISABLE_DEPS) -endif() - # Feature dependencies set(FEATURES) if(CMAKE_SYSTEM_NAME MATCHES .*BSD) @@ -373,6 +338,7 @@ if(USE_MAGICK) endif() if(WANT_ZLIB AND NOT USE_ZLIB) + set(SKIP_INSTALL_ALL ON) add_subdirectory(${CMAKE_SOURCE_DIR}/src/third-party/zlib zlib) set_property(TARGET zlibstatic PROPERTY INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/zlib;${CMAKE_SOURCE_DIR}/src/third-party/zlib) set_property(TARGET zlib PROPERTY EXCLUDE_FROM_ALL ON) @@ -448,15 +414,27 @@ if (USE_LZMA) list(APPEND FEATURES LZMA) endif() -set(FEATURE_DEFINES) foreach(FEATURE IN LISTS FEATURES) - list(APPEND FEATURE_DEFINES "USE_${FEATURE}") + add_definitions("-DUSE_${FEATURE}") endforeach() source_group("Virtual files" FILES ${VFS_SRC}) source_group("Extra features" FILES ${FEATURE_SRC}) source_group("Third-party code" FILES ${THIRD_PARTY_SRC}) +# Platform binaries +if(3DS) + add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/3ds ${CMAKE_BINARY_DIR}/3ds) +endif() + +if(WII) + add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/wii ${CMAKE_BINARY_DIR}/wii) +endif() + +if(PSP2) + add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/psp2 ${CMAKE_BINARY_DIR}/psp2) +endif() + # Binaries set(CORE_SRC ${ARM_SRC} @@ -490,7 +468,7 @@ if(BUILD_SHARED) set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) - set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") + set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) add_dependencies(${BINARY_NAME}-static version-info) endif() @@ -513,7 +491,7 @@ if(UNIX AND NOT APPLE) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) endif() -set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES}") if(BUILD_GL) add_definitions(-DBUILD_GL) @@ -544,18 +522,6 @@ if(BUILD_QT) add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/qt ${CMAKE_BINARY_DIR}/qt) endif() -if(WII) - add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/wii ${CMAKE_BINARY_DIR}) -endif() - -if(PSP2) - add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/psp2 ${CMAKE_BINARY_DIR}) -endif() - -if(3DS) - add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/3ds ${CMAKE_BINARY_DIR}) -endif() - if(BUILD_PERF) set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/test/perf-main.c) if(UNIX AND NOT APPLE) diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 102518650..52e64740f 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -1,5 +1,42 @@ -add_executable(${BINARY_NAME}.elf ${GUI_SRC}) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") -target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} m ${OS_LIB}) -add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx DESTINATION . COMPONENT ${BINARY_NAME}-3ds) +set(USE_VFS_3DS ON CACNE BOOL "Use 3DS-specific file support") +mark_as_advanced(USE_VFS_3DS) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format" PARENT_SCOPE) +set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +list(APPEND OS_LIB sf2d ctru) +file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/3ds-*.c) +set(OS_SRC ${OS_SRC} PARENT_SCOPE) +source_group("3DS-specific code" FILES ${OS_SRC}) + +if(USE_VFS_3DS) + list(APPEND OS_DEFINES USE_VFS_3DS) +else() + list(APPEND OS_DEFINES USE_VFS_FILE) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) +endif() +set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) +set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) + +list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c ctru-heap.c) +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") +target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) + +add_custom_command(OUTPUT ${BINARY_NAME}.smdh + COMMAND ${SMDHTOOL} --create "${PROJECT_NAME}" "${SUMMARY}" "endrift" ${CMAKE_SOURCE_DIR}/res/mgba-48.png ${BINARY_NAME}.smdh) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c + COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) + +add_custom_target(${BINARY_NAME}.3dsx ALL + ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh + DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) + +add_custom_target(run ${3DSLINK} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx + DEPENDS ${BINARY_NAME}.3dsx) + +install(FILES ${BINARY_NAME}.3dsx ${BINARY_NAME}.smdh DESTINATION . COMPONENT ${BINARY_NAME}-3ds) diff --git a/src/platform/3ds/CMakeToolchain.txt b/src/platform/3ds/CMakeToolchain.txt index 66e768cfc..fb22e7723 100644 --- a/src/platform/3ds/CMakeToolchain.txt +++ b/src/platform/3ds/CMakeToolchain.txt @@ -35,8 +35,10 @@ set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") +set(3DSLINK ${toolchain_bin_dir}/3dslink) set(3DSXTOOL ${toolchain_bin_dir}/3dsxtool) set(RAW2C ${toolchain_bin_dir}/raw2c) +set(SMDHTOOL ${toolchain_bin_dir}/smdhtool) set(3DS ON) add_definitions(-D_3DS -DARM11) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 1562b0831..72295d5b6 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,18 +1,30 @@ -file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/*.c) +file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c) +set(OS_SRC ${OS_SRC} PARENT_SCOPE) +source_group("PS Vita-specific code" FILES ${OS_SRC}) -if (${CMAKE_SOURCE_DIR}/res/font.png IS_NEWER_THAN ${CMAKE_BINARY_DIR}/font.o) - execute_process(COMMAND ${OBJCOPY} -I binary -O elf32-littlearm -B arm font.png ${CMAKE_BINARY_DIR}/font.o WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) -endif() +list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) +set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) -if (${CMAKE_SOURCE_DIR}/res/backdrop.png IS_NEWER_THAN ${CMAKE_BINARY_DIR}/backdrop.o) - execute_process(COMMAND ${OBJCOPY} -I binary -O elf32-littlearm -B arm backdrop.png ${CMAKE_BINARY_DIR}/backdrop.o WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -endif() +set(OS_LIB -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lpng -lz -l${M_LIBRARY}) +set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) -set(PLATFORM_LIBRARY -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lpng -lz -l${M_LIBRARY}) +list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) -add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_BINARY_DIR}/font.o ${CMAKE_BINARY_DIR}/backdrop.o) -target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${PLATFORM_LIBRARY}) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") -set_target_properties(${BINARY_NAME}.elf PROPERTIES OUTPUT_NAME ${BINARY_NAME}.elf) -add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${FIXUP} ${BINARY_NAME}.elf ${BINARY_NAME}.velf ${NIDDB} MAIN_DEPENDENCY ${BINARY_NAME}.elf) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_BINARY_DIR}/backdrop.c PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_BINARY_DIR}/font.o ${CMAKE_BINARY_DIR}/backdrop.o main.c) +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") +target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c + COMMAND ${OBJCOPY_CMD} font.png ${CMAKE_CURRENT_BINARY_DIR}/font.o + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.c + COMMAND ${OBJCOPY_CMD} backdrop.png ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) + +add_custom_target(${BINARY_NAME}.velf ALL + ${FIXUP} ${BINARY_NAME}.elf ${BINARY_NAME}.velf ${NIDDB} + DEPENDS ${BINARY_NAME}.elf) + +install(FILES ${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/psp2/memory.c b/src/platform/psp2/psp2-memory.c similarity index 100% rename from src/platform/psp2/memory.c rename to src/platform/psp2/psp2-memory.c diff --git a/src/platform/wii/CMakeLists.txt b/src/platform/wii/CMakeLists.txt index 20412feb2..1ef6e6df0 100644 --- a/src/platform/wii/CMakeLists.txt +++ b/src/platform/wii/CMakeLists.txt @@ -1,7 +1,33 @@ -add_executable(${BINARY_NAME}.elf ${GUI_SRC} ${CMAKE_BINARY_DIR}/font.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5 USE_VFS_FILE) +list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/wii/wii-*.c) +list(APPEND OS_LIB wiiuse bte fat ogc) +set(OS_SRC ${OS_SRC} PARENT_SCOPE) +source_group("Wii-specific code" FILES ${OS_SRC}) +set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) +set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) + +list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c) +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) -add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${ELF2DOL} ${BINARY_NAME}.elf ${BINARY_NAME}.dol) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c + COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/wii/font.tpl + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_target(${BINARY_NAME}.dol ALL + ${ELF2DOL} ${BINARY_NAME}.elf ${BINARY_NAME}.dol + DEPENDS ${BINARY_NAME}.elf) + +add_custom_target(run ${WIILOAD} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.dol + DEPENDS ${BINARY_NAME}.dol) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/meta.xml.in ${CMAKE_CURRENT_BINARY_DIR}/meta.xml) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/icon.png ${CMAKE_CURRENT_BINARY_DIR}/meta.xml DESTINATION . COMPONENT ${BINARY_NAME}-wii) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.dol DESTINATION . RENAME boot.dol COMPONENT ${BINARY_NAME}-wii) diff --git a/src/platform/wii/CMakeToolchain.txt b/src/platform/wii/CMakeToolchain.txt index 8d6b061ae..26ecd3fa7 100644 --- a/src/platform/wii/CMakeToolchain.txt +++ b/src/platform/wii/CMakeToolchain.txt @@ -37,6 +37,7 @@ set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") set(ELF2DOL ${toolchain_bin_dir}/elf2dol) set(GXTEXCONV ${toolchain_bin_dir}/gxtexconv) set(RAW2C ${toolchain_bin_dir}/raw2c) +set(WIILOAD ${toolchain_bin_dir}/wiiload) set(WII ON) add_definitions(-DGEKKO) diff --git a/version.cmake b/version.cmake index 22bc5e7b5..5ae523ab4 100644 --- a/version.cmake +++ b/version.cmake @@ -6,6 +6,7 @@ set(LIB_VERSION_MINOR 4) set(LIB_VERSION_PATCH 0) set(LIB_VERSION_ABI 0.4) set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) +set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator") execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) From 4cf016d442f966605d818f9fee26b375dcebac35 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 4 Sep 2015 22:50:20 -0700 Subject: [PATCH 002/127] GUI: Support for touch/cursor --- src/platform/3ds/main.c | 15 +++++++++++++- src/platform/psp2/CMakeLists.txt | 2 +- src/platform/psp2/main.c | 19 +++++++++++++++--- src/platform/wii/main.c | 24 +++++++++++++++++++--- src/util/gui.c | 31 +++++++++++++++++++++++++++++ src/util/gui.h | 14 ++++++++++++- src/util/gui/menu.c | 34 +++++++++++++++++++++++--------- 7 files changed, 121 insertions(+), 18 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 22aae06e8..5b99ff675 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -235,6 +235,18 @@ static uint32_t _pollInput(void) { return keys; } +static enum GUICursorState _pollCursor(int* x, int* y) { + hidScanInput(); + if (!(hidKeysHeld() & KEY_TOUCH)) { + return GUI_CURSOR_UP; + } + touchPosition pos; + hidTouchRead(&pos); + *x = pos.px; + *y = pos.py; + return GUI_CURSOR_DOWN; +} + static void _sampleRotation(struct GBARotationSource* source) { struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source; // Work around ctrulib getting the entries wrong @@ -322,7 +334,8 @@ int main() { .params = { 320, 240, font, "/", - _drawStart, _drawEnd, _pollInput, + _drawStart, _drawEnd, + _pollInput, _pollCursor, 0, 0, GUI_PARAMS_TRAIL diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 72295d5b6..d03f61182 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -5,7 +5,7 @@ source_group("PS Vita-specific code" FILES ${OS_SRC}) list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lpng -lz -l${M_LIBRARY}) +set(OS_LIB -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lpng -lz -l${M_LIBRARY}) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 31393c037..0dd1ad8a8 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -63,15 +64,27 @@ static uint32_t _pollInput(void) { return input; } -int main() { - printf("%s initializing", projectName); +static enum GUICursorState _pollCursor(int* x, int* y) { + SceTouchData touch; + sceTouchPeek(0, &touch, 1); + if (touch.reportNum < 1) { + return GUI_CURSOR_UP; + } + *x = touch.report[0].x / 2; + *y = touch.report[0].y / 2; + return GUI_CURSOR_DOWN; +} + +int main() { vita2d_init(); struct GUIFont* font = GUIFontCreate(); struct GBAGUIRunner runner = { .params = { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, - font, "cache0:", _drawStart, _drawEnd, _pollInput, 0, 0, + font, "cache0:", _drawStart, _drawEnd, + _pollInput, _pollCursor, + 0, 0, GUI_PARAMS_TRAIL }, diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 8f37b6cf5..b4277c512 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -34,6 +34,7 @@ static int32_t _readGyroZ(struct GBARotationSource* source); static void _drawStart(void); static void _drawEnd(void); static uint32_t _pollInput(void); +static enum GUICursorState _pollCursor(int* x, int* y); static void _guiPrepare(void); static void _guiFinish(void); @@ -68,6 +69,7 @@ int main() { VIDEO_Init(); PAD_Init(); WPAD_Init(); + WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR); AUDIO_Init(0); AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ); AUDIO_RegisterDMACallback(_audioDMA); @@ -159,7 +161,8 @@ int main() { .params = { 352, 230, font, "/", - _drawStart, _drawEnd, _pollInput, + _drawStart, _drawEnd, + _pollInput, _pollCursor, _guiPrepare, _guiFinish, GUI_PARAMS_TRAIL @@ -254,7 +257,7 @@ static uint32_t _pollInput(void) { ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) { keys |= 1 << GUI_INPUT_SELECT; } - if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || + if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || (wiiPad & WPAD_BUTTON_B) || ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) { keys |= 1 << GUI_INPUT_BACK; } @@ -281,6 +284,22 @@ static uint32_t _pollInput(void) { return keys; } +static enum GUICursorState _pollCursor(int* x, int* y) { + ir_t ir; + WPAD_IR(0, &ir); + if (!ir.smooth_valid) { + return GUI_CURSOR_NOT_PRESENT; + } + *x = ir.sx; + *y = ir.sy; + WPAD_ScanPads(); + u32 wiiPad = WPAD_ButtonsHeld(0); + if (wiiPad & WPAD_BUTTON_A) { + return GUI_CURSOR_DOWN; + } + return GUI_CURSOR_UP; +} + void _guiPrepare(void) { Mtx44 proj; guOrtho(proj, -20, 240, 0, 352, 0, 300); @@ -324,7 +343,6 @@ void _gameUnloaded(struct GBAGUIRunner* runner) { } void _gameLoaded(struct GBAGUIRunner* runner) { - WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC); if (runner->context.gba->memory.hw.devices & HW_GYRO) { int i; for (i = 0; i < 6; ++i) { diff --git a/src/util/gui.c b/src/util/gui.c index 0d03fae4b..85c09a1c7 100644 --- a/src/util/gui.c +++ b/src/util/gui.c @@ -31,6 +31,37 @@ void GUIPollInput(struct GUIParams* params, uint32_t* newInputOut, uint32_t* hel } } +enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y) { + if (!params->pollCursor) { + return GUI_CURSOR_NOT_PRESENT; + } + enum GUICursorState state = params->pollCursor(x, y); + if (params->cursorState == GUI_CURSOR_DOWN) { + int dragX = *x - params->cx; + int dragY = *y - params->cy; + if (dragX * dragX + dragY * dragY > 25) { + params->cursorState = GUI_CURSOR_DRAGGING; + return GUI_CURSOR_DRAGGING; + } + if (state == GUI_CURSOR_UP) { + params->cursorState = GUI_CURSOR_UP; + return GUI_CURSOR_CLICKED; + } + } else { + params->cx = *x; + params->cy = *y; + } + if (params->cursorState == GUI_CURSOR_DRAGGING) { + if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { + params->cursorState = GUI_CURSOR_UP; + return GUI_CURSOR_UP; + } + return GUI_CURSOR_DRAGGING; + } + params->cursorState = state; + return params->cursorState; +} + void GUIInvalidateKeys(struct GUIParams* params) { for (int i = 0; i < GUI_INPUT_MAX; ++i) { params->inputHistory[i] = 0; diff --git a/src/util/gui.h b/src/util/gui.h index 263e2c10c..621565152 100644 --- a/src/util/gui.h +++ b/src/util/gui.h @@ -28,6 +28,14 @@ enum GUIInput { GUI_INPUT_MAX = 0x20 }; +enum GUICursorState { + GUI_CURSOR_NOT_PRESENT, + GUI_CURSOR_UP, + GUI_CURSOR_DOWN, + GUI_CURSOR_CLICKED, + GUI_CURSOR_DRAGGING +}; + struct GUIBackground { void (*draw)(struct GUIBackground*, void* context); }; @@ -41,21 +49,25 @@ struct GUIParams { void (*drawStart)(void); void (*drawEnd)(void); uint32_t (*pollInput)(void); + enum GUICursorState (*pollCursor)(int* x, int* y); void (*guiPrepare)(void); void (*guiFinish)(void); // State int inputHistory[GUI_INPUT_MAX]; + enum GUICursorState cursorState; + int cx, cy; // Directories char currentPath[PATH_MAX]; size_t fileIndex; }; -#define GUI_PARAMS_TRAIL {}, "", 0 +#define GUI_PARAMS_TRAIL {}, GUI_CURSOR_NOT_PRESENT, 0, 0, "", 0 void GUIInit(struct GUIParams* params); void GUIPollInput(struct GUIParams* params, uint32_t* newInput, uint32_t* heldInput); +enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y); void GUIInvalidateKeys(struct GUIParams* params); #endif diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 5469ed786..9481cbd70 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -12,17 +12,21 @@ DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem); enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem* item) { size_t start = 0; - size_t pageSize = params->height / GUIFontHeight(params->font); + size_t lineHeight = GUIFontHeight(params->font); + size_t pageSize = params->height / lineHeight; if (pageSize > 4) { pageSize -= 4; } else { pageSize = 1; } + int cursorOverItem = 0; GUIInvalidateKeys(params); while (true) { uint32_t newInput = 0; GUIPollInput(params, &newInput, 0); + int cx, cy; + enum GUICursorState cursor = GUIPollCursor(params, &cx, &cy); if (newInput & (1 << GUI_INPUT_UP) && menu->index > 0) { --menu->index; @@ -44,17 +48,28 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men menu->index = GUIMenuItemListSize(&menu->items) - 1; } } + if (cursor != GUI_CURSOR_NOT_PRESENT) { + int index = (cy / lineHeight) - 2; + if (index >= 0 && index + start < GUIMenuItemListSize(&menu->items)) { + if (menu->index != index + start || !cursorOverItem) { + cursorOverItem = 1; + } + menu->index = index + start; + } else { + cursorOverItem = 0; + } + } if (menu->index < start) { start = menu->index; } - while ((menu->index - start + 4) * GUIFontHeight(params->font) > params->height) { + while ((menu->index - start + 4) * lineHeight > params->height) { ++start; } if (newInput & (1 << GUI_INPUT_CANCEL)) { break; } - if (newInput & (1 << GUI_INPUT_SELECT)) { + if (newInput & (1 << GUI_INPUT_SELECT) || (cursorOverItem == 2 && cursor == GUI_CURSOR_CLICKED)) { *item = *GUIMenuItemListGetPointer(&menu->items, menu->index); if (item->submenu) { enum GUIMenuExitReason reason = GUIShowMenu(params, item->submenu, item); @@ -65,6 +80,9 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men return GUI_MENU_EXIT_ACCEPT; } } + if ((cursorOverItem == 1 && cursor == GUI_CURSOR_UP)) { + cursorOverItem = 2; + } if (newInput & (1 << GUI_INPUT_BACK)) { return GUI_MENU_EXIT_BACK; } @@ -76,9 +94,9 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men if (params->guiPrepare) { params->guiPrepare(); } - unsigned y = GUIFontHeight(params->font); + unsigned y = lineHeight; GUIFontPrint(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, menu->title); - y += 2 * GUIFontHeight(params->font); + y += 2 * lineHeight; size_t i; for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) { int color = 0xE0A0A0A0; @@ -88,16 +106,14 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men bullet = '>'; } GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, GUIMenuItemListGetPointer(&menu->items, i)->title); - y += GUIFontHeight(params->font); - if (y + GUIFontHeight(params->font) > params->height) { + y += lineHeight; + if (y + lineHeight > params->height) { break; } } if (params->guiFinish) { params->guiFinish(); } - y += GUIFontHeight(params->font) * 2; - params->drawEnd(); } return GUI_MENU_EXIT_CANCEL; From 0af12911a1da84a03826de69876e4cdd1a239338 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 4 Sep 2015 23:04:46 -0700 Subject: [PATCH 003/127] 3DS, PSP2: CMake fixes --- src/platform/3ds/CMakeLists.txt | 6 +++--- src/platform/psp2/CMakeLists.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 52e64740f..5b92201ca 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -33,10 +33,10 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) add_custom_target(${BINARY_NAME}.3dsx ALL - ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh - DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) + ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh + DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) add_custom_target(run ${3DSLINK} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx DEPENDS ${BINARY_NAME}.3dsx) -install(FILES ${BINARY_NAME}.3dsx ${BINARY_NAME}.smdh DESTINATION . COMPONENT ${BINARY_NAME}-3ds) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh DESTINATION . COMPONENT ${BINARY_NAME}-3ds) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index d03f61182..9cc458698 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -11,11 +11,11 @@ set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_BINARY_DIR}/backdrop.c PROPERTIES GENERATED ON) -add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_BINARY_DIR}/font.o ${CMAKE_BINARY_DIR}/backdrop.o main.c) +add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_BINARY_DIR}/backdrop.o main.c) set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o COMMAND ${OBJCOPY_CMD} font.png ${CMAKE_CURRENT_BINARY_DIR}/font.o WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) @@ -27,4 +27,4 @@ add_custom_target(${BINARY_NAME}.velf ALL ${FIXUP} ${BINARY_NAME}.elf ${BINARY_NAME}.velf ${NIDDB} DEPENDS ${BINARY_NAME}.elf) -install(FILES ${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) From db994ef8aba0ba122521a600eda9fdb62190cfae Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 4 Sep 2015 23:07:52 -0700 Subject: [PATCH 004/127] PSP2: More CMake fixes --- src/platform/psp2/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 9cc458698..fca272fd2 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -10,8 +10,8 @@ set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) -set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_BINARY_DIR}/backdrop.c PROPERTIES GENERATED ON) -add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_BINARY_DIR}/backdrop.o main.c) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o main.c) set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) @@ -19,9 +19,9 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o COMMAND ${OBJCOPY_CMD} font.png ${CMAKE_CURRENT_BINARY_DIR}/font.o WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.c +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o COMMAND ${OBJCOPY_CMD} backdrop.png ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${BINARY_NAME}.velf ALL ${FIXUP} ${BINARY_NAME}.elf ${BINARY_NAME}.velf ${NIDDB} From 346503cd41793fe6a7dfe7de6d018032d10cda97 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 5 Sep 2015 00:50:51 -0700 Subject: [PATCH 005/127] GUI: Fix non-touch support --- src/platform/3ds/main.c | 2 +- src/platform/psp2/main.c | 2 +- src/util/gui.c | 2 +- src/util/gui/menu.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 5b99ff675..b35005b33 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -238,7 +238,7 @@ static uint32_t _pollInput(void) { static enum GUICursorState _pollCursor(int* x, int* y) { hidScanInput(); if (!(hidKeysHeld() & KEY_TOUCH)) { - return GUI_CURSOR_UP; + return GUI_CURSOR_NOT_PRESENT; } touchPosition pos; hidTouchRead(&pos); diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 0dd1ad8a8..e1ca9285e 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -68,7 +68,7 @@ static enum GUICursorState _pollCursor(int* x, int* y) { SceTouchData touch; sceTouchPeek(0, &touch, 1); if (touch.reportNum < 1) { - return GUI_CURSOR_UP; + return GUI_CURSOR_NOT_PRESENT; } *x = touch.report[0].x / 2; *y = touch.report[0].y / 2; diff --git a/src/util/gui.c b/src/util/gui.c index 85c09a1c7..b391876b6 100644 --- a/src/util/gui.c +++ b/src/util/gui.c @@ -43,7 +43,7 @@ enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y) { params->cursorState = GUI_CURSOR_DRAGGING; return GUI_CURSOR_DRAGGING; } - if (state == GUI_CURSOR_UP) { + if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { params->cursorState = GUI_CURSOR_UP; return GUI_CURSOR_CLICKED; } diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 9481cbd70..ab9ad8846 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -80,7 +80,7 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men return GUI_MENU_EXIT_ACCEPT; } } - if ((cursorOverItem == 1 && cursor == GUI_CURSOR_UP)) { + if (cursorOverItem == 1 && (cursor == GUI_CURSOR_UP || cursor == GUI_CURSOR_NOT_PRESENT)) { cursorOverItem = 2; } if (newInput & (1 << GUI_INPUT_BACK)) { From 20559ac2ea54f80cb5c6d3192129b2cfbcbb6381 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 5 Sep 2015 00:51:26 -0700 Subject: [PATCH 006/127] GUI: More detailed scanning information --- src/util/gui/file-select.c | 58 +++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index dab190040..1d3a56809 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -12,7 +12,13 @@ #include #define ITERATION_SIZE 5 -#define SCANNING_THRESHOLD 20 +#define SCANNING_THRESHOLD_1 50 +#ifdef _3DS +// 3DS is slooooow at opening files +#define SCANNING_THRESHOLD_2 10 +#else +#define SCANNING_THRESHOLD_2 50 +#endif static void _cleanFiles(struct GUIMenuItemList* currentFiles) { size_t size = GUIMenuItemListSize(currentFiles); @@ -52,10 +58,11 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, } *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" }; size_t i = 0; + size_t items = 0; struct VDirEntry* de; while ((de = dir->listNext(dir))) { ++i; - if (!(i % SCANNING_THRESHOLD)) { + if (!(i % SCANNING_THRESHOLD_1)) { uint32_t input = 0; GUIPollInput(params, &input, 0); if (input & (1 << GUI_INPUT_CANCEL)) { @@ -67,7 +74,7 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, params->guiPrepare(); } GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu)", i); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %lu)", i); if (params->guiFinish) { params->guiFinish(); } @@ -77,21 +84,46 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, if (name[0] == '.') { continue; } - if (de->type(de) == VFS_FILE) { - struct VFile* vf = dir->openFile(dir, name, O_RDONLY); - if (!vf) { - continue; + *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) }; + ++items; + } + qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp); + i = 0; + size_t item = 0; + while (item < GUIMenuItemListSize(currentFiles)) { + ++i; + if (!(i % SCANNING_THRESHOLD_2)) { + uint32_t input = 0; + GUIPollInput(params, &input, 0); + if (input & (1 << GUI_INPUT_CANCEL)) { + return false; } - if (!filter || filter(vf)) { - *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) }; + + params->drawStart(); + if (params->guiPrepare) { + params->guiPrepare(); } - vf->close(vf); - } else { - *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) }; + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu of %lu)", i, items); + if (params->guiFinish) { + params->guiFinish(); + } + params->drawEnd(); } + struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY); + if (!vf) { + ++item; + continue; + } + if (filter && !filter(vf)) { + free(GUIMenuItemListGetPointer(currentFiles, item)->title); + GUIMenuItemListShift(currentFiles, item, 1); + } else { + ++item; + } + vf->close(vf); } dir->close(dir); - qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp); return true; } From 14ee1589f0783be6b6d99597e682d84a7869c351 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 5 Sep 2015 02:54:21 -0700 Subject: [PATCH 007/127] 3DS: Tweak alignment, probably does nothing --- src/platform/3ds/main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index b35005b33..58f604c98 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -81,7 +81,7 @@ static void _setup(struct GBAGUIRunner* runner) { } GBAVideoSoftwareRendererCreate(&renderer); - renderer.outputBuffer = linearMemAlign(256 * 256 * 2, 0x100); + renderer.outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80); renderer.outputBufferStride = 256; runner->context.renderer = &renderer.d; @@ -163,7 +163,7 @@ static void _drawFrame(struct GBAGUIRunner* runner, bool faded) { static void _drawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded) { UNUSED(runner); - u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x100); + u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80); unsigned y, x; for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { @@ -308,8 +308,8 @@ int main() { } if (hasSound) { - audioLeft = linearAlloc(AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - audioRight = linearAlloc(AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); + audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); + audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); } sf2d_init(); From 3849901dabb58695246f528896d124d1cb701272 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 5 Sep 2015 13:05:38 -0700 Subject: [PATCH 008/127] PSP2: Drop psp2sdk --- src/platform/psp2/CMakeToolchain.psp2sdk | 55 ------------------------ 1 file changed, 55 deletions(-) delete mode 100644 src/platform/psp2/CMakeToolchain.psp2sdk diff --git a/src/platform/psp2/CMakeToolchain.psp2sdk b/src/platform/psp2/CMakeToolchain.psp2sdk deleted file mode 100644 index 4b86e0a83..000000000 --- a/src/platform/psp2/CMakeToolchain.psp2sdk +++ /dev/null @@ -1,55 +0,0 @@ -if(DEFINED ENV{DEVKITPRO}) - set(DEVKITPRO $ENV{DEVKITPRO}) -else() - message(FATAL_ERROR "Could not find DEVKITPRO in environment") -endif() - -if(DEFINED ENV{DEVKITARM}) - set(DEVKITARM $ENV{DEVKITARM}) -else() - set(DEVKITARM ${DEVKITPRO}/devkitARM) -endif() - -if(NOT DEFINED UNITY) - set(UNITY ON) -endif() - -set(toolchain_dir ${DEVKITARM}/psp2) -set(toolchain_bin_dir ${toolchain_dir}/bin) -set(cross_prefix ${DEVKITARM}/bin/arm-none-eabi-) -set(inc_flags -I${toolchain_dir}/include) -set(arch_flags "-march=armv7-a -mtune=cortex-a9 -specs=psp2.specs") -set(link_flags "-L${toolchain_dir}/lib") - -set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") -set(CMAKE_SYSTEM_PROCESSOR armv7a- CACHE INTERNAL "processor") -set(CMAKE_LIBRARY_ARCHITECTURE arm-none-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "ranlib") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -if(NOT UNITY) - set(common_flags "${arch_flags} -fno-reorder-functions -fno-optimize-sibling-calls ${inc_flags}") -else() - set(common_flags "${arch_flags} -fno-gcse -fno-tree-vectorize ${inc_flags}") -endif() -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") -set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") - -set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") -set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") -set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") - -set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE) - -set(FIXUP ${toolchain_bin_dir}/psp2-fixup -q -S) -set(OBJCOPY ${cross_prefix}objcopy) - -set(PSP2 ON) -add_definitions(-DPSP2) -set(M_LIBRARY m_stub) - -set(CMAKE_C_COMPILER_WORKS 1) # Skip test From a1232b898fd57a557e54253bf0de675d262265c2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 5 Sep 2015 13:11:31 -0700 Subject: [PATCH 009/127] PSP2: Strip binaries --- src/platform/psp2/CMakeLists.txt | 3 ++- src/platform/psp2/CMakeToolchain.vitasdk | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index fca272fd2..1ee0cf443 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -24,7 +24,8 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${BINARY_NAME}.velf ALL - ${FIXUP} ${BINARY_NAME}.elf ${BINARY_NAME}.velf ${NIDDB} + ${STRIP} --strip-unneeded -go ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf + COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} DEPENDS ${BINARY_NAME}.elf) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/psp2/CMakeToolchain.vitasdk b/src/platform/psp2/CMakeToolchain.vitasdk index 51e3d1090..49100b723 100644 --- a/src/platform/psp2/CMakeToolchain.vitasdk +++ b/src/platform/psp2/CMakeToolchain.vitasdk @@ -33,6 +33,7 @@ set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE) set(FIXUP ${toolchain_bin_dir}/vita-elf-create) set(OBJCOPY ${cross_prefix}objcopy) set(NIDDB ${VITASDK}/db.json) +set(STRIP ${cross_prefix}strip) set(PSP2 ON) add_definitions(-DPSP2) From 7f904876f57d862dfeea232c3591d27cb6f39c0c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 5 Sep 2015 18:15:50 -0700 Subject: [PATCH 010/127] 3DS: CIA build --- src/platform/3ds/CMakeLists.txt | 22 ++- src/platform/3ds/CMakeToolchain.txt | 3 + src/platform/3ds/bios.wav | Bin 0 -> 235844 bytes src/platform/3ds/cia.rsf.in | 238 ++++++++++++++++++++++++++++ src/platform/3ds/logo.png | Bin 0 -> 33996 bytes 5 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 src/platform/3ds/bios.wav create mode 100644 src/platform/3ds/cia.rsf.in create mode 100644 src/platform/3ds/logo.png diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 5b92201ca..e9da116d8 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -27,16 +27,32 @@ set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DE target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) add_custom_command(OUTPUT ${BINARY_NAME}.smdh - COMMAND ${SMDHTOOL} --create "${PROJECT_NAME}" "${SUMMARY}" "endrift" ${CMAKE_SOURCE_DIR}/res/mgba-48.png ${BINARY_NAME}.smdh) + COMMAND ${BANNERTOOL} makesmdh -s "${PROJECT_NAME}" -l "${SUMMARY}" -p "endrift" -i ${CMAKE_SOURCE_DIR}/res/mgba-48.png -o ${BINARY_NAME}.smdh + DEPENDS ${CMAKE_SOURCE_DIR}/res/mgba-48.png) + +add_custom_command(OUTPUT ${BINARY_NAME}.bnr + COMMAND ${BANNERTOOL} makebanner -i ${CMAKE_CURRENT_SOURCE_DIR}/logo.png -a ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav -o ${BINARY_NAME}.bnr + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/logo.png ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c - COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) + COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw + DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) add_custom_target(${BINARY_NAME}.3dsx ALL ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) +add_custom_target(${BINARY_NAME}.cia ALL + ${STRIP} -o ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf + COMMAND ${MAKEROM} -f cia -o ${BINARY_NAME}.cia -rsf cia.rsf -target t -exefslogo -elf ${BINARY_NAME}-stripped.elf -icon ${BINARY_NAME}.smdh -banner ${BINARY_NAME}.bnr + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf ${BINARY_NAME}.elf ${BINARY_NAME}.smdh ${BINARY_NAME}.bnr) + add_custom_target(run ${3DSLINK} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx DEPENDS ${BINARY_NAME}.3dsx) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh DESTINATION . COMPONENT ${BINARY_NAME}-3ds) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cia.rsf.in ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.cia + DESTINATION . COMPONENT ${BINARY_NAME}-3ds) diff --git a/src/platform/3ds/CMakeToolchain.txt b/src/platform/3ds/CMakeToolchain.txt index fb22e7723..66dda1dcd 100644 --- a/src/platform/3ds/CMakeToolchain.txt +++ b/src/platform/3ds/CMakeToolchain.txt @@ -37,8 +37,11 @@ set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") set(3DSLINK ${toolchain_bin_dir}/3dslink) set(3DSXTOOL ${toolchain_bin_dir}/3dsxtool) +set(BANNERTOOL ${toolchain_bin_dir}/bannertool) +set(MAKEROM ${toolchain_bin_dir}/makerom) set(RAW2C ${toolchain_bin_dir}/raw2c) set(SMDHTOOL ${toolchain_bin_dir}/smdhtool) +set(STRIP ${cross_prefix}strip) set(3DS ON) add_definitions(-D_3DS -DARM11) diff --git a/src/platform/3ds/bios.wav b/src/platform/3ds/bios.wav new file mode 100644 index 0000000000000000000000000000000000000000..377c588473864437f35cffc9d7dfb8ee0baeddec GIT binary patch literal 235844 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/ie`{e} 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 RKm;NXfe1t(0{_1V{0D)YAJPB- literal 0 HcmV?d00001 diff --git a/src/platform/3ds/cia.rsf.in b/src/platform/3ds/cia.rsf.in new file mode 100644 index 000000000..4252aa368 --- /dev/null +++ b/src/platform/3ds/cia.rsf.in @@ -0,0 +1,238 @@ +BasicInfo: + Title : "${PROJECT_NAME}" + CompanyCode : "00" + ProductCode : "CTR-P-MGBA" + ContentType : Application + Logo : Nintendo # Nintendo / Licensed / Distributed / iQue / iQueForSystem + +#Rom: + # Specifies the root path of the file system to include in the ROM. + # HostRoot : "romfs" + + +TitleInfo: + UniqueId : 0x1A1E + Category : Application + +CardInfo: + MediaSize : 128MB # 128MB / 256MB / 512MB / 1GB / 2GB / 4GB / 8GB / 16GB / 32GB + MediaType : Card1 # Card1 / Card2 + CardDevice : None # NorFlash(Pick this if you use savedata) / None + + +Option: + UseOnSD : true # true if App is to be installed to SD + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : true # Compresses exefs code + +ExeFs: # these are the program segments from the ELF, check your elf for the appropriate segment names + ReadOnly: + - .rodata + - RO + ReadWrite: + - .data + - RO + Text: + - .init + - .text + - STUP_ENTRY + +PlainRegion: # only used with SDK ELFs + # - .module_id + +AccessControlInfo: + # UseOtherVariationSaveData : true + # UseExtSaveData : true + # ExtSaveDataId: 0xffffffff + # SystemSaveDataId1: 0x220 + # SystemSaveDataId2: 0x00040010 + # OtherUserSaveDataId1: 0x220 + # OtherUserSaveDataId2: 0x330 + # OtherUserSaveDataId3: 0x440 + # UseExtendedSaveDataAccessControl: true + # AccessibleSaveDataIds: [0x101, 0x202, 0x303, 0x404, 0x505, 0x606] + FileSystemAccess: + # - CategorySystemApplication + # - CategoryHardwareCheck + # - CategoryFileSystemTool + - Debug + # - TwlCardBackup + # - TwlNandData + # - Boss + - DirectSdmc + # - Core + # - CtrNandRo + # - CtrNandRw + # - CtrNandRoWrite + # - CategorySystemSettings + # - CardBoard + # - ExportImportIvs + # - DirectSdmcWrite + # - SwitchCleanup + # - SaveDataMove + # - Shop + # - Shell + # - CategoryHomeMenu + IoAccessControl: + # - FsMountNand + # - FsMountNandRoWrite + # - FsMountTwln + # - FsMountWnand + # - FsMountCardSpi + # - UseSdif3 + # - CreateSeed + # - UseCardSpi + + IdealProcessor : 0 + AffinityMask : 1 + + Priority : 16 + + MaxCpu : 0x9E # Default + + DisableDebug : true + EnableForceDebug : false + CanWriteSharedPage : true + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : true + PermitMainFunctionArgument : true + CanShareDeviceMemory : true + RunnableOnSleep : false + SpecialMemoryArrange : true + + CoreVersion : 2 + DescVersion : 2 + + ReleaseKernelMajor : "02" + ReleaseKernelMinor : "33" + MemoryType : Application # Application / System / Base + HandleTableSize: 512 + IORegisterMapping: + - 1ff50000-1ff57fff + - 1ff70000-1ff77fff + MemoryMapping: + - 1f000000-1f5fffff:r + SystemCallAccess: + ArbitrateAddress: 34 + Break: 60 + CancelTimer: 28 + ClearEvent: 25 + ClearTimer: 29 + CloseHandle: 35 + ConnectToPort: 45 + ControlMemory: 1 + CreateAddressArbiter: 33 + CreateEvent: 23 + CreateMemoryBlock: 30 + CreateMutex: 19 + CreateSemaphore: 21 + CreateThread: 8 + CreateTimer: 26 + DuplicateHandle: 39 + ExitProcess: 3 + ExitThread: 9 + GetCurrentProcessorNumber: 17 + GetHandleInfo: 41 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetProcessIdealProcessor: 6 + GetProcessInfo: 43 + GetResourceLimit: 56 + GetResourceLimitCurrentValues: 58 + GetResourceLimitLimitValues: 57 + GetSystemInfo: 42 + GetSystemTick: 40 + GetThreadContext: 59 + GetThreadId: 55 + GetThreadIdealProcessor: 15 + GetThreadInfo: 44 + GetThreadPriority: 11 + MapMemoryBlock: 31 + OutputDebugString: 61 + QueryMemory: 2 + ReleaseMutex: 20 + ReleaseSemaphore: 22 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + SetThreadPriority: 12 + SetTimer: 27 + SignalEvent: 24 + SleepThread: 10 + UnmapMemoryBlock: 32 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + InterruptNumbers: + ServiceAccessControl: + - APT:U + - $hioFIO + - $hostio0 + - $hostio1 + - ac:u + - boss:U + - cam:u + - cecd:u + - cfg:u + - dlp:FKCL + - dlp:SRVR + - dsp::DSP + - frd:u + - fs:USER + - gsp::Gpu + - hid:USER + - http:C + - mic:u + - ndm:u + - news:u + - nwm::UDS + - ptm:u + - pxi:dev + - soc:U + - ssl:C + - y2r:u + - ldr:ro + - ir:USER + - ir:u + - csnd:SND + + +SystemControlInfo: + SaveDataSize: 0KB # It doesn't use any save data. + RemasterVersion: 2 + StackSize: 0x40000 + # JumpId: 0 + Dependency: + ac: 0x0004013000002402L + am: 0x0004013000001502L + boss: 0x0004013000003402L + camera: 0x0004013000001602L + cecd: 0x0004013000002602L + cfg: 0x0004013000001702L + codec: 0x0004013000001802L + csnd: 0x0004013000002702L + dlp: 0x0004013000002802L + dsp: 0x0004013000001a02L + friends: 0x0004013000003202L + gpio: 0x0004013000001b02L + gsp: 0x0004013000001c02L + hid: 0x0004013000001d02L + http: 0x0004013000002902L + i2c: 0x0004013000001e02L + ir: 0x0004013000003302L + mcu: 0x0004013000001f02L + mic: 0x0004013000002002L + ndm: 0x0004013000002b02L + news: 0x0004013000003502L + nim: 0x0004013000002c02L + nwm: 0x0004013000002d02L + pdn: 0x0004013000002102L + ps: 0x0004013000003102L + ptm: 0x0004013000002202L + ro: 0x0004013000003702L + socket: 0x0004013000002e02L + spi: 0x0004013000002302L + ssl: 0x0004013000002f02L diff --git a/src/platform/3ds/logo.png b/src/platform/3ds/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..af5c27dcb56ab54e0e425f3072b129bca669bde7 GIT binary patch literal 33996 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 IM6N<$g4kyFEC2ui literal 0 HcmV?d00001 From f155780eba1e4008f0b0548a92e67cfdf69df218 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 17:09:29 -0700 Subject: [PATCH 011/127] GUI: Make sort case-insensitive --- src/util/gui/file-select.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index 1d3a56809..2366e4bbb 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -46,7 +46,7 @@ static void _upDirectory(char* currentPath) { } static int _strpcmp(const void* a, const void* b) { - return strcmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); + return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); } static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) { From 90ea4cbe33b098e274f229a754d30069d08bb810 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 19:24:30 -0700 Subject: [PATCH 012/127] GBA: Fix BIOS check on big endian --- CHANGES | 1 + src/gba/gba.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b080e881f..00bc6e51f 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Bugfixes: - Qt: Reenable double buffering, as disabling it broke some Windows configs - GBA Video: Start on the scanline BIOS finishes on if no BIOS is loaded - GBA: Deinit savegame when unloading a ROM + - GBA: Fix BIOS check on big endian Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/gba.c b/src/gba/gba.c index 25dccec13..6760efce4 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -688,7 +688,9 @@ bool GBAIsBIOS(struct VFile* vf) { } int i; for (i = 0; i < 7; ++i) { - if ((interruptTable[i] & 0xFFFF0000) != 0xEA000000) { + uint32_t interrupt; + LOAD_32(interrupt, i * sizeof(uint32_t), interruptTable); + if ((interrupt & 0xFFFF0000) != 0xEA000000) { return false; } } From f25486eca3b6e8d1e56c507e25f994e189f0848c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 21:45:19 -0700 Subject: [PATCH 013/127] 3DS, Wii: Add config directories --- src/gba/context/config.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gba/context/config.c b/src/gba/context/config.c index 0e04f65b5..db575e2e3 100644 --- a/src/gba/context/config.c +++ b/src/gba/context/config.c @@ -22,6 +22,10 @@ #include #endif +#ifdef _3DS +#include "platform/3ds/3ds-vfs.h" +#endif + #define SECTION_NAME_MAX 128 static const char* _lookupValue(const struct GBAConfig* config, const char* key) { @@ -151,7 +155,7 @@ void GBAConfigMakePortable(const struct GBAConfig* config) { WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, MAX_PATH, 0, 0); StringCchCatA(out, MAX_PATH, "\\portable.ini"); portable = VFileOpen(out, O_WRONLY | O_CREAT); -#elif defined(PSP2) +#elif defined(PSP2) || defined(_3DS) || defined(GEKKO) // Already portable #else char out[PATH_MAX]; @@ -189,8 +193,15 @@ void GBAConfigDirectory(char* out, size_t outLength) { WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0); #elif defined(PSP2) UNUSED(portable); - snprintf(out, outLength, "cache0:/%s", binaryName); + snprintf(out, outLength, "cache0:/%s", projectName); sceIoMkdir(out, 0777); +#elif defined(GEKKO) + UNUSED(portable); + snprintf(out, outLength, "/%s", projectName); + mkdir(out, 0777); +#elif defined(_3DS) + snprintf(out, outLength, "/%s", projectName); + FSUSER_CreateDirectory(0, sdmcArchive, FS_makePath(PATH_CHAR, out)); #else getcwd(out, outLength); strncat(out, PATH_SEP "portable.ini", outLength - strlen(out)); From 8452e880c2d976b4054ce0e2cb5df0a2c230ee15 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 21:46:36 -0700 Subject: [PATCH 014/127] GBA Context: Move logging and option parsing into GBAContext --- src/gba/context/context.c | 54 ++++++++++++++++++++++++++++---- src/platform/3ds/main.c | 32 ++----------------- src/platform/libretro/libretro.c | 1 - src/platform/psp2/main.c | 2 +- src/platform/psp2/psp2-context.c | 1 - src/platform/wii/main.c | 26 +-------------- 6 files changed, 52 insertions(+), 64 deletions(-) diff --git a/src/gba/context/context.c b/src/gba/context/context.c index e771b0cfd..fe219cc88 100644 --- a/src/gba/context/context.c +++ b/src/gba/context/context.c @@ -10,10 +10,14 @@ #include "util/memory.h" #include "util/vfs.h" +static struct VFile* _logFile = 0; +static void _GBAContextLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); + bool GBAContextInit(struct GBAContext* context, const char* port) { context->gba = anonymousMemoryMap(sizeof(struct GBA)); context->cpu = anonymousMemoryMap(sizeof(struct ARMCore)); context->rom = 0; + context->bios = 0; context->fname = 0; context->save = 0; context->renderer = 0; @@ -28,16 +32,34 @@ bool GBAContextInit(struct GBAContext* context, const char* port) { } return false; } - - GBAConfigInit(&context->config, port); - if (port) { - GBAConfigLoad(&context->config); - } - GBACreate(context->gba); ARMSetComponents(context->cpu, &context->gba->d, 0, context->components); ARMInit(context->cpu); + GBAConfigInit(&context->config, port); + if (port) { + if (!_logFile) { + char logPath[PATH_MAX]; + GBAConfigDirectory(logPath, PATH_MAX); + strncat(logPath, PATH_SEP "log", PATH_MAX - strlen(logPath)); + _logFile = VFileOpen(logPath, O_WRONLY | O_CREAT | O_TRUNC); + } + context->gba->logHandler = _GBAContextLog; + + char biosPath[PATH_MAX]; + GBAConfigDirectory(biosPath, PATH_MAX); + strncat(biosPath, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(biosPath)); + + struct GBAOptions opts = { + .bios = biosPath, + .useBios = true, + .idleOptimization = IDLE_LOOP_DETECT, + .logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS + }; + GBAConfigLoadDefaults(&context->config, &opts); + GBAConfigLoad(&context->config); + } + context->gba->sync = 0; return true; } @@ -130,6 +152,10 @@ bool GBAContextStart(struct GBAContext* context) { } GBAConfigMap(&context->config, &opts); + + if (!context->bios && opts.bios) { + GBAContextLoadBIOS(context, opts.bios); + } if (opts.useBios && context->bios) { GBALoadBIOS(context->gba, context->bios); } @@ -166,3 +192,19 @@ void GBAContextFrame(struct GBAContext* context, uint16_t keys) { ARMRunLoop(context->cpu); } } + +static void _GBAContextLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { + UNUSED(thread); + UNUSED(level); + // TODO: Make this local + if (!_logFile) { + return; + } + char out[256]; + size_t len = vsnprintf(out, sizeof(out), format, args); + if (len >= sizeof(out)) { + len = sizeof(out) - 1; + } + out[len] = '\n'; + _logFile->write(_logFile, out, len + 1); +} diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 58f604c98..1be1465dd 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -39,7 +39,6 @@ static struct GBA3DSRotationSource { angularRate gyro; } rotation; -static struct VFile* logFile; static bool hasSound; // TODO: Move into context static struct GBAVideoSoftwareRenderer renderer; @@ -50,7 +49,6 @@ static size_t audioPos = 0; static sf2d_texture* tex; extern bool allocateRomBuffer(void); -static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio); @@ -68,13 +66,6 @@ static void _drawEnd(void) { } static void _setup(struct GBAGUIRunner* runner) { - struct GBAOptions opts = { - .useBios = true, - .logLevel = 0, - .idleOptimization = IDLE_LOOP_DETECT - }; - GBAConfigLoadDefaults(&runner->context.config, &opts); - runner->context.gba->logHandler = GBA3DSLog; runner->context.gba->rotationSource = &rotation.d; if (hasSound) { runner->context.gba->stream = &stream; @@ -323,7 +314,6 @@ int main() { }; FSUSER_OpenArchive(0, &sdmcArchive); - logFile = VFileOpen("/mgba.log", O_WRONLY | O_CREAT | O_TRUNC); struct GUIFont* font = GUIFontCreate(); if (!font) { @@ -352,17 +342,14 @@ int main() { .incrementScreenMode = _incrementScreenMode, .pollGameInput = _pollGameInput }; - GBAGUIInit(&runner, 0); + + GBAGUIInit(&runner, "3ds"); GBAGUIRunloop(&runner); GBAGUIDeinit(&runner); cleanup: linearFree(renderer.outputBuffer); - if (logFile) { - logFile->close(logFile); - } - sf2d_free_texture(tex); sf2d_fini(); @@ -373,18 +360,3 @@ cleanup: csndExit(); return 0; } - -static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { - UNUSED(thread); - UNUSED(level); - if (!logFile) { - return; - } - char out[256]; - size_t len = vsnprintf(out, sizeof(out), format, args); - if (len >= sizeof(out)) { - len = sizeof(out) - 1; - } - out[len] = '\n'; - logFile->write(logFile, out, len + 1); -} diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 8c6b481dc..71814f49a 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -158,7 +158,6 @@ void retro_init(void) { GBAContextInit(&context, 0); struct GBAOptions opts = { .useBios = true, - .logLevel = 0, .idleOptimization = IDLE_LOOP_REMOVE }; GBAConfigLoadDefaults(&context.config, &opts); diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index e1ca9285e..01a685b7c 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -100,7 +100,7 @@ int main() { .pollGameInput = GBAPSP2PollInput }; - GBAGUIInit(&runner, 0); + GBAGUIInit(&runner, "psvita"); GBAGUIRunloop(&runner); GBAGUIDeinit(&runner); diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index e5ed8ae33..ddef7c400 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -139,7 +139,6 @@ void GBAPSP2Setup(struct GBAGUIRunner* runner) { scePowerSetArmClockFrequency(80); struct GBAOptions opts = { .useBios = true, - .logLevel = 0, .idleOptimization = IDLE_LOOP_DETECT }; GBAConfigLoadDefaults(&runner->context.config, &opts); diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index b4277c512..ecff90c98 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -22,8 +22,6 @@ #define SAMPLES 1024 -static void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); - static void _audioDMA(void); static void _setRumble(struct GBARumble* rumble, int enable); static void _sampleRotation(struct GBARotationSource* source); @@ -47,7 +45,6 @@ static uint16_t _pollGameInput(struct GBAGUIRunner* runner); static struct GBAVideoSoftwareRenderer renderer; static struct GBARumble rumble; static struct GBARotationSource rotation; -static FILE* logfile; static GXRModeObj* mode; static Mtx model, view, modelview; static uint16_t* texmem; @@ -148,8 +145,6 @@ int main() { fatInitDefault(); - logfile = fopen("/mgba.log", "w"); - rumble.setRumble = _setRumble; rotation.sample = _sampleRotation; @@ -177,11 +172,10 @@ int main() { .unpaused = 0, .pollGameInput = _pollGameInput }; - GBAGUIInit(&runner, 0); + GBAGUIInit(&runner, "wii"); GBAGUIRunloop(&runner); GBAGUIDeinit(&runner); - fclose(logfile); free(fifo); free(renderer.outputBuffer); @@ -190,17 +184,6 @@ int main() { return 0; } -void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { - UNUSED(thread); - UNUSED(level); - if (!logfile) { - return; - } - vfprintf(logfile, format, args); - fprintf(logfile, "\n"); - fflush(logfile); -} - static void _audioDMA(void) { if (!audioBufferSize) { return; @@ -313,13 +296,6 @@ void _guiFinish(void) { } void _setup(struct GBAGUIRunner* runner) { - struct GBAOptions opts = { - .useBios = true, - .logLevel = 0, - .idleOptimization = IDLE_LOOP_DETECT - }; - GBAConfigLoadDefaults(&runner->context.config, &opts); - runner->context.gba->logHandler = GBAWiiLog; runner->context.gba->rumble = &rumble; runner->context.gba->rotationSource = &rotation; From afbd795c60dd853a66befa4c5fa6163847104775 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 21:47:18 -0700 Subject: [PATCH 015/127] GBA Context: Save config when closing down a GUI context --- src/gba/gui/gui-runner.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 37fdf62a3..3de451857 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -109,6 +109,9 @@ void GBAGUIDeinit(struct GBAGUIRunner* runner) { if (runner->teardown) { runner->teardown(runner); } + if (runner->context.config.port) { + GBAConfigSave(&runner->context.config); + } GBAContextDeinit(&runner->context); } From 31686c374ed78d8f15190c814276ed555fb0949a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 22:16:50 -0700 Subject: [PATCH 016/127] Libretro: Fix a memory leak with the render buffer --- CHANGES | 1 + src/platform/libretro/libretro.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 00bc6e51f..da17455b2 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Bugfixes: - GBA Video: Start on the scanline BIOS finishes on if no BIOS is loaded - GBA: Deinit savegame when unloading a ROM - GBA: Fix BIOS check on big endian + - Libretro: Fix a memory leak with the render buffer Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 71814f49a..172a4541b 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -193,6 +193,7 @@ void retro_init(void) { void retro_deinit(void) { GBAContextDeinit(&context); + free(renderer.outputBuffer); } void retro_run(void) { From 11dc9f516112f2915957328bda860d24bde6acb2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 22:21:25 -0700 Subject: [PATCH 017/127] Libretro: Use anonymous memory mappers for large blocks of memor --- CHANGES | 1 + src/platform/libretro/libretro.c | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index da17455b2..504fccb97 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Misc: - GBA: Better memory handling with PNG savestates - GBA Audio: Allow GBAAVStream to have no video callback - ARM7: Force disable LTO on two files to work around a GCC bug + - Libretro: Use anonymous memory mappers for large blocks of memory 0.3.0: (2015-08-16) Features: diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 172a4541b..0c251b7ab 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -11,6 +11,7 @@ #include "gba/serialize.h" #include "gba/context/context.h" #include "util/circle-buffer.h" +#include "util/memory.h" #include "util/vfs.h" #define SAMPLES 1024 @@ -37,6 +38,7 @@ static void _updateLux(struct GBALuminanceSource* lux); static struct GBAContext context; static struct GBAVideoSoftwareRenderer renderer; static void* data; +static size_t dataSize; static void* savedata; static struct GBAAVStream stream; static int rumbleLevel; @@ -246,7 +248,8 @@ void retro_reset(void) { bool retro_load_game(const struct retro_game_info* game) { struct VFile* rom; if (game->data) { - data = malloc(game->size); + data = anonymousMemoryMap(game->size); + dataSize = game->size; memcpy(data, game->data, game->size); rom = VFileFromMemory(data, game->size); } else { @@ -258,11 +261,11 @@ bool retro_load_game(const struct retro_game_info* game) { } if (!GBAIsROM(rom)) { rom->close(rom); - free(data); + mappedMemoryFree(data, game->size); return false; } - savedata = malloc(SIZE_CART_FLASH1M); + savedata = anonymousMemoryMap(SIZE_CART_FLASH1M); struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); GBAContextLoadROMFromVFile(&context, rom, save); @@ -272,9 +275,9 @@ bool retro_load_game(const struct retro_game_info* game) { void retro_unload_game(void) { GBAContextStop(&context); - free(data); + mappedMemoryFree(data, dataSize); data = 0; - free(savedata); + mappedMemoryFree(savedata, SIZE_CART_FLASH1M); savedata = 0; CircleBufferDeinit(&rumbleHistory); } From 04272ff8072e331e27de8ef7ec26c145d6e1f0e3 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 7 Sep 2015 22:25:20 -0700 Subject: [PATCH 018/127] Libretro: Fix build --- src/platform/libretro/libretro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 0c251b7ab..2bb06da27 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -183,7 +183,7 @@ void retro_init(void) { GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); renderer.outputBufferStride = 256; - context->renderer = &renderer.d; + context.renderer = &renderer.d; GBAAudioResizeBuffer(&context.gba->audio, SAMPLES); From 31d409c8b16d9676dd78681db43dc851c4e54000 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 9 Sep 2015 01:44:31 -0700 Subject: [PATCH 019/127] Qt: Add 'Apply' button to settings window (fixes #103) --- CHANGES | 1 + src/platform/qt/SettingsView.cpp | 5 +++++ src/platform/qt/SettingsView.ui | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 504fccb97..a62101b35 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Misc: - GBA Audio: Allow GBAAVStream to have no video callback - ARM7: Force disable LTO on two files to work around a GCC bug - Libretro: Use anonymous memory mappers for large blocks of memory + - Qt: Add 'Apply' button to settings window 0.3.0: (2015-08-16) Features: diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 244d25b9a..23c370f34 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -89,6 +89,11 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent) connect(m_ui.biosBrowse, SIGNAL(clicked()), this, SLOT(selectBios())); connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); + connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { + if (m_ui.buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) { + updateConfig(); + } + }); } void SettingsView::selectBios() { diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index f9302a87c..ed0765fd0 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -549,7 +549,7 @@ - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok From f0c3f6e42a783b607e3acbe77e0ff82c4f2b7187 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 9 Sep 2015 02:00:52 -0700 Subject: [PATCH 020/127] Qt: Fix shortcuts being updated improperly when changing input type (fixes #102) --- src/platform/qt/ShortcutView.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp index d1f694da5..00d4385a6 100644 --- a/src/platform/qt/ShortcutView.cpp +++ b/src/platform/qt/ShortcutView.cpp @@ -22,10 +22,14 @@ ShortcutView::ShortcutView(QWidget* parent) m_ui.keyEdit->setValueKey(0); connect(m_ui.gamepadButton, &QAbstractButton::pressed, [this]() { + bool signalsBlocked = m_ui.keyEdit->blockSignals(true); m_ui.keyEdit->setValueButton(-1); + m_ui.keyEdit->blockSignals(signalsBlocked); }); connect(m_ui.keyboardButton, &QAbstractButton::pressed, [this]() { + bool signalsBlocked = m_ui.keyEdit->blockSignals(true); m_ui.keyEdit->setValueKey(0); + m_ui.keyEdit->blockSignals(signalsBlocked); }); connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int))); connect(m_ui.keyEdit, SIGNAL(axisChanged(int, int)), this, SLOT(updateAxis(int, int))); From 31daa4cfc5537c9347f36d9cd06886e64ad9253f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 11 Sep 2015 19:58:13 -0700 Subject: [PATCH 021/127] GUI: Remove obsolete calls to guiFinish --- src/gba/gui/gui-runner.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 3de451857..df7d5ee39 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -173,9 +173,6 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { while (true) { char path[256]; if (!GUISelectFile(&runner->params, path, sizeof(path), GBAIsROM)) { - if (runner->params.guiFinish) { - runner->params.guiFinish(); - } break; } @@ -273,9 +270,6 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { while (keys) { GUIPollInput(&runner->params, 0, &keys); } - if (runner->params.guiFinish) { - runner->params.guiFinish(); - } if (runner->unpaused) { runner->unpaused(runner); } From 486616461367427c5798c84b7a8aa5676c9c6261 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 11 Sep 2015 20:31:33 -0700 Subject: [PATCH 022/127] Libretro: Fix build when features are enabled on the standalone --- CMakeLists.txt | 8 ++++---- src/platform/3ds/CMakeLists.txt | 2 +- src/platform/psp2/CMakeLists.txt | 2 +- src/platform/wii/CMakeLists.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b926a19f3..d00dab860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,8 +414,9 @@ if (USE_LZMA) list(APPEND FEATURES LZMA) endif() +set(FEATURE_DEFINES) foreach(FEATURE IN LISTS FEATURES) - add_definitions("-DUSE_${FEATURE}") + list(APPEND FEATURE_DEFINES "USE_${FEATURE}") endforeach() source_group("Virtual files" FILES ${VFS_SRC}) @@ -465,10 +466,9 @@ endif() if(BUILD_SHARED) add_library(${BINARY_NAME} SHARED ${SRC}) - set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) - set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") + set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) add_dependencies(${BINARY_NAME}-static version-info) endif() @@ -477,6 +477,7 @@ else() endif() add_dependencies(${BINARY_NAME} version-info) +set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB}) install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) @@ -491,7 +492,6 @@ if(UNIX AND NOT APPLE) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) endif() -set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES}") if(BUILD_GL) add_definitions(-DBUILD_GL) diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index e9da116d8..1878c7968 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -23,7 +23,7 @@ list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DI set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c ctru-heap.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) add_custom_command(OUTPUT ${BINARY_NAME}.smdh diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 1ee0cf443..4e6136a6a 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -12,7 +12,7 @@ list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o main.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o diff --git a/src/platform/wii/CMakeLists.txt b/src/platform/wii/CMakeLists.txt index 1ef6e6df0..41eefe361 100644 --- a/src/platform/wii/CMakeLists.txt +++ b/src/platform/wii/CMakeLists.txt @@ -14,7 +14,7 @@ list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DI set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES}") +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c From 83e23e9cce428677b86e07362b1522f6705e45b9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 11 Sep 2015 21:22:57 -0700 Subject: [PATCH 023/127] GUI: Only wait up to 30 frames when unpausing --- src/gba/gui/gui-runner.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index df7d5ee39..4d8eedd46 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -267,7 +267,13 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { break; } } - while (keys) { + int frames = 0; + GUIPollInput(&runner->params, 0, &keys); + while (keys && frames < 30) { + ++frames; + runner->params.drawStart(); + runner->drawFrame(runner, true); + runner->params.drawEnd(); GUIPollInput(&runner->params, 0, &keys); } if (runner->unpaused) { From d07b4a4a7bc7ea5ccdb741c4b463482afcb77529 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 12 Sep 2015 00:22:07 -0700 Subject: [PATCH 024/127] Libretro: Stop using videoFrame callbacks --- src/platform/libretro/libretro.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 2bb06da27..a0eac13d4 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -30,7 +30,6 @@ static retro_set_rumble_state_t rumbleCallback; static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio); -static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); static void _setRumble(struct GBARumble* rumble, int enable); static uint8_t _readLux(struct GBALuminanceSource* lux); static void _updateLux(struct GBALuminanceSource* lux); @@ -155,7 +154,7 @@ void retro_init(void) { stream.postAudioFrame = 0; stream.postAudioBuffer = _postAudioBuffer; - stream.postVideoFrame = _postVideoFrame; + stream.postVideoFrame = 0; GBAContextInit(&context, 0); struct GBAOptions opts = { @@ -235,6 +234,7 @@ void retro_run(void) { } GBAContextFrame(&context, keys); + videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * renderer.outputBufferStride); } void retro_reset(void) { @@ -408,14 +408,6 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) audioCallback(samples, SAMPLES); } -static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) { - UNUSED(stream); - const void* pixels; - unsigned stride; - renderer->getPixels(renderer, &stride, &pixels); - videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride); -} - static void _setRumble(struct GBARumble* rumble, int enable) { UNUSED(rumble); if (!rumbleCallback) { From 57bdbcd91ebf6d7d1fdf6fed0149075759b9e7f7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 19:24:47 -0700 Subject: [PATCH 025/127] GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers --- CHANGES | 1 + src/gba/io.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index a62101b35..264a3af39 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Bugfixes: - GBA: Deinit savegame when unloading a ROM - GBA: Fix BIOS check on big endian - Libretro: Fix a memory leak with the render buffer + - GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/io.c b/src/gba/io.c index 688805af2..04fd8ca7b 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -328,7 +328,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { break; case REG_SOUND3CNT_HI: GBAAudioWriteSOUND3CNT_HI(&gba->audio, value); - value &= 0xE000; + value &= 0xE03F; break; case REG_SOUND3CNT_X: GBAAudioWriteSOUND3CNT_X(&gba->audio, value); @@ -337,7 +337,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { break; case REG_SOUND4CNT_LO: GBAAudioWriteSOUND4CNT_LO(&gba->audio, value); - value &= 0xFF00; + value &= 0xFF3F; break; case REG_SOUND4CNT_HI: GBAAudioWriteSOUND4CNT_HI(&gba->audio, value); From 98529063ba822b0e2524a2882f0ea3b13f537482 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 19:25:48 -0700 Subject: [PATCH 026/127] GBA Audio: Fix audio channels being silenced at the wrong time --- CHANGES | 1 + src/gba/audio.c | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 264a3af39..255232226 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Bugfixes: - GBA: Fix BIOS check on big endian - Libretro: Fix a memory leak with the render buffer - GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers + - GBA Audio: Fix audio channels being silenced at the wrong time Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/audio.c b/src/gba/audio.c index 6fac940b3..1b2d121e9 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -345,12 +345,10 @@ void GBAAudioWriteSOUND1CNT_X(struct GBAAudio* audio, uint16_t value) { audio->nextCh1 = 0; } audio->playingCh1 = 1; - if (audio->ch1.envelope.stepTime) { - audio->ch1.envelope.nextStep = 0; - } else { - audio->ch1.envelope.nextStep = INT_MAX; - } audio->ch1.envelope.currentVolume = audio->ch1.envelope.initialVolume; + if (audio->ch1.envelope.currentVolume > 0) { + audio->ch1.envelope.dead = 0; + } if (audio->ch1.envelope.stepTime) { audio->ch1.envelope.nextStep = 0; } else { @@ -372,6 +370,9 @@ void GBAAudioWriteSOUND2CNT_HI(struct GBAAudio* audio, uint16_t value) { if (GBAAudioRegisterControlIsRestart(value)) { audio->playingCh2 = 1; audio->ch2.envelope.currentVolume = audio->ch2.envelope.initialVolume; + if (audio->ch2.envelope.currentVolume > 0) { + audio->ch2.envelope.dead = 0; + } if (audio->ch2.envelope.stepTime) { audio->ch2.envelope.nextStep = 0; } else { @@ -419,6 +420,9 @@ void GBAAudioWriteSOUND4CNT_HI(struct GBAAudio* audio, uint16_t value) { if (GBAAudioRegisterCh4ControlIsRestart(value)) { audio->playingCh4 = 1; audio->ch4.envelope.currentVolume = audio->ch4.envelope.initialVolume; + if (audio->ch4.envelope.currentVolume > 0) { + audio->ch4.envelope.dead = 0; + } if (audio->ch4.envelope.stepTime) { audio->ch4.envelope.nextStep = 0; } else { From 5b7e39e45f03f0d30857d91158a293ff742506e6 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 19:51:59 -0700 Subject: [PATCH 027/127] Qt: Prevent savestate window from opening while in multiplayer --- CHANGES | 1 + src/platform/qt/Window.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 255232226..fe93e9f5f 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ Misc: - ARM7: Force disable LTO on two files to work around a GCC bug - Libretro: Use anonymous memory mappers for large blocks of memory - Qt: Add 'Apply' button to settings window + - Qt: Prevent savestate window from opening while in multiplayer 0.3.0: (2015-08-16) Features: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 0893b43bd..882ed87e3 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -693,6 +693,10 @@ void Window::openStateWindow(LoadSave ls) { if (m_stateWindow) { return; } + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer && multiplayer->attached() > 1) { + return; + } bool wasPaused = m_controller->isPaused(); m_stateWindow = new LoadSaveState(m_controller); connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(close())); From 11d9b492cdd71ec0b01bd0f5be505628d2c9ffc9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 20:21:16 -0700 Subject: [PATCH 028/127] Qt: Disable menu items in multiplayer that don't make sense to have enabled --- CHANGES | 1 + src/platform/qt/GBAApp.cpp | 2 ++ src/platform/qt/MultiplayerController.cpp | 2 ++ src/platform/qt/MultiplayerController.h | 10 +++++++- src/platform/qt/Window.cpp | 28 +++++++++++++++++++++++ src/platform/qt/Window.h | 3 +++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fe93e9f5f..6152465a1 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Misc: - Libretro: Use anonymous memory mappers for large blocks of memory - Qt: Add 'Apply' button to settings window - Qt: Prevent savestate window from opening while in multiplayer + - Qt: Disable menu items in multiplayer that don't make sense to have enabled 0.3.0: (2015-08-16) Features: diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index 72c72cda8..24f59af4a 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -86,6 +86,7 @@ GBAApp::GBAApp(int& argc, char* argv[]) w->show(); w->controller()->setMultiplayerController(&m_multiplayer); + w->multiplayerChanged(); } bool GBAApp::event(QEvent* event) { @@ -110,6 +111,7 @@ Window* GBAApp::newWindow() { w->loadConfig(); w->show(); w->controller()->setMultiplayerController(&m_multiplayer); + w->multiplayerChanged(); return w; } diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index 827900c70..20da04e2e 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -35,6 +35,7 @@ bool MultiplayerController::attachGame(GameController* controller) { } thread->sioDrivers.multiplayer = &node->d; controller->threadContinue(); + emit gameAttached(); return true; } @@ -60,6 +61,7 @@ void MultiplayerController::detachGame(GameController* controller) { } MutexUnlock(&m_lockstep.mutex); controller->threadContinue(); + emit gameDetached(); } int MultiplayerController::attached() { diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index ac3514c1b..f849d8ad1 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -6,6 +6,8 @@ #ifndef QGBA_MULTIPLAYER_CONTROLLER #define QGBA_MULTIPLAYER_CONTROLLER +#include + extern "C" { #include "gba/sio/lockstep.h" } @@ -14,7 +16,9 @@ namespace QGBA { class GameController; -class MultiplayerController { +class MultiplayerController : public QObject { +Q_OBJECT + public: MultiplayerController(); ~MultiplayerController(); @@ -24,6 +28,10 @@ public: int attached(); +signals: + void gameAttached(); + void gameDetached(); + private: GBASIOLockstep m_lockstep; }; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 882ed87e3..3cdf65f44 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -271,6 +271,22 @@ void Window::replaceROM() { } } +void Window::multiplayerChanged() { + disconnect(nullptr, this, SLOT(multiplayerChanged())); + int attached = 1; + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer) { + attached = multiplayer->attached(); + connect(multiplayer, SIGNAL(gameAttached()), this, SLOT(multiplayerChanged())); + connect(multiplayer, SIGNAL(gameDetached()), this, SLOT(multiplayerChanged())); + } + if (m_controller->isLoaded()) { + for (QAction* action : m_nonMpActions) { + action->setDisabled(attached > 1); + } + } +} + void Window::selectBIOS() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS")); if (!filename.isEmpty()) { @@ -569,6 +585,7 @@ void Window::gameStarted(GBAThread* context) { foreach (QAction* action, m_gameActions) { action->setDisabled(false); } + multiplayerChanged(); if (context->fname) { setWindowFilePath(context->fname); appendMRU(context->fname); @@ -740,12 +757,14 @@ void Window::setupMenu(QMenuBar* menubar) { loadState->setShortcut(tr("F10")); connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); }); m_gameActions.append(loadState); + m_nonMpActions.append(loadState); addControlledAction(fileMenu, loadState, "loadState"); QAction* saveState = new QAction(tr("&Save state"), fileMenu); saveState->setShortcut(tr("Shift+F10")); connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); }); m_gameActions.append(saveState); + m_nonMpActions.append(saveState); addControlledAction(fileMenu, saveState, "saveState"); QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); @@ -756,11 +775,13 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); connect(quickLoad, SIGNAL(triggered()), m_controller, SLOT(loadState())); m_gameActions.append(quickLoad); + m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); connect(quickSave, SIGNAL(triggered()), m_controller, SLOT(saveState())); m_gameActions.append(quickSave); + m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, "quickSave"); quickLoadMenu->addSeparator(); @@ -770,12 +791,14 @@ void Window::setupMenu(QMenuBar* menubar) { undoLoadState->setShortcut(tr("F11")); connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState())); m_gameActions.append(undoLoadState); + m_nonMpActions.append(undoLoadState); addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); undoSaveState->setShortcut(tr("Shift+F11")); connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState())); m_gameActions.append(undoSaveState); + m_nonMpActions.append(undoSaveState); addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); quickLoadMenu->addSeparator(); @@ -787,12 +810,14 @@ void Window::setupMenu(QMenuBar* menubar) { quickLoad->setShortcut(tr("F%1").arg(i)); connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); m_gameActions.append(quickLoad); + m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); quickSave->setShortcut(tr("Shift+F%1").arg(i)); connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); m_gameActions.append(quickSave); + m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } @@ -861,6 +886,7 @@ void Window::setupMenu(QMenuBar* menubar) { frameAdvance->setShortcut(tr("Ctrl+N")); connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance())); m_gameActions.append(frameAdvance); + m_nonMpActions.append(frameAdvance); addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); emulationMenu->addSeparator(); @@ -901,6 +927,7 @@ void Window::setupMenu(QMenuBar* menubar) { rewind->setShortcut(tr("`")); connect(rewind, SIGNAL(triggered()), m_controller, SLOT(rewind())); m_gameActions.append(rewind); + m_nonMpActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind"); QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu); @@ -909,6 +936,7 @@ void Window::setupMenu(QMenuBar* menubar) { m_controller->rewind(1); }); m_gameActions.append(frameRewind); + m_nonMpActions.append(frameRewind); addControlledAction(emulationMenu, frameRewind, "frameRewind"); ConfigOption* videoSync = m_config->addOption("videoSync"); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index eeff65df6..1a9ef24f0 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -69,6 +69,8 @@ public slots: void replaceROM(); + void multiplayerChanged(); + void importSharkport(); void exportSharkport(); @@ -149,6 +151,7 @@ private: GameController* m_controller; Display* m_display; QList m_gameActions; + QList m_nonMpActions; QMap m_frameSizes; LogController m_log; LogView* m_logView; From 03d97baeec34aed9fb54b8b8d79cfc74a7528456 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 23:25:19 -0700 Subject: [PATCH 029/127] GUI, 3DS: Fix some warnings --- src/platform/3ds/ctru-heap.c | 4 ++++ src/platform/3ds/main.c | 6 ++++-- src/util/gui/file-select.c | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/platform/3ds/ctru-heap.c b/src/platform/3ds/ctru-heap.c index f35dd9857..e7ca8044e 100644 --- a/src/platform/3ds/ctru-heap.c +++ b/src/platform/3ds/ctru-heap.c @@ -22,6 +22,8 @@ #include <3ds/types.h> #include <3ds/svc.h> +#include "util/common.h" + extern char* fake_heap_start; extern char* fake_heap_end; u32 __linear_heap; @@ -69,6 +71,8 @@ void __system_allocateHeaps() { void __attribute__((noreturn)) __libctru_exit(int rc) { + UNUSED(rc); + u32 tmp=0; // Unmap the linear heap diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 1be1465dd..4caeb7a15 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -122,6 +122,7 @@ static void _drawTex(bool faded) { sf2d_draw_texture_scale_blend(tex, 80, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0)); break; case SM_PA_BOTTOM: + default: sf2d_draw_texture_scale_blend(tex, 40, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0)); break; case SM_AF_TOP: @@ -141,8 +142,8 @@ static void _drawTex(bool faded) { static void _drawFrame(struct GBAGUIRunner* runner, bool faded) { UNUSED(runner); - GSPGPU_FlushDataCache(0, renderer.outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); - GX_SetDisplayTransfer(0, renderer.outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202); + GSPGPU_FlushDataCache(0, (u8*) renderer.outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); + GX_SetDisplayTransfer(0, (u32*) renderer.outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202); _drawTex(faded); #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF if (!hasSound) { @@ -173,6 +174,7 @@ static void _drawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, } static uint16_t _pollGameInput(struct GBAGUIRunner* runner) { + UNUSED(runner); hidScanInput(); uint32_t activeKeys = hidKeysHeld() & 0xF00003FF; activeKeys |= activeKeys >> 24; diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index 2366e4bbb..023fe4039 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -74,7 +74,7 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, params->guiPrepare(); } GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %lu)", i); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i); if (params->guiFinish) { params->guiFinish(); } @@ -104,7 +104,7 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, params->guiPrepare(); } GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu of %lu)", i, items); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items); if (params->guiFinish) { params->guiFinish(); } From 532261af2c689bb1fc1c722c5a58950fbaae8488 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 23:25:53 -0700 Subject: [PATCH 030/127] GBA: Move screenshot functionality from Thread to Serialize --- src/gba/serialize.c | 22 +++++++++ src/gba/serialize.h | 2 + src/gba/supervisor/thread.c | 16 +------ src/util/vfs.c | 92 +++++++++++++++++++++++++++++++++++++ src/util/vfs/vfs-dirent.c | 92 ------------------------------------- 5 files changed, 117 insertions(+), 107 deletions(-) diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 1a98efde6..682983231 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -432,3 +432,25 @@ int GBARewind(struct GBAThread* thread, int nStates) { void GBARewindAll(struct GBAThread* thread) { GBARewind(thread, thread->rewindBufferSize); } + +void GBATakeScreenshot(struct GBA* gba, struct VDir* dir) { +#ifdef USE_PNG + unsigned stride; + const void* pixels = 0; + struct VFile* vf = VDirOptionalOpenIncrementFile(dir, gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY); + bool success = false; + if (vf) { + gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels); + png_structp png = PNGWriteOpen(vf); + png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); + PNGWriteClose(png, info); + vf->close(vf); + } + if (success) { + GBALog(gba, GBA_LOG_STATUS, "Screenshot saved"); + return; + } +#endif + GBALog(gba, GBA_LOG_STATUS, "Failed to take screenshot"); +} diff --git a/src/gba/serialize.h b/src/gba/serialize.h index 4c23f4bb7..32b322a8c 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -345,4 +345,6 @@ void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int new int GBARewind(struct GBAThread* thread, int nStates); void GBARewindAll(struct GBAThread* thread); +void GBATakeScreenshot(struct GBA* gba, struct VDir* dir); + #endif diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index c1664af43..2b0ef4bb8 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -16,7 +16,6 @@ #include "debugger/debugger.h" #include "util/patch.h" -#include "util/png-io.h" #include "util/vfs.h" #include "platform/commandline.h" @@ -754,22 +753,9 @@ struct GBAThread* GBAThreadGetContext(void) { } #endif -#ifdef USE_PNG void GBAThreadTakeScreenshot(struct GBAThread* threadContext) { - unsigned stride; - const void* pixels = 0; - struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY); - threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels); - png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - bool success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); - PNGWriteClose(png, info); - vf->close(vf); - if (success) { - GBALog(threadContext->gba, GBA_LOG_STATUS, "Screenshot saved"); - } + GBATakeScreenshot(threadContext->gba, threadContext->stateDir); } -#endif #else struct GBAThread* GBAThreadGetContext(void) { diff --git a/src/util/vfs.c b/src/util/vfs.c index 74aec0b3d..b3dd87fb0 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -167,3 +167,95 @@ struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const } return vf; } + +struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + char realPrefix[PATH_MAX]; + realPrefix[PATH_MAX - 1] = '\0'; + if (!dir) { + if (!realPath) { + return 0; + } + const char* separatorPoint = strrchr(realPath, '/'); + const char* dotPoint; + size_t len; + if (!separatorPoint) { + strcpy(path, "./"); + separatorPoint = realPath; + dotPoint = strrchr(realPath, '.'); + } else { + path[0] = '\0'; + dotPoint = strrchr(separatorPoint, '.'); + + if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + len = separatorPoint - realPath; + strncat(path, realPath, len); + path[len] = '\0'; + ++separatorPoint; + } + + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + if (dotPoint >= separatorPoint) { + len = dotPoint - separatorPoint; + } else { + len = PATH_MAX - 1; + } + + strncpy(realPrefix, separatorPoint, len); + realPrefix[len] = '\0'; + + prefix = realPrefix; + dir = VDirOpen(path); + } + if (!dir) { + // This shouldn't be possible + return 0; + } + dir->rewind(dir); + struct VDirEntry* dirent; + size_t prefixLen = strlen(prefix); + size_t infixLen = strlen(infix); + unsigned next = 0; + while ((dirent = dir->listNext(dir))) { + const char* filename = dirent->name(dirent); + char* dotPoint = strrchr(filename, '.'); + size_t len = strlen(filename); + if (dotPoint) { + len = (dotPoint - filename); + } + const char* separator = strnrstr(filename, infix, len); + if (!separator) { + continue; + } + len = separator - filename; + if (len != prefixLen) { + continue; + } + if (strncmp(filename, prefix, prefixLen) == 0) { + int nlen; + separator += infixLen; + snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); + unsigned increment; + if (sscanf(separator, path, &increment, &nlen) < 1) { + continue; + } + len = strlen(separator); + if (nlen < (ssize_t) len) { + continue; + } + if (next <= increment) { + next = increment + 1; + } + } + } + snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); + path[PATH_MAX - 1] = '\0'; + return dir->openFile(dir, path, mode); +} diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index 80662a092..0d53c3f52 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -58,98 +58,6 @@ struct VDir* VDirOpen(const char* path) { return &vd->d; } -struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - char realPrefix[PATH_MAX]; - realPrefix[PATH_MAX - 1] = '\0'; - if (!dir) { - if (!realPath) { - return 0; - } - const char* separatorPoint = strrchr(realPath, '/'); - const char* dotPoint; - size_t len; - if (!separatorPoint) { - strcpy(path, "./"); - separatorPoint = realPath; - dotPoint = strrchr(realPath, '.'); - } else { - path[0] = '\0'; - dotPoint = strrchr(separatorPoint, '.'); - - if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - len = separatorPoint - realPath; - strncat(path, realPath, len); - path[len] = '\0'; - ++separatorPoint; - } - - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - if (dotPoint >= separatorPoint) { - len = dotPoint - separatorPoint; - } else { - len = PATH_MAX - 1; - } - - strncpy(realPrefix, separatorPoint, len); - realPrefix[len] = '\0'; - - prefix = realPrefix; - dir = VDirOpen(path); - } - if (!dir) { - // This shouldn't be possible - return 0; - } - dir->rewind(dir); - struct VDirEntry* dirent; - size_t prefixLen = strlen(prefix); - size_t infixLen = strlen(infix); - unsigned next = 0; - while ((dirent = dir->listNext(dir))) { - const char* filename = dirent->name(dirent); - char* dotPoint = strrchr(filename, '.'); - size_t len = strlen(filename); - if (dotPoint) { - len = (dotPoint - filename); - } - const char* separator = strnrstr(filename, infix, len); - if (!separator) { - continue; - } - len = separator - filename; - if (len != prefixLen) { - continue; - } - if (strncmp(filename, prefix, prefixLen) == 0) { - int nlen; - separator += infixLen; - snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); - unsigned increment; - if (sscanf(separator, path, &increment, &nlen) < 1) { - continue; - } - len = strlen(separator); - if (nlen < (ssize_t) len) { - continue; - } - if (next <= increment) { - next = increment + 1; - } - } - } - snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); - path[PATH_MAX - 1] = '\0'; - return dir->openFile(dir, path, mode); -} - bool _vdClose(struct VDir* vd) { struct VDirDE* vdde = (struct VDirDE*) vd; if (closedir(vdde->de) < 0) { From 54ea3dbbcf39eec26519a6ef8246bd1d9c0cdbb1 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 14 Sep 2015 23:26:20 -0700 Subject: [PATCH 031/127] Util: Fix setjmp buffer for PNG failing in PNGWriteHeader --- src/util/png-io.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util/png-io.c b/src/util/png-io.c index 3e0ca13b1..b18b64202 100644 --- a/src/util/png-io.c +++ b/src/util/png-io.c @@ -43,6 +43,9 @@ png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) { if (!info) { return 0; } + if (setjmp(png_jmpbuf(png))) { + return 0; + } png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png, info); return info; From 19b81a21634c6fccac226a14b03c63ea66a96e5b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 00:06:43 -0700 Subject: [PATCH 032/127] VFS: Fix return values of VFileFILE.read and .write --- CHANGES | 1 + src/util/vfs/vfs-file.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 6152465a1..23f6db3ce 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Bugfixes: - Libretro: Fix a memory leak with the render buffer - GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers - GBA Audio: Fix audio channels being silenced at the wrong time + - VFS: Fix return values of VFileFILE.read and .write Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/util/vfs/vfs-file.c b/src/util/vfs/vfs-file.c index d140b08af..218d5b904 100644 --- a/src/util/vfs/vfs-file.c +++ b/src/util/vfs/vfs-file.c @@ -79,12 +79,12 @@ off_t _vffSeek(struct VFile* vf, off_t offset, int whence) { ssize_t _vffRead(struct VFile* vf, void* buffer, size_t size) { struct VFileFILE* vff = (struct VFileFILE*) vf; - return fread(buffer, size, 1, vff->file); + return fread(buffer, 1, size, vff->file); } ssize_t _vffWrite(struct VFile* vf, const void* buffer, size_t size) { struct VFileFILE* vff = (struct VFileFILE*) vf; - return fwrite(buffer, size, 1, vff->file); + return fwrite(buffer, 1, size, vff->file); } static void* _vffMap(struct VFile* vf, size_t size, int flags) { From d85548ac181988e544adc84c869bdbf0009930c0 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 00:16:06 -0700 Subject: [PATCH 033/127] Util: Fix PowerPC PNG read/write pixel order --- CHANGES | 1 + src/util/png-io.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 23f6db3ce..f8028b54b 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Bugfixes: - GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers - GBA Audio: Fix audio channels being silenced at the wrong time - VFS: Fix return values of VFileFILE.read and .write + - Util: Fix PowerPC PNG read/write pixel order Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/util/png-io.c b/src/util/png-io.c index b18b64202..30b864557 100644 --- a/src/util/png-io.c +++ b/src/util/png-io.c @@ -65,9 +65,15 @@ bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned s for (i = 0; i < height; ++i) { unsigned x; for (x = 0; x < width; ++x) { +#if defined(__POWERPC__) || defined(__PPC__) + row[x * 3] = pixelData[stride * i * 4 + x * 4 + 3]; + row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 2]; + row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 1]; +#else row[x * 3] = pixelData[stride * i * 4 + x * 4]; row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 1]; row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 2]; +#endif } png_write_row(png, row); } @@ -167,9 +173,16 @@ bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width png_read_row(png, row, 0); unsigned x; for (x = 0; x < pngWidth; ++x) { + +#if defined(__POWERPC__) || defined(__PPC__) + pixelData[stride * i * 4 + x * 4 + 3] = row[x * 3]; + pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 1]; + pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 2]; +#else pixelData[stride * i * 4 + x * 4] = row[x * 3]; pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 1]; pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 2]; +#endif } } free(row); From ddaaf4ed688f4d736ce937aae8b72662e4fec53c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 00:21:48 -0700 Subject: [PATCH 034/127] GBA: Make GBA BIOS check endian-agnostic --- src/gba/gba.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index 6760efce4..ada9ffee3 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -682,15 +682,13 @@ bool GBAIsBIOS(struct VFile* vf) { if (vf->seek(vf, 0, SEEK_SET) < 0) { return false; } - uint32_t interruptTable[7]; + uint8_t interruptTable[7 * 4]; if (vf->read(vf, &interruptTable, sizeof(interruptTable)) != sizeof(interruptTable)) { return false; } int i; for (i = 0; i < 7; ++i) { - uint32_t interrupt; - LOAD_32(interrupt, i * sizeof(uint32_t), interruptTable); - if ((interrupt & 0xFFFF0000) != 0xEA000000) { + if (interruptTable[4 * i + 3] != 0xEA || interruptTable[4 * i + 2]) { return false; } } From 590d23ea8a208093afc829a7d67c43da9fd338b8 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 00:22:08 -0700 Subject: [PATCH 035/127] GUI: Add screenshot feature --- src/gba/gui/gui-runner.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 4d8eedd46..241f865c1 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -19,6 +19,7 @@ enum { RUNNER_EXIT = 2, RUNNER_SAVE_STATE = 3, RUNNER_LOAD_STATE = 4, + RUNNER_SCREENSHOT = 5, RUNNER_COMMAND_MASK = 0xFFFF, RUNNER_STATE_1 = 0x10000, @@ -168,6 +169,7 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) }; *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) }; #endif + *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT }; while (true) { @@ -263,6 +265,9 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { vf->close(vf); } break; + case RUNNER_SCREENSHOT: + GBATakeScreenshot(runner->context.gba, 0); + break; case RUNNER_CONTINUE: break; } From c939d363a389e0d9fdb3b343084710e4f947d4e7 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Tue, 15 Sep 2015 23:14:54 -0300 Subject: [PATCH 036/127] 3DS: Remove now unnecessary asm -> __asm__ defines ctrulib has already fixed their header file. --- src/platform/3ds/3ds-memory.c | 2 -- src/platform/3ds/3ds-vfs.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/platform/3ds/3ds-memory.c b/src/platform/3ds/3ds-memory.c index fd736fb76..e3417c828 100644 --- a/src/platform/3ds/3ds-memory.c +++ b/src/platform/3ds/3ds-memory.c @@ -5,8 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/memory.h" -#define asm __asm__ - #include <3ds.h> void* anonymousMemoryMap(size_t size) { diff --git a/src/platform/3ds/3ds-vfs.h b/src/platform/3ds/3ds-vfs.h index 5a386f154..f2d8a7a89 100644 --- a/src/platform/3ds/3ds-vfs.h +++ b/src/platform/3ds/3ds-vfs.h @@ -8,8 +8,6 @@ #include "util/vfs.h" -#define asm __asm__ - #include <3ds.h> extern FS_archive sdmcArchive; From bcf6e5879be2b03b062d9e6f83185e76072c51df Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 22:23:32 -0700 Subject: [PATCH 037/127] Util: Refactor localtime_r replacement code into formatting.h --- CMakeLists.txt | 5 +++++ src/gba/hardware.c | 19 +------------------ src/util/formatting.c | 27 +++++++++++++++++++++++++++ src/util/formatting.h | 4 ++++ 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d00dab860..9453cb683 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,7 @@ endif() include(CheckFunctionExists) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) +check_function_exists(localtime_r HAVE_LOCALTIME_R) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") check_function_exists(snprintf_l HAVE_SNPRINTF_L) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -216,6 +217,10 @@ if(HAVE_STRNDUP) add_definitions(-DHAVE_STRNDUP) endif() +if(HAVE_LOCALTIME_R) + add_definitions(-DHAVE_LOCALTIME_R) +endif() + if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE) add_definitions(-DHAVE_LOCALE) if (HAVE_STRTOF_L) diff --git a/src/gba/hardware.c b/src/gba/hardware.c index 95e5bef3c..af0a6a283 100644 --- a/src/gba/hardware.c +++ b/src/gba/hardware.c @@ -7,12 +7,9 @@ #include "gba/io.h" #include "gba/serialize.h" +#include "util/formatting.h" #include "util/hash.h" -#ifdef PSP2 -#include -#endif - const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; static void _readPins(struct GBACartridgeHardware* hw); @@ -281,21 +278,7 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) { t = time(0); } struct tm date; -#ifdef _WIN32 - localtime_s(&date, &t); -#elif defined(PSP2) - SceRtcTime sceRtc; - sceRtcSetTime_t(&sceRtc, t); - date.tm_year = sceRtc.year; - date.tm_mon = sceRtc.month; - date.tm_mday = sceRtc.day; - date.tm_hour = sceRtc.hour; - date.tm_min = sceRtc.minutes; - date.tm_sec = sceRtc.seconds; - date.tm_wday = sceRtcGetDayOfWeek(sceRtc.year, sceRtc.month, sceRtc.day); -#else localtime_r(&t, &date); -#endif hw->rtc.time[0] = _rtcBCD(date.tm_year - 100); hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1); hw->rtc.time[2] = _rtcBCD(date.tm_mday); diff --git a/src/util/formatting.c b/src/util/formatting.c index 0a0008c4f..f7457c9dc 100644 --- a/src/util/formatting.c +++ b/src/util/formatting.c @@ -70,3 +70,30 @@ float strtof_u(const char* restrict str, char** restrict end) { #endif return res; } + +#ifndef HAVE_LOCALTIME_R +#ifdef PSP2 +#include +#endif + +struct tm* localtime_r(const time_t* t, struct tm* date) { +#ifdef _WIN32 + localtime_s(date, t); + return date; +#elif defined(PSP2) + SceRtcTime sceRtc; + sceRtcSetTime_t(&sceRtc, *t); + date->tm_year = sceRtc.year; + date->tm_mon = sceRtc.month; + date->tm_mday = sceRtc.day; + date->tm_hour = sceRtc.hour; + date->tm_min = sceRtc.minutes; + date->tm_sec = sceRtc.seconds; + date->tm_wday = sceRtcGetDayOfWeek(sceRtc.year, sceRtc.month, sceRtc.day); + return date; +#else +#warning localtime_r not emulated on this platform + return 0; +#endif +} +#endif diff --git a/src/util/formatting.h b/src/util/formatting.h index df35beeae..43372f80c 100644 --- a/src/util/formatting.h +++ b/src/util/formatting.h @@ -27,4 +27,8 @@ float strtof_l(const char* restrict str, char** restrict end, locale_t locale); int ftostr_u(char* restrict str, size_t size, float f); float strtof_u(const char* restrict str, char** restrict end); +#ifndef HAVE_LOCALTIME_R +struct tm* localtime_r(const time_t* timep, struct tm* result); +#endif + #endif From accac1525f2bc921e7aa94adfc2cabe13d7167f2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 22:35:41 -0700 Subject: [PATCH 038/127] 3DS: Add CpuSpeed flag for N3DS CIAs --- src/platform/3ds/cia.rsf.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/3ds/cia.rsf.in b/src/platform/3ds/cia.rsf.in index 4252aa368..a6ebf7ff8 100644 --- a/src/platform/3ds/cia.rsf.in +++ b/src/platform/3ds/cia.rsf.in @@ -91,6 +91,7 @@ AccessControlInfo: Priority : 16 MaxCpu : 0x9E # Default + CpuSpeed : 804mhz DisableDebug : true EnableForceDebug : false From c7533287341922730d15c210141594c8e3a9f70f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 22:47:19 -0700 Subject: [PATCH 039/127] Util: Fix Windows build --- src/util/formatting.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/formatting.c b/src/util/formatting.c index f7457c9dc..67a6a3ea8 100644 --- a/src/util/formatting.c +++ b/src/util/formatting.c @@ -6,6 +6,7 @@ #include "formatting.h" #include +#include int ftostr_l(char* restrict str, size_t size, float f, locale_t locale) { #ifdef HAVE_SNPRINTF_L From aae0efbd09c5f89882725cc35607542fe0b32b5e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 15 Sep 2015 23:00:02 -0700 Subject: [PATCH 040/127] PSP2: Savestate viewing --- src/platform/psp2/main.c | 1 + src/platform/psp2/psp2-context.c | 24 +++++++++++++++++++++++- src/platform/psp2/psp2-context.h | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 01a685b7c..d06aff0e6 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -94,6 +94,7 @@ int main() { .gameUnloaded = GBAPSP2UnloadROM, .prepareForFrame = GBAPSP2PrepareForFrame, .drawFrame = GBAPSP2Draw, + .drawScreenshot = GBAPSP2DrawScreenshot, .paused = 0, .unpaused = 0, .incrementScreenMode = GBAPSP2IncrementScreenMode, diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index ddef7c400..910878b6c 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -38,6 +38,7 @@ static enum ScreenMode { static struct GBAVideoSoftwareRenderer renderer; static vita2d_texture* tex; +static vita2d_texture* screenshot; static Thread audioThread; static struct GBASceRotationSource { struct GBARotationSource d; @@ -159,6 +160,7 @@ void GBAPSP2Setup(struct GBAGUIRunner* runner) { GBAInputBindAxis(&runner->context.inputMap, PSP2_INPUT, 1, &desc); tex = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); + screenshot = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = vita2d_texture_get_datap(tex); @@ -220,7 +222,7 @@ void GBAPSP2UnloadROM(struct GBAGUIRunner* runner) { void GBAPSP2Teardown(struct GBAGUIRunner* runner) { UNUSED(runner); vita2d_free_texture(tex); - vita2d_free_texture(backdrop); + vita2d_free_texture(screenshot); } void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded) { @@ -238,6 +240,26 @@ void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded) { } } +void GBAPSP2DrawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded) { + UNUSED(runner); + uint32_t* texpixels = vita2d_texture_get_datap(screenshot); + int y; + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + memcpy(&texpixels[256 * y], &pixels[VIDEO_HORIZONTAL_PIXELS * y], VIDEO_HORIZONTAL_PIXELS * 4); + } + switch (screenMode) { + case SM_BACKDROP: + vita2d_draw_texture_tint(backdrop, 0, 0, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); + // Fall through + case SM_PLAIN: + vita2d_draw_texture_tint_part_scale(screenshot, 120, 32, 0, 0, 240, 160, 3.0f, 3.0f, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); + break; + case SM_FULL: + vita2d_draw_texture_tint_scale(screenshot, 0, 0, 960.0f / 240.0f, 544.0f / 160.0f, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); + break; + } +} + void GBAPSP2IncrementScreenMode(struct GBAGUIRunner* runner) { screenMode = (screenMode + 1) % SM_MAX; } diff --git a/src/platform/psp2/psp2-context.h b/src/platform/psp2/psp2-context.h index 118da5328..fc61d800c 100644 --- a/src/platform/psp2/psp2-context.h +++ b/src/platform/psp2/psp2-context.h @@ -17,6 +17,7 @@ void GBAPSP2LoadROM(struct GBAGUIRunner* runner); void GBAPSP2UnloadROM(struct GBAGUIRunner* runner); void GBAPSP2PrepareForFrame(struct GBAGUIRunner* runner); void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded); +void GBAPSP2DrawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded); void GBAPSP2IncrementScreenMode(struct GBAGUIRunner* runner); uint16_t GBAPSP2PollInput(struct GBAGUIRunner* runner); From 822df237a3e927e3dec634b5f2db71aedf155081 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 16 Sep 2015 00:20:38 -0700 Subject: [PATCH 041/127] Qt: Disable rewinding when in multiplayer --- src/platform/qt/GameController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 8f3d6678c..68023a07a 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -534,6 +534,9 @@ void GameController::startRewinding() { if (!m_gameOpen || m_rewindTimer.isActive()) { return; } + if (m_multiplayer && m_multiplayer->attached() > 1) { + return; + } m_wasPaused = isPaused(); if (!GBAThreadIsPaused(&m_threadContext)) { GBAThreadPause(&m_threadContext); From 4d24b16735001b96dcee2bebc49560185f969b03 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 16 Sep 2015 00:34:24 -0700 Subject: [PATCH 042/127] Qt: Dropping multiplayer windows works more cleanly now --- CHANGES | 1 + src/platform/qt/MultiplayerController.cpp | 14 ++++++++++++++ src/platform/qt/MultiplayerController.h | 1 + src/platform/qt/Window.cpp | 1 + 4 files changed, 17 insertions(+) diff --git a/CHANGES b/CHANGES index f8028b54b..f172e8c19 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Misc: - Qt: Add 'Apply' button to settings window - Qt: Prevent savestate window from opening while in multiplayer - Qt: Disable menu items in multiplayer that don't make sense to have enabled + - Qt: Dropping multiplayer windows works more cleanly now 0.3.0: (2015-08-16) Features: diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index 20da04e2e..547368af1 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -64,6 +64,20 @@ void MultiplayerController::detachGame(GameController* controller) { emit gameDetached(); } +int MultiplayerController::playerId(GameController* controller) { + MutexLock(&m_lockstep.mutex); + int id = -1; + for (int i = 0; i < m_lockstep.attached; ++i) { + GBAThread* thread = controller->thread(); + if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) { + id = i; + break; + } + } + MutexUnlock(&m_lockstep.mutex); + return id; +} + int MultiplayerController::attached() { int num; MutexLock(&m_lockstep.mutex); diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index f849d8ad1..24357bef7 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -27,6 +27,7 @@ public: void detachGame(GameController*); int attached(); + int playerId(GameController*); signals: void gameAttached(); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 3cdf65f44..4b4370d40 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -279,6 +279,7 @@ void Window::multiplayerChanged() { attached = multiplayer->attached(); connect(multiplayer, SIGNAL(gameAttached()), this, SLOT(multiplayerChanged())); connect(multiplayer, SIGNAL(gameDetached()), this, SLOT(multiplayerChanged())); + m_playerId = multiplayer->playerId(m_controller); } if (m_controller->isLoaded()) { for (QAction* action : m_nonMpActions) { From 6c780a39e114fc62ecf5d8671a949d8b4ad938d7 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Thu, 10 Sep 2015 20:39:16 -0300 Subject: [PATCH 043/127] 3DS: Rewrite GUI backend to use GPU directly instead of sf2dlib This removes a dependency, reduces binary size a bit and fixes filtering when scaling the game viewport. --- src/platform/3ds/CMakeLists.txt | 35 ++- src/platform/3ds/CMakeToolchain.txt | 18 +- src/platform/3ds/ctr-gpu.c | 405 ++++++++++++++++++++++++++++ src/platform/3ds/ctr-gpu.h | 41 +++ src/platform/3ds/gui-font.c | 40 +-- src/platform/3ds/main.c | 127 ++++++--- src/platform/3ds/uishader.vsh | 41 +++ 7 files changed, 650 insertions(+), 57 deletions(-) create mode 100644 src/platform/3ds/ctr-gpu.c create mode 100644 src/platform/3ds/ctr-gpu.h create mode 100644 src/platform/3ds/uishader.vsh diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 1878c7968..f26660c4a 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format" PARENT_SCOPE) set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5) include_directories(${CMAKE_CURRENT_BINARY_DIR}) -list(APPEND OS_LIB sf2d ctru) +list(APPEND OS_LIB ctru) file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/3ds-*.c) set(OS_SRC ${OS_SRC} PARENT_SCOPE) source_group("3DS-specific code" FILES ${OS_SRC}) @@ -19,9 +19,22 @@ endif() set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) -list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) +list(APPEND GUI_SRC + ${CMAKE_CURRENT_BINARY_DIR}/font.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h -set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) + ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c + ${CMAKE_CURRENT_SOURCE_DIR}/ctr-gpu.c + ${CMAKE_CURRENT_SOURCE_DIR}/ctr-gpu.h) + +set_source_files_properties( + ${CMAKE_CURRENT_BINARY_DIR}/font.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c ctru-heap.c) set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) @@ -38,6 +51,22 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh + COMMAND ${PICASSO} + -o ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + -h ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh + COMMENT "picasso uishader.vsh") + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.c ${CMAKE_CURRENT_BINARY_DIR}/uishader.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + COMMAND ${RAW2C} ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "raw2c uishader.shbin") + add_custom_target(${BINARY_NAME}.3dsx ALL ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) diff --git a/src/platform/3ds/CMakeToolchain.txt b/src/platform/3ds/CMakeToolchain.txt index 66dda1dcd..9cb3548e0 100644 --- a/src/platform/3ds/CMakeToolchain.txt +++ b/src/platform/3ds/CMakeToolchain.txt @@ -9,12 +9,24 @@ if(DEFINED ENV{DEVKITARM}) else() set(DEVKITARM ${DEVKITPRO}/devkitARM) endif() - set(toolchain_bin_dir ${DEVKITARM}/bin) + +if(DEFINED ENV{CTRULIB}) + set(CTRULIB $ENV{CTRULIB}) +else() + set(CTRULIB ${DEVKITPRO}/libctru) +endif() + +if(DEFINED ENV{PICASSO}) + set(PICASSO $ENV{PICASSO}) +else() + set(PICASSO ${toolchain_bin_dir}/picasso) +endif() + set(cross_prefix ${toolchain_bin_dir}/arm-none-eabi-) -set(inc_flags -I${DEVKITPRO}/libctru/include) +set(inc_flags -I${CTRULIB}/include) set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard") -set(link_flags "-L${DEVKITPRO}/libctru/lib -lctru -lm -specs=3dsx.specs ${arch_flags}") +set(link_flags "-L${CTRULIB}/lib -lctru -lm -specs=3dsx.specs ${arch_flags}") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor") diff --git a/src/platform/3ds/ctr-gpu.c b/src/platform/3ds/ctr-gpu.c new file mode 100644 index 000000000..1ac1b7ece --- /dev/null +++ b/src/platform/3ds/ctr-gpu.c @@ -0,0 +1,405 @@ +/* Copyright (c) 2015 Yuri Kunde Schlesner + * + * 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 <3ds.h> +#include +#include +#include + +#include "ctr-gpu.h" + +#include "uishader.h" +#include "uishader.shbin.h" + +struct ctrUIVertex { + s16 x,y; + s16 u,v; + u32 abgr; +}; + +#define VRAM_BASE 0x18000000u + +#define MAX_NUM_QUADS 1024 +#define COMMAND_LIST_LENGTH (16 * 1024) +// Each quad requires 4 vertices and 2*3 indices for the two triangles used to draw it +#define VERTEX_INDEX_BUFFER_SIZE (MAX_NUM_QUADS * (4 * sizeof(struct ctrUIVertex) + 6 * sizeof(u16))) + +static struct ctrUIVertex* ctrVertexBuffer = NULL; +static u16* ctrIndexBuffer = NULL; +static u16 ctrNumQuads = 0; + +static void* gpuColorBuffer = NULL; +static u32* gpuCommandList = NULL; +static void* screenTexture = NULL; + +static shaderProgram_s gpuShader; +static DVLB_s* passthroughShader = NULL; + +static const struct ctrTexture* activeTexture = NULL; + +static u32 _f24FromFloat(float f) { + u32 i; + memcpy(&i, &f, 4); + + u32 mantissa = (i << 9) >> 9; + s32 exponent = (i << 1) >> 24; + u32 sign = (i << 0) >> 31; + + // Truncate mantissa + mantissa >>= 7; + + // Re-bias exponent + exponent = exponent - 127 + 63; + if (exponent < 0) { + // Underflow: flush to zero + return sign << 23; + } else if (exponent > 0x7F) { + // Overflow: saturate to infinity + return sign << 23 | 0x7F << 16; + } + + return sign << 23 | exponent << 16 | mantissa; +} + +static u32 _f31FromFloat(float f) { + u32 i; + memcpy(&i, &f, 4); + + u32 mantissa = (i << 9) >> 9; + s32 exponent = (i << 1) >> 24; + u32 sign = (i << 0) >> 31; + + // Re-bias exponent + exponent = exponent - 127 + 63; + if (exponent < 0) { + // Underflow: flush to zero + return sign << 30; + } else if (exponent > 0x7F) { + // Overflow: saturate to infinity + return sign << 30 | 0x7F << 23; + } + + return sign << 30 | exponent << 23 | mantissa; +} + +// Replacements for the limiting GPU_SetViewport function in ctrulib +static void _GPU_SetFramebuffer(intptr_t colorBuffer, intptr_t depthBuffer, u16 w, u16 h) { + u32 buf[4]; + + // Unknown + GPUCMD_AddWrite(GPUREG_0111, 0x00000001); + GPUCMD_AddWrite(GPUREG_0110, 0x00000001); + + // Set depth/color buffer address and dimensions + buf[0] = depthBuffer >> 3; + buf[1] = colorBuffer >> 3; + buf[2] = (0x01) << 24 | ((h-1) & 0xFFF) << 12 | (w & 0xFFF) << 0; + GPUCMD_AddIncrementalWrites(GPUREG_DEPTHBUFFER_LOC, buf, 3); + GPUCMD_AddWrite(GPUREG_006E, buf[2]); + + // Set depth/color buffer pixel format + GPUCMD_AddWrite(GPUREG_DEPTHBUFFER_FORMAT, 3 /* D248S */ ); + GPUCMD_AddWrite(GPUREG_COLORBUFFER_FORMAT, 0 /* RGBA8 */ << 16 | 2 /* Unknown */); + GPUCMD_AddWrite(GPUREG_011B, 0); // Unknown + + // Enable color/depth buffers + buf[0] = colorBuffer != 0 ? 0xF : 0x0; + buf[1] = buf[0]; + buf[2] = depthBuffer != 0 ? 0x2 : 0x0; + buf[3] = buf[2]; + GPUCMD_AddIncrementalWrites(GPUREG_0112, buf, 4); +} + +static void _GPU_SetViewportEx(u16 x, u16 y, u16 w, u16 h) { + u32 buf[4]; + + buf[0] = _f24FromFloat(w / 2.0f); + buf[1] = _f31FromFloat(2.0f / w) << 1; + buf[2] = _f24FromFloat(h / 2.0f); + buf[3] = _f31FromFloat(2.0f / h) << 1; + GPUCMD_AddIncrementalWrites(GPUREG_0041, buf, 4); + + GPUCMD_AddWrite(GPUREG_0068, (y & 0xFFFF) << 16 | (x & 0xFFFF) << 0); + + buf[0] = 0; + buf[1] = 0; + buf[2] = ((h-1) & 0xFFFF) << 16 | ((w-1) & 0xFFFF) << 0; + GPUCMD_AddIncrementalWrites(GPUREG_SCISSORTEST_MODE, buf, 3); +} + +static void _setDummyTexEnv(int id) { + GPU_SetTexEnv(id, + GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0), + GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0), + GPU_TEVOPERANDS(0, 0, 0), + GPU_TEVOPERANDS(0, 0, 0), + GPU_REPLACE, + GPU_REPLACE, + 0x00000000); +} + +Result ctrInitGpu() { + Result res = -1; + + // Allocate buffers + gpuColorBuffer = vramAlloc(400 * 240 * 4); + gpuCommandList = linearAlloc(COMMAND_LIST_LENGTH * sizeof(u32)); + ctrVertexBuffer = linearAlloc(VERTEX_INDEX_BUFFER_SIZE); + if (gpuColorBuffer == NULL || gpuCommandList == NULL || ctrVertexBuffer == NULL) { + res = -1; + goto error_allocs; + } + // Both buffers share the same allocation, index buffer follows the vertex buffer + ctrIndexBuffer = (u16*)(ctrVertexBuffer + (4 * MAX_NUM_QUADS)); + + // Load vertex shader binary + passthroughShader = DVLB_ParseFile((u32*)uishader, uishader_size); + if (passthroughShader == NULL) { + res = -1; + goto error_dvlb; + } + + // Create shader + shaderProgramInit(&gpuShader); + res = shaderProgramSetVsh(&gpuShader, &passthroughShader->DVLE[0]); + if (res < 0) { + goto error_shader; + } + + // Initialize the GPU in ctrulib and assign the command buffer to accept submission of commands + GPU_Init(NULL); + GPUCMD_SetBuffer(gpuCommandList, COMMAND_LIST_LENGTH, 0); + + return 0; + +error_shader: + shaderProgramFree(&gpuShader); + +error_dvlb: + if (passthroughShader != NULL) { + DVLB_Free(passthroughShader); + passthroughShader = NULL; + } + +error_allocs: + if (ctrVertexBuffer != NULL) { + linearFree(ctrVertexBuffer); + ctrVertexBuffer = NULL; + ctrIndexBuffer = NULL; + } + + if (gpuCommandList != NULL) { + GPUCMD_SetBuffer(NULL, 0, 0); + linearFree(gpuCommandList); + gpuCommandList = NULL; + } + + if (gpuColorBuffer != NULL) { + vramFree(gpuColorBuffer); + gpuColorBuffer = NULL; + } + return res; +} + +void ctrDeinitGpu() { + shaderProgramFree(&gpuShader); + + DVLB_Free(passthroughShader); + passthroughShader = NULL; + + linearFree(screenTexture); + screenTexture = NULL; + + linearFree(ctrVertexBuffer); + ctrVertexBuffer = NULL; + ctrIndexBuffer = NULL; + + GPUCMD_SetBuffer(NULL, 0, 0); + linearFree(gpuCommandList); + gpuCommandList = NULL; + + vramFree(gpuColorBuffer); + gpuColorBuffer = NULL; +} + +void ctrGpuBeginFrame(void) { + shaderProgramUse(&gpuShader); + + void* gpuColorBufferEnd = (char*)gpuColorBuffer + 240 * 400 * 4; + + GX_SetMemoryFill(NULL, + gpuColorBuffer, 0x00000000, gpuColorBufferEnd, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER, + NULL, 0, NULL, 0); + gspWaitForPSC0(); + + _GPU_SetFramebuffer(osConvertVirtToPhys((u32)gpuColorBuffer), 0, 240, 400); + + // Disable depth and stencil testing + GPU_SetDepthTestAndWriteMask(false, GPU_ALWAYS, GPU_WRITE_COLOR); + GPU_SetStencilTest(false, GPU_ALWAYS, 0, 0xFF, 0); + GPU_SetStencilOp(GPU_STENCIL_KEEP, GPU_STENCIL_KEEP, GPU_STENCIL_KEEP); + GPU_DepthMap(-1.0f, 0.0f); + + // Enable alpha blending + GPU_SetAlphaBlending( + GPU_BLEND_ADD, GPU_BLEND_ADD, // Operation RGB, Alpha + GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, // Color src, dst + GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA); // Alpha src, dst + GPU_SetBlendingColor(0, 0, 0, 0); + + // Disable alpha testing + GPU_SetAlphaTest(false, GPU_ALWAYS, 0); + + // Unknown + GPUCMD_AddMaskedWrite(GPUREG_0062, 0x1, 0); + GPUCMD_AddWrite(GPUREG_0118, 0); + + GPU_SetTexEnv(0, + GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // RGB + GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // Alpha + GPU_TEVOPERANDS(0, 0, 0), // RGB + GPU_TEVOPERANDS(0, 0, 0), // Alpha + GPU_MODULATE, GPU_MODULATE, // Operation RGB, Alpha + 0x00000000); // Constant color + _setDummyTexEnv(1); + _setDummyTexEnv(2); + _setDummyTexEnv(3); + _setDummyTexEnv(4); + _setDummyTexEnv(5); + + // Configure vertex attribute format + u32 bufferOffsets[] = { osConvertVirtToPhys((u32)ctrVertexBuffer) - VRAM_BASE }; + u64 arrayTargetAttributes[] = { 0x210 }; + u8 numAttributesInArray[] = { 3 }; + GPU_SetAttributeBuffers( + 3, // Number of attributes + (u32*)VRAM_BASE, // Base address + GPU_ATTRIBFMT(0, 2, GPU_SHORT) | // Attribute format + GPU_ATTRIBFMT(1, 2, GPU_SHORT) | + GPU_ATTRIBFMT(2, 4, GPU_UNSIGNED_BYTE), + 0xFF8, // Non-fixed vertex inputs + 0x210, // Vertex shader input map + 1, // Use 1 vertex array + bufferOffsets, arrayTargetAttributes, numAttributesInArray); +} + +void ctrGpuEndFrame(void* outputFramebuffer, int w, int h) { + ctrFlushBatch(); + + void* colorBuffer = (u8*)gpuColorBuffer + ((400 - w) * 240 * 4); + + const u32 GX_CROP_INPUT_LINES = (1 << 2); + + GX_SetDisplayTransfer(NULL, + colorBuffer, GX_BUFFER_DIM(240, 400), + outputFramebuffer, GX_BUFFER_DIM(h, w), + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | + GX_CROP_INPUT_LINES); + gspWaitForPPF(); +} + +void ctrSetViewportSize(s16 w, s16 h) { + // Set up projection matrix mapping (0,0) to the top-left and (w,h) to the + // bottom-right, taking into account the 3DS' screens' portrait + // orientation. + float projectionMtx[4 * 4] = { + // Rows are in the order w z y x, because ctrulib + 1.0f, 0.0f, -2.0f / h, 0.0f, + 1.0f, 0.0f, 0.0f, -2.0f / w, + -0.5f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + }; + + GPU_SetFloatUniform(GPU_VERTEX_SHADER, VSH_FVEC_projectionMtx, (u32*)&projectionMtx, 4); + _GPU_SetViewportEx(0, 0, h, w); +} + +void ctrActivateTexture(const struct ctrTexture* texture) { + if (activeTexture == texture) { + return; + } + + ctrFlushBatch(); + + GPU_SetTextureEnable(GPU_TEXUNIT0); + GPU_SetTexture( + GPU_TEXUNIT0, (u32*)osConvertVirtToPhys((u32)texture->data), + texture->width, texture->height, + GPU_TEXTURE_MAG_FILTER(texture->filter) | GPU_TEXTURE_MIN_FILTER(texture->filter) | + GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_BORDER) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_BORDER), + texture->format); + GPU_SetTextureBorderColor(GPU_TEXUNIT0, 0x00000000); + + float textureMtx[2 * 4] = { + // Rows are in the order w z y x, because ctrulib + 0.0f, 0.0f, 0.0f, 1.0f / texture->width, + 0.0f, 0.0f, 1.0f / texture->height, 0.0f, + }; + + GPU_SetFloatUniform(GPU_VERTEX_SHADER, VSH_FVEC_textureMtx, (u32*)&textureMtx, 2); + + activeTexture = texture; +} + +void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh) { + if (ctrNumQuads == MAX_NUM_QUADS) { + ctrFlushBatch(); + } + + u16 index = ctrNumQuads * 4; + struct ctrUIVertex* vtx = &ctrVertexBuffer[index]; + vtx->x = x; vtx->y = y; + vtx->u = u; vtx->v = v; + vtx->abgr = color; + vtx++; + + vtx->x = x + w; vtx->y = y; + vtx->u = u + uw; vtx->v = v; + vtx->abgr = color; + vtx++; + + vtx->x = x; vtx->y = y + h; + vtx->u = u; vtx->v = v + vh; + vtx->abgr = color; + vtx++; + + vtx->x = x + w; vtx->y = y + h; + vtx->u = u + uw; vtx->v = v + vh; + vtx->abgr = color; + + u16* i = &ctrIndexBuffer[ctrNumQuads * 6]; + i[0] = index + 0; i[1] = index + 1; i[2] = index + 2; + i[3] = index + 2; i[4] = index + 1; i[5] = index + 3; + + ctrNumQuads += 1; +} + +void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h) { + ctrAddRectScaled(color, + x, y, w, h, + u, v, w, h); +} + +void ctrFlushBatch(void) { + if (ctrNumQuads == 0) { + return; + } + + GSPGPU_FlushDataCache(NULL, (u8*)ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE); + GPU_DrawElements(GPU_UNKPRIM, (u32*)(osConvertVirtToPhys((u32)ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6); + + GPU_FinishDrawing(); + GPUCMD_Finalize(); + GSPGPU_FlushDataCache(NULL, (u8*)gpuCommandList, COMMAND_LIST_LENGTH * sizeof(u32)); + GPUCMD_FlushAndRun(NULL); + + gspWaitForP3D(); + + GPUCMD_SetBufferOffset(0); + + ctrNumQuads = 0; +} diff --git a/src/platform/3ds/ctr-gpu.h b/src/platform/3ds/ctr-gpu.h new file mode 100644 index 000000000..0a0637d3f --- /dev/null +++ b/src/platform/3ds/ctr-gpu.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2015 Yuri Kunde Schlesner + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GUI_GPU_H +#define GUI_GPU_H + +#include <3ds.h> + +struct ctrTexture { + void* data; + u32 format; + u32 filter; + u16 width; + u16 height; +}; + +inline void ctrTexture_Init(struct ctrTexture* tex) { + tex->data = NULL; + tex->format = GPU_RGB565; + tex->filter = GPU_NEAREST; + tex->width = 0; + tex->height = 0; +} + +Result ctrInitGpu(void); +void ctrDeinitGpu(void); + +void ctrGpuBeginFrame(void); +void ctrGpuEndFrame(void* outputFramebuffer, int w, int h); + +void ctrSetViewportSize(s16 w, s16 h); + +void ctrActivateTexture(const struct ctrTexture* texture); +void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh); +void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h); +void ctrFlushBatch(void); + +#endif diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 88e158d9d..4852f7b08 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -8,15 +8,14 @@ #include "util/png-io.h" #include "util/vfs.h" #include "font.h" - -#include +#include "ctr-gpu.h" #define CELL_HEIGHT 16 #define CELL_WIDTH 16 #define GLYPH_HEIGHT 12 struct GUIFont { - sf2d_texture* tex; + struct ctrTexture texture; }; struct GUIFont* GUIFontCreate(void) { @@ -24,14 +23,21 @@ struct GUIFont* GUIFontCreate(void) { if (!guiFont) { return 0; } - guiFont->tex = sf2d_create_texture(256, 128, TEXFMT_RGB5A1, SF2D_PLACE_RAM); - memcpy(guiFont->tex->data, font, font_size); - guiFont->tex->tiled = 1; + + struct ctrTexture* tex = &guiFont->texture; + ctrTexture_Init(tex); + tex->data = linearAlloc(256 * 128 * 2); + tex->format = GPU_RGBA5551; + tex->width = 256; + tex->height = 128; + memcpy(tex->data, font, font_size); + GSPGPU_FlushDataCache(NULL, tex->data, font_size); + return guiFont; } void GUIFontDestroy(struct GUIFont* font) { - sf2d_free_texture(font->tex); + linearFree(font->texture.data); free(font); } @@ -48,18 +54,18 @@ unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { return defaultFontMetrics[glyph].width; } -void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) { +void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint32_t color, uint32_t glyph) { + ctrActivateTexture(&font->texture); + if (glyph > 0x7F) { glyph = 0; } - color = (color >> 24) | (color << 8); + struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; - sf2d_draw_texture_part_blend(font->tex, - x - metric.padding.left, - y - GLYPH_HEIGHT, - (glyph & 15) * CELL_WIDTH, - (glyph >> 4) * CELL_HEIGHT, - CELL_WIDTH, - CELL_HEIGHT, - color); + u16 x = glyph_x - metric.padding.left; + u16 y = glyph_y - GLYPH_HEIGHT; + u16 u = (glyph % 16u) * CELL_WIDTH; + u16 v = (glyph / 16u) * CELL_HEIGHT; + + ctrAddRect(color, x, y, u, v, CELL_WIDTH, CELL_HEIGHT); } diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 4caeb7a15..f15c59a24 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -14,9 +14,9 @@ #include "util/memory.h" #include "3ds-vfs.h" +#include "ctr-gpu.h" #include <3ds.h> -#include static enum ScreenMode { SM_PA_BOTTOM, @@ -46,23 +46,29 @@ static struct GBAAVStream stream; static int16_t* audioLeft = 0; static int16_t* audioRight = 0; static size_t audioPos = 0; -static sf2d_texture* tex; +static struct ctrTexture gbaOutputTexture; extern bool allocateRomBuffer(void); static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio); static void _drawStart(void) { + ctrGpuBeginFrame(); if (screenMode < SM_PA_TOP) { - sf2d_start_frame(GFX_BOTTOM, GFX_LEFT); + ctrSetViewportSize(320, 240); } else { - sf2d_start_frame(GFX_TOP, GFX_LEFT); + ctrSetViewportSize(400, 240); } } static void _drawEnd(void) { - sf2d_end_frame(); - sf2d_swapbuffers(); + int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + u16 width = 0, height = 0; + + void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); + ctrGpuEndFrame(outputFramebuffer, width, height); + gfxSwapBuffersGpu(); + gspWaitForVBlank(); } static void _setup(struct GBAGUIRunner* runner) { @@ -117,64 +123,105 @@ static void _gameUnloaded(struct GBAGUIRunner* runner) { } static void _drawTex(bool faded) { + u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF; + + int screen_w = screenMode < SM_PA_TOP ? 320 : 400; + int screen_h = 240; + + int w, h; + switch (screenMode) { case SM_PA_TOP: - sf2d_draw_texture_scale_blend(tex, 80, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0)); - break; case SM_PA_BOTTOM: default: - sf2d_draw_texture_scale_blend(tex, 40, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + w = VIDEO_HORIZONTAL_PIXELS; + h = VIDEO_VERTICAL_PIXELS; break; case SM_AF_TOP: - sf2d_draw_texture_scale_blend(tex, 20, 384, 1.5, -1.5, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + w = 360; + h = 240; break; case SM_AF_BOTTOM: - sf2d_draw_texture_scale_blend(tex, 0, 368 - 40 / 3, 4 / 3.0, -4 / 3.0, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + // Largest possible size with 3:2 aspect ratio and integer dimensions + w = 318; + h = 212; break; case SM_SF_TOP: - sf2d_draw_texture_scale_blend(tex, 0, 384, 5 / 3.0, -1.5, 0xFFFFFF3F | (faded ? 0 : 0xC0)); - break; case SM_SF_BOTTOM: - sf2d_draw_texture_scale_blend(tex, 0, 384, 4 / 3.0, -1.5, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + w = screen_w; + h = screen_h; break; } + + int x = (screen_w - w) / 2; + int y = (screen_h - h) / 2; + + ctrAddRectScaled(color, x, y, w, h, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); } static void _drawFrame(struct GBAGUIRunner* runner, bool faded) { UNUSED(runner); - GSPGPU_FlushDataCache(0, (u8*) renderer.outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); - GX_SetDisplayTransfer(0, (u32*) renderer.outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202); - _drawTex(faded); + + void* outputBuffer = renderer.outputBuffer; + struct ctrTexture* tex = &gbaOutputTexture; + + GSPGPU_FlushDataCache(NULL, outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); + GX_SetDisplayTransfer(NULL, + outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), + tex->data, GX_BUFFER_DIM(256, 256), + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1)); + #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF if (!hasSound) { blip_clear(runner->context.gba->audio.left); blip_clear(runner->context.gba->audio.right); } #endif + + gspWaitForPPF(); + ctrActivateTexture(tex); + _drawTex(faded); } static void _drawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded) { UNUSED(runner); - u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80); - unsigned y, x; - for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { - for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { - u16 pixel = (*pixels >> 19) & 0x1F; - pixel |= (*pixels >> 5) & 0x7C0; - pixel |= (*pixels << 8) & 0xF800; - newPixels[y * 256 + x] = pixel; - ++pixels; + + struct ctrTexture* tex = &gbaOutputTexture; + + u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100); + + // Convert image from RGBX8 to BGR565 + for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { + // 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB + u32 p = *pixels++; + newPixels[y * 256 + x] = + (p << 24 >> (24 + 3) << 11) | // R + (p << 16 >> (24 + 2) << 5) | // G + (p << 8 >> (24 + 3) << 0); // B } - memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, 32); + memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32)); } - GSPGPU_FlushDataCache(0, (void*) newPixels, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 2); - GX_SetDisplayTransfer(0, (void*) newPixels, GX_BUFFER_DIM(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202); + + GSPGPU_FlushDataCache(NULL, (void*)newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32)); + GX_SetDisplayTransfer(NULL, + (void*)newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), + tex->data, GX_BUFFER_DIM(256, 256), + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1)); + gspWaitForPPF(); linearFree(newPixels); + + ctrActivateTexture(tex); _drawTex(faded); } static uint16_t _pollGameInput(struct GBAGUIRunner* runner) { UNUSED(runner); + hidScanInput(); uint32_t activeKeys = hidKeysHeld() & 0xF00003FF; activeKeys |= activeKeys >> 24; @@ -305,9 +352,19 @@ int main() { audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); } - sf2d_init(); - sf2d_set_clear_color(0); - tex = sf2d_create_texture(256, 256, TEXFMT_RGB565, SF2D_PLACE_VRAM); + gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false); + + if (ctrInitGpu() < 0) { + goto cleanup; + } + + ctrTexture_Init(&gbaOutputTexture); + gbaOutputTexture.format = GPU_RGB565; + gbaOutputTexture.filter = GPU_LINEAR; + gbaOutputTexture.width = 256; + gbaOutputTexture.height = 256; + gbaOutputTexture.data = linearAlloc(256 * 256 * 2); + memset(gbaOutputTexture.data, 0, 256 * 256 * 2); sdmcArchive = (FS_archive) { ARCH_SDMC, @@ -352,8 +409,10 @@ int main() { cleanup: linearFree(renderer.outputBuffer); - sf2d_free_texture(tex); - sf2d_fini(); + ctrDeinitGpu(); + linearFree(gbaOutputTexture.data); + + gfxExit(); if (hasSound) { linearFree(audioLeft); diff --git a/src/platform/3ds/uishader.vsh b/src/platform/3ds/uishader.vsh new file mode 100644 index 000000000..a86c8eb2b --- /dev/null +++ b/src/platform/3ds/uishader.vsh @@ -0,0 +1,41 @@ +; Copyright (c) 2015 Yuri Kunde Schlesner +; +; 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/. + +; uishader.vsh - Simply multiplies input position and texcoords with +; corresponding matrices before outputting + +; Uniforms +.fvec projectionMtx[4] +.fvec textureMtx[2] + +; Constants +.constf consts1(0.0, 1.0, 0.0039215686, 0.0) + +; Outputs : here only position and color +.out out_pos position +.out out_tc0 texcoord0 +.out out_col color + +; Inputs : here we have only vertices +.alias in_pos v0 +.alias in_tc0 v1 +.alias in_col v2 + +.proc main + dp4 out_pos.x, projectionMtx[0], in_pos + dp4 out_pos.y, projectionMtx[1], in_pos + dp4 out_pos.z, projectionMtx[2], in_pos + dp4 out_pos.w, projectionMtx[3], in_pos + + dp4 out_tc0.x, textureMtx[0], in_tc0 + dp4 out_tc0.y, textureMtx[1], in_tc0 + mov out_tc0.zw, consts1.xxxy + + ; Normalize color by multiplying by 1 / 255 + mul out_col, consts1.z, in_col + + end +.end From 58f97980e72884beed5da52f02efbabb4edc3722 Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Tue, 15 Sep 2015 22:56:33 -0300 Subject: [PATCH 044/127] 3DS: Don't wait for VBlank when running behind refresh rate --- src/platform/3ds/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index f15c59a24..07161a346 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -68,7 +68,7 @@ static void _drawEnd(void) { void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); ctrGpuEndFrame(outputFramebuffer, width, height); gfxSwapBuffersGpu(); - gspWaitForVBlank(); + gspWaitForEvent(GSPEVENT_VBlank0, false); } static void _setup(struct GBAGUIRunner* runner) { From a623bcadc3933fcd21c31376b3ccc0264efa40fc Mon Sep 17 00:00:00 2001 From: Yuri Kunde Schlesner Date: Wed, 16 Sep 2015 22:27:05 -0300 Subject: [PATCH 045/127] 3DS: Allocate memory for textures in VRAM --- src/platform/3ds/gui-font.c | 10 ++++++---- src/platform/3ds/main.c | 12 +++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 4852f7b08..8ae3fb11e 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -26,18 +26,20 @@ struct GUIFont* GUIFontCreate(void) { struct ctrTexture* tex = &guiFont->texture; ctrTexture_Init(tex); - tex->data = linearAlloc(256 * 128 * 2); + tex->data = vramAlloc(256 * 128 * 2); tex->format = GPU_RGBA5551; tex->width = 256; tex->height = 128; - memcpy(tex->data, font, font_size); - GSPGPU_FlushDataCache(NULL, tex->data, font_size); + + GSPGPU_FlushDataCache(NULL, (u8*)font, font_size); + GX_RequestDma(NULL, (u32*)font, tex->data, font_size); + gspWaitForDMA(); return guiFont; } void GUIFontDestroy(struct GUIFont* font) { - linearFree(font->texture.data); + vramFree(font->texture.data); free(font); } diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 07161a346..56a4b2b17 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -363,8 +363,14 @@ int main() { gbaOutputTexture.filter = GPU_LINEAR; gbaOutputTexture.width = 256; gbaOutputTexture.height = 256; - gbaOutputTexture.data = linearAlloc(256 * 256 * 2); - memset(gbaOutputTexture.data, 0, 256 * 256 * 2); + gbaOutputTexture.data = vramAlloc(256 * 256 * 2); + void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2; + + // Zero texture data to make sure no garbage around the border interferes with filtering + GX_SetMemoryFill(NULL, + gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER, + NULL, 0, NULL, 0); + gspWaitForPSC0(); sdmcArchive = (FS_archive) { ARCH_SDMC, @@ -410,7 +416,7 @@ cleanup: linearFree(renderer.outputBuffer); ctrDeinitGpu(); - linearFree(gbaOutputTexture.data); + vramFree(gbaOutputTexture.data); gfxExit(); From 1ef3c9e5b086fe62addf5657314becc0fdd92295 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 16 Sep 2015 20:24:36 -0700 Subject: [PATCH 046/127] VFS: Fix warnings --- src/util/vfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/vfs.c b/src/util/vfs.c index b3dd87fb0..a4097f6c7 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vfs.h" +#include "util/string.h" + #ifdef PSP2 #include "platform/psp2/sce-vfs.h" #endif From b5a34c9fe772ad08dfb04dc0eac7e1b3ef3dc6d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 16 Sep 2015 20:25:39 -0700 Subject: [PATCH 047/127] GBA BIOS: Implement RegisterRamReset for SIO registers --- CHANGES | 1 + src/gba/bios.c | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f172e8c19..89edf1514 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Misc: - Qt: Prevent savestate window from opening while in multiplayer - Qt: Disable menu items in multiplayer that don't make sense to have enabled - Qt: Dropping multiplayer windows works more cleanly now + - GBA BIOS: Implement RegisterRamReset for SIO registers 0.3.0: (2015-08-16) Features: diff --git a/src/gba/bios.c b/src/gba/bios.c index a20c5ff59..9ea5b5dc9 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -63,7 +63,12 @@ static void _RegisterRamReset(struct GBA* gba) { memset(gba->video.oam.raw, 0, SIZE_OAM); } if (registers & 0x20) { - GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on SIO unimplemented"); + cpu->memory.store16(cpu, BASE_IO | REG_SIOCNT, 0x0000, 0); + cpu->memory.store16(cpu, BASE_IO | REG_RCNT, RCNT_INITIAL, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SIOMLT_SEND, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_JOYCNT, 0, 0); + cpu->memory.store32(cpu, BASE_IO | REG_JOY_RECV, 0, 0); + cpu->memory.store32(cpu, BASE_IO | REG_JOY_TRANS, 0, 0); } if (registers & 0x40) { GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on Audio unimplemented"); From ea1f87d745a4b686ecb0b74a2303a71a5a27dda2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 16 Sep 2015 20:27:42 -0700 Subject: [PATCH 048/127] GBA Video: Fix edge case with sprite blend modes and semitransparency --- CHANGES | 1 + src/gba/renderers/software-mode0.c | 4 ---- src/gba/renderers/software-obj.c | 2 +- src/gba/renderers/software-private.h | 18 +++++++++--------- src/gba/renderers/video-software.c | 28 ++++++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 89edf1514..6f024d7ba 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Bugfixes: - GBA Audio: Fix audio channels being silenced at the wrong time - VFS: Fix return values of VFileFILE.read and .write - Util: Fix PowerPC PNG read/write pixel order + - GBA Video: Fix edge case with sprite blend modes and semitransparency Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index 6ff17e55b..8953a255d 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -473,10 +473,6 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); objwinFlags |= flags; flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); - if (renderer->blda == 0x10 && renderer->bldb == 0) { - flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - } uint32_t screenBase; uint32_t charBase; diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index eb793db23..4af726329 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -126,7 +126,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); - if (GBAObjAttributesCGetPriority(sprite->c) < target2) { + if ((1 << GBAObjAttributesCGetPriority(sprite->c)) < target2) { variant = 0; } } diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index 6d5d7e93e..4d4efed5d 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -42,7 +42,7 @@ static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* render if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = current & 0x00FFFFFF; + color = current & (0x00FFFFFF | FLAG_TARGET_1); } } else { color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); @@ -55,7 +55,7 @@ static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* rend if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = current & 0x00FFFFFF; + color = current & (0x00FFFFFF | FLAG_TARGET_1); } } else { color = color & ~FLAG_TARGET_2; @@ -67,16 +67,20 @@ static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* rend uint32_t current) { UNUSED(renderer); if (color < current) { - *pixel = color | (current & FLAG_OBJWIN); + color |= (current & FLAG_OBJWIN); + } else { + color = current & (0x00FFFFFF | FLAG_TARGET_1); } + *pixel = color; } static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { UNUSED(renderer); - if (color < current) { - *pixel = color; + if (color >= current) { + color = current & (0x00FFFFFF | FLAG_TARGET_1); } + *pixel = color; } #define COMPOSITE_16_OBJWIN(BLEND) \ @@ -180,10 +184,6 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re objwinFlags |= flags; \ flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && \ GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ - if (renderer->blda == 0x10 && renderer->bldb == 0) { \ - flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - } \ int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ color_t* palette = renderer->normalPalette; \ diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 28986778c..81dd0b560 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -523,7 +523,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render if (softwareRenderer->target2Bd) { x = 0; for (w = 0; w < softwareRenderer->nWindows; ++w) { - uint32_t backdrop = FLAG_UNWRITTEN; + uint32_t backdrop = FLAG_UNWRITTEN; if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { backdrop |= softwareRenderer->normalPalette[0]; } else { @@ -532,12 +532,36 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render int end = softwareRenderer->windows[w].endX; for (; x < end; ++x) { uint32_t color = softwareRenderer->row[x]; - if (color & FLAG_TARGET_1) { + if (color & FLAG_TARGET_1 && color & FLAG_UNWRITTEN) { softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color); } } } } + if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) { + x = 0; + for (w = 0; w < softwareRenderer->nWindows; ++w) { + if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { + continue; + } + int end = softwareRenderer->windows[w].endX; + if (softwareRenderer->blendEffect == BLEND_DARKEN) { + for (; x < end; ++x) { + uint32_t color = softwareRenderer->row[x]; + if (color & FLAG_TARGET_1 && !(color & FLAG_UNWRITTEN)) { + softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy); + } + } + } else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) { + for (; x < end; ++x) { + uint32_t color = softwareRenderer->row[x]; + if (color & FLAG_TARGET_1 && !(color & FLAG_UNWRITTEN)) { + softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy); + } + } + } + } + } #ifdef COLOR_16_BIT #if defined(__ARM_NEON) && !defined(__APPLE__) From f6e0239cd339da24e04b30e394f84cffd4197a79 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 16 Sep 2015 21:03:42 -0700 Subject: [PATCH 049/127] GBA Video: Fix objwin and blending interaction on sprites --- CHANGES | 1 + src/gba/renderers/software-obj.c | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/CHANGES b/CHANGES index 6f024d7ba..559dac8ea 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Bugfixes: - VFS: Fix return values of VFileFILE.read and .write - Util: Fix PowerPC PNG read/write pixel order - GBA Video: Fix edge case with sprite blend modes and semitransparency + - GBA Video: Fix objwin and blending interaction on sprites Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 4af726329..4067051d7 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -75,6 +75,19 @@ } \ } +#define SPRITE_DRAW_PIXEL_16_NORMAL_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \ + renderer->spriteLayer[outX] = color | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + #define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ @@ -97,6 +110,19 @@ } \ } +#define SPRITE_DRAW_PIXEL_256_NORMAL_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \ + renderer->spriteLayer[outX] = color | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + #define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ @@ -131,8 +157,14 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } } color_t* palette = &renderer->normalPalette[0x100]; + color_t* objwinPalette = palette; + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); + if (variant) { palette = &renderer->variantPalette[0x100]; + if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) { + objwinPalette = palette; + } } int inY = y - (int) GBAObjAttributesAGetY(sprite->a); @@ -159,12 +191,17 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(16, OBJWIN); + } else if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN); } else { SPRITE_TRANSFORMED_LOOP(16, NORMAL); } } else { if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(256, OBJWIN); + } else if (objwinSlowPath) { + SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN); } else { SPRITE_TRANSFORMED_LOOP(256, NORMAL); } @@ -200,6 +237,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re SPRITE_NORMAL_LOOP(16, OBJWIN); } else if (GBAObjAttributesAIsMosaic(sprite->a)) { SPRITE_MOSAIC_LOOP(16, NORMAL); + } else if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_NORMAL_LOOP(16, NORMAL_OBJWIN); } else { SPRITE_NORMAL_LOOP(16, NORMAL); } @@ -208,6 +248,8 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re SPRITE_NORMAL_LOOP(256, OBJWIN); } else if (GBAObjAttributesAIsMosaic(sprite->a)) { SPRITE_MOSAIC_LOOP(256, NORMAL); + } else if (objwinSlowPath) { + SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN); } else { SPRITE_NORMAL_LOOP(256, NORMAL); } From bafcee7b1879b61e66302110aecf54f62b672325 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 19:42:09 -0700 Subject: [PATCH 050/127] GBA Video: Fix regression when fixing sprite blending --- src/gba/renderers/software-private.h | 8 ++++---- src/gba/renderers/video-software.c | 4 ++-- src/gba/renderers/video-software.h | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index 4d4efed5d..b24ffdaf4 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -42,7 +42,7 @@ static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* render if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = current & (0x00FFFFFF | FLAG_TARGET_1); + color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); } } else { color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); @@ -55,7 +55,7 @@ static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* rend if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = current & (0x00FFFFFF | FLAG_TARGET_1); + color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); } } else { color = color & ~FLAG_TARGET_2; @@ -69,7 +69,7 @@ static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* rend if (color < current) { color |= (current & FLAG_OBJWIN); } else { - color = current & (0x00FFFFFF | FLAG_TARGET_1); + color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); } *pixel = color; } @@ -78,7 +78,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re uint32_t current) { UNUSED(renderer); if (color >= current) { - color = current & (0x00FFFFFF | FLAG_TARGET_1); + color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); } *pixel = color; } diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 81dd0b560..cf8cf6522 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -548,14 +548,14 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render if (softwareRenderer->blendEffect == BLEND_DARKEN) { for (; x < end; ++x) { uint32_t color = softwareRenderer->row[x]; - if (color & FLAG_TARGET_1 && !(color & FLAG_UNWRITTEN)) { + if ((color & 0xFF000000) == FLAG_REBLEND) { softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy); } } } else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) { for (; x < end; ++x) { uint32_t color = softwareRenderer->row[x]; - if (color & FLAG_TARGET_1 && !(color & FLAG_UNWRITTEN)) { + if ((color & 0xFF000000) == FLAG_REBLEND) { softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy); } } diff --git a/src/gba/renderers/video-software.h b/src/gba/renderers/video-software.h index f88d4d840..a213d32a9 100644 --- a/src/gba/renderers/video-software.h +++ b/src/gba/renderers/video-software.h @@ -74,6 +74,7 @@ enum { #define FLAG_TARGET_1 0x02000000 #define FLAG_TARGET_2 0x01000000 #define FLAG_OBJWIN 0x01000000 +#define FLAG_REBLEND 0x01000000 #define FLAG_ORDER_MASK 0xF8000000 #define IS_WRITABLE(PIXEL) ((PIXEL) & 0xFE000000) From a7cb0ec85c908589b5c8b690e75eb08484962ec3 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 19:47:29 -0700 Subject: [PATCH 051/127] GBA Video: Restore a fixed version of a removed optimization --- src/gba/renderers/software-mode0.c | 4 ++++ src/gba/renderers/software-private.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index 8953a255d..7b0ccc754 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -473,6 +473,10 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); objwinFlags |= flags; flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); + if (renderer->blendEffect == BLEND_ALPHA && renderer->blda == 0x10 && renderer->bldb == 0) { + flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); + } uint32_t screenBase; uint32_t charBase; diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index b24ffdaf4..7648f8dd2 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -184,6 +184,10 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re objwinFlags |= flags; \ flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && \ GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ + if (renderer->blendEffect == BLEND_ALPHA && renderer->blda == 0x10 && renderer->bldb == 0) { \ + flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + } \ int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \ (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ color_t* palette = renderer->normalPalette; \ From 04b6cf5e4c6582051049b45f81333d24b894418b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 19:49:45 -0700 Subject: [PATCH 052/127] GBA Config: Add more APIs --- src/gba/context/config.c | 12 ++++++++++++ src/gba/context/config.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/gba/context/config.c b/src/gba/context/config.c index db575e2e3..f7f6956fc 100644 --- a/src/gba/context/config.c +++ b/src/gba/context/config.c @@ -224,6 +224,18 @@ const char* GBAConfigGetValue(const struct GBAConfig* config, const char* key) { return _lookupValue(config, key); } +bool GBAConfigGetIntValue(const struct GBAConfig* config, const char* key, int* value) { + return _lookupIntValue(config, key, value); +} + +bool GBAConfigGetUIntValue(const struct GBAConfig* config, const char* key, unsigned* value) { + return _lookupUIntValue(config, key, value); +} + +bool GBAConfigGetFloatValue(const struct GBAConfig* config, const char* key, float* value) { + return _lookupFloatValue(config, key, value); +} + void GBAConfigSetValue(struct GBAConfig* config, const char* key, const char* value) { ConfigurationSetValue(&config->configTable, config->port, key, value); } diff --git a/src/gba/context/config.h b/src/gba/context/config.h index 35d822838..8d4039d92 100644 --- a/src/gba/context/config.h +++ b/src/gba/context/config.h @@ -59,6 +59,9 @@ void GBAConfigMakePortable(const struct GBAConfig*); void GBAConfigDirectory(char* out, size_t outLength); const char* GBAConfigGetValue(const struct GBAConfig*, const char* key); +bool GBAConfigGetIntValue(const struct GBAConfig*, const char* key, int* value); +bool GBAConfigGetUIntValue(const struct GBAConfig*, const char* key, unsigned* value); +bool GBAConfigGetFloatValue(const struct GBAConfig*, const char* key, float* value); void GBAConfigSetValue(struct GBAConfig*, const char* key, const char* value); void GBAConfigSetIntValue(struct GBAConfig*, const char* key, int value); From 94ffa0d2d7fbf71d1cb7f14373482a810e5785c2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 19:50:40 -0700 Subject: [PATCH 053/127] All: Minor cleanup --- src/gba/gui/gui-runner.c | 14 ++++++++++---- src/platform/3ds/main.c | 2 +- src/platform/psp2/psp2-context.c | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 241f865c1..b5256287c 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -14,12 +14,18 @@ #include "util/png-io.h" #include "util/vfs.h" +#include + +#define FPS_GRANULARITY 120 +#define FPS_BUFFER_SIZE 3 + enum { RUNNER_CONTINUE = 1, - RUNNER_EXIT = 2, - RUNNER_SAVE_STATE = 3, - RUNNER_LOAD_STATE = 4, - RUNNER_SCREENSHOT = 5, + RUNNER_EXIT, + RUNNER_SAVE_STATE, + RUNNER_LOAD_STATE, + RUNNER_SCREENSHOT, + RUNNER_CONFIG, RUNNER_COMMAND_MASK = 0xFFFF, RUNNER_STATE_1 = 0x10000, diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 56a4b2b17..bdeb30261 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -26,7 +26,7 @@ static enum ScreenMode { SM_AF_TOP, SM_SF_TOP, SM_MAX -} screenMode = SM_PA_BOTTOM; +} screenMode = SM_PA_TOP; #define AUDIO_SAMPLES 0x80 #define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 24) diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 910878b6c..548087c9a 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -229,6 +229,7 @@ void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded) { UNUSED(runner); switch (screenMode) { case SM_BACKDROP: + default: vita2d_draw_texture_tint(backdrop, 0, 0, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); // Fall through case SM_PLAIN: @@ -249,6 +250,7 @@ void GBAPSP2DrawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, } switch (screenMode) { case SM_BACKDROP: + default: vita2d_draw_texture_tint(backdrop, 0, 0, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); // Fall through case SM_PLAIN: From c45315b96b3322e4b29f933b27d461f2a73e7bc7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 19:53:27 -0700 Subject: [PATCH 054/127] GBA Context: Configuration options --- src/gba/context/context.c | 2 +- src/gba/gui/gui-config.c | 89 ++++++++++++++++++++++++++++++++ src/gba/gui/gui-config.h | 15 ++++++ src/gba/gui/gui-runner.c | 13 +++-- src/gba/gui/gui-runner.h | 3 ++ src/platform/3ds/main.c | 32 +++++++++++- src/platform/psp2/main.c | 16 ++++++ src/platform/psp2/psp2-context.c | 8 ++- src/util/gui/file-select.c | 4 +- src/util/gui/menu.c | 28 +++++++--- src/util/gui/menu.h | 4 +- 11 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 src/gba/gui/gui-config.c create mode 100644 src/gba/gui/gui-config.h diff --git a/src/gba/context/context.c b/src/gba/context/context.c index fe219cc88..18f70094c 100644 --- a/src/gba/context/context.c +++ b/src/gba/context/context.c @@ -56,8 +56,8 @@ bool GBAContextInit(struct GBAContext* context, const char* port) { .idleOptimization = IDLE_LOOP_DETECT, .logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS }; - GBAConfigLoadDefaults(&context->config, &opts); GBAConfigLoad(&context->config); + GBAConfigLoadDefaults(&context->config, &opts); } context->gba->sync = 0; diff --git a/src/gba/gui/gui-config.c b/src/gba/gui/gui-config.c new file mode 100644 index 000000000..c3d6f5ad3 --- /dev/null +++ b/src/gba/gui/gui-config.c @@ -0,0 +1,89 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gui-config.h" + +#include "gba/gui/gui-runner.h" +#include "util/gui/file-select.h" +#include "util/gui/menu.h" + +void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra) { + struct GUIMenu menu = { + .title = "Configure", + .index = 0, + .background = &runner->background.d + }; + GUIMenuItemListInit(&menu.items, 0); + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Use BIOS if found", + .data = "useBios", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On", 0 + } + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select BIOS path", + .data = "bios", + }; + size_t i; + for (i = 0; i < nExtra; ++i) { + *GUIMenuItemListAppend(&menu.items) = extra[i]; + } + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Save", + .data = "[SAVE]", + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Cancel", + .data = 0, + }; + enum GUIMenuExitReason reason; + char biosPath[256] = ""; + + struct GUIMenuItem* item; + for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { + item = GUIMenuItemListGetPointer(&menu.items, i); + if (!item->validStates || !item->data) { + continue; + } + GBAConfigGetUIntValue(&runner->context.config, item->data, &item->state); + } + + while (true) { + reason = GUIShowMenu(&runner->params, &menu, &item); + if (reason != GUI_MENU_EXIT_ACCEPT || !item->data) { + break; + } + if (!strcmp(item->data, "[SAVE]")) { + if (biosPath[0]) { + GBAConfigSetValue(&runner->context.config, "bios", biosPath); + } + for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { + item = GUIMenuItemListGetPointer(&menu.items, i); + if (!item->validStates || !item->data) { + continue; + } + GBAConfigSetUIntValue(&runner->context.config, item->data, item->state); + } + GBAConfigSave(&runner->context.config); + break; + } + if (!strcmp(item->data, "bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, biosPath, sizeof(biosPath), GBAIsBIOS)) { + biosPath[0] = '\0'; + } + continue; + } + if (item->validStates) { + ++item->state; + if (!item->validStates[item->state]) { + item->state = 0; + } + } + } +} diff --git a/src/gba/gui/gui-config.h b/src/gba/gui/gui-config.h new file mode 100644 index 000000000..2f38dbb1a --- /dev/null +++ b/src/gba/gui/gui-config.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GUI_CONFIG_H +#define GUI_CONFIG_H + +#include "util/common.h" + +struct GBAGUIRunner; +struct GUIMenuItem; +void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra); + +#endif diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index b5256287c..57330d781 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gui-runner.h" +#include "gba/gui/gui-config.h" #include "gba/interface.h" #include "gba/serialize.h" #include "util/gui/file-select.h" @@ -176,6 +177,7 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) }; #endif *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT }; + *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT }; while (true) { @@ -248,24 +250,24 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { } GUIInvalidateKeys(&runner->params); uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable! - struct GUIMenuItem item; + struct GUIMenuItem* item; enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item); if (reason == GUI_MENU_EXIT_ACCEPT) { struct VFile* vf; - switch (((int) item.data) & RUNNER_COMMAND_MASK) { + switch (((int) item->data) & RUNNER_COMMAND_MASK) { case RUNNER_EXIT: running = false; keys = 0; break; case RUNNER_SAVE_STATE: - vf = GBAGetState(runner->context.gba, 0, ((int) item.data) >> 16, true); + vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, true); if (vf) { GBASaveStateNamed(runner->context.gba, vf, true); vf->close(vf); } break; case RUNNER_LOAD_STATE: - vf = GBAGetState(runner->context.gba, 0, ((int) item.data) >> 16, false); + vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, false); if (vf) { GBALoadStateNamed(runner->context.gba, vf); vf->close(vf); @@ -274,6 +276,9 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { case RUNNER_SCREENSHOT: GBATakeScreenshot(runner->context.gba, 0); break; + case RUNNER_CONFIG: + GBAGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra); + break; case RUNNER_CONTINUE: break; } diff --git a/src/gba/gui/gui-runner.h b/src/gba/gui/gui-runner.h index 4fb739b72..0ca400940 100644 --- a/src/gba/gui/gui-runner.h +++ b/src/gba/gui/gui-runner.h @@ -35,6 +35,9 @@ struct GBAGUIRunner { struct GBAGUIBackground background; struct GBAGUIRunnerLux luminanceSource; + struct GUIMenuItem* configExtra; + size_t nConfigExtra; + void (*setup)(struct GBAGUIRunner*); void (*teardown)(struct GBAGUIRunner*); void (*gameLoaded)(struct GBAGUIRunner*); diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index bdeb30261..1b76515e1 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -11,6 +11,7 @@ #include "util/gui.h" #include "util/gui/file-select.h" #include "util/gui/font.h" +#include "util/gui/menu.h" #include "util/memory.h" #include "3ds-vfs.h" @@ -82,6 +83,11 @@ static void _setup(struct GBAGUIRunner* runner) { renderer.outputBufferStride = 256; runner->context.renderer = &renderer.d; + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode < SM_MAX) { + screenMode = mode; + } + GBAAudioResizeBuffer(&runner->context.gba->audio, AUDIO_SAMPLES); } @@ -235,7 +241,13 @@ static void _incrementScreenMode(struct GBAGUIRunner* runner) { _drawEnd(); _drawStart(); _drawEnd(); - screenMode = (screenMode + 1) % SM_MAX; + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { + screenMode = mode; + } else { + screenMode = (screenMode + 1) % SM_MAX; + GBAConfigSetUIntValue(&runner->context.config, "screenMode", screenMode); + } } static uint32_t _pollInput(void) { @@ -395,6 +407,24 @@ int main() { GUI_PARAMS_TRAIL }, + .configExtra = (struct GUIMenuItem[]) { + { + .title = "Screen mode", + .data = "screenMode", + .submenu = 0, + .state = SM_PA_TOP, + .validStates = (const char*[]) { + "Pixel-Accurate/Bottom", + "Aspect-Ratio Fit/Bottom", + "Stretched/Bottom", + "Pixel-Accurate/Top", + "Aspect-Ratio Fit/Top", + "Stretched/Top", + 0 + } + } + }, + .nConfigExtra = 1, .setup = _setup, .teardown = 0, .gameLoaded = _gameLoaded, diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index d06aff0e6..67f386888 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -10,6 +10,7 @@ #include "util/gui.h" #include "util/gui/font.h" #include "util/gui/file-select.h" +#include "util/gui/menu.h" #include #include @@ -88,6 +89,21 @@ int main() { GUI_PARAMS_TRAIL }, + .configExtra = (struct GUIMenuItem[]) { + { + .title = "Screen mode", + .data = "screenMode", + .submenu = 0, + .state = 0, + .validStates = (const char*[]) { + "With Background", + "Without Background", + "Stretched", + 0 + } + } + }, + .nConfigExtra = 1, .setup = GBAPSP2Setup, .teardown = GBAPSP2Teardown, .gameLoaded = GBAPSP2LoadROM, diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 548087c9a..6ad390f12 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -263,7 +263,13 @@ void GBAPSP2DrawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, } void GBAPSP2IncrementScreenMode(struct GBAGUIRunner* runner) { - screenMode = (screenMode + 1) % SM_MAX; + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { + screenMode = mode; + } else { + screenMode = (screenMode + 1) % SM_MAX; + GBAConfigSetUIntValue(&runner->context.config, "screenMode", screenMode); + } } __attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) { diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index 023fe4039..a8537a5b6 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -137,7 +137,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool _refreshDirectory(params, params->currentPath, &menu.items, filter); while (true) { - struct GUIMenuItem item; + struct GUIMenuItem* item; enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item); params->fileIndex = menu.index; if (reason == GUI_MENU_EXIT_CANCEL) { @@ -155,7 +155,7 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool if (params->currentPath[len - 1] == *sep) { sep = ""; } - snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item.title); + snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title); struct GUIMenuItemList newFiles; GUIMenuItemListInit(&newFiles, 0); diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index ab9ad8846..9c0c536a9 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -10,7 +10,7 @@ DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem); -enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem* item) { +enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) { size_t start = 0; size_t lineHeight = GUIFontHeight(params->font); size_t pageSize = params->height / lineHeight; @@ -35,14 +35,24 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men ++menu->index; } if (newInput & (1 << GUI_INPUT_LEFT)) { - if (menu->index >= pageSize) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->validStates) { + if (item->state > 0) { + --item->state; + } + } else if (menu->index >= pageSize) { menu->index -= pageSize; } else { menu->index = 0; } } if (newInput & (1 << GUI_INPUT_RIGHT)) { - if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->validStates) { + if (item->validStates[item->state + 1]) { + ++item->state; + } + } else if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) { menu->index += pageSize; } else { menu->index = GUIMenuItemListSize(&menu->items) - 1; @@ -70,9 +80,9 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men break; } if (newInput & (1 << GUI_INPUT_SELECT) || (cursorOverItem == 2 && cursor == GUI_CURSOR_CLICKED)) { - *item = *GUIMenuItemListGetPointer(&menu->items, menu->index); - if (item->submenu) { - enum GUIMenuExitReason reason = GUIShowMenu(params, item->submenu, item); + *item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if ((*item)->submenu) { + enum GUIMenuExitReason reason = GUIShowMenu(params, (*item)->submenu, item); if (reason != GUI_MENU_EXIT_BACK) { return reason; } @@ -105,7 +115,11 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men color = 0xFFFFFFFF; bullet = '>'; } - GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, GUIMenuItemListGetPointer(&menu->items, i)->title); + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i); + GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, item->title); + if (item->validStates) { + GUIFontPrintf(params->font, params->width, y, GUI_TEXT_RIGHT, color, "%s ", item->validStates[item->state]); + } y += lineHeight; if (y + lineHeight > params->height) { break; diff --git a/src/util/gui/menu.h b/src/util/gui/menu.h index 3ffb1c686..0ce52588a 100644 --- a/src/util/gui/menu.h +++ b/src/util/gui/menu.h @@ -12,6 +12,8 @@ struct GUIMenu; struct GUIMenuItem { const char* title; void* data; + unsigned state; + const char** validStates; struct GUIMenu* submenu; }; @@ -32,6 +34,6 @@ enum GUIMenuExitReason { }; struct GUIParams; -enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem* item); +enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item); #endif From fafcfebf2efa65db88d5fadc80d0a19ef06f8111 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 19:54:07 -0700 Subject: [PATCH 055/127] GBA Context: Add fps counter and frameskip --- src/gba/context/context.c | 7 ++++++- src/gba/context/context.h | 2 ++ src/gba/gui/gui-config.c | 18 +++++++++++++++++ src/gba/gui/gui-runner.c | 41 +++++++++++++++++++++++++++++++++++++++ src/gba/gui/gui-runner.h | 6 ++++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/gba/context/context.c b/src/gba/context/context.c index 18f70094c..3aecf2a55 100644 --- a/src/gba/context/context.c +++ b/src/gba/context/context.c @@ -60,7 +60,7 @@ bool GBAContextInit(struct GBAContext* context, const char* port) { GBAConfigLoadDefaults(&context->config, &opts); } - context->gba->sync = 0; + context->gba->sync = &context->sync; return true; } @@ -191,6 +191,11 @@ void GBAContextFrame(struct GBAContext* context, uint16_t keys) { while (frameCounter == context->gba->video.frameCounter) { ARMRunLoop(context->cpu); } + if (context->sync.videoFrameSkip < 0) { + int frameskip = 0; + GBAConfigGetIntValue(&context->config, "frameskip", &frameskip); + context->sync.videoFrameSkip = frameskip; + } } static void _GBAContextLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { diff --git a/src/gba/context/context.h b/src/gba/context/context.h index fd86dca7c..1fcde3f24 100644 --- a/src/gba/context/context.h +++ b/src/gba/context/context.h @@ -9,6 +9,7 @@ #include "util/common.h" #include "gba/context/config.h" +#include "gba/context/sync.h" #include "gba/input.h" struct GBAContext { @@ -23,6 +24,7 @@ struct GBAContext { struct GBAConfig config; struct GBAOptions opts; struct GBAInputMap inputMap; + struct GBASync sync; }; bool GBAContextInit(struct GBAContext* context, const char* port); diff --git a/src/gba/gui/gui-config.c b/src/gba/gui/gui-config.c index c3d6f5ad3..67380fe38 100644 --- a/src/gba/gui/gui-config.c +++ b/src/gba/gui/gui-config.c @@ -16,6 +16,24 @@ void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, si .background = &runner->background.d }; GUIMenuItemListInit(&menu.items, 0); + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Frameskip", + .data = "frameskip", + .submenu = 0, + .state = 0, + .validStates = (const char*[]) { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 0 + } + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Show framerate", + .data = "fpsCounter", + .submenu = 0, + .state = false, + .validStates = (const char*[]) { + "Off", "On", 0 + } + }; *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { .title = "Use BIOS if found", .data = "useBios", diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 57330d781..a0751d41c 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -108,6 +108,10 @@ void GBAGUIInit(struct GBAGUIRunner* runner, const char* port) { runner->context.gba->luminanceSource = &runner->luminanceSource.d; runner->background.d.draw = _drawBackground; runner->background.p = runner; + runner->fps = 0; + runner->lastFpsCheck = 0; + runner->totalDelta = 0; + CircleBufferInit(&runner->fpsBuffer, FPS_BUFFER_SIZE * sizeof(uint32_t)); if (runner->setup) { runner->setup(runner); } @@ -120,6 +124,7 @@ void GBAGUIDeinit(struct GBAGUIRunner* runner) { if (runner->context.config.port) { GBAConfigSave(&runner->context.config); } + CircleBufferDeinit(&runner->fpsBuffer); GBAContextDeinit(&runner->context); } @@ -212,8 +217,16 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { if (runner->gameLoaded) { runner->gameLoaded(runner); } + bool running = true; while (running) { + CircleBufferClear(&runner->fpsBuffer); + runner->totalDelta = 0; + runner->fps = 0; + struct timeval tv; + gettimeofday(&tv, 0); + runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; + while (true) { uint32_t guiKeys; GUIPollInput(&runner->params, &guiKeys, 0); @@ -239,9 +252,37 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { } GBAContextFrame(&runner->context, keys); if (runner->drawFrame) { + int drawFps = false; + GBAConfigGetIntValue(&runner->context.config, "fpsCounter", &drawFps); + runner->params.drawStart(); runner->drawFrame(runner, false); + if (drawFps) { + GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_TEXT_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps); + } runner->params.drawEnd(); + + if (runner->context.gba->video.frameCounter % FPS_GRANULARITY == 0) { + if (drawFps) { + struct timeval tv; + gettimeofday(&tv, 0); + uint64_t t = 1000000LL * tv.tv_sec + tv.tv_usec; + uint64_t delta = t - runner->lastFpsCheck; + runner->lastFpsCheck = t; + if (delta > 0x7FFFFFFFLL) { + CircleBufferClear(&runner->fpsBuffer); + runner->fps = 0; + } + if (CircleBufferSize(&runner->fpsBuffer) == CircleBufferCapacity(&runner->fpsBuffer)) { + int32_t last; + CircleBufferRead32(&runner->fpsBuffer, &last); + runner->totalDelta -= last; + } + CircleBufferWrite32(&runner->fpsBuffer, delta); + runner->totalDelta += delta; + runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t)); + } + } } } diff --git a/src/gba/gui/gui-runner.h b/src/gba/gui/gui-runner.h index 0ca400940..df6bbca45 100644 --- a/src/gba/gui/gui-runner.h +++ b/src/gba/gui/gui-runner.h @@ -7,6 +7,7 @@ #define GUI_RUNNER_H #include "gba/context/context.h" +#include "util/circle-buffer.h" #include "util/gui.h" enum GBAGUIInput { @@ -38,6 +39,11 @@ struct GBAGUIRunner { struct GUIMenuItem* configExtra; size_t nConfigExtra; + float fps; + int64_t lastFpsCheck; + int32_t totalDelta; + struct CircleBuffer fpsBuffer; + void (*setup)(struct GBAGUIRunner*); void (*teardown)(struct GBAGUIRunner*); void (*gameLoaded)(struct GBAGUIRunner*); From 00909284654d924fa51bdbcb8e5bcbdaa2412660 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 17 Sep 2015 23:25:35 -0700 Subject: [PATCH 056/127] 3DS: Always draw GUI on bottom screen --- src/gba/gui/gui-runner.c | 6 ++++++ src/platform/3ds/main.c | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index a0751d41c..5a604fa60 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -258,7 +258,13 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { runner->params.drawStart(); runner->drawFrame(runner, false); if (drawFps) { + if (runner->params.guiPrepare) { + runner->params.guiPrepare(); + } GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_TEXT_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps); + if (runner->params.guiPrepare) { + runner->params.guiFinish(); + } } runner->params.drawEnd(); diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 1b76515e1..b3b198569 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -48,6 +48,7 @@ static int16_t* audioLeft = 0; static int16_t* audioRight = 0; static size_t audioPos = 0; static struct ctrTexture gbaOutputTexture; +static bool guiDrawn; extern bool allocateRomBuffer(void); @@ -60,16 +61,46 @@ static void _drawStart(void) { } else { ctrSetViewportSize(400, 240); } + guiDrawn = false; } static void _drawEnd(void) { + if (!guiDrawn) { + int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + u16 width = 0, height = 0; + + void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); + ctrGpuEndFrame(outputFramebuffer, width, height); + } + gfxSwapBuffersGpu(); + gspWaitForEvent(GSPEVENT_VBlank0, false); +} + +static void _guiPrepare(void) { int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + if (screen == GFX_BOTTOM) { + return; + } + u16 width = 0, height = 0; void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); ctrGpuEndFrame(outputFramebuffer, width, height); - gfxSwapBuffersGpu(); - gspWaitForEvent(GSPEVENT_VBlank0, false); + guiDrawn = true; + + ctrGpuBeginFrame(); + ctrSetViewportSize(320, 240); +} + +static void _guiFinish(void) { + int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + if (screen == GFX_BOTTOM) { + return; + } + + u16 width = 0, height = 0; + void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); + ctrGpuEndFrame(outputFramebuffer, width, height); } static void _setup(struct GBAGUIRunner* runner) { @@ -403,7 +434,7 @@ int main() { font, "/", _drawStart, _drawEnd, _pollInput, _pollCursor, - 0, 0, + _guiPrepare, _guiFinish, GUI_PARAMS_TRAIL }, From 91fb4407b9a3bc64c84d74eaffe9673d89059a10 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 18 Sep 2015 00:21:41 -0700 Subject: [PATCH 057/127] 3DS: GUI fixes when game on top screen --- src/platform/3ds/main.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index b3b198569..131aa5a00 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -48,7 +48,12 @@ static int16_t* audioLeft = 0; static int16_t* audioRight = 0; static size_t audioPos = 0; static struct ctrTexture gbaOutputTexture; -static bool guiDrawn; +static int guiDrawn; + +enum { + GUI_ACTIVE = 1, + GUI_THIS_FRAME = 2 +}; extern bool allocateRomBuffer(void); @@ -56,27 +61,32 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) static void _drawStart(void) { ctrGpuBeginFrame(); - if (screenMode < SM_PA_TOP) { + if (screenMode < SM_PA_TOP || (guiDrawn & GUI_ACTIVE)) { ctrSetViewportSize(320, 240); } else { ctrSetViewportSize(400, 240); } - guiDrawn = false; + guiDrawn &= ~GUI_THIS_FRAME; } static void _drawEnd(void) { - if (!guiDrawn) { - int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; - u16 width = 0, height = 0; + int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + u16 width = 0, height = 0; + if (guiDrawn & GUI_ACTIVE) { + screen = GFX_BOTTOM; + } + if (!(guiDrawn & GUI_THIS_FRAME) || screen == GFX_BOTTOM) { void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); ctrGpuEndFrame(outputFramebuffer, width, height); } + gfxSwapBuffersGpu(); gspWaitForEvent(GSPEVENT_VBlank0, false); } static void _guiPrepare(void) { + guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME; int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; if (screen == GFX_BOTTOM) { return; @@ -86,13 +96,13 @@ static void _guiPrepare(void) { void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); ctrGpuEndFrame(outputFramebuffer, width, height); - guiDrawn = true; ctrGpuBeginFrame(); ctrSetViewportSize(320, 240); } static void _guiFinish(void) { + guiDrawn &= ~GUI_ACTIVE; int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; if (screen == GFX_BOTTOM) { return; From c3aededf05edf9337f89191dfdbfc3189885fb14 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 19 Sep 2015 00:32:49 -0700 Subject: [PATCH 058/127] GUI: Header with time and battery level --- src/platform/3ds/main.c | 20 +++++++- src/platform/psp2/main.c | 1 + src/platform/wii/main.c | 1 + src/util/gui.c | 37 --------------- src/util/gui.h | 11 +++++ src/util/gui/file-select.c | 7 +-- src/util/gui/menu.c | 95 ++++++++++++++++++++++++++++++++++++++ src/util/gui/menu.h | 4 ++ 8 files changed, 135 insertions(+), 41 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 131aa5a00..988095f4d 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -85,6 +85,21 @@ static void _drawEnd(void) { gspWaitForEvent(GSPEVENT_VBlank0, false); } +static int _batteryState(void) { + u8 charge; + u8 adapter; + PTMU_GetBatteryLevel(0, &charge); + PTMU_GetBatteryChargeState(0, &adapter); + int state = 0; + if (adapter) { + state |= BATTERY_CHARGING; + } + if (charge > 0) { + --charge; + } + return state | charge; +} + static void _guiPrepare(void) { guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME; int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; @@ -385,6 +400,7 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) } int main() { + ptmInit(); hasSound = !csndInit(); rotation.d.sample = _sampleRotation; @@ -444,12 +460,13 @@ int main() { font, "/", _drawStart, _drawEnd, _pollInput, _pollCursor, + _batteryState, _guiPrepare, _guiFinish, GUI_PARAMS_TRAIL }, .configExtra = (struct GUIMenuItem[]) { - { + { .title = "Screen mode", .data = "screenMode", .submenu = 0, @@ -496,5 +513,6 @@ cleanup: linearFree(audioRight); } csndExit(); + ptmExit(); return 0; } diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 67f386888..fcc0552cf 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -85,6 +85,7 @@ int main() { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, font, "cache0:", _drawStart, _drawEnd, _pollInput, _pollCursor, + 0, 0, 0, GUI_PARAMS_TRAIL diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index ecff90c98..7ca9e8e52 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -158,6 +158,7 @@ int main() { font, "/", _drawStart, _drawEnd, _pollInput, _pollCursor, + 0, _guiPrepare, _guiFinish, GUI_PARAMS_TRAIL diff --git a/src/util/gui.c b/src/util/gui.c index b391876b6..4bb7b6c11 100644 --- a/src/util/gui.c +++ b/src/util/gui.c @@ -30,40 +30,3 @@ void GUIPollInput(struct GUIParams* params, uint32_t* newInputOut, uint32_t* hel *heldInput = input; } } - -enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y) { - if (!params->pollCursor) { - return GUI_CURSOR_NOT_PRESENT; - } - enum GUICursorState state = params->pollCursor(x, y); - if (params->cursorState == GUI_CURSOR_DOWN) { - int dragX = *x - params->cx; - int dragY = *y - params->cy; - if (dragX * dragX + dragY * dragY > 25) { - params->cursorState = GUI_CURSOR_DRAGGING; - return GUI_CURSOR_DRAGGING; - } - if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { - params->cursorState = GUI_CURSOR_UP; - return GUI_CURSOR_CLICKED; - } - } else { - params->cx = *x; - params->cy = *y; - } - if (params->cursorState == GUI_CURSOR_DRAGGING) { - if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { - params->cursorState = GUI_CURSOR_UP; - return GUI_CURSOR_UP; - } - return GUI_CURSOR_DRAGGING; - } - params->cursorState = state; - return params->cursorState; -} - -void GUIInvalidateKeys(struct GUIParams* params) { - for (int i = 0; i < GUI_INPUT_MAX; ++i) { - params->inputHistory[i] = 0; - } -} diff --git a/src/util/gui.h b/src/util/gui.h index 621565152..9d98cf4a8 100644 --- a/src/util/gui.h +++ b/src/util/gui.h @@ -36,6 +36,16 @@ enum GUICursorState { GUI_CURSOR_DRAGGING }; +enum { + BATTERY_EMPTY = 0, + BATTERY_LOW = 1, + BATTERY_HALF = 2, + BATTERY_HIGH = 3, + BATTERY_FULL = 4, + + BATTERY_CHARGING = 8 +}; + struct GUIBackground { void (*draw)(struct GUIBackground*, void* context); }; @@ -50,6 +60,7 @@ struct GUIParams { void (*drawEnd)(void); uint32_t (*pollInput)(void); enum GUICursorState (*pollCursor)(int* x, int* y); + int (*batteryState)(void); void (*guiPrepare)(void); void (*guiFinish)(void); diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index a8537a5b6..a9f00f3dc 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -103,8 +103,8 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, if (params->guiPrepare) { params->guiPrepare(); } - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); if (params->guiFinish) { params->guiFinish(); } @@ -130,7 +130,8 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) { struct GUIMenu menu = { - .title = params->currentPath, + .title = "Select file", + .subtitle = params->currentPath, .index = params->fileIndex, }; GUIMenuItemListInit(&menu.items, 0); diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 9c0c536a9..95a633489 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -106,6 +106,9 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men } unsigned y = lineHeight; GUIFontPrint(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, menu->title); + if (menu->subtitle) { + GUIFontPrint(params->font, 0, y * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, menu->subtitle); + } y += 2 * lineHeight; size_t i; for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) { @@ -125,6 +128,10 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men break; } } + + GUIDrawBattery(params); + GUIDrawClock(params); + if (params->guiFinish) { params->guiFinish(); } @@ -132,3 +139,91 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men } return GUI_MENU_EXIT_CANCEL; } + +enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y) { + if (!params->pollCursor) { + return GUI_CURSOR_NOT_PRESENT; + } + enum GUICursorState state = params->pollCursor(x, y); + if (params->cursorState == GUI_CURSOR_DOWN) { + int dragX = *x - params->cx; + int dragY = *y - params->cy; + if (dragX * dragX + dragY * dragY > 25) { + params->cursorState = GUI_CURSOR_DRAGGING; + return GUI_CURSOR_DRAGGING; + } + if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { + params->cursorState = GUI_CURSOR_UP; + return GUI_CURSOR_CLICKED; + } + } else { + params->cx = *x; + params->cy = *y; + } + if (params->cursorState == GUI_CURSOR_DRAGGING) { + if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { + params->cursorState = GUI_CURSOR_UP; + return GUI_CURSOR_UP; + } + return GUI_CURSOR_DRAGGING; + } + params->cursorState = state; + return params->cursorState; +} + +void GUIInvalidateKeys(struct GUIParams* params) { + for (int i = 0; i < GUI_INPUT_MAX; ++i) { + params->inputHistory[i] = 0; + } +} + +void GUIDrawBattery(struct GUIParams* params) { + if (!params->batteryState) { + return; + } + int state = params->batteryState(); + uint32_t color = 0xFF000000; + if (state == (BATTERY_CHARGING | BATTERY_FULL)) { + color |= 0x2020FF; + } else if (state & BATTERY_CHARGING) { + color |= 0x20FF20; + } else if (state >= BATTERY_HALF) { + color |= 0xFFFFFF; + } else if (state == BATTERY_LOW) { + color |= 0x20FFFF; + } else { + color |= 0xFF2020; + } + + const char* batteryText; + switch (state & ~BATTERY_CHARGING) { + case BATTERY_EMPTY: + batteryText = "[ ]"; + break; + case BATTERY_LOW: + batteryText = "[I ]"; + break; + case BATTERY_HALF: + batteryText = "[II ]"; + break; + case BATTERY_HIGH: + batteryText = "[III ]"; + break; + case BATTERY_FULL: + batteryText = "[IIII]"; + break; + default: + batteryText = "[????]"; + break; + } + + GUIFontPrint(params->font, params->width, GUIFontHeight(params->font), GUI_TEXT_RIGHT, color, batteryText); +} + +void GUIDrawClock(struct GUIParams* params) { + char buffer[32]; + time_t t = time(0); + struct tm* tm = localtime(&t); + strftime(buffer, sizeof(buffer), "%H:%M:%S", tm); + GUIFontPrint(params->font, params->width / 2, GUIFontHeight(params->font), GUI_TEXT_CENTER, 0xFFFFFFFF, buffer); +} diff --git a/src/util/gui/menu.h b/src/util/gui/menu.h index 0ce52588a..f07f249ea 100644 --- a/src/util/gui/menu.h +++ b/src/util/gui/menu.h @@ -22,6 +22,7 @@ DECLARE_VECTOR(GUIMenuItemList, struct GUIMenuItem); struct GUIBackground; struct GUIMenu { const char* title; + const char* subtitle; struct GUIMenuItemList items; size_t index; struct GUIBackground* background; @@ -36,4 +37,7 @@ enum GUIMenuExitReason { struct GUIParams; enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item); +void GUIDrawBattery(struct GUIParams* params); +void GUIDrawClock(struct GUIParams* params); + #endif From 336993497dbf1bd02202b2100da5d724bcfaea02 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 19 Sep 2015 18:19:21 -0700 Subject: [PATCH 059/127] GBA Video: Fix another blending regression --- src/gba/renderers/video-software.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index cf8cf6522..fb7755b52 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -523,7 +523,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render if (softwareRenderer->target2Bd) { x = 0; for (w = 0; w < softwareRenderer->nWindows; ++w) { - uint32_t backdrop = FLAG_UNWRITTEN; + uint32_t backdrop = 0; if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { backdrop |= softwareRenderer->normalPalette[0]; } else { @@ -532,7 +532,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render int end = softwareRenderer->windows[w].endX; for (; x < end; ++x) { uint32_t color = softwareRenderer->row[x]; - if (color & FLAG_TARGET_1 && color & FLAG_UNWRITTEN) { + if (color & FLAG_TARGET_1) { softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color); } } From 8ffcb116f2f65fbf09122d7568e59ad61fa5bfc3 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 19 Sep 2015 19:41:43 -0700 Subject: [PATCH 060/127] GUI: Minor fixes --- src/util/gui/file-select.c | 4 ++-- src/util/gui/menu.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index a9f00f3dc..04a59ee03 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -73,8 +73,8 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, if (params->guiPrepare) { params->guiPrepare(); } - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); if (params->guiFinish) { params->guiFinish(); } diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 95a633489..3121da829 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -184,7 +184,7 @@ void GUIDrawBattery(struct GUIParams* params) { int state = params->batteryState(); uint32_t color = 0xFF000000; if (state == (BATTERY_CHARGING | BATTERY_FULL)) { - color |= 0x2020FF; + color |= 0xFF2020; } else if (state & BATTERY_CHARGING) { color |= 0x20FF20; } else if (state >= BATTERY_HALF) { @@ -192,7 +192,7 @@ void GUIDrawBattery(struct GUIParams* params) { } else if (state == BATTERY_LOW) { color |= 0x20FFFF; } else { - color |= 0xFF2020; + color |= 0x2020FF; } const char* batteryText; From 465dc2b40048e70380ed26a7451d208f4e643d6c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 19 Sep 2015 19:42:34 -0700 Subject: [PATCH 061/127] 3DS: Allow for multiple screens, increasing async ability --- src/platform/3ds/ctr-gpu.c | 101 +++++++++++++++++++++++++++++-------- src/platform/3ds/ctr-gpu.h | 6 ++- src/platform/3ds/main.c | 15 +++--- 3 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/platform/3ds/ctr-gpu.c b/src/platform/3ds/ctr-gpu.c index 1ac1b7ece..65d29e174 100644 --- a/src/platform/3ds/ctr-gpu.c +++ b/src/platform/3ds/ctr-gpu.c @@ -31,13 +31,15 @@ static struct ctrUIVertex* ctrVertexBuffer = NULL; static u16* ctrIndexBuffer = NULL; static u16 ctrNumQuads = 0; -static void* gpuColorBuffer = NULL; +static void* gpuColorBuffer[2] = { NULL, NULL }; static u32* gpuCommandList = NULL; static void* screenTexture = NULL; static shaderProgram_s gpuShader; static DVLB_s* passthroughShader = NULL; +static int pendingEvents = 0; + static const struct ctrTexture* activeTexture = NULL; static u32 _f24FromFloat(float f) { @@ -85,6 +87,17 @@ static u32 _f31FromFloat(float f) { return sign << 30 | exponent << 23 | mantissa; } +void ctrClearPending(int events) { + int toClear = events & pendingEvents; + if (toClear & (1 << GSPEVENT_PSC0)) { + gspWaitForPSC0(); + } + if (toClear & (1 << GSPEVENT_PPF)) { + gspWaitForPPF(); + } + pendingEvents ^= toClear; +} + // Replacements for the limiting GPU_SetViewport function in ctrulib static void _GPU_SetFramebuffer(intptr_t colorBuffer, intptr_t depthBuffer, u16 w, u16 h) { u32 buf[4]; @@ -145,10 +158,11 @@ Result ctrInitGpu() { Result res = -1; // Allocate buffers - gpuColorBuffer = vramAlloc(400 * 240 * 4); + gpuColorBuffer[0] = vramAlloc(400 * 240 * 4); + gpuColorBuffer[1] = vramAlloc(320 * 240 * 4); gpuCommandList = linearAlloc(COMMAND_LIST_LENGTH * sizeof(u32)); ctrVertexBuffer = linearAlloc(VERTEX_INDEX_BUFFER_SIZE); - if (gpuColorBuffer == NULL || gpuCommandList == NULL || ctrVertexBuffer == NULL) { + if (gpuColorBuffer[0] == NULL || gpuColorBuffer[1] == NULL || gpuCommandList == NULL || ctrVertexBuffer == NULL) { res = -1; goto error_allocs; } @@ -197,9 +211,14 @@ error_allocs: gpuCommandList = NULL; } - if (gpuColorBuffer != NULL) { - vramFree(gpuColorBuffer); - gpuColorBuffer = NULL; + if (gpuColorBuffer[0] != NULL) { + vramFree(gpuColorBuffer[0]); + gpuColorBuffer[0] = NULL; + } + + if (gpuColorBuffer[1] != NULL) { + vramFree(gpuColorBuffer[1]); + gpuColorBuffer[1] = NULL; } return res; } @@ -221,22 +240,31 @@ void ctrDeinitGpu() { linearFree(gpuCommandList); gpuCommandList = NULL; - vramFree(gpuColorBuffer); - gpuColorBuffer = NULL; + vramFree(gpuColorBuffer[0]); + gpuColorBuffer[0] = NULL; + + vramFree(gpuColorBuffer[1]); + gpuColorBuffer[1] = NULL; } -void ctrGpuBeginFrame(void) { +void ctrGpuBeginFrame(int screen) { + if (screen > 1) { + return; + } + + int fw; + if (screen == 0) { + fw = 400; + } else { + fw = 320; + } + + _GPU_SetFramebuffer(osConvertVirtToPhys((u32)gpuColorBuffer[screen]), 0, 240, fw); +} + +void ctrGpuBeginDrawing(void) { shaderProgramUse(&gpuShader); - void* gpuColorBufferEnd = (char*)gpuColorBuffer + 240 * 400 * 4; - - GX_SetMemoryFill(NULL, - gpuColorBuffer, 0x00000000, gpuColorBufferEnd, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER, - NULL, 0, NULL, 0); - gspWaitForPSC0(); - - _GPU_SetFramebuffer(osConvertVirtToPhys((u32)gpuColorBuffer), 0, 240, 400); - // Disable depth and stencil testing GPU_SetDepthTestAndWriteMask(false, GPU_ALWAYS, GPU_WRITE_COLOR); GPU_SetStencilTest(false, GPU_ALWAYS, 0, 0xFF, 0); @@ -286,20 +314,47 @@ void ctrGpuBeginFrame(void) { bufferOffsets, arrayTargetAttributes, numAttributesInArray); } -void ctrGpuEndFrame(void* outputFramebuffer, int w, int h) { +void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h) { + if (screen > 1) { + return; + } + + int fw; + if (screen == 0) { + fw = 400; + } else { + fw = 320; + } + ctrFlushBatch(); - void* colorBuffer = (u8*)gpuColorBuffer + ((400 - w) * 240 * 4); + void* colorBuffer = (u8*)gpuColorBuffer[screen] + ((fw - w) * 240 * 4); const u32 GX_CROP_INPUT_LINES = (1 << 2); + ctrClearPending(1 << GSPEVENT_PSC0); + ctrClearPending(1 << GSPEVENT_PPF); + GX_SetDisplayTransfer(NULL, - colorBuffer, GX_BUFFER_DIM(240, 400), + colorBuffer, GX_BUFFER_DIM(240, fw), outputFramebuffer, GX_BUFFER_DIM(h, w), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_CROP_INPUT_LINES); - gspWaitForPPF(); + pendingEvents |= (1 << GSPEVENT_PPF); +} + +void ctrGpuEndDrawing(void) { + ctrClearPending(1 << GSPEVENT_PPF); + gfxSwapBuffersGpu(); + gspWaitForEvent(GSPEVENT_VBlank0, false); + + void* gpuColorBuffer0End = (char*)gpuColorBuffer[0] + 240 * 400 * 4; + void* gpuColorBuffer1End = (char*)gpuColorBuffer[1] + 240 * 320 * 4; + GX_SetMemoryFill(NULL, + gpuColorBuffer[0], 0x00000000, gpuColorBuffer0End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER, + gpuColorBuffer[1], 0x00000000, gpuColorBuffer1End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER); + pendingEvents |= 1 << GSPEVENT_PSC0; } void ctrSetViewportSize(s16 w, s16 h) { @@ -389,6 +444,8 @@ void ctrFlushBatch(void) { return; } + ctrClearPending((1 << GSPEVENT_PSC0)); + GSPGPU_FlushDataCache(NULL, (u8*)ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE); GPU_DrawElements(GPU_UNKPRIM, (u32*)(osConvertVirtToPhys((u32)ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6); diff --git a/src/platform/3ds/ctr-gpu.h b/src/platform/3ds/ctr-gpu.h index 0a0637d3f..a761815a0 100644 --- a/src/platform/3ds/ctr-gpu.h +++ b/src/platform/3ds/ctr-gpu.h @@ -28,8 +28,10 @@ inline void ctrTexture_Init(struct ctrTexture* tex) { Result ctrInitGpu(void); void ctrDeinitGpu(void); -void ctrGpuBeginFrame(void); -void ctrGpuEndFrame(void* outputFramebuffer, int w, int h); +void ctrGpuBeginDrawing(void); +void ctrGpuBeginFrame(int screen); +void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h); +void ctrGpuEndDrawing(void); void ctrSetViewportSize(s16 w, s16 h); diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 988095f4d..70b0236c9 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -60,10 +60,12 @@ extern bool allocateRomBuffer(void); static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio); static void _drawStart(void) { - ctrGpuBeginFrame(); + ctrGpuBeginDrawing(); if (screenMode < SM_PA_TOP || (guiDrawn & GUI_ACTIVE)) { + ctrGpuBeginFrame(GFX_BOTTOM); ctrSetViewportSize(320, 240); } else { + ctrGpuBeginFrame(GFX_TOP); ctrSetViewportSize(400, 240); } guiDrawn &= ~GUI_THIS_FRAME; @@ -78,11 +80,10 @@ static void _drawEnd(void) { if (!(guiDrawn & GUI_THIS_FRAME) || screen == GFX_BOTTOM) { void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); - ctrGpuEndFrame(outputFramebuffer, width, height); + ctrGpuEndFrame(screen, outputFramebuffer, width, height); } - gfxSwapBuffersGpu(); - gspWaitForEvent(GSPEVENT_VBlank0, false); + ctrGpuEndDrawing(); } static int _batteryState(void) { @@ -110,9 +111,9 @@ static void _guiPrepare(void) { u16 width = 0, height = 0; void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); - ctrGpuEndFrame(outputFramebuffer, width, height); + ctrGpuEndFrame(screen, outputFramebuffer, width, height); - ctrGpuBeginFrame(); + ctrGpuBeginFrame(GFX_BOTTOM); ctrSetViewportSize(320, 240); } @@ -125,7 +126,7 @@ static void _guiFinish(void) { u16 width = 0, height = 0; void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); - ctrGpuEndFrame(outputFramebuffer, width, height); + ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); } static void _setup(struct GBAGUIRunner* runner) { From 9a538f872a78f7964d808d5a042474d563b4eb71 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 20 Sep 2015 00:48:03 -0700 Subject: [PATCH 062/127] 3DS, PSP2, Wii: Clean up CMake toolchain/list separation --- src/platform/3ds/CMakeLists.txt | 8 +++++ src/platform/3ds/CMakeToolchain.txt | 42 +++++++++--------------- src/platform/psp2/CMakeLists.txt | 5 +++ src/platform/psp2/CMakeToolchain.vitasdk | 33 ++++++++++--------- src/platform/wii/CMakeLists.txt | 5 +++ src/platform/wii/CMakeToolchain.txt | 36 ++++++++++---------- 6 files changed, 69 insertions(+), 60 deletions(-) diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index f26660c4a..15e96a932 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -1,6 +1,14 @@ set(USE_VFS_3DS ON CACNE BOOL "Use 3DS-specific file support") mark_as_advanced(USE_VFS_3DS) +find_program(3DSLINK 3dslink) +find_program(3DSXTOOL 3dsxtool) +find_program(BANNERTOOL bannertool) +find_program(MAKEROM makerom) +find_program(PICASSO picasso) +find_program(RAW2C raw2c) +find_program(STRIP ${cross_prefix}strip) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format" PARENT_SCOPE) set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5) diff --git a/src/platform/3ds/CMakeToolchain.txt b/src/platform/3ds/CMakeToolchain.txt index 9cb3548e0..bab0b4a95 100644 --- a/src/platform/3ds/CMakeToolchain.txt +++ b/src/platform/3ds/CMakeToolchain.txt @@ -9,7 +9,6 @@ if(DEFINED ENV{DEVKITARM}) else() set(DEVKITARM ${DEVKITPRO}/devkitARM) endif() -set(toolchain_bin_dir ${DEVKITARM}/bin) if(DEFINED ENV{CTRULIB}) set(CTRULIB $ENV{CTRULIB}) @@ -17,43 +16,34 @@ else() set(CTRULIB ${DEVKITPRO}/libctru) endif() -if(DEFINED ENV{PICASSO}) - set(PICASSO $ENV{PICASSO}) -else() - set(PICASSO ${toolchain_bin_dir}/picasso) +set(extension) +if (CMAKE_HOST_WIN32) + set(extension .exe) endif() -set(cross_prefix ${toolchain_bin_dir}/arm-none-eabi-) -set(inc_flags -I${CTRULIB}/include) +set(CMAKE_PROGRAM_PATH ${DEVKITARM}/bin) +set(cross_prefix ${CMAKE_PROGRAM_PATH}/arm-none-eabi-) set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard") -set(link_flags "-L${CTRULIB}/lib -lctru -lm -specs=3dsx.specs ${arch_flags}") +set(inc_flags "-I${CTRULIB}/include ${arch_flags} -mword-relocations") +set(link_flags "-L${CTRULIB}/lib -lctru -specs=3dsx.specs ${arch_flags}") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor") set(CMAKE_LIBRARY_ARCHITECTURE arm-none-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "ranlib") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -set(common_flags "${arch_flags} -mword-relocations ${inc_flags}") -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_C_FLAGS_RELEASE -Ofast CACHE INTERNAL "c compiler flags (release)") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") + +set(CMAKE_AR ${cross_prefix}gcc-ar${extension} CACHE INTERNAL "archiver") +set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib${extension} CACHE INTERNAL "archiver") +set(CMAKE_C_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "c compiler") +set(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension} CACHE INTERNAL "cxx compiler") +set(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "assembler") +set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") -set(3DSLINK ${toolchain_bin_dir}/3dslink) -set(3DSXTOOL ${toolchain_bin_dir}/3dsxtool) -set(BANNERTOOL ${toolchain_bin_dir}/bannertool) -set(MAKEROM ${toolchain_bin_dir}/makerom) -set(RAW2C ${toolchain_bin_dir}/raw2c) -set(SMDHTOOL ${toolchain_bin_dir}/smdhtool) -set(STRIP ${cross_prefix}strip) - set(3DS ON) add_definitions(-D_3DS -DARM11) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 4e6136a6a..1fa634f3c 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,3 +1,8 @@ +find_program(FIXUP vita-elf-create) +find_program(OBJCOPY ${cross_prefix}objcopy) +find_file(NIDDB db.json PATHS ${VITASDK}) +find_program(STRIP ${cross_prefix}strip) + file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c) set(OS_SRC ${OS_SRC} PARENT_SCOPE) source_group("PS Vita-specific code" FILES ${OS_SRC}) diff --git a/src/platform/psp2/CMakeToolchain.vitasdk b/src/platform/psp2/CMakeToolchain.vitasdk index 49100b723..e3b4d499e 100644 --- a/src/platform/psp2/CMakeToolchain.vitasdk +++ b/src/platform/psp2/CMakeToolchain.vitasdk @@ -4,24 +4,30 @@ else() message(FATAL_ERROR "Could not find VITASDK in environment") endif() +set(extension) +if (CMAKE_HOST_WIN32) + set(extension .exe) +endif() + set(toolchain_dir ${VITASDK}) -set(toolchain_bin_dir ${toolchain_dir}/bin) -set(cross_prefix ${toolchain_bin_dir}/arm-vita-eabi-) +set(CMAKE_PROGRAM_PATH ${toolchain_dir}/bin) + +set(cross_prefix ${CMAKE_PROGRAM_PATH}/arm-vita-eabi-) set(inc_flags -I${toolchain_dir}/include) set(link_flags "-L${toolchain_dir}/lib -Wl,-q") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR armv7-a CACHE INTERNAL "processor") set(CMAKE_LIBRARY_ARCHITECTURE arm-vita-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "ranlib") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -set(common_flags "${inc_flags}") -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") + +set(CMAKE_AR ${cross_prefix}gcc-ar${extension} CACHE INTERNAL "archiver") +set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib${extension} CACHE INTERNAL "archiver") +set(CMAKE_C_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "c compiler") +set(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension} CACHE INTERNAL "cxx compiler") +set(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "assembler") +set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") @@ -30,11 +36,6 @@ set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE) -set(FIXUP ${toolchain_bin_dir}/vita-elf-create) -set(OBJCOPY ${cross_prefix}objcopy) -set(NIDDB ${VITASDK}/db.json) -set(STRIP ${cross_prefix}strip) - set(PSP2 ON) add_definitions(-DPSP2) set(M_LIBRARY m) diff --git a/src/platform/wii/CMakeLists.txt b/src/platform/wii/CMakeLists.txt index 41eefe361..b4bd6e509 100644 --- a/src/platform/wii/CMakeLists.txt +++ b/src/platform/wii/CMakeLists.txt @@ -1,3 +1,8 @@ +find_program(ELF2DOL elf2dol) +find_program(GXTEXCONV gxtexconv) +find_program(RAW2C raw2c) +find_program(WIILOAD wiiload) + set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5 USE_VFS_FILE) list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) diff --git a/src/platform/wii/CMakeToolchain.txt b/src/platform/wii/CMakeToolchain.txt index 26ecd3fa7..59cf0b249 100644 --- a/src/platform/wii/CMakeToolchain.txt +++ b/src/platform/wii/CMakeToolchain.txt @@ -10,34 +10,34 @@ else() set(DEVKITPPC ${DEVKITPRO}/devkitPPC) endif() -set(toolchain_bin_dir ${DEVKITPPC}/bin) -set(cross_prefix ${toolchain_bin_dir}/powerpc-eabi-) -set(inc_flags -I${DEVKITPRO}/libogc/include) +set(extension) +if (CMAKE_HOST_WIN32) + set(extension .exe) +endif() + +set(CMAKE_PROGRAM_PATH ${DEVKITPPC}/bin) +set(cross_prefix ${CMAKE_PROGRAM_PATH}/powerpc-eabi-) set(arch_flags "-mrvl -mcpu=750 -meabi -mhard-float -g") +set(inc_flags "-I${DEVKITPRO}/libogc/include ${arch_flags}") set(link_flags "-L${DEVKITPRO}/libogc/lib/wii ${arch_flags}") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") -set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor") +set(CMAKE_SYSTEM_PROCESSOR powerpc CACHE INTERNAL "processor") set(CMAKE_LIBRARY_ARCHITECTURE powerpc-none-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "archiver") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -set(common_flags "${arch_flags} ${inc_flags}") -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") + +set(CMAKE_AR ${cross_prefix}gcc-ar${extension} CACHE INTERNAL "archiver") +set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib${extension} CACHE INTERNAL "archiver") +set(CMAKE_C_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "c compiler") +set(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension} CACHE INTERNAL "cxx compiler") +set(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "assembler") +set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") -set(ELF2DOL ${toolchain_bin_dir}/elf2dol) -set(GXTEXCONV ${toolchain_bin_dir}/gxtexconv) -set(RAW2C ${toolchain_bin_dir}/raw2c) -set(WIILOAD ${toolchain_bin_dir}/wiiload) - set(WII ON) add_definitions(-DGEKKO) From e4873318ddbc751439112f14bbec96e71ae8f0d6 Mon Sep 17 00:00:00 2001 From: Juan Ruvalcaba Date: Sun, 20 Sep 2015 16:35:02 -0700 Subject: [PATCH 063/127] -Wii Classic Controller left stick support -Reduce overscan for 4:3 TVs --- src/platform/wii/main.c | 102 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 7ca9e8e52..70395aed4 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -42,6 +42,9 @@ static void _gameUnloaded(struct GBAGUIRunner* runner); static void _drawFrame(struct GBAGUIRunner* runner, bool faded); static uint16_t _pollGameInput(struct GBAGUIRunner* runner); +static s8 WPAD_StickX(u8 chan, u8 right); +static s8 WPAD_StickY(u8 chan, u8 right); + static struct GBAVideoSoftwareRenderer renderer; static struct GBARumble rumble; static struct GBARotationSource rotation; @@ -225,16 +228,18 @@ static uint32_t _pollInput(void) { int keys = 0; int x = PAD_StickX(0); int y = PAD_StickY(0); - if (x < -0x40) { + int w_x = WPAD_StickX(0,0); + int w_y = WPAD_StickY(0,0); + if (x < -0x40 || w_x < -0x40) { keys |= 1 << GUI_INPUT_LEFT; } - if (x > 0x40) { + if (x > 0x40 || w_x > 0x40) { keys |= 1 << GUI_INPUT_RIGHT; } - if (y < -0x40) { + if (y < -0x40 || w_y <- 0x40) { keys |= 1 << GUI_INPUT_DOWN; } - if (y > 0x40) { + if (y > 0x40 || w_y > 0x40) { keys |= 1 << GUI_INPUT_UP; } if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || @@ -292,7 +297,9 @@ void _guiPrepare(void) { void _guiFinish(void) { Mtx44 proj; - guOrtho(proj, -10, VIDEO_VERTICAL_PIXELS + 10, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300); + short top = (CONF_GetAspectRatio() == CONF_ASPECT_16_9) ? 10 : 20; + short bottom = VIDEO_VERTICAL_PIXELS + top; + guOrtho(proj, -top, bottom, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300); GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); } @@ -448,16 +455,18 @@ uint16_t _pollGameInput(struct GBAGUIRunner* runner) { } int x = PAD_StickX(0); int y = PAD_StickY(0); - if (x < -0x40) { + int w_x = WPAD_StickX(0,0); + int w_y = WPAD_StickY(0,0); + if (x < -0x40 || w_x < -0x40) { keys |= 1 << GBA_KEY_LEFT; } - if (x > 0x40) { + if (x > 0x40 || w_x > 0x40) { keys |= 1 << GBA_KEY_RIGHT; } - if (y < -0x40) { + if (y < -0x40 || w_y <- 0x40) { keys |= 1 << GBA_KEY_DOWN; } - if (y > 0x40) { + if (y > 0x40 || w_y > 0x40) { keys |= 1 << GBA_KEY_UP; } return keys; @@ -505,3 +514,78 @@ int32_t _readGyroZ(struct GBARotationSource* source) { UNUSED(source); return gyroZ; } + +static s8 WPAD_StickX(u8 chan, u8 right) { + float mag = 0.0; + float ang = 0.0; + WPADData *data = WPAD_Data(chan); + + switch (data->exp.type) { + case WPAD_EXP_NUNCHUK: + case WPAD_EXP_GUITARHERO3: + if (right == 0) { + mag = data->exp.nunchuk.js.mag; + ang = data->exp.nunchuk.js.ang; + } + break; + case WPAD_EXP_CLASSIC: + if (right == 0) { + mag = data->exp.classic.ljs.mag; + ang = data->exp.classic.ljs.ang; + } else { + mag = data->exp.classic.rjs.mag; + ang = data->exp.classic.rjs.ang; + } + break; + default: + break; + } + + /* calculate X value (angle need to be converted into radian) */ + if (mag > 1.0) { + mag = 1.0; + } else if (mag < -1.0) { + mag = -1.0; + } + double val = mag * sinf(M_PI * ang / 180.0f); + + return (s8)(val * 128.0f); +} + + +static s8 WPAD_StickY(u8 chan, u8 right) { + float mag = 0.0; + float ang = 0.0; + WPADData *data = WPAD_Data(chan); + + switch (data->exp.type) { + case WPAD_EXP_NUNCHUK: + case WPAD_EXP_GUITARHERO3: + if (right == 0) { + mag = data->exp.nunchuk.js.mag; + ang = data->exp.nunchuk.js.ang; + } + break; + case WPAD_EXP_CLASSIC: + if (right == 0) { + mag = data->exp.classic.ljs.mag; + ang = data->exp.classic.ljs.ang; + } else { + mag = data->exp.classic.rjs.mag; + ang = data->exp.classic.rjs.ang; + } + break; + default: + break; + } + + /* calculate X value (angle need to be converted into radian) */ + if (mag > 1.0) { + mag = 1.0; + } else if (mag < -1.0) { + mag = -1.0; + } + double val = mag * cosf(M_PI * ang / 180.0f); + + return (s8)(val * 128.0f); +} From 2cbf40cc139713a2edd660b99f74b8f651dd6c0a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 20 Sep 2015 20:27:53 -0700 Subject: [PATCH 064/127] PSP2: Battery status --- src/platform/psp2/main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index fcc0552cf..b9d5a6f70 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,16 @@ static enum GUICursorState _pollCursor(int* x, int* y) { return GUI_CURSOR_DOWN; } +static int _batteryState(void) { + int charge = scePowerGetBatteryLifePercent(); + int adapter = scePowerIsPowerOnline(); + int state = 0; + if (adapter) { + state |= BATTERY_CHARGING; + } + charge /= 25; + return state | charge; +} int main() { vita2d_init(); @@ -85,7 +96,7 @@ int main() { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, font, "cache0:", _drawStart, _drawEnd, _pollInput, _pollCursor, - 0, + _batteryState, 0, 0, GUI_PARAMS_TRAIL From 3b8639aa0598d00f03bbecf5379ad3a2e3ac0c48 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 20 Sep 2015 21:10:02 -0700 Subject: [PATCH 065/127] GBA Video: Fix OBJWIN mosaic sprites --- src/gba/renderers/software-obj.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 4067051d7..0322394b2 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -236,7 +236,12 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (flags & FLAG_OBJWIN) { SPRITE_NORMAL_LOOP(16, OBJWIN); } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(16, NORMAL); + if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_MOSAIC_LOOP(16, NORMAL_OBJWIN); + } else { + SPRITE_MOSAIC_LOOP(16, NORMAL); + } } else if (objwinSlowPath) { objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; SPRITE_NORMAL_LOOP(16, NORMAL_OBJWIN); @@ -247,7 +252,12 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (flags & FLAG_OBJWIN) { SPRITE_NORMAL_LOOP(256, OBJWIN); } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(256, NORMAL); + if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN); + } else { + SPRITE_MOSAIC_LOOP(256, NORMAL); + } } else if (objwinSlowPath) { SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN); } else { From 7f2ab299f58b49b1b98953f9f30e28604aeb7611 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 20 Sep 2015 21:25:51 -0700 Subject: [PATCH 066/127] GBA Video: Minor sprite optimizations --- src/gba/renderers/software-obj.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 0322394b2..afa3738e0 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -42,6 +42,8 @@ #define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \ unsigned tileData; \ + unsigned widthMask = ~(width - 1); \ + unsigned heightMask = ~(height - 1); \ for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \ if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ continue; \ @@ -51,7 +53,7 @@ int localX = (xAccum >> 8) + (width >> 1); \ int localY = (yAccum >> 8) + (height >> 1); \ \ - if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \ + if (localX & widthMask || localY & heightMask) { \ continue; \ } \ \ @@ -96,7 +98,7 @@ } #define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6); -#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; +#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 8; #define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ @@ -168,6 +170,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } int inY = y - (int) GBAObjAttributesAGetY(sprite->a); + int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80; uint32_t current; if (GBAObjAttributesAIsTransformed(sprite->a)) { From 320033e72427cc07075ce48d3b0bf7e936a2d20d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 20 Sep 2015 22:05:05 -0700 Subject: [PATCH 067/127] GBA Video: Mode 2 optimizations --- src/gba/renderers/software-bg.c | 71 ++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/gba/renderers/software-bg.c b/src/gba/renderers/software-bg.c index 6c6817866..e3f81014d 100644 --- a/src/gba/renderers/software-bg.c +++ b/src/gba/renderers/software-bg.c @@ -7,6 +7,35 @@ #include "gba/gba.h" +#define DRAW_BACKGROUND_MODE_2(BLEND, OBJWIN) \ + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \ + x += background->dx; \ + y += background->dy; \ + \ + if (!mosaicWait) { \ + if (background->overflow) { \ + localX = x & (sizeAdjusted - 1); \ + localY = y & (sizeAdjusted - 1); \ + } else if ((x | y) & ~(sizeAdjusted - 1)) { \ + continue; \ + } else { \ + localX = x; \ + localY = y; \ + } \ + mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \ + pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; \ + \ + mosaicWait = mosaicH; \ + } else { \ + --mosaicWait; \ + } \ + \ + uint32_t current = *pixel; \ + if (pixelData && IS_WRITABLE(current)) { \ + COMPOSITE_256_ ## OBJWIN (BLEND); \ + } \ + } + void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { int sizeAdjusted = 0x8000 << background->size; @@ -15,44 +44,22 @@ void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer uint32_t screenBase = background->screenBase; uint32_t charBase = background->charBase; uint8_t mapData; - uint8_t tileData = 0; + uint8_t pixelData = 0; int outX; uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - x += background->dx; - y += background->dy; - if (!mosaicWait) { - if (background->overflow) { - localX = x & (sizeAdjusted - 1); - localY = y & (sizeAdjusted - 1); - } else if ((x | y) & ~(sizeAdjusted - 1)) { - continue; - } else { - localX = x; - localY = y; - } - mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; - tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; - - mosaicWait = mosaicH; + if (!objwinSlowPath) { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + DRAW_BACKGROUND_MODE_2(NoBlend, NO_OBJWIN); } else { - --mosaicWait; + DRAW_BACKGROUND_MODE_2(Blend, NO_OBJWIN); } - - uint32_t current = *pixel; - if (tileData && IS_WRITABLE(current)) { - if (!objwinSlowPath) { - _compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); - } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { - color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); - } + } else { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + DRAW_BACKGROUND_MODE_2(NoBlend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_2(Blend, OBJWIN); } } } From 2dbaf2edd62c5ca3e07d63f01731640b0f3c26ab Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 20 Sep 2015 22:19:22 -0700 Subject: [PATCH 068/127] Wii: Silence warnings --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9453cb683..24513f992 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,10 +183,14 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*") enable_language(ASM) endif() -if(PSP2) +if(PSP2 OR WII) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format") endif() +if(WII) + add_definitions(-U__STRICT_ANSI__) +endif() + include(CheckFunctionExists) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) From cf5bac466a4afbfc5b5923981db223a47d5b5745 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 00:27:54 -0700 Subject: [PATCH 069/127] All: Attempt travis build for OS X --- .travis.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3e9e6cc79..9f200427f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,22 @@ +os: + - linux + - osx + language: c compiler: - gcc - clang before_install: - - sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y - - sudo apt-add-repository ppa:zoogie/sdl2-snapshots -y - - sudo apt-get update -qq - - sudo apt-get purge cmake -qq - - sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev + - if [ $TRAVIS_OS_NAME -eq "osx"] + - then + - brew install qt5 ffmpeg imagemagick sdl2 libzip libpng + - else + - sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y + - sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y + - sudo apt-get update -qq + - sudo apt-get purge cmake -qq + - sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev + - fi script: mkdir build && cd build && cmake .. && make From 45f52898dc202ebd931720a537af24b33a7e47a6 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 00:46:57 -0700 Subject: [PATCH 070/127] All: Travis deps install script --- .travis-deps.sh | 15 +++++++++++++++ .travis.yml | 11 +---------- 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100755 .travis-deps.sh diff --git a/.travis-deps.sh b/.travis-deps.sh new file mode 100755 index 000000000..77ab3832a --- /dev/null +++ b/.travis-deps.sh @@ -0,0 +1,15 @@ +#!/bin/sh +if [ $TRAVIS_OS_NAME -eq "osx"]; then + brew install qt5 ffmpeg imagemagick sdl2 libzip libpng +else + sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y + sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y + sudo add-apt-repository ppa:immerrr-k/qt5-backport -y + sudo add-apt-repository ppa:spvkgn/ffmpeg+mpv -y + sudo apt-get update -qq + sudo apt-get purge cmake -qq + sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev \ + libpng-devlibsdl2-dev libzip-dev qtbase5-dev \ + libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \ + libavutil-dev libavformat-dev libavresample-dev libswscale-dev +fi diff --git a/.travis.yml b/.travis.yml index 9f200427f..5bc51c00a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,6 @@ compiler: - clang before_install: - - if [ $TRAVIS_OS_NAME -eq "osx"] - - then - - brew install qt5 ffmpeg imagemagick sdl2 libzip libpng - - else - - sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y - - sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y - - sudo apt-get update -qq - - sudo apt-get purge cmake -qq - - sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev - - fi + - ./.travis-deps.sh script: mkdir build && cd build && cmake .. && make From 92561e68665e6477d2ab4b0e94fa9c1a1649b7c1 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 00:50:27 -0700 Subject: [PATCH 071/127] All: Travis attempt 3 --- .travis-deps.sh | 2 +- .travis.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis-deps.sh b/.travis-deps.sh index 77ab3832a..ce3d80b66 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -1,5 +1,5 @@ #!/bin/sh -if [ $TRAVIS_OS_NAME -eq "osx"]; then +if [ $TRAVIS_OS_NAME -eq "osx" ]; then brew install qt5 ffmpeg imagemagick sdl2 libzip libpng else sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y diff --git a/.travis.yml b/.travis.yml index 5bc51c00a..2daa7a14f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ compiler: - gcc - clang +sudo: required + before_install: - ./.travis-deps.sh From 8438797d53c3135319dbc7bb0711fe11f8ede0e0 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 00:53:27 -0700 Subject: [PATCH 072/127] All: Travis attempt 4 --- .travis-deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis-deps.sh b/.travis-deps.sh index ce3d80b66..84e2032af 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -1,5 +1,5 @@ #!/bin/sh -if [ $TRAVIS_OS_NAME -eq "osx" ]; then +if [ $TRAVIS_OS_NAME = "osx" ]; then brew install qt5 ffmpeg imagemagick sdl2 libzip libpng else sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y @@ -9,7 +9,7 @@ else sudo apt-get update -qq sudo apt-get purge cmake -qq sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev \ - libpng-devlibsdl2-dev libzip-dev qtbase5-dev \ + libpng-dev libsdl2-dev libzip-dev qtbase5-dev \ libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \ libavutil-dev libavformat-dev libavresample-dev libswscale-dev fi From a58458b9438354970adb306d79caa53735c90807 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 01:06:11 -0700 Subject: [PATCH 073/127] All: Travis attempt 5 --- .travis-deps.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis-deps.sh b/.travis-deps.sh index 84e2032af..98cf3df85 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -6,10 +6,13 @@ else sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y sudo add-apt-repository ppa:immerrr-k/qt5-backport -y sudo add-apt-repository ppa:spvkgn/ffmpeg+mpv -y + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test sudo apt-get update -qq sudo apt-get purge cmake -qq sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev \ - libpng-dev libsdl2-dev libzip-dev qtbase5-dev \ + g++-4.8 libpng-dev libsdl2-dev libzip-dev qtbase5-dev \ libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \ libavutil-dev libavformat-dev libavresample-dev libswscale-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100 fi From 1325da2958200c0a91efdfa8fff22e20f0418bda Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 00:01:24 -0700 Subject: [PATCH 074/127] PSP2: Update for newer vitasdk --- CMakeLists.txt | 6 +++++- src/platform/psp2/CMakeLists.txt | 5 +++-- src/util/formatting.c | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24513f992..f4ea5722a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,7 +194,11 @@ endif() include(CheckFunctionExists) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) -check_function_exists(localtime_r HAVE_LOCALTIME_R) +if(NOT PSP2) + check_function_exists(localtime_r HAVE_LOCALTIME_R) +else() + set(HAVE_LOCALTIME_R OFF) +endif() if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") check_function_exists(snprintf_l HAVE_SNPRINTF_L) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 1fa634f3c..f5963295f 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,6 +1,7 @@ find_program(FIXUP vita-elf-create) find_program(OBJCOPY ${cross_prefix}objcopy) find_file(NIDDB db.json PATHS ${VITASDK}) +find_file(EXTRADB extra.json PATHS ${VITASDK}) find_program(STRIP ${cross_prefix}strip) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c) @@ -10,7 +11,7 @@ source_group("PS Vita-specific code" FILES ${OS_SRC}) list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lpng -lz -l${M_LIBRARY}) +set(OS_LIB -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -lpng -lz -l${M_LIBRARY}) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) @@ -30,7 +31,7 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o add_custom_target(${BINARY_NAME}.velf ALL ${STRIP} --strip-unneeded -go ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf - COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} + COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} ${EXTRADB} DEPENDS ${BINARY_NAME}.elf) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/util/formatting.c b/src/util/formatting.c index 67a6a3ea8..e841f98ed 100644 --- a/src/util/formatting.c +++ b/src/util/formatting.c @@ -83,7 +83,11 @@ struct tm* localtime_r(const time_t* t, struct tm* date) { return date; #elif defined(PSP2) SceRtcTime sceRtc; + uint64_t tick, localtick; sceRtcSetTime_t(&sceRtc, *t); + sceRtcGetTick(&sceRtc, &tick); + sceRtcConvertUtcToLocalTime(&tick, &localtick); + sceRtcSetTick(&sceRtc, &localtick); date->tm_year = sceRtc.year; date->tm_mon = sceRtc.month; date->tm_mday = sceRtc.day; From 4f24b82036e7219fe80fabe7161811902e4c5e0e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 00:01:40 -0700 Subject: [PATCH 075/127] GUI: Use localtime_r instead of localtime --- src/util/gui/menu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index 3121da829..d23433101 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -223,7 +223,8 @@ void GUIDrawBattery(struct GUIParams* params) { void GUIDrawClock(struct GUIParams* params) { char buffer[32]; time_t t = time(0); - struct tm* tm = localtime(&t); - strftime(buffer, sizeof(buffer), "%H:%M:%S", tm); + struct tm tm; + localtime_r(&t, &tm); + strftime(buffer, sizeof(buffer), "%H:%M:%S", &tm); GUIFontPrint(params->font, params->width / 2, GUIFontHeight(params->font), GUI_TEXT_CENTER, 0xFFFFFFFF, buffer); } From f128f844a67e4cf6ddbe5b90171a4f983c1b5459 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 20:32:15 -0700 Subject: [PATCH 076/127] GBA: Revamp frameskip --- src/gba/context/context.c | 7 +------ src/gba/context/context.h | 1 - src/gba/context/sync.c | 26 +++++++------------------- src/gba/context/sync.h | 4 +--- src/gba/gui/gui-runner.c | 1 + src/gba/supervisor/thread.c | 2 +- src/gba/video.c | 11 ++++++++--- src/gba/video.h | 2 ++ src/platform/qt/DisplayGL.cpp | 2 +- src/platform/qt/GameController.cpp | 13 +++++++------ src/platform/sdl/gl-sdl.c | 2 +- src/platform/sdl/gles2-sdl.c | 2 +- src/platform/sdl/pandora-sdl.c | 2 +- src/platform/sdl/sw-sdl.c | 2 +- src/platform/test/perf-main.c | 2 +- 15 files changed, 34 insertions(+), 45 deletions(-) diff --git a/src/gba/context/context.c b/src/gba/context/context.c index 3aecf2a55..18f70094c 100644 --- a/src/gba/context/context.c +++ b/src/gba/context/context.c @@ -60,7 +60,7 @@ bool GBAContextInit(struct GBAContext* context, const char* port) { GBAConfigLoadDefaults(&context->config, &opts); } - context->gba->sync = &context->sync; + context->gba->sync = 0; return true; } @@ -191,11 +191,6 @@ void GBAContextFrame(struct GBAContext* context, uint16_t keys) { while (frameCounter == context->gba->video.frameCounter) { ARMRunLoop(context->cpu); } - if (context->sync.videoFrameSkip < 0) { - int frameskip = 0; - GBAConfigGetIntValue(&context->config, "frameskip", &frameskip); - context->sync.videoFrameSkip = frameskip; - } } static void _GBAContextLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { diff --git a/src/gba/context/context.h b/src/gba/context/context.h index 1fcde3f24..0269e90af 100644 --- a/src/gba/context/context.h +++ b/src/gba/context/context.h @@ -24,7 +24,6 @@ struct GBAContext { struct GBAConfig config; struct GBAOptions opts; struct GBAInputMap inputMap; - struct GBASync sync; }; bool GBAContextInit(struct GBAContext* context, const char* port); diff --git a/src/gba/context/sync.c b/src/gba/context/sync.c index 335ebda4d..d0b13cf47 100644 --- a/src/gba/context/sync.c +++ b/src/gba/context/sync.c @@ -22,15 +22,12 @@ void GBASyncPostFrame(struct GBASync* sync) { MutexLock(&sync->videoFrameMutex); ++sync->videoFramePending; - --sync->videoFrameSkip; - if (sync->videoFrameSkip < 0) { - do { - ConditionWake(&sync->videoFrameAvailableCond); - if (sync->videoFrameWait) { - ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); - } - } while (sync->videoFrameWait && sync->videoFramePending); - } + do { + ConditionWake(&sync->videoFrameAvailableCond); + if (sync->videoFrameWait) { + ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); + } + } while (sync->videoFrameWait && sync->videoFramePending); MutexUnlock(&sync->videoFrameMutex); } @@ -44,7 +41,7 @@ void GBASyncForceFrame(struct GBASync* sync) { MutexUnlock(&sync->videoFrameMutex); } -bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { +bool GBASyncWaitFrameStart(struct GBASync* sync) { if (!sync) { return true; } @@ -60,7 +57,6 @@ bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { } } sync->videoFramePending = 0; - sync->videoFrameSkip = frameskip; return true; } @@ -72,14 +68,6 @@ void GBASyncWaitFrameEnd(struct GBASync* sync) { MutexUnlock(&sync->videoFrameMutex); } -bool GBASyncDrawingFrame(struct GBASync* sync) { - if (!sync) { - return true; - } - - return sync->videoFrameSkip <= 0; -} - void GBASyncSetVideoSync(struct GBASync* sync, bool wait) { if (!sync) { return; diff --git a/src/gba/context/sync.h b/src/gba/context/sync.h index a1aa65bcc..7440cc502 100644 --- a/src/gba/context/sync.h +++ b/src/gba/context/sync.h @@ -13,7 +13,6 @@ struct GBASync { int videoFramePending; bool videoFrameWait; - int videoFrameSkip; bool videoFrameOn; Mutex videoFrameMutex; Condition videoFrameAvailableCond; @@ -26,9 +25,8 @@ struct GBASync { void GBASyncPostFrame(struct GBASync* sync); void GBASyncForceFrame(struct GBASync* sync); -bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip); +bool GBASyncWaitFrameStart(struct GBASync* sync); void GBASyncWaitFrameEnd(struct GBASync* sync); -bool GBASyncDrawingFrame(struct GBASync* sync); void GBASyncSetVideoSync(struct GBASync* sync, bool wait); void GBASyncProduceAudio(struct GBASync* sync, bool wait); diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 5a604fa60..ffded97cd 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -325,6 +325,7 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { break; case RUNNER_CONFIG: GBAGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra); + GBAConfigGetIntValue(&runner->context.config, "frameskip", &runner->context.gba->video.frameskip); break; case RUNNER_CONTINUE: break; diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 2b0ef4bb8..d5c35e190 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -140,6 +140,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { gba.logLevel = threadContext->logLevel; gba.logHandler = threadContext->logHandler; gba.stream = threadContext->stream; + gba.video.frameskip = threadContext->frameskip; struct GBAThreadStop stop; if (threadContext->stopCallback) { @@ -386,7 +387,6 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->activeKeys = 0; threadContext->state = THREAD_INITIALIZED; threadContext->sync.videoFrameOn = true; - threadContext->sync.videoFrameSkip = 0; threadContext->rewindBuffer = 0; threadContext->rewindScreenBuffer = 0; diff --git a/src/gba/video.c b/src/gba/video.c index d843d2470..6e9804f36 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -59,6 +59,7 @@ static struct GBAVideoRenderer dummyRenderer = { void GBAVideoInit(struct GBAVideo* video) { video->renderer = &dummyRenderer; video->vram = 0; + video->frameskip = 0; } void GBAVideoReset(struct GBAVideo* video) { @@ -80,6 +81,7 @@ void GBAVideoReset(struct GBAVideo* video) { video->nextVcounterIRQ = 0; video->frameCounter = 0; + video->frameskipCounter = 0; if (video->vram) { mappedMemoryFree(video->vram, SIZE_VRAM); @@ -154,7 +156,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) { break; case VIDEO_VERTICAL_PIXELS: video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat); - if (GBASyncDrawingFrame(video->p->sync)) { + if (video->frameskipCounter <= 0) { video->renderer->finishFrame(video->renderer); } video->nextVblankIRQ = video->nextEvent + VIDEO_TOTAL_LENGTH; @@ -163,7 +165,10 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) { GBARaiseIRQ(video->p, IRQ_VBLANK); } GBAFrameEnded(video->p); - GBASyncPostFrame(video->p->sync); + --video->frameskipCounter; + if (video->frameskipCounter < 0) { + video->frameskipCounter = video->frameskip; + } ++video->frameCounter; break; case VIDEO_VERTICAL_TOTAL_PIXELS - 1: @@ -178,7 +183,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) { video->nextHblank = video->nextEvent + VIDEO_HDRAW_LENGTH; video->nextHblankIRQ = video->nextHblank; - if (video->vcount < VIDEO_VERTICAL_PIXELS && GBASyncDrawingFrame(video->p->sync)) { + if (video->vcount < VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) { video->renderer->drawScanline(video->renderer, video->vcount); } diff --git a/src/gba/video.h b/src/gba/video.h index 11957e4de..5250c123e 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -199,6 +199,8 @@ struct GBAVideo { union GBAOAM oam; int32_t frameCounter; + int frameskip; + int frameskipCounter; }; void GBAVideoInit(struct GBAVideo* video); diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index d0a5f4db3..62fb91ede 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -208,7 +208,7 @@ void PainterGL::draw() { if (m_queue.isEmpty()) { return; } - if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip) || !m_queue.isEmpty()) { + if (GBASyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) { dequeue(); m_painter.begin(m_gl->context()->device()); performDraw(); diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 68023a07a..a4db8ba5f 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -124,12 +124,8 @@ GameController::GameController(QObject* parent) m_threadContext.frameCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); - if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { - memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); - } else { - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, nullptr)); - } + memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); + QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { GBAThreadPauseFromThread(context); QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(GBAThread*, context)); @@ -773,7 +769,12 @@ void GameController::setAudioSync(bool set) { } void GameController::setFrameskip(int skip) { + threadInterrupt(); m_threadContext.frameskip = skip; + if (m_gameOpen) { + m_threadContext.gba->video.frameskip = skip; + } + threadContinue(); } void GameController::setVolume(int volume) { diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 12073b12a..e343f1a8b 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -61,7 +61,7 @@ void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* rend #endif } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { v->postFrame(v, renderer->d.outputBuffer); } v->drawFrame(v); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index a3a617471..cf2bf8dc5 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -114,7 +114,7 @@ void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* r GBASDLHandleEvent(context, &renderer->player, &event); } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { v->postFrame(v, renderer->d.outputBuffer); } v->drawFrame(v); diff --git a/src/platform/sdl/pandora-sdl.c b/src/platform/sdl/pandora-sdl.c index 0c95838e5..e9b2e19f4 100644 --- a/src/platform/sdl/pandora-sdl.c +++ b/src/platform/sdl/pandora-sdl.c @@ -71,7 +71,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render GBASDLHandleEvent(context, &renderer->player, &event); } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { int arg = 0; ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg); diff --git a/src/platform/sdl/sw-sdl.c b/src/platform/sdl/sw-sdl.c index b7090f61d..1a3a9b96c 100644 --- a/src/platform/sdl/sw-sdl.c +++ b/src/platform/sdl/sw-sdl.c @@ -93,7 +93,7 @@ void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* rend GBASDLHandleEvent(context, &renderer->player, &event); } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_UnlockTexture(renderer->sdlTex); SDL_RenderCopy(renderer->sdlRenderer, renderer->sdlTex, 0, 0); diff --git a/src/platform/test/perf-main.c b/src/platform/test/perf-main.c index a7009c01a..c99ef6177 100644 --- a/src/platform/test/perf-main.c +++ b/src/platform/test/perf-main.c @@ -167,7 +167,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) *frames = 0; int lastFrames = 0; while (context->state < THREAD_EXITING) { - if (GBASyncWaitFrameStart(&context->sync, 0)) { + if (GBASyncWaitFrameStart(&context->sync)) { ++*frames; ++lastFrames; if (!quiet) { From e05e1a12ce2afc14b2cdd42f24fbd283dda08b69 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 21:38:18 -0700 Subject: [PATCH 077/127] GUI: Tweak battery colors --- src/util/gui/menu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index d23433101..da28fe5bd 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -184,15 +184,15 @@ void GUIDrawBattery(struct GUIParams* params) { int state = params->batteryState(); uint32_t color = 0xFF000000; if (state == (BATTERY_CHARGING | BATTERY_FULL)) { - color |= 0xFF2020; + color |= 0xFFC060; } else if (state & BATTERY_CHARGING) { - color |= 0x20FF20; + color |= 0x60FF60; } else if (state >= BATTERY_HALF) { color |= 0xFFFFFF; } else if (state == BATTERY_LOW) { - color |= 0x20FFFF; + color |= 0x30FFFF; } else { - color |= 0x2020FF; + color |= 0x3030FF; } const char* batteryText; From 71aa72c4d324acffde5514cb09edb189f1f76469 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 22:27:18 -0700 Subject: [PATCH 078/127] 3DS: Revamp multi-screen GUIs (fixes #124) --- src/platform/3ds/main.c | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 70b0236c9..889f9b836 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -52,7 +52,10 @@ static int guiDrawn; enum { GUI_ACTIVE = 1, - GUI_THIS_FRAME = 2 + GUI_THIS_FRAME = 2, + GUI_CLEANUP_1 = 4, + GUI_CLEANUP_2 = 8, + GUI_CLEANUP = GUI_CLEANUP_1 | GUI_CLEANUP_2 }; extern bool allocateRomBuffer(void); @@ -74,13 +77,21 @@ static void _drawStart(void) { static void _drawEnd(void) { int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; u16 width = 0, height = 0; - if (guiDrawn & GUI_ACTIVE) { - screen = GFX_BOTTOM; - } - if (!(guiDrawn & GUI_THIS_FRAME) || screen == GFX_BOTTOM) { - void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); - ctrGpuEndFrame(screen, outputFramebuffer, width, height); + void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); + ctrGpuEndFrame(screen, outputFramebuffer, width, height); + + if (guiDrawn & (GUI_CLEANUP | GUI_THIS_FRAME | GUI_ACTIVE) && screen == GFX_TOP) { + if (!(guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE))) { + ctrGpuBeginFrame(GFX_BOTTOM); + if (guiDrawn & GUI_CLEANUP_1) { + guiDrawn &= ~GUI_CLEANUP_1; + } else if (guiDrawn & GUI_CLEANUP_2) { + guiDrawn &= ~GUI_CLEANUP_2; + } + } + void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); } ctrGpuEndDrawing(); @@ -102,31 +113,19 @@ static int _batteryState(void) { } static void _guiPrepare(void) { - guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME; + guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME | GUI_CLEANUP; int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; if (screen == GFX_BOTTOM) { return; } - u16 width = 0, height = 0; - - void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); - ctrGpuEndFrame(screen, outputFramebuffer, width, height); - + ctrFlushBatch(); ctrGpuBeginFrame(GFX_BOTTOM); ctrSetViewportSize(320, 240); } static void _guiFinish(void) { guiDrawn &= ~GUI_ACTIVE; - int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; - if (screen == GFX_BOTTOM) { - return; - } - - u16 width = 0, height = 0; - void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); - ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); } static void _setup(struct GBAGUIRunner* runner) { From 273a21eb2512b940c6d4795e406100b573b1d997 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 21 Sep 2015 22:54:54 -0700 Subject: [PATCH 079/127] 3DS: Reload screen mode and improve screen cleanup (fixes #127) --- src/platform/3ds/main.c | 66 ++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 889f9b836..5413fbb18 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -49,13 +49,20 @@ static int16_t* audioRight = 0; static size_t audioPos = 0; static struct ctrTexture gbaOutputTexture; static int guiDrawn; +static int screenCleanup; enum { GUI_ACTIVE = 1, GUI_THIS_FRAME = 2, - GUI_CLEANUP_1 = 4, - GUI_CLEANUP_2 = 8, - GUI_CLEANUP = GUI_CLEANUP_1 | GUI_CLEANUP_2 +}; + +enum { + SCREEN_CLEANUP_TOP_1 = 1, + SCREEN_CLEANUP_TOP_2 = 2, + SCREEN_CLEANUP_TOP = SCREEN_CLEANUP_TOP_1 | SCREEN_CLEANUP_TOP_2, + SCREEN_CLEANUP_BOTTOM_1 = 4, + SCREEN_CLEANUP_BOTTOM_2 = 8, + SCREEN_CLEANUP_BOTTOM = SCREEN_CLEANUP_BOTTOM_1 | SCREEN_CLEANUP_BOTTOM_2, }; extern bool allocateRomBuffer(void); @@ -81,17 +88,31 @@ static void _drawEnd(void) { void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); ctrGpuEndFrame(screen, outputFramebuffer, width, height); - if (guiDrawn & (GUI_CLEANUP | GUI_THIS_FRAME | GUI_ACTIVE) && screen == GFX_TOP) { - if (!(guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE))) { + if (screen != GFX_BOTTOM) { + if (guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE)) { + void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); + } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM) { ctrGpuBeginFrame(GFX_BOTTOM); - if (guiDrawn & GUI_CLEANUP_1) { - guiDrawn &= ~GUI_CLEANUP_1; - } else if (guiDrawn & GUI_CLEANUP_2) { - guiDrawn &= ~GUI_CLEANUP_2; + if (screenCleanup & SCREEN_CLEANUP_BOTTOM_1) { + screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_1; + } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM_2) { + screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_2; } + void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); } - void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); - ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); + } + + if ((screenCleanup & SCREEN_CLEANUP_TOP) && screen != GFX_TOP) { + ctrGpuBeginFrame(GFX_TOP); + if (screenCleanup & SCREEN_CLEANUP_TOP_1) { + screenCleanup &= ~SCREEN_CLEANUP_TOP_1; + } else if (screenCleanup & SCREEN_CLEANUP_TOP_2) { + screenCleanup &= ~SCREEN_CLEANUP_TOP_2; + } + void* outputFramebuffer = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_TOP, outputFramebuffer, width, height); } ctrGpuEndDrawing(); @@ -113,7 +134,7 @@ static int _batteryState(void) { } static void _guiPrepare(void) { - guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME | GUI_CLEANUP; + guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME; int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; if (screen == GFX_BOTTOM) { return; @@ -126,6 +147,7 @@ static void _guiPrepare(void) { static void _guiFinish(void) { guiDrawn &= ~GUI_ACTIVE; + screenCleanup |= SCREEN_CLEANUP_BOTTOM; } static void _setup(struct GBAGUIRunner* runner) { @@ -167,6 +189,11 @@ static void _gameLoaded(struct GBAGUIRunner* runner) { csndPlaySound(0x8, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, -1.0, audioLeft, audioLeft, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); csndPlaySound(0x9, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, 1.0, audioRight, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); } + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { + screenMode = mode; + screenCleanup |= SCREEN_CLEANUP_BOTTOM | SCREEN_CLEANUP_TOP; + } } static void _gameUnloaded(struct GBAGUIRunner* runner) { @@ -292,18 +319,9 @@ static uint16_t _pollGameInput(struct GBAGUIRunner* runner) { static void _incrementScreenMode(struct GBAGUIRunner* runner) { UNUSED(runner); - // Clear the buffer - _drawStart(); - _drawEnd(); - _drawStart(); - _drawEnd(); - unsigned mode; - if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { - screenMode = mode; - } else { - screenMode = (screenMode + 1) % SM_MAX; - GBAConfigSetUIntValue(&runner->context.config, "screenMode", screenMode); - } + screenCleanup |= SCREEN_CLEANUP_TOP | SCREEN_CLEANUP_BOTTOM; + screenMode = (screenMode + 1) % SM_MAX; + GBAConfigSetUIntValue(&runner->context.config, "screenMode", screenMode); } static uint32_t _pollInput(void) { From c335fece103d69f1800681c9de5059a9f3caff9d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:23:29 -0700 Subject: [PATCH 080/127] PSP2: Use some alternate paths for the database files --- src/platform/psp2/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index f5963295f..e93a8e3ce 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,7 +1,7 @@ find_program(FIXUP vita-elf-create) find_program(OBJCOPY ${cross_prefix}objcopy) -find_file(NIDDB db.json PATHS ${VITASDK}) -find_file(EXTRADB extra.json PATHS ${VITASDK}) +find_file(NIDDB db.json PATHS ${VITASDK} ${VITASDK}/bin ${VITASDK}/share) +find_file(EXTRADB extra.json PATHS ${VITASDK}${VITASDK}/bin ${VITASDK}/share) find_program(STRIP ${cross_prefix}strip) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c) From d38a65485acab89f16583e77c72029640d10180e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:07:48 -0700 Subject: [PATCH 081/127] All: Isolate function defines --- CMakeLists.txt | 37 ++++++++++++++++++++------------ src/platform/3ds/CMakeLists.txt | 2 +- src/platform/psp2/CMakeLists.txt | 2 +- src/platform/qt/CMakeLists.txt | 2 +- src/platform/sdl/CMakeLists.txt | 4 ++-- src/platform/wii/CMakeLists.txt | 2 +- 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f4ea5722a..69dd4b884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,11 +194,7 @@ endif() include(CheckFunctionExists) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) -if(NOT PSP2) - check_function_exists(localtime_r HAVE_LOCALTIME_R) -else() - set(HAVE_LOCALTIME_R OFF) -endif() +check_function_exists(localtime_r HAVE_LOCALTIME_R) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") check_function_exists(snprintf_l HAVE_SNPRINTF_L) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -217,30 +213,43 @@ else() set(MINIMAL_CORE ON) endif() +check_function_exists(chmod HAVE_CHMOD) +check_function_exists(umask HAVE_UMASK) + +set(FUNCTION_DEFINES) + if(HAVE_STRDUP) - add_definitions(-DHAVE_STRDUP) + list(APPEND FUNCTION_DEFINES HAVE_STRDUP) endif() if(HAVE_STRNDUP) - add_definitions(-DHAVE_STRNDUP) + list(APPEND FUNCTION_DEFINES HAVE_STRNDUP) endif() if(HAVE_LOCALTIME_R) - add_definitions(-DHAVE_LOCALTIME_R) + list(APPEND FUNCTION_DEFINES HAVE_LOCALTIME_R) endif() if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE) - add_definitions(-DHAVE_LOCALE) + list(APPEND FUNCTION_DEFINES HAVE_LOCALE) if (HAVE_STRTOF_L) - add_definitions(-DHAVE_STRTOF_L) + list(APPEND FUNCTION_DEFINES HAVE_STRTOF_L) endif() if (HAVE_SNPRINTF_L) - add_definitions(-DHAVE_SNPRINTF_L) + list(APPEND FUNCTION_DEFINES HAVE_SNPRINTF_L) endif() endif() if(HAVE_SETLOCALE) - add_definitions(-DHAVE_SETLOCALE) + list(APPEND FUNCTION_DEFINES HAVE_SETLOCALE) +endif() + +if(HAVE_CHMOD) + list(APPEND FUNCTION_DEFINES HAVE_CHMOD) +endif() + +if(HAVE_UMASK) + list(APPEND FUNCTION_DEFINES HAVE_UMASK) endif() # Feature dependencies @@ -481,7 +490,7 @@ if(BUILD_SHARED) add_library(${BINARY_NAME} SHARED ${SRC}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) - set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") + set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) add_dependencies(${BINARY_NAME}-static version-info) endif() @@ -490,7 +499,7 @@ else() endif() add_dependencies(${BINARY_NAME} version-info) -set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB}) install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 15e96a932..08b11e325 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -44,7 +44,7 @@ set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c ctru-heap.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) add_custom_command(OUTPUT ${BINARY_NAME}.smdh diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index e93a8e3ce..9b324d36f 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -18,7 +18,7 @@ list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o main.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index dc142a687..95f0cc285 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -155,7 +155,7 @@ if(WIN32) endif() endif() add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES}) -set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index 3368a15ce..c6e5c7bc1 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -57,7 +57,7 @@ if(BUILD_RASPI) set(BUILD_GLES2 ON CACHE BOOL "Using OpenGL|ES 2" FORCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline") add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC}) - set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") + set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGLES2_LIBRARY}) install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi) unset(OPENGLES2_INCLUDE_DIR} CACHE) # Clear NOTFOUND @@ -80,7 +80,7 @@ else() endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) -set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl) diff --git a/src/platform/wii/CMakeLists.txt b/src/platform/wii/CMakeLists.txt index b4bd6e509..b449d23f1 100644 --- a/src/platform/wii/CMakeLists.txt +++ b/src/platform/wii/CMakeLists.txt @@ -19,7 +19,7 @@ list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DI set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c From bb2b82d15e66bf05ba209f6097f26d756be0605b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:08:06 -0700 Subject: [PATCH 082/127] PSP2: Let PSP2 find its packages if in portlibs --- CMakeLists.txt | 8 ++++---- src/platform/psp2/CMakeToolchain.vitasdk | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69dd4b884..abaf40080 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,11 +277,11 @@ if(BUILD_GLES2 AND NOT BUILD_RASPI) endif() set(WANT_ZLIB ${USE_ZLIB}) set(WANT_PNG ${USE_PNG}) +set(WANT_LIBZIP ${USE_LIBZIP}) + find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") -if(NOT PSP2) - find_feature(USE_ZLIB "ZLIB") - find_feature(USE_PNG "PNG") -endif() +find_feature(USE_ZLIB "ZLIB") +find_feature(USE_PNG "PNG") find_feature(USE_LIBZIP "libzip") find_feature(USE_MAGICK "MagickWand") diff --git a/src/platform/psp2/CMakeToolchain.vitasdk b/src/platform/psp2/CMakeToolchain.vitasdk index e3b4d499e..301dbec58 100644 --- a/src/platform/psp2/CMakeToolchain.vitasdk +++ b/src/platform/psp2/CMakeToolchain.vitasdk @@ -34,6 +34,7 @@ set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") +set(CMAKE_PREFIX_PATH ${VITASDK}/arm-vita-eabi) set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE) set(PSP2 ON) From b4c86ea161856a61117e3712d00307f6b5cd15e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:15:13 -0700 Subject: [PATCH 083/127] PSP2: Use sceKernelLibcLocaltime_r instead of faking it --- src/platform/psp2/CMakeLists.txt | 2 +- src/util/formatting.c | 19 +------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 9b324d36f..53f44973d 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -11,7 +11,7 @@ source_group("PS Vita-specific code" FILES ${OS_SRC}) list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -lpng -lz -l${M_LIBRARY}) +set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -lpng -lz -l${M_LIBRARY}) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) diff --git a/src/util/formatting.c b/src/util/formatting.c index e841f98ed..e0f200ed6 100644 --- a/src/util/formatting.c +++ b/src/util/formatting.c @@ -73,29 +73,12 @@ float strtof_u(const char* restrict str, char** restrict end) { } #ifndef HAVE_LOCALTIME_R -#ifdef PSP2 -#include -#endif - struct tm* localtime_r(const time_t* t, struct tm* date) { #ifdef _WIN32 localtime_s(date, t); return date; #elif defined(PSP2) - SceRtcTime sceRtc; - uint64_t tick, localtick; - sceRtcSetTime_t(&sceRtc, *t); - sceRtcGetTick(&sceRtc, &tick); - sceRtcConvertUtcToLocalTime(&tick, &localtick); - sceRtcSetTick(&sceRtc, &localtick); - date->tm_year = sceRtc.year; - date->tm_mon = sceRtc.month; - date->tm_mday = sceRtc.day; - date->tm_hour = sceRtc.hour; - date->tm_min = sceRtc.minutes; - date->tm_sec = sceRtc.seconds; - date->tm_wday = sceRtcGetDayOfWeek(sceRtc.year, sceRtc.month, sceRtc.day); - return date; + return sceKernelLibcLocaltime_r(t, date); #else #warning localtime_r not emulated on this platform return 0; From 5b22a628fa50820e8c20732210614adb0143edc3 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:18:46 -0700 Subject: [PATCH 084/127] VFS: Add VDirOpenArchive --- src/gba/context/context.c | 20 +++++++++++++++++++- src/gba/supervisor/thread.c | 9 +-------- src/util/vfs.c | 15 +++++++++++++++ src/util/vfs.h | 1 + 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/gba/context/context.c b/src/gba/context/context.c index 18f70094c..4818df6c7 100644 --- a/src/gba/context/context.c +++ b/src/gba/context/context.c @@ -73,7 +73,25 @@ void GBAContextDeinit(struct GBAContext* context) { } bool GBAContextLoadROM(struct GBAContext* context, const char* path, bool autoloadSave) { - context->rom = VFileOpen(path, O_RDONLY); + struct VDir* dir = VDirOpenArchive(path); + if (dir) { + struct VDirEntry* de; + while ((de = dir->listNext(dir))) { + struct VFile* vf = dir->openFile(dir, de->name(de), O_RDONLY); + if (!vf) { + continue; + } + if (GBAIsROM(vf)) { + context->rom = vf; + break; + } + vf->close(vf); + } + dir->close(dir); + } else { + context->rom = VFileOpen(path, O_RDONLY); + } + if (!context->rom) { return false; } diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index d5c35e190..b8e8730bd 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -683,16 +683,9 @@ void GBAThreadPauseFromThread(struct GBAThread* threadContext) { void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname) { threadContext->rom = VFileOpen(fname, O_RDONLY); threadContext->gameDir = 0; -#if USE_LIBZIP if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpenZip(fname, 0); + threadContext->gameDir = VDirOpenArchive(fname); } -#endif -#if USE_LZMA - if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpen7z(fname, 0); - } -#endif } static void _loadGameDir(struct GBAThread* threadContext) { diff --git a/src/util/vfs.c b/src/util/vfs.c index a4097f6c7..b50603b25 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -96,6 +96,21 @@ struct VFile* VFileOpen(const char* path, int flags) { #endif } +struct VDir* VDirOpenArchive(const char* path) { + struct VDir* dir = 0; +#if USE_LIBZIP + if (!dir) { + dir = VDirOpenZip(path, 0); + } +#endif +#if USE_LZMA + if (!dir) { + dir = VDirOpen7z(path, 0); + } +#endif + return dir; +} + ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { size_t bytesRead = 0; while (bytesRead < size - 1) { diff --git a/src/util/vfs.h b/src/util/vfs.h index 8f9fbb534..4763aefa5 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -69,6 +69,7 @@ struct VFile* VFileFromMemory(void* mem, size_t size); struct VFile* VFileFromFILE(FILE* file); struct VDir* VDirOpen(const char* path); +struct VDir* VDirOpenArchive(const char* path); #ifdef USE_LIBZIP struct VDir* VDirOpenZip(const char* path, int flags); From 6cf4179a974d6745953d0d354ea8266512d84b83 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:19:55 -0700 Subject: [PATCH 085/127] VFS: Add VDir.openDir --- src/platform/3ds/3ds-vfs.c | 21 ++++++++++++++++++++- src/platform/psp2/sce-vfs.c | 20 +++++++++++++++++++- src/util/vfs.h | 1 + src/util/vfs/vfs-dirent.c | 19 +++++++++++++++++++ src/util/vfs/vfs-lzma.c | 8 ++++++++ src/util/vfs/vfs-zip.c | 8 ++++++++ 6 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/platform/3ds/3ds-vfs.c b/src/platform/3ds/3ds-vfs.c index b7a8075d8..101a5a49c 100644 --- a/src/platform/3ds/3ds-vfs.c +++ b/src/platform/3ds/3ds-vfs.c @@ -44,6 +44,7 @@ static bool _vd3dClose(struct VDir* vd); static void _vd3dRewind(struct VDir* vd); static struct VDirEntry* _vd3dListNext(struct VDir* vd); static struct VFile* _vd3dOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path); static const char* _vd3deName(struct VDirEntry* vde); static enum VFSType _vd3deType(struct VDirEntry* vde); @@ -185,8 +186,9 @@ struct VDir* VDirOpen(const char* path) { vd3d->d.close = _vd3dClose; vd3d->d.rewind = _vd3dRewind; - vd3d->d.listNext = _vd3dListNext; //// Crashes here for no good reason + vd3d->d.listNext = _vd3dListNext; vd3d->d.openFile = _vd3dOpenFile; + vd3d->d.openDir = _vd3dOpenDir; vd3d->vde.d.name = _vd3deName; vd3d->vde.d.type = _vd3deType; @@ -235,6 +237,23 @@ static struct VFile* _vd3dOpenFile(struct VDir* vd, const char* path, int mode) return file; } +static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path) { + struct VDir3DS* vd3d = (struct VDir3DS*) vd; + if (!path) { + return 0; + } + const char* dir = vd3d->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s/%s", dir, path); + + struct VDir* vd2 = VDirOpen(combined); + if (!vd2) { + vd2 = VDirOpenArchive(combined); + } + free(combined); + return vd2; +} + static const char* _vd3deName(struct VDirEntry* vde) { struct VDirEntry3DS* vd3de = (struct VDirEntry3DS*) vde; if (!vd3de->utf8Name[0]) { diff --git a/src/platform/psp2/sce-vfs.c b/src/platform/psp2/sce-vfs.c index a694763cf..e949e0056 100644 --- a/src/platform/psp2/sce-vfs.c +++ b/src/platform/psp2/sce-vfs.c @@ -42,6 +42,7 @@ static bool _vdsceClose(struct VDir* vd); static void _vdsceRewind(struct VDir* vd); static struct VDirEntry* _vdsceListNext(struct VDir* vd); static struct VFile* _vdsceOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdsceOpenDir(struct VDir* vd, const char* path); static const char* _vdesceName(struct VDirEntry* vde); static enum VFSType _vdesceType(struct VDirEntry* vde); @@ -150,6 +151,7 @@ struct VDir* VDirOpen(const char* path) { vd->d.rewind = _vdsceRewind; vd->d.listNext = _vdsceListNext; vd->d.openFile = _vdsceOpenFile; + vd->d.openDir = _vdsceOpenDir; vd->path = strdup(path); vd->de.d.name = _vdesceName; @@ -190,13 +192,29 @@ struct VFile* _vdsceOpenFile(struct VDir* vd, const char* path, int mode) { const char* dir = vdsce->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + strlen(PATH_SEP) + 1)); sprintf(combined, "%s%s%s", dir, PATH_SEP, path); - printf("Opening %s\n", combined); struct VFile* file = VFileOpen(combined, mode); free(combined); return file; } +struct VDir* _vdsceOpenDir(struct VDir* vd, const char* path) { + struct VDirSce* vdsce = (struct VDirSce*) vd; + if (!path) { + return 0; + } + const char* dir = vdsce->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + strlen(PATH_SEP) + 1)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + struct VDir* vd2 = VDirOpen(combined); + if (!vd2) { + vd2 = VDirOpenArchive(combined); + } + free(combined); + return vd2; +} + static const char* _vdesceName(struct VDirEntry* vde) { struct VDirEntrySce* vdesce = (struct VDirEntrySce*) vde; return vdesce->ent.d_name; diff --git a/src/util/vfs.h b/src/util/vfs.h index 4763aefa5..f08322654 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -58,6 +58,7 @@ struct VDir { void (*rewind)(struct VDir* vd); struct VDirEntry* (*listNext)(struct VDir* vd); struct VFile* (*openFile)(struct VDir* vd, const char* name, int mode); + struct VDir* (*openDir)(struct VDir* vd, const char* name); }; struct VFile* VFileOpen(const char* path, int flags); diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index 0d53c3f52..c3ce64321 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -14,6 +14,7 @@ static bool _vdClose(struct VDir* vd); static void _vdRewind(struct VDir* vd); static struct VDirEntry* _vdListNext(struct VDir* vd); static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdOpenDir(struct VDir* vd, const char* path); static const char* _vdeName(struct VDirEntry* vde); static enum VFSType _vdeType(struct VDirEntry* vde); @@ -48,6 +49,7 @@ struct VDir* VDirOpen(const char* path) { vd->d.rewind = _vdRewind; vd->d.listNext = _vdListNext; vd->d.openFile = _vdOpenFile; + vd->d.openDir = _vdOpenDir; vd->path = strdup(path); vd->de = de; @@ -97,6 +99,23 @@ struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) { return file; } +struct VDir* _vdOpenDir(struct VDir* vd, const char* path) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (!path) { + return 0; + } + const char* dir = vdde->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + struct VDir* vd2 = VDirOpen(combined); + if (!vd2) { + vd2 = VDirOpenArchive(combined); + } + free(combined); + return vd2; +} + const char* _vdeName(struct VDirEntry* vde) { struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; if (vdede->ent) { diff --git a/src/util/vfs/vfs-lzma.c b/src/util/vfs/vfs-lzma.c index 97b711deb..6867ec456 100644 --- a/src/util/vfs/vfs-lzma.c +++ b/src/util/vfs/vfs-lzma.c @@ -62,6 +62,7 @@ static bool _vd7zClose(struct VDir* vd); static void _vd7zRewind(struct VDir* vd); static struct VDirEntry* _vd7zListNext(struct VDir* vd); static struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vd7zOpenDir(struct VDir* vd, const char* path); static const char* _vde7zName(struct VDirEntry* vde); static enum VFSType _vde7zType(struct VDirEntry* vde); @@ -111,6 +112,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) { vd->d.rewind = _vd7zRewind; vd->d.listNext = _vd7zListNext; vd->d.openFile = _vd7zOpenFile; + vd->d.openDir = _vd7zOpenDir; return &vd->d; } @@ -301,6 +303,12 @@ struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode) { return &vf->d; } +struct VDir* _vd7zOpenDir(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return 0; +} + bool _vf7zSync(struct VFile* vf, const void* memory, size_t size) { UNUSED(vf); UNUSED(memory); diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 314085302..3c37b125a 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -49,6 +49,7 @@ static bool _vdzClose(struct VDir* vd); static void _vdzRewind(struct VDir* vd); static struct VDirEntry* _vdzListNext(struct VDir* vd); static struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdzOpenDir(struct VDir* vd, const char* path); static const char* _vdezName(struct VDirEntry* vde); static enum VFSType _vdezType(struct VDirEntry* vde); @@ -72,6 +73,7 @@ struct VDir* VDirOpenZip(const char* path, int flags) { vd->d.rewind = _vdzRewind; vd->d.listNext = _vdzListNext; vd->d.openFile = _vdzOpenFile; + vd->d.openDir = _vdzOpenDir; vd->z = z; vd->dirent.d.name = _vdezName; @@ -297,6 +299,12 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) { return &vfz->d; } +struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return 0; +} + bool _vfzSync(struct VFile* vf, const void* memory, size_t size) { UNUSED(vf); UNUSED(memory); From 5578273eb1faaa3614570e29b25d67fa94b5a3ff Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:38:58 -0700 Subject: [PATCH 086/127] GUI: Test archives now, too --- src/util/gui/file-select.c | 55 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index 04a59ee03..61b26cf79 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -110,18 +110,42 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, } params->drawEnd(); } - struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY); - if (!vf) { + if (!filter) { ++item; continue; } - if (filter && !filter(vf)) { - free(GUIMenuItemListGetPointer(currentFiles, item)->title); - GUIMenuItemListShift(currentFiles, item, 1); - } else { - ++item; + struct VDir* vd = dir->openDir(dir, GUIMenuItemListGetPointer(currentFiles, item)->title); + if (vd) { + bool success = false; + struct VDirEntry* de; + while ((de = vd->listNext(vd)) && !success) { + struct VFile* vf2 = vd->openFile(vd, de->name(de), O_RDONLY); + if (!vf2) { + continue; + } + if (filter(vf2)) { + success = true; + } + vf2->close(vf2); + } + vd->close(vd); + if (success) { + ++item; + continue; + } } - vf->close(vf); + struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY); + if (vf) { + if (filter(vf)) { + ++item; + } else { + free(GUIMenuItemListGetPointer(currentFiles, item)->title); + GUIMenuItemListShift(currentFiles, item, 1); + } + vf->close(vf); + continue; + } + ++item; } dir->close(dir); @@ -163,18 +187,9 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool if (!_refreshDirectory(params, outPath, &newFiles, filter)) { _cleanFiles(&newFiles); GUIMenuItemListDeinit(&newFiles); - struct VFile* vf = VFileOpen(outPath, O_RDONLY); - if (!vf) { - continue; - } - if (!filter || filter(vf)) { - vf->close(vf); - _cleanFiles(&menu.items); - GUIMenuItemListDeinit(&menu.items); - return true; - } - vf->close(vf); - break; + _cleanFiles(&menu.items); + GUIMenuItemListDeinit(&menu.items); + return true; } else { _cleanFiles(&menu.items); GUIMenuItemListDeinit(&menu.items); From b3aefd1ee948497536adaf83d9c9d52951a67014 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 21:40:08 -0700 Subject: [PATCH 087/127] GBA Context: Fix failed loads --- src/gba/gui/gui-runner.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index ffded97cd..d169a5c79 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -209,6 +209,7 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) { GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Load failed!"); runner->params.drawEnd(); } + continue; } if (runner->params.guiFinish) { runner->params.guiFinish(); From 5bff3951425e4ddcca46674eb397d6c60f2572cb Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 23:14:59 -0700 Subject: [PATCH 088/127] Perf, Test: Fix build --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index abaf40080..7f62fc2cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -552,6 +552,7 @@ if(BUILD_PERF) add_executable(${BINARY_NAME}-perf ${PERF_SRC}) target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB}) + set_target_properties(${BINARY_NAME}-perf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") install(TARGETS ${BINARY_NAME}-perf DESTINATION bin COMPONENT ${BINARY_NAME}-perf) install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf) endif() @@ -559,6 +560,7 @@ endif() if(BUILD_TEST) add_executable(${BINARY_NAME}-fuzz ${CMAKE_SOURCE_DIR}/src/platform/test/fuzz-main.c) target_link_libraries(${BINARY_NAME}-fuzz ${BINARY_NAME}) + set_target_properties(${BINARY_NAME}-fuzz PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") install(TARGETS ${BINARY_NAME}-fuzz DESTINATION bin COMPONENT ${BINARY_NAME}-test) endif() From 5384eac1760608d42bcd60574349f39bf90993b1 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 22 Sep 2015 23:17:48 -0700 Subject: [PATCH 089/127] All: Travis attempt 6 --- .travis-deps.sh | 1 + .travis.yml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis-deps.sh b/.travis-deps.sh index 98cf3df85..0a249f533 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -1,5 +1,6 @@ #!/bin/sh if [ $TRAVIS_OS_NAME = "osx" ]; then + brew update brew install qt5 ffmpeg imagemagick sdl2 libzip libpng else sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y diff --git a/.travis.yml b/.travis.yml index 2daa7a14f..be94f9f10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ os: - linux - osx +env: + - CMAKE_MODULE_PATH=/usr/local/opt/qt + language: c compiler: - gcc From 8c2daf3be5cdd475217eceb4eecd4e411c850898 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 23 Sep 2015 00:16:13 -0700 Subject: [PATCH 090/127] All: Travis attempt 7 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index be94f9f10..c7c5cb6d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ os: - osx env: - - CMAKE_MODULE_PATH=/usr/local/opt/qt + - CMAKE_MODULE_PATH=/usr/local/opt/qt5 language: c compiler: From 27d002105a837f7789715b63eaad1618fd1990fb Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 23 Sep 2015 00:28:35 -0700 Subject: [PATCH 091/127] All: Travis attempt 8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c7c5cb6d7..35a8096f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ os: - osx env: - - CMAKE_MODULE_PATH=/usr/local/opt/qt5 + - CMAKE_PREFIX_PATH=/usr/local/opt/qt5 language: c compiler: From 4d1b7b4591261bf9ef97774849e7fed8b801387a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 23 Sep 2015 20:28:53 -0700 Subject: [PATCH 092/127] Wii: Don't wait for vsync if we missed the target --- src/platform/wii/main.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 70395aed4..4924c5872 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -22,6 +23,8 @@ #define SAMPLES 1024 +static void _retraceCallback(u32 count); + static void _audioDMA(void); static void _setRumble(struct GBARumble* rumble, int enable); static void _sampleRotation(struct GBARotationSource* source); @@ -55,6 +58,8 @@ static GXTexObj tex; static int32_t tiltX; static int32_t tiltY; static int32_t gyroZ; +static uint32_t retraceCount; +static uint32_t referenceRetraceCount; static void* framebuffer[2]; static int whichFb = 0; @@ -144,6 +149,8 @@ int main() { memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL); GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE); + VIDEO_SetPostRetraceCallback(_retraceCallback); + font = GUIFontCreate(); fatInitDefault(); @@ -199,7 +206,13 @@ static void _audioDMA(void) { } static void _drawStart(void) { - VIDEO_WaitVSync(); + u32 level = 0; + _CPU_ISR_Disable(level); + if (referenceRetraceCount >= retraceCount) { + VIDEO_WaitVSync(); + } + _CPU_ISR_Restore(level); + GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GX_SetColorUpdate(GX_TRUE); @@ -214,6 +227,11 @@ static void _drawEnd(void) { GX_CopyDisp(framebuffer[whichFb], GX_TRUE); VIDEO_SetNextFramebuffer(framebuffer[whichFb]); VIDEO_Flush(); + + u32 level = 0; + _CPU_ISR_Disable(level); + ++referenceRetraceCount; + _CPU_ISR_Restore(level); } static uint32_t _pollInput(void) { @@ -337,6 +355,10 @@ void _gameLoaded(struct GBAGUIRunner* runner) { sleep(1); } } + u32 level = 0; + _CPU_ISR_Disable(level); + referenceRetraceCount = retraceCount; + _CPU_ISR_Restore(level); } void _drawFrame(struct GBAGUIRunner* runner, bool faded) { @@ -463,7 +485,7 @@ uint16_t _pollGameInput(struct GBAGUIRunner* runner) { if (x > 0x40 || w_x > 0x40) { keys |= 1 << GBA_KEY_RIGHT; } - if (y < -0x40 || w_y <- 0x40) { + if (y < -0x40 || w_y < -0x40) { keys |= 1 << GBA_KEY_DOWN; } if (y > 0x40 || w_y > 0x40) { @@ -589,3 +611,10 @@ static s8 WPAD_StickY(u8 chan, u8 right) { return (s8)(val * 128.0f); } + +void _retraceCallback(u32 count) { + u32 level = 0; + _CPU_ISR_Disable(level); + retraceCount = count; + _CPU_ISR_Restore(level); +} From 5ef980116a65c9eb31343859f87d6e3b74ffd52f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 23 Sep 2015 20:36:57 -0700 Subject: [PATCH 093/127] GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops --- CHANGES | 1 + src/gba/renderers/software-obj.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 559dac8ea..eb5c5296b 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Bugfixes: - Util: Fix PowerPC PNG read/write pixel order - GBA Video: Fix edge case with sprite blend modes and semitransparency - GBA Video: Fix objwin and blending interaction on sprites + - GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index afa3738e0..36185aa8d 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -147,7 +147,10 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) { return 0; } - int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); + int variant = renderer->target1Obj && + GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && + (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN) && + GBAObjAttributesAGetMode(sprite->a) != OBJ_MODE_SEMITRANSPARENT; if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { int target2 = renderer->target2Bd << 4; target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); From 2083aa921be4658815534aa2b89bab9e041df1ff Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 23 Sep 2015 21:21:37 -0700 Subject: [PATCH 094/127] Qt: Exclude keypad events from being modifiers --- src/platform/qt/ShortcutController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp index 7d1189dcd..dcbe004fc 100644 --- a/src/platform/qt/ShortcutController.cpp +++ b/src/platform/qt/ShortcutController.cpp @@ -311,9 +311,9 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { } int key = keyEvent->key(); if (!isModifierKey(key)) { - key |= keyEvent->modifiers(); + key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); } else { - key = toModifierKey(key | keyEvent->modifiers()); + key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); } auto item = m_heldKeys.find(key); if (item != m_heldKeys.end()) { From d0c2d4e46ba6776c3572159329d50213baff2725 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 24 Sep 2015 01:42:21 -0700 Subject: [PATCH 095/127] Wii: Tiny refactor --- src/platform/wii/main.c | 58 +++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 4924c5872..3fbecd0d3 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -51,7 +51,7 @@ static s8 WPAD_StickY(u8 chan, u8 right); static struct GBAVideoSoftwareRenderer renderer; static struct GBARumble rumble; static struct GBARotationSource rotation; -static GXRModeObj* mode; +static GXRModeObj* vmode; static Mtx model, view, modelview; static uint16_t* texmem; static GXTexObj tex; @@ -61,7 +61,7 @@ static int32_t gyroZ; static uint32_t retraceCount; static uint32_t referenceRetraceCount; -static void* framebuffer[2]; +static void* framebuffer[2] = { 0, 0 }; static int whichFb = 0; static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32))); @@ -70,6 +70,33 @@ static volatile int currentAudioBuffer = 0; static struct GUIFont* font; +static void reconfigureScreen(GXRModeObj* vmode) { + free(framebuffer[0]); + free(framebuffer[1]); + + framebuffer[0] = SYS_AllocateFramebuffer(vmode); + framebuffer[1] = SYS_AllocateFramebuffer(vmode); + + VIDEO_SetBlack(true); + VIDEO_Configure(vmode); + VIDEO_SetNextFramebuffer(framebuffer[whichFb]); + VIDEO_SetBlack(false); + VIDEO_Flush(); + VIDEO_WaitVSync(); + if (vmode->viTVMode & VI_NON_INTERLACE) { + VIDEO_WaitVSync(); + } + GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1); + + f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight); + u32 xfbHeight = GX_SetDispCopyYScale(yscale); + GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth); + GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight); + GX_SetDispCopyDst(vmode->fbWidth, xfbHeight); + GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter); + GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE)); +}; + int main() { VIDEO_Init(); PAD_Init(); @@ -85,33 +112,15 @@ int main() { #error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5 #endif - mode = VIDEO_GetPreferredMode(0); - framebuffer[0] = SYS_AllocateFramebuffer(mode); - framebuffer[1] = SYS_AllocateFramebuffer(mode); - - VIDEO_Configure(mode); - VIDEO_SetNextFramebuffer(framebuffer[whichFb]); - VIDEO_SetBlack(FALSE); - VIDEO_Flush(); - VIDEO_WaitVSync(); - if (mode->viTVMode & VI_NON_INTERLACE) { - VIDEO_WaitVSync(); - } + vmode = VIDEO_GetPreferredMode(0); GXColor bg = { 0, 0, 0, 0xFF }; void* fifo = memalign(32, 0x40000); memset(fifo, 0, 0x40000); GX_Init(fifo, 0x40000); GX_SetCopyClear(bg, 0x00FFFFFF); - GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1); - f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight); - u32 xfbHeight = GX_SetDispCopyYScale(yscale); - GX_SetScissor(0, 0, mode->viWidth, mode->viWidth); - GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight); - GX_SetDispCopyDst(mode->fbWidth, xfbHeight); - GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter); - GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE)); + reconfigureScreen(vmode); GX_SetCullMode(GX_CULL_NONE); GX_CopyDisp(framebuffer[whichFb], GX_TRUE); @@ -192,6 +201,9 @@ int main() { free(renderer.outputBuffer); GUIFontDestroy(font); + free(framebuffer[0]); + free(framebuffer[1]); + return 0; } @@ -216,7 +228,7 @@ static void _drawStart(void) { GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GX_SetColorUpdate(GX_TRUE); - GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1); + GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1); } static void _drawEnd(void) { From e9c97bed0069f52007f429182cb64b16adc18cfd Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 24 Sep 2015 20:24:22 -0700 Subject: [PATCH 096/127] GBA Video: Fix OBJ semitransparency interaction properly... --- src/gba/renderers/software-obj.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 36185aa8d..4f6a7e63e 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -149,15 +149,14 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && - (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN) && - GBAObjAttributesAGetMode(sprite->a) != OBJ_MODE_SEMITRANSPARENT; + (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { int target2 = renderer->target2Bd << 4; target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); - if ((1 << GBAObjAttributesCGetPriority(sprite->c)) < target2) { + if ((1 << GBAObjAttributesCGetPriority(sprite->c)) <= target2) { variant = 0; } } From bbb5c5ff94e104b34fdf4f45c07a87d7a01c1996 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 26 Sep 2015 01:03:38 -0700 Subject: [PATCH 097/127] GBA Video: Fix another blending regression --- src/gba/renderers/software-private.h | 8 ++++---- src/gba/renderers/video-software.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index 7648f8dd2..6fd4fc134 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -42,7 +42,7 @@ static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* render if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } } else { color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); @@ -55,7 +55,7 @@ static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* rend if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } } else { color = color & ~FLAG_TARGET_2; @@ -69,7 +69,7 @@ static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* rend if (color < current) { color |= (current & FLAG_OBJWIN); } else { - color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } *pixel = color; } @@ -78,7 +78,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re uint32_t current) { UNUSED(renderer); if (color >= current) { - color = (current & 0x00FFFFFF) | ((current >> 1) & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } *pixel = color; } diff --git a/src/gba/renderers/video-software.h b/src/gba/renderers/video-software.h index a213d32a9..906d63444 100644 --- a/src/gba/renderers/video-software.h +++ b/src/gba/renderers/video-software.h @@ -71,10 +71,10 @@ enum { #define FLAG_INDEX 0x30000000 #define FLAG_IS_BACKGROUND 0x08000000 #define FLAG_UNWRITTEN 0xFC000000 +#define FLAG_REBLEND 0x04000000 #define FLAG_TARGET_1 0x02000000 #define FLAG_TARGET_2 0x01000000 #define FLAG_OBJWIN 0x01000000 -#define FLAG_REBLEND 0x01000000 #define FLAG_ORDER_MASK 0xF8000000 #define IS_WRITABLE(PIXEL) ((PIXEL) & 0xFE000000) From 9b667209554def8e1afc5dfe1246cd984090e11f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 26 Sep 2015 14:31:02 -0700 Subject: [PATCH 098/127] Libretro: Fix build --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f62fc2cb..ece7899db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -531,7 +531,7 @@ endif() if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) - set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING") + set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) endif() From d2804505cbcb66106ba79da9d132f4a415e2534e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 1 Oct 2015 22:16:22 -0700 Subject: [PATCH 099/127] GBA: Add DMA timer check to savestate loading --- CHANGES | 1 + src/gba/serialize.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index eb5c5296b..fcc197230 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Misc: - Qt: Disable menu items in multiplayer that don't make sense to have enabled - Qt: Dropping multiplayer windows works more cleanly now - GBA BIOS: Implement RegisterRamReset for SIO registers + - GBA: Additional savestate sanity checks 0.3.0: (2015-08-16) Features: diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 682983231..18ab22d41 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -107,6 +107,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: timer nextEvent is negative"); error = true; } + if (state->dma[0].nextEvent < 0 || state->dma[1].nextEvent < 0 || state->dma[2].nextEvent < 0 || state->dma[3].nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: DMA nextEvent is negative"); + error = true; + } if (state->audio.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative"); error = true; From 9034de481702900f8032adca86c2a877b0bac881 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 1 Oct 2015 22:16:35 -0700 Subject: [PATCH 100/127] Test: Fix build --- src/platform/test/fuzz-main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index 0e7a3eb70..9d7c545d9 100644 --- a/src/platform/test/fuzz-main.c +++ b/src/platform/test/fuzz-main.c @@ -84,7 +84,7 @@ int main(int argc, char** argv) { GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = malloc(256 * 256 * 4); renderer.outputBufferStride = 256; - context->renderer = &renderer.d; + context.renderer = &renderer.d; } GBAContextStart(&context); From 65c290131abe68cf204cf12f7866471753b34935 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 3 Oct 2015 21:06:19 -0700 Subject: [PATCH 101/127] GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2 --- CHANGES | 1 + src/gba/context/overrides.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fcc197230..51bd624c2 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,7 @@ Bugfixes: - GBA Video: Fix edge case with sprite blend modes and semitransparency - GBA Video: Fix objwin and blending interaction on sprites - GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops + - GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2 Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/context/overrides.c b/src/gba/context/overrides.c index ef9632e27..2f37e6c81 100644 --- a/src/gba/context/overrides.c +++ b/src/gba/context/overrides.c @@ -128,7 +128,7 @@ static const struct GBACartridgeOverride _overrides[] = { // Super Mario Advance 2 { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, - { "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, + { "AA2P", SAVEDATA_AUTODETECT, HW_NONE, 0x800052E }, // Super Mario Advance 3 { "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, From 62304e3aa2a5cabbfab5f5288ab461c59876cf1d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 3 Oct 2015 21:27:03 -0700 Subject: [PATCH 102/127] PSP2: Only wait on vblank if we hit the frame target --- src/platform/psp2/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index b9d5a6f70..485a0fac7 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -13,6 +13,7 @@ #include "util/gui/menu.h" #include +#include #include #include #include @@ -24,12 +25,17 @@ PSP2_MODULE_INFO(0, 0, "mGBA"); static void _drawStart(void) { + vita2d_set_vblank_wait(false); vita2d_start_drawing(); vita2d_clear_screen(); } static void _drawEnd(void) { + static int oldVCount = 0; + int vcount = oldVCount; vita2d_end_drawing(); + oldVCount = sceDisplayGetVcount(); + vita2d_set_vblank_wait(oldVCount == vcount); vita2d_swap_buffers(); } From ed0802b46f2d7134f6ac7781521887e573f7dc89 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 3 Oct 2015 21:33:31 -0700 Subject: [PATCH 103/127] GBA: More savestate sanitization --- src/gba/serialize.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 18ab22d41..57a861935 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -99,6 +99,17 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative"); error = true; } + if (state->video.nextEvent < state->cpu.cycles) { + uint16_t dispstat = state->io[REG_DISPSTAT >> 1]; + if (GBARegisterDISPSTATIsInHblank(dispstat) && state->video.eventDiff + state->cpu.cycles > state->video.nextHblank) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank will be negative"); + error = true; + } + if (!GBARegisterDISPSTATIsInHblank(dispstat) && state->video.eventDiff + state->cpu.cycles > state->video.lastHblank + VIDEO_HBLANK_LENGTH) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank will be negative"); + error = true; + } + } if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); error = true; From 2ea44603a810320af7a90d0dd4bb96116aca239d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 4 Oct 2015 21:19:17 -0700 Subject: [PATCH 104/127] GBA Memory: Fix bad BIOS Load16 on big endian --- CHANGES | 1 + src/gba/memory.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 51bd624c2..661f114d8 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Bugfixes: - GBA Video: Fix objwin and blending interaction on sprites - GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops - GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2 + - GBA Memory: Fix bad BIOS Load16 on big endian Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/memory.c b/src/gba/memory.c index 3a6e11408..db048188c 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -442,7 +442,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { LOAD_16(value, address, memory->bios); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load16: 0x%08X", address); - LOAD_16(value, address & 2, &memory->biosPrefetch); + value = (memory->biosPrefetch >> ((address & 2) * 8)) & 0xFFFF; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); From 268e9138c6a4e0fb3101c735cd4430f013e38134 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 4 Oct 2015 21:21:07 -0700 Subject: [PATCH 105/127] GBA Video: Deserialization fixes on big endian --- src/gba/video.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/gba/video.c b/src/gba/video.c index 6e9804f36..054476b51 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -91,10 +91,10 @@ void GBAVideoReset(struct GBAVideo* video) { int i; for (i = 0; i < 128; ++i) { - video->oam.raw[i * 4] = 0x0200; - video->oam.raw[i * 4 + 1] = 0x0000; - video->oam.raw[i * 4 + 2] = 0x0000; - video->oam.raw[i * 4 + 3] = 0x0000; + STORE_16(0x0200, i * 8 + 0, video->oam.raw); + STORE_16(0x0000, i * 8 + 2, video->oam.raw); + STORE_16(0x0000, i * 8 + 4, video->oam.raw); + STORE_16(0x0000, i * 8 + 6, video->oam.raw); } video->renderer->deinit(video->renderer); @@ -288,12 +288,15 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) { memcpy(video->renderer->vram, state->vram, SIZE_VRAM); + uint16_t value; int i; for (i = 0; i < SIZE_OAM; i += 2) { - GBAStore16(video->p->cpu, BASE_OAM | i, state->oam[i >> 1], 0); + LOAD_16(value, i, state->oam); + GBAStore16(video->p->cpu, BASE_OAM | i, value, 0); } for (i = 0; i < SIZE_PALETTE_RAM; i += 2) { - GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, state->pram[i >> 1], 0); + LOAD_16(value, i, state->pram); + GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, value, 0); } video->nextEvent = state->video.nextEvent; video->eventDiff = state->video.eventDiff; From 3a15553c097c406d9c07fb110e6df95abc42f916 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 5 Oct 2015 19:20:13 -0700 Subject: [PATCH 106/127] All: Reset next event to cycles instead of zero to interrupt --- CHANGES | 1 + src/arm/isa-inlines.h | 2 +- src/debugger/debugger.c | 2 +- src/gba/gba.c | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 661f114d8..5e7e79100 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ Misc: - Qt: Dropping multiplayer windows works more cleanly now - GBA BIOS: Implement RegisterRamReset for SIO registers - GBA: Additional savestate sanity checks + - All: Reset next event to cycles instead of zero to interrupt 0.3.0: (2015-08-16) Features: diff --git a/src/arm/isa-inlines.h b/src/arm/isa-inlines.h index cdf6f9cba..42a581b4a 100644 --- a/src/arm/isa-inlines.h +++ b/src/arm/isa-inlines.h @@ -86,7 +86,7 @@ static inline void _ARMSetMode(struct ARMCore* cpu, enum ExecutionMode execution case MODE_THUMB: cpu->cpsr.t = 1; } - cpu->nextEvent = 0; + cpu->nextEvent = cpu->cycles; } static inline void _ARMReadCPSR(struct ARMCore* cpu) { diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index 62d47680b..14e7da62a 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -103,7 +103,7 @@ void ARMDebuggerRun(struct ARMDebugger* debugger) { void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) { debugger->state = DEBUGGER_PAUSED; struct ARMCore* cpu = debugger->cpu; - cpu->nextEvent = 0; + cpu->nextEvent = cpu->cycles; if (reason == DEBUGGER_ENTER_BREAKPOINT) { struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu)); debugger->currentBreakpoint = breakpoint; diff --git a/src/gba/gba.c b/src/gba/gba.c index ada9ffee3..0b39e3f1a 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -574,12 +574,12 @@ void GBATestIRQ(struct ARMCore* cpu) { struct GBA* gba = (struct GBA*) cpu->master; if (gba->memory.io[REG_IME >> 1] && gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) { gba->springIRQ = 1; - gba->cpu->nextEvent = 0; + gba->cpu->nextEvent = gba->cpu->cycles; } } void GBAHalt(struct GBA* gba) { - gba->cpu->nextEvent = 0; + gba->cpu->nextEvent = gba->cpu->cycles; gba->cpu->halted = 1; } @@ -587,7 +587,7 @@ void GBAStop(struct GBA* gba) { if (!gba->stopCallback) { return; } - gba->cpu->nextEvent = 0; + gba->cpu->nextEvent = gba->cpu->cycles; gba->stopCallback->stop(gba->stopCallback); } From d490f9a013a1a76856dfc8f83e0b10637eab86f0 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 5 Oct 2015 19:21:21 -0700 Subject: [PATCH 107/127] GBA Video: Remove lastHblank, as it is implied --- CHANGES | 1 + src/gba/video.c | 8 ++------ src/gba/video.h | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 5e7e79100..bca461710 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,7 @@ Misc: - GBA BIOS: Implement RegisterRamReset for SIO registers - GBA: Additional savestate sanity checks - All: Reset next event to cycles instead of zero to interrupt + - GBA Video: Remove lastHblank, as it is implied 0.3.0: (2015-08-16) Features: diff --git a/src/gba/video.c b/src/gba/video.c index 054476b51..2450ebb41 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -71,7 +71,6 @@ void GBAVideoReset(struct GBAVideo* video) { } video->p->memory.io[REG_VCOUNT >> 1] = video->vcount; - video->lastHblank = 0; video->nextHblank = VIDEO_HDRAW_LENGTH; video->nextEvent = video->nextHblank; video->eventDiff = 0; @@ -120,7 +119,6 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) { video->eventDiff += cycles; if (video->nextEvent <= 0) { int32_t lastEvent = video->nextEvent; - video->lastHblank -= video->eventDiff; video->nextHblank -= video->eventDiff; video->nextHblankIRQ -= video->eventDiff; video->nextVcounterIRQ -= video->eventDiff; @@ -178,8 +176,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) { } else { // Begin Hblank dispstat = GBARegisterDISPSTATFillInHblank(dispstat); - video->lastHblank = video->nextHblank; - video->nextEvent = video->lastHblank + VIDEO_HBLANK_LENGTH; + video->nextEvent = video->nextHblank + VIDEO_HBLANK_LENGTH; video->nextHblank = video->nextEvent + VIDEO_HDRAW_LENGTH; video->nextHblankIRQ = video->nextHblank; @@ -278,7 +275,7 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* memcpy(state->pram, video->palette, SIZE_PALETTE_RAM); state->video.nextEvent = video->nextEvent; state->video.eventDiff = video->eventDiff; - state->video.lastHblank = video->lastHblank; + state->video.lastHblank = video->nextHblank - VIDEO_HBLANK_LENGTH; state->video.nextHblank = video->nextHblank; state->video.nextHblankIRQ = video->nextHblankIRQ; state->video.nextVblankIRQ = video->nextVblankIRQ; @@ -300,7 +297,6 @@ void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState } video->nextEvent = state->video.nextEvent; video->eventDiff = state->video.eventDiff; - video->lastHblank = state->video.lastHblank; video->nextHblank = state->video.nextHblank; video->nextHblankIRQ = state->video.nextHblankIRQ; video->nextVblankIRQ = state->video.nextVblankIRQ; diff --git a/src/gba/video.h b/src/gba/video.h index 5250c123e..0be6b177e 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -185,7 +185,6 @@ struct GBAVideo { // VCOUNT int vcount; - int32_t lastHblank; int32_t nextHblank; int32_t nextEvent; int32_t eventDiff; From a90eeea92438672cec7701b46d6756a182f246bf Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 5 Oct 2015 19:22:44 -0700 Subject: [PATCH 108/127] Test: Rearrange fuzz-main for AFL additions --- src/platform/test/fuzz-main.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index 9d7c545d9..4fa86d575 100644 --- a/src/platform/test/fuzz-main.c +++ b/src/platform/test/fuzz-main.c @@ -68,18 +68,9 @@ int main(int argc, char** argv) { return !parsed; } - struct VFile* rom = VFileOpen(args.fname, O_RDONLY); - - context.gba->hardCrash = false; - GBAContextLoadROMFromVFile(&context, rom, 0); - struct GBAVideoSoftwareRenderer renderer; renderer.outputBuffer = 0; - struct VFile* savestate = 0; - struct VFile* savestateOverlay = 0; - size_t overlayOffset; - if (!fuzzOpts.noVideo) { GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = malloc(256 * 256 * 4); @@ -87,6 +78,19 @@ int main(int argc, char** argv) { context.renderer = &renderer.d; } +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + + struct VFile* rom = VFileOpen(args.fname, O_RDONLY); + + context.gba->hardCrash = false; + GBAContextLoadROMFromVFile(&context, rom, 0); + + struct VFile* savestate = 0; + struct VFile* savestateOverlay = 0; + size_t overlayOffset; + GBAContextStart(&context); if (fuzzOpts.savestate) { @@ -121,18 +125,21 @@ int main(int argc, char** argv) { _GBAFuzzRunloop(&context, fuzzOpts.frames); + GBAContextStop(&context); + GBAContextUnloadROM(&context); + if (savestate) { savestate->close(savestate); } if (savestateOverlay) { savestateOverlay->close(savestateOverlay); } - GBAContextStop(&context); - GBAContextDeinit(&context); + freeArguments(&args); if (renderer.outputBuffer) { free(renderer.outputBuffer); } + GBAContextDeinit(&context); return 0; } From 32ca05312b2803a8e778426f805ba541cb777616 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 5 Oct 2015 19:24:06 -0700 Subject: [PATCH 109/127] GBA: Check for cycle count being too high --- CHANGES | 1 + src/gba/serialize.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index bca461710..6e7e13b02 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,7 @@ Misc: - GBA: Additional savestate sanity checks - All: Reset next event to cycles instead of zero to interrupt - GBA Video: Remove lastHblank, as it is implied + - GBA: Check for cycle count being too high 0.3.0: (2015-08-16) Features: diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 57a861935..5a0d657bb 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -87,6 +87,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); error = true; } + if (state->cpu.cycles >= (int32_t) GBA_ARM7TDMI_FREQUENCY) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are too high"); + error = true; + } if (state->cpu.nextEvent < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: Next event is negative"); error = true; From 19453933df4dc383c1569767080d152031366806 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 5 Oct 2015 19:34:19 -0700 Subject: [PATCH 110/127] GBA: Remove no-longer necessary sanitization checks --- CHANGES | 1 - src/gba/serialize.c | 57 --------------------------------------------- 2 files changed, 58 deletions(-) diff --git a/CHANGES b/CHANGES index 6e7e13b02..374ab5165 100644 --- a/CHANGES +++ b/CHANGES @@ -34,7 +34,6 @@ Misc: - Qt: Disable menu items in multiplayer that don't make sense to have enabled - Qt: Dropping multiplayer windows works more cleanly now - GBA BIOS: Implement RegisterRamReset for SIO registers - - GBA: Additional savestate sanity checks - All: Reset next event to cycles instead of zero to interrupt - GBA Video: Remove lastHblank, as it is implied - GBA: Check for cycle count being too high diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 5a0d657bb..c7fa7b016 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -91,67 +91,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are too high"); error = true; } - if (state->cpu.nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: Next event is negative"); - error = true; - } if (state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative"); error = true; } - if (state->video.nextHblank - state->video.eventDiff < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative"); - error = true; - } - if (state->video.nextEvent < state->cpu.cycles) { - uint16_t dispstat = state->io[REG_DISPSTAT >> 1]; - if (GBARegisterDISPSTATIsInHblank(dispstat) && state->video.eventDiff + state->cpu.cycles > state->video.nextHblank) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank will be negative"); - error = true; - } - if (!GBARegisterDISPSTATIsInHblank(dispstat) && state->video.eventDiff + state->cpu.cycles > state->video.lastHblank + VIDEO_HBLANK_LENGTH) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank will be negative"); - error = true; - } - } - if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); - error = true; - } - if (state->timers[0].nextEvent < 0 || state->timers[1].nextEvent < 0 || state->timers[2].nextEvent < 0 || state->timers[3].nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: timer nextEvent is negative"); - error = true; - } - if (state->dma[0].nextEvent < 0 || state->dma[1].nextEvent < 0 || state->dma[2].nextEvent < 0 || state->dma[3].nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: DMA nextEvent is negative"); - error = true; - } - if (state->audio.eventDiff < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative"); - error = true; - } - if (!state->audio.ch1Dead && (state->audio.ch1.envelopeNextStep < 0 || - state->audio.ch1.waveNextStep < 0 || - state->audio.ch1.sweepNextStep < 0 || - state->audio.ch1.nextEvent < 0)) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative"); - error = true; - } - if (!state->audio.ch2Dead && (state->audio.ch2.envelopeNextStep < 0 || - state->audio.ch2.waveNextStep < 0 || - state->audio.ch2.nextEvent < 0)) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative"); - error = true; - } - if (state->audio.ch3.nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative"); - error = true; - } - if (!state->audio.ch4Dead && (state->audio.ch4.envelopeNextStep < 0 || - state->audio.ch4.nextEvent < 0)) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative"); - error = true; - } int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET); if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM"); From 4db61f400be9e49fb754c0473768143010cd2c20 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 6 Oct 2015 21:25:45 -0700 Subject: [PATCH 111/127] GBA Memory: Fix bad Load8 on big endian --- CHANGES | 1 + src/gba/memory.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 374ab5165..f18dffa86 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugfixes: - GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops - GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2 - GBA Memory: Fix bad BIOS Load16 on big endian + - GBA Memory: Fix bad Load8 on big endian Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/gba/memory.c b/src/gba/memory.c index db048188c..af0d6a634 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -535,12 +535,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = ((uint8_t*) memory->bios)[address]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address); - value = ((uint8_t*) &memory->biosPrefetch)[address & 3]; + value = (memory->biosPrefetch >> ((address & 3) * 8)) & 0xFF; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); LOAD_BAD; - value = ((uint8_t*) &value)[address & 3]; + value = (value >> ((address & 3) * 8)) & 0xFF; } break; case REGION_WORKING_RAM: @@ -602,7 +602,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); LOAD_BAD; - value = ((uint8_t*) &value)[address & 3]; + value = (value >> ((address & 3) * 8)) & 0xFF; break; } From 9f5bfeeebb70f0ad05157df55009273726e39109 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 6 Oct 2015 21:26:25 -0700 Subject: [PATCH 112/127] GBA Context: Fix waiting on frames --- src/gba/video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/video.c b/src/gba/video.c index 2450ebb41..8855c018a 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -165,6 +165,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) { GBAFrameEnded(video->p); --video->frameskipCounter; if (video->frameskipCounter < 0) { + GBASyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; } ++video->frameCounter; From 3f36f3d88eff2f62c1e571943f6cb698207788c4 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 6 Oct 2015 21:27:25 -0700 Subject: [PATCH 113/127] GBA Config: Add "override" layer for better one-time configuration --- CHANGES | 1 + src/gba/context/config.c | 28 ++++++++++++++++++++++++++++ src/gba/context/config.h | 6 ++++++ src/platform/commandline.c | 12 ++++++------ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index f18dffa86..46d2cac48 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,7 @@ Misc: - All: Reset next event to cycles instead of zero to interrupt - GBA Video: Remove lastHblank, as it is implied - GBA: Check for cycle count being too high + - GBA Config: Add "override" layer for better one-time configuration` 0.3.0: (2015-08-16) Features: diff --git a/src/gba/context/config.c b/src/gba/context/config.c index f7f6956fc..ee4eccb09 100644 --- a/src/gba/context/config.c +++ b/src/gba/context/config.c @@ -30,6 +30,16 @@ static const char* _lookupValue(const struct GBAConfig* config, const char* key) { const char* value; + if (config->port) { + value = ConfigurationGetValue(&config->overridesTable, config->port, key); + if (value) { + return value; + } + } + value = ConfigurationGetValue(&config->overridesTable, 0, key); + if (value) { + return value; + } if (config->port) { value = ConfigurationGetValue(&config->configTable, config->port, key); if (value) { @@ -106,6 +116,7 @@ static bool _lookupFloatValue(const struct GBAConfig* config, const char* key, f void GBAConfigInit(struct GBAConfig* config, const char* port) { ConfigurationInit(&config->configTable); ConfigurationInit(&config->defaultsTable); + ConfigurationInit(&config->overridesTable); if (port) { config->port = malloc(strlen("ports.") + strlen(port) + 1); snprintf(config->port, strlen("ports.") + strlen(port) + 1, "ports.%s", port); @@ -117,6 +128,7 @@ void GBAConfigInit(struct GBAConfig* config, const char* port) { void GBAConfigDeinit(struct GBAConfig* config) { ConfigurationDeinit(&config->configTable); ConfigurationDeinit(&config->defaultsTable); + ConfigurationDeinit(&config->overridesTable); free(config->port); } @@ -268,6 +280,22 @@ void GBAConfigSetDefaultFloatValue(struct GBAConfig* config, const char* key, fl ConfigurationSetFloatValue(&config->defaultsTable, config->port, key, value); } +void GBAConfigSetOverrideValue(struct GBAConfig* config, const char* key, const char* value) { + ConfigurationSetValue(&config->overridesTable, config->port, key, value); +} + +void GBAConfigSetOverrideIntValue(struct GBAConfig* config, const char* key, int value) { + ConfigurationSetIntValue(&config->overridesTable, config->port, key, value); +} + +void GBAConfigSetOverrideUIntValue(struct GBAConfig* config, const char* key, unsigned value) { + ConfigurationSetUIntValue(&config->overridesTable, config->port, key, value); +} + +void GBAConfigSetOverrideFloatValue(struct GBAConfig* config, const char* key, float value) { + ConfigurationSetFloatValue(&config->overridesTable, config->port, key, value); +} + void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { _lookupCharValue(config, "bios", &opts->bios); _lookupIntValue(config, "logLevel", &opts->logLevel); diff --git a/src/gba/context/config.h b/src/gba/context/config.h index 8d4039d92..659a02fa2 100644 --- a/src/gba/context/config.h +++ b/src/gba/context/config.h @@ -15,6 +15,7 @@ struct GBAConfig { struct Configuration configTable; struct Configuration defaultsTable; + struct Configuration overridesTable; char* port; }; @@ -73,6 +74,11 @@ void GBAConfigSetDefaultIntValue(struct GBAConfig*, const char* key, int value); void GBAConfigSetDefaultUIntValue(struct GBAConfig*, const char* key, unsigned value); void GBAConfigSetDefaultFloatValue(struct GBAConfig*, const char* key, float value); +void GBAConfigSetOverrideValue(struct GBAConfig*, const char* key, const char* value); +void GBAConfigSetOverrideIntValue(struct GBAConfig*, const char* key, int value); +void GBAConfigSetOverrideUIntValue(struct GBAConfig*, const char* key, unsigned value); +void GBAConfigSetOverrideFloatValue(struct GBAConfig*, const char* key, float value); + void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts); void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts); diff --git a/src/platform/commandline.c b/src/platform/commandline.c index e4142fffd..1a62c222c 100644 --- a/src/platform/commandline.c +++ b/src/platform/commandline.c @@ -71,7 +71,7 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg while ((ch = getopt_long(argc, argv, options, _options, 0)) != -1) { switch (ch) { case 'b': - GBAConfigSetDefaultValue(config, "bios", optarg); + GBAConfigSetOverrideValue(config, "bios", optarg); break; case 'c': opts->cheatsFile = strdup(optarg); @@ -99,13 +99,13 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg opts->showHelp = true; break; case 'l': - GBAConfigSetDefaultValue(config, "logLevel", optarg); + GBAConfigSetOverrideValue(config, "logLevel", optarg); break; case 'p': opts->patch = strdup(optarg); break; case 's': - GBAConfigSetDefaultValue(config, "frameskip", optarg); + GBAConfigSetOverrideValue(config, "frameskip", optarg); break; case 'v': opts->movie = strdup(optarg); @@ -154,7 +154,7 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o switch (option) { case 'f': graphicsOpts->fullscreen = true; - GBAConfigSetDefaultIntValue(config, "fullscreen", 1); + GBAConfigSetOverrideIntValue(config, "fullscreen", 1); return true; case '1': case '2': @@ -166,8 +166,8 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o return false; } graphicsOpts->multiplier = option - '0'; - GBAConfigSetDefaultIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier); - GBAConfigSetDefaultIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier); + GBAConfigSetOverrideIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier); + GBAConfigSetOverrideIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier); return true; default: return false; From 47eabe1bc78729082e2b65235e99940a51b7ccc7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 8 Oct 2015 19:52:38 -0700 Subject: [PATCH 114/127] ARM7: Fix instruction decoding of Thumb shifts --- CHANGES | 1 + src/arm/decoder-thumb.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 46d2cac48..77b196e5f 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Bugfixes: - GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2 - GBA Memory: Fix bad BIOS Load16 on big endian - GBA Memory: Fix bad Load8 on big endian + - ARM7: Fix instruction decoding of Thumb shifts Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper diff --git a/src/arm/decoder-thumb.c b/src/arm/decoder-thumb.c index 27338ed4f..da0b156cd 100644 --- a/src/arm/decoder-thumb.c +++ b/src/arm/decoder-thumb.c @@ -18,7 +18,7 @@ #define DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(NAME, MNEMONIC) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->op3.immediate = (opcode >> 6) & 0x0007; \ + info->op3.immediate = (opcode >> 6) & 0x001F; \ info->op1.reg = opcode & 0x0007; \ info->op2.reg = (opcode >> 3) & 0x0007; \ info->affectsCPSR = 1; \ From 1a50718c26c6336b77cb119d0b1a4b399a6434e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 10 Oct 2015 20:26:52 -0700 Subject: [PATCH 115/127] Qt: Start I/O viewer --- CHANGES | 2 + src/platform/qt/CMakeLists.txt | 2 + src/platform/qt/IOViewer.cpp | 169 ++++++++++++++ src/platform/qt/IOViewer.h | 44 ++++ src/platform/qt/IOViewer.ui | 414 +++++++++++++++++++++++++++++++++ src/platform/qt/Window.cpp | 11 + src/platform/qt/Window.h | 1 + 7 files changed, 643 insertions(+) create mode 100644 src/platform/qt/IOViewer.cpp create mode 100644 src/platform/qt/IOViewer.h create mode 100644 src/platform/qt/IOViewer.ui diff --git a/CHANGES b/CHANGES index 77b196e5f..221fc0dfa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,6 @@ 0.4.0: (Future) +Features: + - I/O viewer Bugfixes: - Qt: Windows no longer spawn in the top left on first launch - Qt: Fix install path of XDG desktop file with DESTDIR diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 95f0cc285..fcb164be1 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -75,6 +75,7 @@ set(SOURCE_FILES GameController.cpp GamepadAxisEvent.cpp GamepadButtonEvent.cpp + IOViewer.cpp InputController.cpp InputProfile.cpp KeyEditor.cpp @@ -101,6 +102,7 @@ qt5_wrap_ui(UI_FILES AboutScreen.ui CheatsView.ui GIFView.ui + IOViewer.ui LoadSaveState.ui LogView.ui MemoryView.ui diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp new file mode 100644 index 000000000..e31ee72d1 --- /dev/null +++ b/src/platform/qt/IOViewer.cpp @@ -0,0 +1,169 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "IOViewer.h" + +#include "GameController.h" + +#include + +extern "C" { +#include "gba/io.h" +} + +using namespace QGBA; + +IOViewer::IOViewer(GameController* controller, QWidget* parent) + : QDialog(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + for (unsigned i = 0; i < REG_MAX >> 1; ++i) { + const char* reg = GBAIORegisterNames[i]; + if (!reg) { + continue; + } + m_ui.regSelect->addItem(QString::asprintf("0x0400%04X: %s", i << 1, reg), i << 1); + } + + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + m_ui.regValue->setFont(font); + + connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*))); + connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); + connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister())); + + connect(m_ui.b0, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b1, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b2, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b3, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b4, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b5, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b6, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b7, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b8, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.b9, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.bA, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.bB, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.bC, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.bD, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.bE, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_ui.bF, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + + selectRegister(0); +} + +void IOViewer::update() { + m_value = 0; + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + m_value = GBAIORead(m_controller->thread()->gba, m_register); + } + m_controller->threadContinue(); + + m_ui.regValue->setText(QString::asprintf("0x%04X", m_value)); + bool signalsBlocked; + signalsBlocked = m_ui.b0->blockSignals(true); + m_ui.b0->setChecked(m_value & 0x0001 ? Qt::Checked : Qt::Unchecked); + m_ui.b0->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b1->blockSignals(true); + m_ui.b1->setChecked(m_value & 0x0002 ? Qt::Checked : Qt::Unchecked); + m_ui.b1->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b2->blockSignals(true); + m_ui.b2->setChecked(m_value & 0x0004 ? Qt::Checked : Qt::Unchecked); + m_ui.b2->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b3->blockSignals(true); + m_ui.b3->setChecked(m_value & 0x0008 ? Qt::Checked : Qt::Unchecked); + m_ui.b3->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b4->blockSignals(true); + m_ui.b4->setChecked(m_value & 0x0010 ? Qt::Checked : Qt::Unchecked); + m_ui.b4->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b5->blockSignals(true); + m_ui.b5->setChecked(m_value & 0x0020 ? Qt::Checked : Qt::Unchecked); + m_ui.b5->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b6->blockSignals(true); + m_ui.b6->setChecked(m_value & 0x0040 ? Qt::Checked : Qt::Unchecked); + m_ui.b6->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b7->blockSignals(true); + m_ui.b7->setChecked(m_value & 0x0080 ? Qt::Checked : Qt::Unchecked); + m_ui.b7->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b8->blockSignals(true); + m_ui.b8->setChecked(m_value & 0x0100 ? Qt::Checked : Qt::Unchecked); + m_ui.b8->blockSignals(signalsBlocked); + signalsBlocked = m_ui.b9->blockSignals(true); + m_ui.b9->setChecked(m_value & 0x0200 ? Qt::Checked : Qt::Unchecked); + m_ui.b9->blockSignals(signalsBlocked); + signalsBlocked = m_ui.bA->blockSignals(true); + m_ui.bA->setChecked(m_value & 0x0400 ? Qt::Checked : Qt::Unchecked); + m_ui.bA->blockSignals(signalsBlocked); + signalsBlocked = m_ui.bB->blockSignals(true); + m_ui.bB->setChecked(m_value & 0x0800 ? Qt::Checked : Qt::Unchecked); + m_ui.bB->blockSignals(signalsBlocked); + signalsBlocked = m_ui.bC->blockSignals(true); + m_ui.bC->setChecked(m_value & 0x1000 ? Qt::Checked : Qt::Unchecked); + m_ui.bC->blockSignals(signalsBlocked); + signalsBlocked = m_ui.bD->blockSignals(true); + m_ui.bD->setChecked(m_value & 0x2000 ? Qt::Checked : Qt::Unchecked); + m_ui.bD->blockSignals(signalsBlocked); + signalsBlocked = m_ui.bE->blockSignals(true); + m_ui.bE->setChecked(m_value & 0x4000 ? Qt::Checked : Qt::Unchecked); + m_ui.bE->blockSignals(signalsBlocked); + signalsBlocked = m_ui.bF->blockSignals(true); + m_ui.bF->setChecked(m_value & 0x8000 ? Qt::Checked : Qt::Unchecked); + m_ui.bF->blockSignals(signalsBlocked); +} + +void IOViewer::bitFlipped() { + m_value = 0; + m_value |= m_ui.b0->isChecked() << 0x0; + m_value |= m_ui.b1->isChecked() << 0x1; + m_value |= m_ui.b2->isChecked() << 0x2; + m_value |= m_ui.b3->isChecked() << 0x3; + m_value |= m_ui.b4->isChecked() << 0x4; + m_value |= m_ui.b5->isChecked() << 0x5; + m_value |= m_ui.b6->isChecked() << 0x6; + m_value |= m_ui.b7->isChecked() << 0x7; + m_value |= m_ui.b8->isChecked() << 0x8; + m_value |= m_ui.b9->isChecked() << 0x9; + m_value |= m_ui.bA->isChecked() << 0xA; + m_value |= m_ui.bB->isChecked() << 0xB; + m_value |= m_ui.bC->isChecked() << 0xC; + m_value |= m_ui.bD->isChecked() << 0xD; + m_value |= m_ui.bE->isChecked() << 0xE; + m_value |= m_ui.bF->isChecked() << 0xF; + m_ui.regValue->setText(QString::asprintf("0x%04X", m_value)); +} + +void IOViewer::writeback() { + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + GBAIOWrite(m_controller->thread()->gba, m_register, m_value); + } + m_controller->threadContinue(); + update(); +} + +void IOViewer::selectRegister(unsigned address) { + m_register = address; + update(); +} + +void IOViewer::selectRegister() { + selectRegister(m_ui.regSelect->currentData().toUInt()); +} + +void IOViewer::buttonPressed(QAbstractButton* button) { + switch (m_ui.buttonBox->standardButton(button)) { + case QDialogButtonBox::Reset: + update(); + break; + case QDialogButtonBox::Apply: + writeback(); + break; + default: + break; + } +} diff --git a/src/platform/qt/IOViewer.h b/src/platform/qt/IOViewer.h new file mode 100644 index 000000000..52557f58f --- /dev/null +++ b/src/platform/qt/IOViewer.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_IOVIEWER +#define QGBA_IOVIEWER + +#include + +#include "ui_IOViewer.h" + +namespace QGBA { + +class GameController; + +class IOViewer : public QDialog { +Q_OBJECT + +public: + IOViewer(GameController* controller, QWidget* parent = nullptr); + +public slots: + void update(); + void selectRegister(unsigned address); + +private slots: + void buttonPressed(QAbstractButton* button); + void bitFlipped(); + void writeback(); + void selectRegister(); + +private: + Ui::IOViewer m_ui; + + unsigned m_register; + uint16_t m_value; + + GameController* m_controller; +}; + +} + +#endif diff --git a/src/platform/qt/IOViewer.ui b/src/platform/qt/IOViewer.ui new file mode 100644 index 000000000..9a7783553 --- /dev/null +++ b/src/platform/qt/IOViewer.ui @@ -0,0 +1,414 @@ + + + IOViewer + + + + 0 + 0 + 343 + 342 + + + + I/O Viewer + + + + + + + + + 0x0000 + + + + + + + + + + + + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 2 + + + + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 5 + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 4 + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 7 + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 0 + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 9 + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 1 + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 3 + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 8 + + + + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + C + + + + + + + + 16777215 + 80 + + + + + 10 + + + + E + + + + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + 6 + + + + + + + + + + + + + + + + + 16777215 + 80 + + + + + 10 + + + + D + + + + + + + + 16777215 + 80 + + + + + 10 + + + + F + + + + + + + + 16777215 + 80 + + + + + 10 + + + + A + + + + + + + + 16777215 + 80 + + + + + 10 + + + + B + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Reset + + + + + + + regSelect + b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7 + bE + b8 + b9 + bA + bB + bC + bD + bF + + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 4b4370d40..c514cbd7b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -24,6 +24,7 @@ #include "GDBController.h" #include "GDBWindow.h" #include "GIFView.h" +#include "IOViewer.h" #include "LoadSaveState.h" #include "LogView.h" #include "MultiplayerController.h" @@ -374,6 +375,11 @@ void Window::openMemoryWindow() { openView(memoryWindow); } +void Window::openIOViewer() { + IOViewer* ioViewer = new IOViewer(m_controller); + openView(ioViewer); +} + void Window::openAboutScreen() { AboutScreen* about = new AboutScreen(); openView(about); @@ -1164,6 +1170,11 @@ void Window::setupMenu(QMenuBar* menubar) { m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); + QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu); + connect(ioViewer, SIGNAL(triggered()), this, SLOT(openIOViewer())); + m_gameActions.append(ioViewer); + addControlledAction(toolsMenu, ioViewer, "ioViewer"); + ConfigOption* skipBios = m_config->addOption("skipBios"); skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 1a9ef24f0..9e8afec42 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -84,6 +84,7 @@ public slots: void openPaletteWindow(); void openMemoryWindow(); + void openIOViewer(); void openAboutScreen(); From 8284e7ded2596317ff1700c71bdada0f504fb0d2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 10 Oct 2015 20:50:23 -0700 Subject: [PATCH 116/127] GUI: Don't scan subdirectories immediately --- src/util/gui/file-select.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/util/gui/file-select.c b/src/util/gui/file-select.c index 61b26cf79..7844f359d 100644 --- a/src/util/gui/file-select.c +++ b/src/util/gui/file-select.c @@ -116,23 +116,9 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, } struct VDir* vd = dir->openDir(dir, GUIMenuItemListGetPointer(currentFiles, item)->title); if (vd) { - bool success = false; - struct VDirEntry* de; - while ((de = vd->listNext(vd)) && !success) { - struct VFile* vf2 = vd->openFile(vd, de->name(de), O_RDONLY); - if (!vf2) { - continue; - } - if (filter(vf2)) { - success = true; - } - vf2->close(vf2); - } vd->close(vd); - if (success) { - ++item; - continue; - } + ++item; + continue; } struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY); if (vf) { From c53e3546dd071f1458144ce3e659ebb851ec7d78 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 10 Oct 2015 21:04:05 -0700 Subject: [PATCH 117/127] Qt: Fix pre-5.5 build --- src/platform/qt/IOViewer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp index e31ee72d1..36a0a4572 100644 --- a/src/platform/qt/IOViewer.cpp +++ b/src/platform/qt/IOViewer.cpp @@ -26,7 +26,7 @@ IOViewer::IOViewer(GameController* controller, QWidget* parent) if (!reg) { continue; } - m_ui.regSelect->addItem(QString::asprintf("0x0400%04X: %s", i << 1, reg), i << 1); + m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1); } const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); @@ -64,7 +64,7 @@ void IOViewer::update() { } m_controller->threadContinue(); - m_ui.regValue->setText(QString::asprintf("0x%04X", m_value)); + m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper()); bool signalsBlocked; signalsBlocked = m_ui.b0->blockSignals(true); m_ui.b0->setChecked(m_value & 0x0001 ? Qt::Checked : Qt::Unchecked); From a8110342ce130ebf751e40ae69ccaec01cbddab5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 10 Oct 2015 21:09:57 -0700 Subject: [PATCH 118/127] Qt: Really fix the build this time --- src/platform/qt/IOViewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp index 36a0a4572..1f18c1947 100644 --- a/src/platform/qt/IOViewer.cpp +++ b/src/platform/qt/IOViewer.cpp @@ -134,7 +134,7 @@ void IOViewer::bitFlipped() { m_value |= m_ui.bD->isChecked() << 0xD; m_value |= m_ui.bE->isChecked() << 0xE; m_value |= m_ui.bF->isChecked() << 0xF; - m_ui.regValue->setText(QString::asprintf("0x%04X", m_value)); + m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper()); } void IOViewer::writeback() { From d31326a0245b8a9525b993221ffd183bbb490a06 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 11 Oct 2015 17:33:11 -0700 Subject: [PATCH 119/127] SDL: Allow GBASDLAudio to be used without a thread context --- CHANGES | 3 ++- src/platform/sdl/sdl-audio.c | 51 ++++++++++++++++++++++-------------- src/platform/sdl/sdl-audio.h | 1 + 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index 221fc0dfa..9379a2139 100644 --- a/CHANGES +++ b/CHANGES @@ -41,7 +41,8 @@ Misc: - All: Reset next event to cycles instead of zero to interrupt - GBA Video: Remove lastHblank, as it is implied - GBA: Check for cycle count being too high - - GBA Config: Add "override" layer for better one-time configuration` + - GBA Config: Add "override" layer for better one-time configuration + - SDL: Allow GBASDLAudio to be used without a thread context 0.3.0: (2015-08-16) Features: diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c index 321679a96..db2e51b6b 100644 --- a/src/platform/sdl/sdl-audio.c +++ b/src/platform/sdl/sdl-audio.c @@ -41,19 +41,23 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; } - context->thread = threadContext; context->samples = context->obtainedSpec.samples; - float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); - threadContext->audioBuffers = context->samples / ratio; - if (context->samples > threadContext->audioBuffers) { - threadContext->audioBuffers = context->samples * 2; - } + + if (threadContext) { + context->thread = threadContext; + float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); + threadContext->audioBuffers = context->samples / ratio; + if (context->samples > threadContext->audioBuffers) { + threadContext->audioBuffers = context->samples * 2; + } #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_PauseAudioDevice(context->deviceId, 0); + SDL_PauseAudioDevice(context->deviceId, 0); #else - SDL_PauseAudio(0); + SDL_PauseAudio(0); #endif + } + return true; } @@ -89,12 +93,15 @@ void GBASDLResumeAudio(struct GBASDLAudio* context) { static void _GBASDLAudioCallback(void* context, Uint8* data, int len) { struct GBASDLAudio* audioContext = context; - if (!context || !audioContext->thread || !audioContext->thread->gba) { + if (!context || (!audioContext->gba && (!audioContext->thread || !audioContext->thread->gba))) { memset(data, 0, len); return; } + if (!audioContext->gba) { + audioContext->gba = audioContext->thread->gba; + } #if RESAMPLE_LIBRARY == RESAMPLE_NN - audioContext->ratio = GBAAudioCalculateRatio(audioContext->thread->gba->audio.sampleRate, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq); + audioContext->ratio = GBAAudioCalculateRatio(audioContext->gba->audio.sampleRate, audioContext->fpsTarget, audioContext->obtainedSpec.freq); if (audioContext->ratio == INFINITY) { memset(data, 0, len); return; @@ -102,23 +109,29 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len) { struct GBAStereoSample* ssamples = (struct GBAStereoSample*) data; len /= 2 * audioContext->obtainedSpec.channels; if (audioContext->obtainedSpec.channels == 2) { - GBAAudioResampleNN(&audioContext->thread->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len); + GBAAudioResampleNN(&audioContext->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len); } #elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF - double fauxClock = GBAAudioCalculateRatio(1, audioContext->thread->fpsTarget, 1); - GBASyncLockAudio(&audioContext->thread->sync); - blip_set_rates(audioContext->thread->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); - blip_set_rates(audioContext->thread->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); + double fauxClock = 1; + if (audioContext->thread) { + GBAAudioCalculateRatio(1, audioContext->thread->fpsTarget, 1); + GBASyncLockAudio(&audioContext->thread->sync); + } + blip_set_rates(audioContext->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); + blip_set_rates(audioContext->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); len /= 2 * audioContext->obtainedSpec.channels; - int available = blip_samples_avail(audioContext->thread->gba->audio.left); + int available = blip_samples_avail(audioContext->gba->audio.left); if (available > len) { available = len; } - blip_read_samples(audioContext->thread->gba->audio.left, (short*) data, available, audioContext->obtainedSpec.channels == 2); + blip_read_samples(audioContext->gba->audio.left, (short*) data, available, audioContext->obtainedSpec.channels == 2); if (audioContext->obtainedSpec.channels == 2) { - blip_read_samples(audioContext->thread->gba->audio.right, ((short*) data) + 1, available, 1); + blip_read_samples(audioContext->gba->audio.right, ((short*) data) + 1, available, 1); + } + + if (audioContext->thread) { + GBASyncConsumeAudio(&audioContext->thread->sync); } - GBASyncConsumeAudio(&audioContext->thread->sync); if (available < len) { memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short)); } diff --git a/src/platform/sdl/sdl-audio.h b/src/platform/sdl/sdl-audio.h index f2f0b8df2..1df21e336 100644 --- a/src/platform/sdl/sdl-audio.h +++ b/src/platform/sdl/sdl-audio.h @@ -31,6 +31,7 @@ struct GBASDLAudio { #endif struct GBAThread* thread; + struct GBA* gba; }; bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext); From d9a60540f7af478cdc996ccf8b8346f36eca4ae3 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 11 Oct 2015 19:11:15 -0700 Subject: [PATCH 120/127] Qt: Start adding I/O register info --- src/platform/qt/IOViewer.cpp | 260 +++++++++++++++++++++++------------ src/platform/qt/IOViewer.h | 21 ++- src/platform/qt/IOViewer.ui | 2 +- 3 files changed, 194 insertions(+), 89 deletions(-) diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp index 1f18c1947..f08aa7649 100644 --- a/src/platform/qt/IOViewer.cpp +++ b/src/platform/qt/IOViewer.cpp @@ -8,6 +8,7 @@ #include "GameController.h" #include +#include extern "C" { #include "gba/io.h" @@ -15,6 +16,119 @@ extern "C" { using namespace QGBA; + +QList IOViewer::s_registers; + +const QList& IOViewer::registerDescriptions() { + if (!s_registers.isEmpty()) { + return s_registers; + } + // 0x04000000: DISPCNT + s_registers.append({ + { tr("Background mode"), 0, 3 }, + { tr("CGB Mode"), 3, 1, true }, + { tr("Frame select"), 4 }, + { tr("Unlocked HBlank"), 5 }, + { tr("Linear OBJ tile mapping"), 6 }, + { tr("Force blank screen"), 7 }, + { tr("Enable background 0"), 8 }, + { tr("Enable background 1"), 9 }, + { tr("Enable background 2"), 10 }, + { tr("Enable background 3"), 11 }, + { tr("Enable OBJ"), 12 }, + { tr("Enable Window 0"), 13 }, + { tr("Enable Window 1"), 14 }, + { tr("Enable OBJ Window"), 15 }, + }); + // 0x04000002: Green swap (undocumented and unimplemented) + s_registers.append(RegisterDescription()); + // 0x04000004: DISPSTAT + s_registers.append({ + { tr("Currently in VBlank"), 0, 1, true }, + { tr("Currently in HBlank"), 1, 1, true }, + { tr("Currently in VCounter"), 2, 1, true }, + { tr("Enable VBlank IRQ generation"), 3 }, + { tr("Enable HBlank IRQ generation"), 4 }, + { tr("Enable VCounter IRQ generation"), 5 }, + { tr("VCounter scanline"), 8, 8 }, + }); + // 0x04000006: VCOUNT + s_registers.append({ + { tr("Current scanline"), 0, 8, true }, + }); + // 0x04000008: BG0CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x0400000A: BG1CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x0400000C: BG2CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Overflow wraps"), 9 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x0400000E: BG3CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Overflow wraps"), 9 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x04000010: BG0HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x04000012: BG0VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + // 0x04000014: BG1HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x04000016: BG1VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + // 0x04000018: BG2HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x0400001A: BG2VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + // 0x0400001C: BG3HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x0400001E: BG3VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + return s_registers; +} + IOViewer::IOViewer(GameController* controller, QWidget* parent) : QDialog(parent) , m_controller(controller) @@ -36,104 +150,50 @@ IOViewer::IOViewer(GameController* controller, QWidget* parent) connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister())); - connect(m_ui.b0, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b1, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b2, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b3, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b4, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b5, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b6, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b7, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b8, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.b9, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.bA, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.bB, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.bC, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.bD, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.bE, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); - connect(m_ui.bF, SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + m_b[0] = m_ui.b0; + m_b[1] = m_ui.b1; + m_b[2] = m_ui.b2; + m_b[3] = m_ui.b3; + m_b[4] = m_ui.b4; + m_b[5] = m_ui.b5; + m_b[6] = m_ui.b6; + m_b[7] = m_ui.b7; + m_b[8] = m_ui.b8; + m_b[9] = m_ui.b9; + m_b[10] = m_ui.bA; + m_b[11] = m_ui.bB; + m_b[12] = m_ui.bC; + m_b[13] = m_ui.bD; + m_b[14] = m_ui.bE; + m_b[15] = m_ui.bF; + + for (int i = 0; i < 16; ++i) { + connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + } selectRegister(0); } -void IOViewer::update() { +void IOViewer::updateRegister() { m_value = 0; + uint16_t value = 0; m_controller->threadInterrupt(); if (m_controller->isLoaded()) { - m_value = GBAIORead(m_controller->thread()->gba, m_register); + value = GBAIORead(m_controller->thread()->gba, m_register); } m_controller->threadContinue(); - m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper()); - bool signalsBlocked; - signalsBlocked = m_ui.b0->blockSignals(true); - m_ui.b0->setChecked(m_value & 0x0001 ? Qt::Checked : Qt::Unchecked); - m_ui.b0->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b1->blockSignals(true); - m_ui.b1->setChecked(m_value & 0x0002 ? Qt::Checked : Qt::Unchecked); - m_ui.b1->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b2->blockSignals(true); - m_ui.b2->setChecked(m_value & 0x0004 ? Qt::Checked : Qt::Unchecked); - m_ui.b2->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b3->blockSignals(true); - m_ui.b3->setChecked(m_value & 0x0008 ? Qt::Checked : Qt::Unchecked); - m_ui.b3->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b4->blockSignals(true); - m_ui.b4->setChecked(m_value & 0x0010 ? Qt::Checked : Qt::Unchecked); - m_ui.b4->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b5->blockSignals(true); - m_ui.b5->setChecked(m_value & 0x0020 ? Qt::Checked : Qt::Unchecked); - m_ui.b5->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b6->blockSignals(true); - m_ui.b6->setChecked(m_value & 0x0040 ? Qt::Checked : Qt::Unchecked); - m_ui.b6->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b7->blockSignals(true); - m_ui.b7->setChecked(m_value & 0x0080 ? Qt::Checked : Qt::Unchecked); - m_ui.b7->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b8->blockSignals(true); - m_ui.b8->setChecked(m_value & 0x0100 ? Qt::Checked : Qt::Unchecked); - m_ui.b8->blockSignals(signalsBlocked); - signalsBlocked = m_ui.b9->blockSignals(true); - m_ui.b9->setChecked(m_value & 0x0200 ? Qt::Checked : Qt::Unchecked); - m_ui.b9->blockSignals(signalsBlocked); - signalsBlocked = m_ui.bA->blockSignals(true); - m_ui.bA->setChecked(m_value & 0x0400 ? Qt::Checked : Qt::Unchecked); - m_ui.bA->blockSignals(signalsBlocked); - signalsBlocked = m_ui.bB->blockSignals(true); - m_ui.bB->setChecked(m_value & 0x0800 ? Qt::Checked : Qt::Unchecked); - m_ui.bB->blockSignals(signalsBlocked); - signalsBlocked = m_ui.bC->blockSignals(true); - m_ui.bC->setChecked(m_value & 0x1000 ? Qt::Checked : Qt::Unchecked); - m_ui.bC->blockSignals(signalsBlocked); - signalsBlocked = m_ui.bD->blockSignals(true); - m_ui.bD->setChecked(m_value & 0x2000 ? Qt::Checked : Qt::Unchecked); - m_ui.bD->blockSignals(signalsBlocked); - signalsBlocked = m_ui.bE->blockSignals(true); - m_ui.bE->setChecked(m_value & 0x4000 ? Qt::Checked : Qt::Unchecked); - m_ui.bE->blockSignals(signalsBlocked); - signalsBlocked = m_ui.bF->blockSignals(true); - m_ui.bF->setChecked(m_value & 0x8000 ? Qt::Checked : Qt::Unchecked); - m_ui.bF->blockSignals(signalsBlocked); + for (int i = 0; i < 16; ++i) { + m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked); + } + m_value = value; } void IOViewer::bitFlipped() { m_value = 0; - m_value |= m_ui.b0->isChecked() << 0x0; - m_value |= m_ui.b1->isChecked() << 0x1; - m_value |= m_ui.b2->isChecked() << 0x2; - m_value |= m_ui.b3->isChecked() << 0x3; - m_value |= m_ui.b4->isChecked() << 0x4; - m_value |= m_ui.b5->isChecked() << 0x5; - m_value |= m_ui.b6->isChecked() << 0x6; - m_value |= m_ui.b7->isChecked() << 0x7; - m_value |= m_ui.b8->isChecked() << 0x8; - m_value |= m_ui.b9->isChecked() << 0x9; - m_value |= m_ui.bA->isChecked() << 0xA; - m_value |= m_ui.bB->isChecked() << 0xB; - m_value |= m_ui.bC->isChecked() << 0xC; - m_value |= m_ui.bD->isChecked() << 0xD; - m_value |= m_ui.bE->isChecked() << 0xE; - m_value |= m_ui.bF->isChecked() << 0xF; + for (int i = 0; i < 16; ++i) { + m_value |= m_b[i]->isChecked() << i; + } m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper()); } @@ -143,12 +203,38 @@ void IOViewer::writeback() { GBAIOWrite(m_controller->thread()->gba, m_register, m_value); } m_controller->threadContinue(); - update(); + updateRegister(); } void IOViewer::selectRegister(unsigned address) { m_register = address; - update(); + QLayout* box = m_ui.regDescription->layout(); + if (box) { + // I can't believe there isn't a real way to do this... + while (!box->isEmpty()) { + QLayoutItem* item = box->takeAt(0); + if (item->widget()) { + delete item->widget(); + } + delete item; + } + } else { + box = new QVBoxLayout; + } + if (registerDescriptions().count() > address >> 1) { + // TODO: Remove the check when done filling in register information + const RegisterDescription& description = registerDescriptions().at(address >> 1); + for (const RegisterItem& ri : description) { + QCheckBox* check = new QCheckBox; + check->setText(ri.description); + check->setEnabled(!ri.readonly); + box->addWidget(check); + connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool))); + connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool))); + } + } + m_ui.regDescription->setLayout(box); + updateRegister(); } void IOViewer::selectRegister() { @@ -158,7 +244,7 @@ void IOViewer::selectRegister() { void IOViewer::buttonPressed(QAbstractButton* button) { switch (m_ui.buttonBox->standardButton(button)) { case QDialogButtonBox::Reset: - update(); + updateRegister(); break; case QDialogButtonBox::Apply: writeback(); diff --git a/src/platform/qt/IOViewer.h b/src/platform/qt/IOViewer.h index 52557f58f..6ab596b48 100644 --- a/src/platform/qt/IOViewer.h +++ b/src/platform/qt/IOViewer.h @@ -7,6 +7,7 @@ #define QGBA_IOVIEWER #include +#include #include "ui_IOViewer.h" @@ -18,10 +19,25 @@ class IOViewer : public QDialog { Q_OBJECT public: + struct RegisterItem { + RegisterItem(const QString& description, uint start, uint size = 1, bool readonly = false) + : description(description) + , start(start) + , size(size) + , readonly(readonly) {} + uint start; + uint size; + bool readonly; + QString description; + }; + typedef QList RegisterDescription; + IOViewer(GameController* controller, QWidget* parent = nullptr); + static const QList& registerDescriptions(); + public slots: - void update(); + void updateRegister(); void selectRegister(unsigned address); private slots: @@ -31,11 +47,14 @@ private slots: void selectRegister(); private: + static QList s_registers; Ui::IOViewer m_ui; unsigned m_register; uint16_t m_value; + QCheckBox* m_b[16]; + GameController* m_controller; }; diff --git a/src/platform/qt/IOViewer.ui b/src/platform/qt/IOViewer.ui index 9a7783553..c19819083 100644 --- a/src/platform/qt/IOViewer.ui +++ b/src/platform/qt/IOViewer.ui @@ -372,7 +372,7 @@ - + 0 From ba41f68149e04d110a2f3107efe2e2111e9224d9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 11 Oct 2015 20:31:01 -0700 Subject: [PATCH 121/127] SDL: Fix Pandora build --- src/platform/sdl/pandora-sdl.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/platform/sdl/pandora-sdl.c b/src/platform/sdl/pandora-sdl.c index e9b2e19f4..09060b93b 100644 --- a/src/platform/sdl/pandora-sdl.c +++ b/src/platform/sdl/pandora-sdl.c @@ -12,7 +12,17 @@ #include #include -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { +static bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLGLInit; + renderer->deinit = GBASDLGLDeinit; + renderer->runloop = GBASDLGLRunloop; +} + +bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { SDL_SetVideoMode(800, 480, 16, SDL_FULLSCREEN); renderer->odd = 0; @@ -63,7 +73,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { return true; } -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { +void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { SDL_Event event; while (context->state < THREAD_EXITING) { @@ -87,7 +97,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render } } -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { +void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer) { munmap(renderer->base[0], VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); struct omapfb_plane_info plane; From 92849dee1a9704a34f75b6cf0de0bb0443b5dfc2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 11 Oct 2015 20:39:49 -0700 Subject: [PATCH 122/127] SDL: Fix uninitialized member --- src/platform/sdl/sdl-audio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c index db2e51b6b..eab6d166c 100644 --- a/src/platform/sdl/sdl-audio.c +++ b/src/platform/sdl/sdl-audio.c @@ -42,6 +42,7 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex return false; } context->samples = context->obtainedSpec.samples; + context->gba = 0; if (threadContext) { context->thread = threadContext; From 642ed65ee285f5f0f6d5ced18d219c29fbce6403 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 11 Oct 2015 23:49:56 -0700 Subject: [PATCH 123/127] GBA Video: Minor mode 2 optimization --- src/gba/renderers/software-bg.c | 58 ++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/gba/renderers/software-bg.c b/src/gba/renderers/software-bg.c index e3f81014d..93c520a18 100644 --- a/src/gba/renderers/software-bg.c +++ b/src/gba/renderers/software-bg.c @@ -7,28 +7,41 @@ #include "gba/gba.h" -#define DRAW_BACKGROUND_MODE_2(BLEND, OBJWIN) \ - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \ - x += background->dx; \ - y += background->dy; \ - \ +#define MODE_2_COORD_OVERFLOW \ + localX = x & (sizeAdjusted - 1); \ + localY = y & (sizeAdjusted - 1); \ + +#define MODE_2_COORD_NO_OVERFLOW \ + if ((x | y) & ~(sizeAdjusted - 1)) { \ + continue; \ + } else { \ + localX = x; \ + localY = y; \ + } + +#define MODE_2_MOSAIC(COORD) \ if (!mosaicWait) { \ - if (background->overflow) { \ - localX = x & (sizeAdjusted - 1); \ - localY = y & (sizeAdjusted - 1); \ - } else if ((x | y) & ~(sizeAdjusted - 1)) { \ - continue; \ - } else { \ - localX = x; \ - localY = y; \ - } \ + COORD \ mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \ pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; \ \ mosaicWait = mosaicH; \ } else { \ --mosaicWait; \ - } \ + } + +#define MODE_2_NO_MOSAIC(COORD) \ + COORD \ + mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \ + pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; + + +#define MODE_2_LOOP(MOSAIC, COORD, BLEND, OBJWIN) \ + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \ + x += background->dx; \ + y += background->dy; \ + \ + MOSAIC(COORD) \ \ uint32_t current = *pixel; \ if (pixelData && IS_WRITABLE(current)) { \ @@ -36,6 +49,21 @@ } \ } +#define DRAW_BACKGROUND_MODE_2(BLEND, OBJWIN) \ + if (background->overflow) { \ + if (mosaicH > 1) { \ + MODE_2_LOOP(MODE_2_MOSAIC, MODE_2_COORD_OVERFLOW, BLEND, OBJWIN); \ + } else { \ + MODE_2_LOOP(MODE_2_NO_MOSAIC, MODE_2_COORD_OVERFLOW, BLEND, OBJWIN); \ + } \ + } else { \ + if (mosaicH > 1) { \ + MODE_2_LOOP(MODE_2_MOSAIC, MODE_2_COORD_NO_OVERFLOW, BLEND, OBJWIN); \ + } else { \ + MODE_2_LOOP(MODE_2_NO_MOSAIC, MODE_2_COORD_NO_OVERFLOW, BLEND, OBJWIN); \ + } \ + } + void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { int sizeAdjusted = 0x8000 << background->size; From 808855f37737df917ab86d8a8afbeec0b7913aa4 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 11 Oct 2015 23:59:48 -0700 Subject: [PATCH 124/127] All: Pandora buildfixes --- src/platform/posix/threading.h | 5 ++++- src/platform/sdl/CMakeLists.txt | 4 ++++ src/platform/sdl/pandora-sdl.c | 28 +++++++++++++++++++--------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/platform/posix/threading.h b/src/platform/posix/threading.h index 71015526a..9a49779b2 100644 --- a/src/platform/posix/threading.h +++ b/src/platform/posix/threading.h @@ -82,8 +82,11 @@ static inline int ThreadSetName(const char* name) { #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); return 0; -#else +#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old return pthread_setname_np(pthread_self(), name); +#else + UNUSED(name); + return 0; #endif } diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index c6e5c7bc1..ac3c079fb 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -37,6 +37,10 @@ elseif(APPLE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE) endif() +if(NOT SDLMAIN_LIBRARY) + set(SDLMAIN_LIBRARY "") +endif() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VERSION_DEBIAN}" PARENT_SCOPE) file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c) diff --git a/src/platform/sdl/pandora-sdl.c b/src/platform/sdl/pandora-sdl.c index 09060b93b..c7bf2e8b1 100644 --- a/src/platform/sdl/pandora-sdl.c +++ b/src/platform/sdl/pandora-sdl.c @@ -12,17 +12,27 @@ #include #include -static bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer); -static void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); -static void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer); +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +static bool GBASDLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer); void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { - renderer->init = GBASDLGLInit; - renderer->deinit = GBASDLGLDeinit; - renderer->runloop = GBASDLGLRunloop; + renderer->init = GBASDLInit; + renderer->deinit = GBASDLDeinit; + renderer->runloop = GBASDLRunloop; } -bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { +void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLInit; + renderer->deinit = GBASDLDeinit; + renderer->runloop = GBASDLRunloop; +} + +bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { SDL_SetVideoMode(800, 480, 16, SDL_FULLSCREEN); renderer->odd = 0; @@ -73,7 +83,7 @@ bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { return true; } -void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { +void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { SDL_Event event; while (context->state < THREAD_EXITING) { @@ -97,7 +107,7 @@ void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* rend } } -void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer) { +void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { munmap(renderer->base[0], VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); struct omapfb_plane_info plane; From b597d5197b46e033760c9c1ecc96f075558ec067 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 12 Oct 2015 00:00:16 -0700 Subject: [PATCH 125/127] SDL: Fix vsync on Pandora --- src/platform/sdl/pandora-sdl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/sdl/pandora-sdl.c b/src/platform/sdl/pandora-sdl.c index c7bf2e8b1..24dc1a493 100644 --- a/src/platform/sdl/pandora-sdl.c +++ b/src/platform/sdl/pandora-sdl.c @@ -92,14 +92,14 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render } if (GBASyncWaitFrameStart(&context->sync)) { - int arg = 0; - ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg); - struct fb_var_screeninfo info; ioctl(renderer->fb, FBIOGET_VSCREENINFO, &info); info.yoffset = VIDEO_VERTICAL_PIXELS * renderer->odd; ioctl(renderer->fb, FBIOPAN_DISPLAY, &info); + int arg = 0; + ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg); + renderer->odd = !renderer->odd; renderer->d.outputBuffer = renderer->base[renderer->odd]; } From 4c176ef51bf514d23bc061787c13506b3bb35579 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 13 Oct 2015 21:25:22 -0700 Subject: [PATCH 126/127] Qt: Add missing override keywords --- src/platform/qt/AudioProcessorQt.h | 10 +++++----- src/platform/qt/AudioProcessorSDL.h | 8 ++++---- src/platform/qt/Display.h | 2 +- src/platform/qt/SensorView.h | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index 52d7b606c..05aaae93d 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -19,15 +19,15 @@ Q_OBJECT public: AudioProcessorQt(QObject* parent = nullptr); - virtual void setInput(GBAThread* input); + virtual void setInput(GBAThread* input) override; virtual unsigned sampleRate() const override; public slots: - virtual void start(); - virtual void pause(); + virtual void start() override; + virtual void pause() override; - virtual void setBufferSamples(int samples); - virtual void inputParametersChanged(); + virtual void setBufferSamples(int samples) override; + virtual void inputParametersChanged() override; virtual void requestSampleRate(unsigned) override; diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index c98d00473..bc9b43581 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/platform/qt/AudioProcessorSDL.h @@ -25,11 +25,11 @@ public: virtual unsigned sampleRate() const override; public slots: - virtual void start(); - virtual void pause(); + virtual void start() override; + virtual void pause() override; - virtual void setBufferSamples(int samples); - virtual void inputParametersChanged(); + virtual void setBufferSamples(int samples) override; + virtual void inputParametersChanged() override; virtual void requestSampleRate(unsigned) override; diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 3e6d738f2..7b3605ab2 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -52,7 +52,7 @@ public slots: void showMessage(const QString& message); protected: - void resizeEvent(QResizeEvent*); + virtual void resizeEvent(QResizeEvent*) override; virtual void mouseMoveEvent(QMouseEvent*) override; MessagePainter* messagePainter() { return &m_messagePainter; } diff --git a/src/platform/qt/SensorView.h b/src/platform/qt/SensorView.h index 9313f89fb..1eb4416a8 100644 --- a/src/platform/qt/SensorView.h +++ b/src/platform/qt/SensorView.h @@ -30,7 +30,7 @@ public: protected: bool eventFilter(QObject*, QEvent* event) override; - bool event(QEvent* event); + bool event(QEvent* event) override; private slots: void updateSensors(); From 0211106a3d89f4f9e02da393517a3fd9275a6b05 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 14 Oct 2015 22:00:36 -0700 Subject: [PATCH 127/127] All: Const-correctness for PPC --- src/util/common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/common.h b/src/util/common.h index be005702d..9ffb1ed22 100644 --- a/src/util/common.h +++ b/src/util/common.h @@ -52,13 +52,13 @@ typedef intptr_t ssize_t; #if defined(__PPC__) || defined(__POWERPC__) #define LOAD_32LE(DEST, ADDR, ARR) { \ uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ + const void* _ptr = (ARR); \ __asm__("lwbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ } #define LOAD_16LE(DEST, ADDR, ARR) { \ uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ + const void* _ptr = (ARR); \ __asm__("lhbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ }