diff --git a/CHANGES b/CHANGES index f6f468d83..177987d58 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,8 @@ Features: - Support Discord Rich Presence - Debugger: Add tracing to file - Map viewer supports bitmapped GBA modes + - OpenGL renderer with high-resolution upscaling support + - Experimental high level "XQ" audio for most GBA games Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs @@ -43,6 +45,9 @@ Emulation fixes: - GBA Memory: Fix writing to OBJ memory in modes 3 and 5 - GBA: Fix RTC on non-standard sized ROMs (fixes mgba.io/i/1400) - GBA Memory: Prevent writing to mirrored BG VRAM (fixes mgba.io/i/743) + - GBA Video: Fix sprite mosaic clamping (fixes mgba.io/i/1008) + - GB: Fix HALT when IE and IF unused bits are set (fixes mgba.io/i/1349) + - GBA Video: Implement mosaic on transformed sprites (fixes mgba.io/b/9) Other fixes: - Qt: More app metadata fixes - Qt: Fix load recent from archive (fixes mgba.io/i/1325) @@ -60,6 +65,10 @@ Other fixes: - Wii: Fix aspect ratio (fixes mgba.io/i/500) - Qt: Fix some Qt display driver race conditions - FFmpeg: Fix audio conversion producing gaps + - Core: Improved lockstep driver reliability (Le Hoang Quyen) + - GBA: Fix skipping BIOS on irregularly sized ROMs + - Qt: Fix bounded fast forward with Qt Multimedia + - Qt: Fix saving settings with native FPS target Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash @@ -79,6 +88,9 @@ Misc: - Qt: Open a message box for Qt frontend errors - GBA Video: Clean up dead code in sprite rendering loop - FFmpeg: Support audio-only recording + - Qt: Increase maximum magnifications and scaling + - Qt: Add native FPS button to settings view + - Qt: Improve sync code 0.7.1: (2019-02-24) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 84c2a9cb6..b15ecec57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ file(GLOB GB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/*.c) file(GLOB GB_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/test/*.c) file(GLOB DS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/*.c) file(GLOB GBA_CHEATS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/cheats/*.c) +file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/audio-mixer.c) file(GLOB GBA_RR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB CORE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/*.c) file(GLOB CORE_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/test/*.c) @@ -261,15 +262,10 @@ if(WIN32) endif() elseif(UNIX) set(USE_PTHREADS ON) - add_definitions(-DUSE_PTHREADS) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_definitions(-D_GNU_SOURCE) endif() - if(NOT APPLE AND NOT HAIKU) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") - endif() list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/posix/*.c) @@ -355,6 +351,7 @@ if(3DS OR WII) add_definitions(-D_GNU_SOURCE) endif() +include(CheckCCompilerFlag) include(CheckFunctionExists) include(CheckIncludeFiles) check_function_exists(strdup HAVE_STRDUP) @@ -406,6 +403,27 @@ endif() check_function_exists(chmod HAVE_CHMOD) check_function_exists(umask HAVE_UMASK) +if(USE_PTHREADS) + check_include_files("pthread.h" HAVE_PTHREAD_H) + if(HAVE_PTHREAD_H) + check_c_compiler_flag(-pthread HAVE_PTHREAD) + if(HAVE_PTHREAD AND NOT APPLE AND NOT HAIKU) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + endif() + + check_function_exists(pthread_create HAVE_PTHREAD_CREATE) + if(HAVE_PTHREAD_CREATE) + add_definitions(-DUSE_PTHREADS) + + check_include_files("pthread_np.h" HAVE_PTHREAD_NP_H) + + check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP) + check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) + endif() + endif() +endif() + set(FUNCTION_DEFINES) if(HAVE_STRDUP) @@ -447,6 +465,18 @@ if(HAVE_UMASK) list(APPEND FUNCTION_DEFINES HAVE_UMASK) endif() +if(HAVE_PTHREAD_NP_H) + list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_NP_H) +endif() + +if(HAVE_PTHREAD_SETNAME_NP) + list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SETNAME_NP) +endif() + +if(HAVE_PTHREAD_SET_NAME_NP) + list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SET_NAME_NP) +endif() + # Feature dependencies set(FEATURE_DEFINES) set(FEATURE_FLAGS) diff --git a/README.md b/README.md index cd7669475..921ba623f 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,6 @@ Footnotes [1] Currently missing features on GBA are - OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5)) -- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9)) Missing features on DS are diff --git a/README_DE.md b/README_DE.md index a41d5fbcd..172b5f826 100644 --- a/README_DE.md +++ b/README_DE.md @@ -215,7 +215,6 @@ Fußnoten [1] Zurzeit fehlende Features sind - OBJ-Fenster für die Modi 3, 4 und 5 ([Bug #5](http://mgba.io/b/5)) -- Mosaik-Effekt für umgewandelte OBJs ([Bug #9](http://mgba.io/b/9)) [2] In manchen Fällen ist es nicht möglich, die Größe des Flash-Speichers automatisch zu ermitteln. Diese kann dann zur Laufzeit konfiguriert werden, es wird jedoch empfohlen, den Fehler zu melden. diff --git a/cinema/gba/obj/mosaic-height/baseline_0000.png b/cinema/gba/obj/mosaic-height/baseline_0000.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0000.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0001.png b/cinema/gba/obj/mosaic-height/baseline_0001.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0001.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0002.png b/cinema/gba/obj/mosaic-height/baseline_0002.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0002.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0003.png b/cinema/gba/obj/mosaic-height/baseline_0003.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0003.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0004.png b/cinema/gba/obj/mosaic-height/baseline_0004.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0004.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0005.png b/cinema/gba/obj/mosaic-height/baseline_0005.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0005.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0006.png b/cinema/gba/obj/mosaic-height/baseline_0006.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0006.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0007.png b/cinema/gba/obj/mosaic-height/baseline_0007.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0007.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0008.png b/cinema/gba/obj/mosaic-height/baseline_0008.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0008.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0009.png b/cinema/gba/obj/mosaic-height/baseline_0009.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0009.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0010.png b/cinema/gba/obj/mosaic-height/baseline_0010.png new file mode 100644 index 000000000..448ac5cee Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0010.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0011.png b/cinema/gba/obj/mosaic-height/baseline_0011.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0011.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0012.png b/cinema/gba/obj/mosaic-height/baseline_0012.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0012.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0013.png b/cinema/gba/obj/mosaic-height/baseline_0013.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0013.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0014.png b/cinema/gba/obj/mosaic-height/baseline_0014.png new file mode 100644 index 000000000..7f04ae705 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0014.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0015.png b/cinema/gba/obj/mosaic-height/baseline_0015.png new file mode 100644 index 000000000..ac9d0e361 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0015.png differ diff --git a/cinema/gba/obj/mosaic-height/baseline_0016.png b/cinema/gba/obj/mosaic-height/baseline_0016.png new file mode 100644 index 000000000..ac9d0e361 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/baseline_0016.png differ diff --git a/cinema/gba/obj/mosaic-height/test.mvl b/cinema/gba/obj/mosaic-height/test.mvl new file mode 100644 index 000000000..3ab153bb2 Binary files /dev/null and b/cinema/gba/obj/mosaic-height/test.mvl differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0001.png b/cinema/gba/window/zmc-window-mosaic/baseline_0001.png index 53aee91ec..2e3825472 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0001.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0001.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0002.png b/cinema/gba/window/zmc-window-mosaic/baseline_0002.png index 237eab692..064470242 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0002.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0002.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0003.png b/cinema/gba/window/zmc-window-mosaic/baseline_0003.png index 05f308fa4..c5eb15ff1 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0003.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0003.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0004.png b/cinema/gba/window/zmc-window-mosaic/baseline_0004.png index 6c8b94d45..2fc7c052f 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0004.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0004.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0005.png b/cinema/gba/window/zmc-window-mosaic/baseline_0005.png index 044cf6ebe..906f6bfbe 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0005.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0005.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0006.png b/cinema/gba/window/zmc-window-mosaic/baseline_0006.png index 13ebb47bc..ce920fee6 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0006.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0006.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0007.png b/cinema/gba/window/zmc-window-mosaic/baseline_0007.png index b3835e1d1..ae9f61bc1 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0007.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0007.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0008.png b/cinema/gba/window/zmc-window-mosaic/baseline_0008.png index ef7b0b212..733106331 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0008.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0008.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0009.png b/cinema/gba/window/zmc-window-mosaic/baseline_0009.png index 35d54bf96..e2d9b1337 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0009.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0009.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0010.png b/cinema/gba/window/zmc-window-mosaic/baseline_0010.png index 8b555e49e..dd985ca72 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0010.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0010.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0011.png b/cinema/gba/window/zmc-window-mosaic/baseline_0011.png index cecd0ff34..fa1bd5e99 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0011.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0011.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0012.png b/cinema/gba/window/zmc-window-mosaic/baseline_0012.png index 4e4165d5e..af7e92a27 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0012.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0012.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0013.png b/cinema/gba/window/zmc-window-mosaic/baseline_0013.png index 3f8d3a57a..b76aeaf57 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0013.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0013.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0014.png b/cinema/gba/window/zmc-window-mosaic/baseline_0014.png index eb1e1c9fe..b7172d5dd 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0014.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0014.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0015.png b/cinema/gba/window/zmc-window-mosaic/baseline_0015.png index 3edbaea2b..5a68060cc 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0015.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0015.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0016.png b/cinema/gba/window/zmc-window-mosaic/baseline_0016.png index d3636525a..ea3fa2a16 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0016.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0016.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0017.png b/cinema/gba/window/zmc-window-mosaic/baseline_0017.png index 2d6086460..1d58c3978 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0017.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0017.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0018.png b/cinema/gba/window/zmc-window-mosaic/baseline_0018.png index c8cd44f8c..dc57ebf94 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0018.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0018.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0019.png b/cinema/gba/window/zmc-window-mosaic/baseline_0019.png index 2d9b989e9..1804399aa 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0019.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0019.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0020.png b/cinema/gba/window/zmc-window-mosaic/baseline_0020.png index 706120316..34b722b31 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0020.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0020.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0021.png b/cinema/gba/window/zmc-window-mosaic/baseline_0021.png index 792199be6..d47eb372f 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0021.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0021.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0022.png b/cinema/gba/window/zmc-window-mosaic/baseline_0022.png index 08a6d3dba..c9039b6ae 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0022.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0022.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0023.png b/cinema/gba/window/zmc-window-mosaic/baseline_0023.png index 57d99fa3c..382444fb8 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0023.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0023.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0024.png b/cinema/gba/window/zmc-window-mosaic/baseline_0024.png index a49291156..53d25bf03 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0024.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0024.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0025.png b/cinema/gba/window/zmc-window-mosaic/baseline_0025.png index 1dfa40cbe..bdc9c1218 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0025.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0025.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0026.png b/cinema/gba/window/zmc-window-mosaic/baseline_0026.png index ce427ed73..128561196 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0026.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0026.png differ diff --git a/cinema/gba/window/zmc-window-mosaic/baseline_0027.png b/cinema/gba/window/zmc-window-mosaic/baseline_0027.png index b9f6dc6f2..d6fcf6694 100644 Binary files a/cinema/gba/window/zmc-window-mosaic/baseline_0027.png and b/cinema/gba/window/zmc-window-mosaic/baseline_0027.png differ diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index 468e1460c..38aac8f62 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -12,7 +12,7 @@ CXX_GUARD_START #include #include -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#ifdef HAVE_PTHREAD_NP_H #include #elif defined(__HAIKU__) #include @@ -85,20 +85,15 @@ static inline int ThreadJoin(Thread thread) { } static inline int ThreadSetName(const char* name) { -#ifdef __APPLE__ -#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 +#if defined(__APPLE__) && defined(HAVE_PTHREAD_SETNAME_NP) return pthread_setname_np(name); -#else - UNUSED(name); - return 0; -#endif -#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_set_name_np(pthread_self(), name); return 0; #elif defined(__HAIKU__) rename_thread(find_thread(NULL), name); return 0; -#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old +#elif defined(HAVE_PTHREAD_SETNAME_NP) return pthread_setname_np(pthread_self(), name); #else UNUSED(name); diff --git a/include/mgba/core/cpu.h b/include/mgba/core/cpu.h index fe6caedd3..71f3398c2 100644 --- a/include/mgba/core/cpu.h +++ b/include/mgba/core/cpu.h @@ -13,6 +13,10 @@ CXX_GUARD_START enum mCPUComponentType { CPU_COMPONENT_DEBUGGER, CPU_COMPONENT_CHEAT_DEVICE, + CPU_COMPONENT_MISC_1, + CPU_COMPONENT_MISC_2, + CPU_COMPONENT_MISC_3, + CPU_COMPONENT_MISC_4, CPU_COMPONENT_MAX }; diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index 10f5a7e65..f08f845b6 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -10,10 +10,14 @@ CXX_GUARD_START +#include #include #include #include +#define MP2K_MAGIC 0x68736D53 +#define MP2K_MAX_SOUND_CHANNELS 12 + mLOG_DECLARE_CATEGORY(GBA_AUDIO); struct GBADMA; @@ -44,6 +48,7 @@ DECL_BITFIELD(GBARegisterSOUNDBIAS, uint16_t); DECL_BITS(GBARegisterSOUNDBIAS, Bias, 0, 10); DECL_BITS(GBARegisterSOUNDBIAS, Resolution, 14, 2); +struct GBAAudioMixer; struct GBAAudio { struct GBA* p; @@ -71,6 +76,8 @@ struct GBAAudio { GBARegisterSOUNDBIAS soundbias; + struct GBAAudioMixer* mixer; + bool externalMixing; int32_t sampleInterval; bool forceDisableChA; @@ -85,6 +92,188 @@ struct GBAStereoSample { int16_t right; }; +struct GBAMP2kADSR { + uint8_t attack; + uint8_t decay; + uint8_t sustain; + uint8_t release; +}; + +struct GBAMP2kSoundChannel { + uint8_t status; + uint8_t type; + uint8_t rightVolume; + uint8_t leftVolume; + struct GBAMP2kADSR adsr; + uint8_t ky; + uint8_t envelopeV; + uint8_t envelopeRight; + uint8_t envelopeLeft; + uint8_t echoVolume; + uint8_t echoLength; + uint8_t d1; + uint8_t d2; + uint8_t gt; + uint8_t midiKey; + uint8_t ve; + uint8_t pr; + uint8_t rp; + uint8_t d3[3]; + uint32_t ct; + uint32_t fw; + uint32_t freq; + uint32_t waveData; + uint32_t cp; + uint32_t track; + uint32_t pp; + uint32_t np; + uint32_t d4; + uint16_t xpi; + uint16_t xpc; +}; + +struct GBAMP2kContext { + uint32_t magic; + uint8_t pcmDmaCounter; + uint8_t reverb; + uint8_t maxChans; + uint8_t masterVolume; + uint8_t freq; + uint8_t mode; + uint8_t c15; + uint8_t pcmDmaPeriod; + uint8_t maxLines; + uint8_t gap[3]; + int32_t pcmSamplesPerVBlank; + int32_t pcmFreq; + int32_t divFreq; + uint32_t cgbChans; + uint32_t func; + uint32_t intp; + uint32_t cgbSound; + uint32_t cgbOscOff; + uint32_t midiKeyToCgbFreq; + uint32_t mPlayJumpTable; + uint32_t plynote; + uint32_t extVolPit; + uint8_t gap2[16]; + struct GBAMP2kSoundChannel chans[MP2K_MAX_SOUND_CHANNELS]; +}; + +struct GBAMP2kMusicPlayerInfo { + uint32_t songHeader; + uint32_t status; + uint8_t trackCount; + uint8_t priority; + uint8_t cmd; + uint8_t unk_B; + uint32_t clock; + uint8_t gap[8]; + uint32_t memAccArea; + uint16_t tempoD; + uint16_t tempoU; + uint16_t tempoI; + uint16_t tempoC; + uint16_t fadeOI; + uint16_t fadeOC; + uint16_t fadeOV; + uint32_t tracks; + uint32_t tone; + uint32_t magic; + uint32_t func; + uint32_t intp; +}; + +struct GBAMP2kInstrument { + uint8_t type; + uint8_t key; + uint8_t length; + union { + uint8_t pan; + uint8_t sweep; + } ps; + union { + uint32_t waveData; + uint32_t subTable; + } data; + union { + struct GBAMP2kADSR adsr; + uint32_t map; + } extInfo; +}; + +struct GBAMP2kMusicPlayerTrack { + uint8_t flags; + uint8_t wait; + uint8_t patternLevel; + uint8_t repN; + uint8_t gateTime; + uint8_t key; + uint8_t velocity; + uint8_t runningStatus; + uint8_t keyM; + uint8_t pitM; + int8_t keyShift; + int8_t keyShiftX; + int8_t tune; + uint8_t pitX; + int8_t bend; + uint8_t bendRange; + uint8_t volMR; + uint8_t volML; + uint8_t vol; + uint8_t volX; + int8_t pan; + int8_t panX; + int8_t modM; + uint8_t mod; + uint8_t modT; + uint8_t lfoSpeed; + uint8_t lfoSpeedC; + uint8_t lfoDelay; + uint8_t lfoDelayC; + uint8_t priority; + uint8_t echoVolume; + uint8_t echoLength; + uint32_t chan; + struct GBAMP2kInstrument instrument; + uint8_t gap[10]; + uint16_t unk_3A; + uint32_t unk_3C; + uint32_t cmdPtr; + uint32_t patternStack[3]; +}; + +struct GBAMP2kTrack { + struct GBAMP2kMusicPlayerTrack track; + struct GBAMP2kSoundChannel* channel; + uint8_t lastCommand; + struct CircleBuffer buffer; + uint32_t samplePlaying; + float currentOffset; + bool waiting; +}; + +struct GBAAudioMixer { + struct mCPUComponent d; + struct GBAAudio* p; + + uint32_t contextAddress; + + bool (*engage)(struct GBAAudioMixer* mixer, uint32_t address); + void (*vblank)(struct GBAAudioMixer* mixer); + void (*step)(struct GBAAudioMixer* mixer); + + struct GBAMP2kContext context; + struct GBAMP2kMusicPlayerInfo player; + struct GBAMP2kTrack activeTracks[MP2K_MAX_SOUND_CHANNELS]; + + double tempo; + double frame; + + struct GBAStereoSample last; +}; + void GBAAudioInit(struct GBAAudio* audio, size_t samples); void GBAAudioReset(struct GBAAudio* audio); void GBAAudioDeinit(struct GBAAudio* audio); diff --git a/include/mgba/internal/gba/extra/audio-mixer.h b/include/mgba/internal/gba/extra/audio-mixer.h new file mode 100644 index 000000000..369a4aaa3 --- /dev/null +++ b/include/mgba/internal/gba/extra/audio-mixer.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2013-2017 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 GBA_AUDIO_MIXER_H +#define GBA_AUDIO_MIXER_H + +#include + +CXX_GUARD_START + +#include + +void GBAAudioMixerCreate(struct GBAAudioMixer* mixer); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 209607f1c..cf53c6566 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -62,20 +62,25 @@ struct GBAVideoGLBackground { int32_t refx; int32_t refy; - struct GBAVideoGLAffine affine[4]; + struct GBAVideoGLAffine affine; }; enum { GBA_GL_FBO_OBJ = 0, - GBA_GL_FBO_WINDOW = 1, - GBA_GL_FBO_OUTPUT = 2, + GBA_GL_FBO_BACKDROP, + GBA_GL_FBO_WINDOW, + GBA_GL_FBO_OUTPUT, GBA_GL_FBO_MAX }; enum { GBA_GL_TEX_OBJ_COLOR = 0, - GBA_GL_TEX_OBJ_FLAGS = 1, - GBA_GL_TEX_WINDOW = 6, + GBA_GL_TEX_OBJ_FLAGS, + GBA_GL_TEX_BACKDROP_COLOR, + GBA_GL_TEX_BACKDROP_FLAGS, + GBA_GL_TEX_WINDOW, + GBA_GL_TEX_AFFINE_2, + GBA_GL_TEX_AFFINE_3, GBA_GL_TEX_MAX }; @@ -91,6 +96,8 @@ enum { GBA_GL_BG_OFFSET, GBA_GL_BG_INFLAGS, GBA_GL_BG_TRANSFORM, + GBA_GL_BG_RANGE, + GBA_GL_BG_MOSAIC, GBA_GL_OBJ_VRAM = 2, GBA_GL_OBJ_PALETTE, @@ -101,6 +108,7 @@ enum { GBA_GL_OBJ_TRANSFORM, GBA_GL_OBJ_DIMS, GBA_GL_OBJ_OBJWIN, + GBA_GL_OBJ_MOSAIC, GBA_GL_FINALIZE_SCALE = 2, GBA_GL_FINALIZE_LAYERS, @@ -124,6 +132,7 @@ struct GBAVideoGLRenderer { uint32_t* temporaryBuffer; struct GBAVideoGLBackground bg[4]; + struct GBAVideoGLAffine affine[2][GBA_VIDEO_VERTICAL_PIXELS]; int oamMax; bool oamDirty; @@ -144,6 +153,9 @@ struct GBAVideoGLRenderer { GLuint vramTex; unsigned vramDirty; + uint16_t shadowRegs[0x30]; + uint64_t regsDirty; + struct GBAVideoGLShader bgShader[6]; struct GBAVideoGLShader objShader[2]; struct GBAVideoGLShader finalizeShader; @@ -162,7 +174,7 @@ struct GBAVideoGLRenderer { GBAMosaicControl mosaic; struct GBAVideoGLWindowN { - struct GBAVideoWindowRegion h; + struct GBAVideoWindowRegion h[2]; struct GBAVideoWindowRegion v; GBAWindowControl control; } winN[2]; @@ -171,6 +183,7 @@ struct GBAVideoGLRenderer { GBAWindowControl objwin; int firstAffine; + int firstY; int scale; }; diff --git a/src/feature/thread-proxy.c b/src/feature/thread-proxy.c index 7a67dbedd..6dca713ca 100644 --- a/src/feature/thread-proxy.c +++ b/src/feature/thread-proxy.c @@ -153,10 +153,12 @@ static void _wait(struct mVideoLogger* logger) { _proxyThreadRecover(proxyRenderer); return; } + MutexLock(&proxyRenderer->mutex); while (RingFIFOSize(&proxyRenderer->dirtyQueue)) { ConditionWake(&proxyRenderer->toThreadCond); ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); } + MutexUnlock(&proxyRenderer->mutex); } static void _unlock(struct mVideoLogger* logger) { diff --git a/src/gb/extra/proxy.c b/src/gb/extra/proxy.c index d8538124d..03119226e 100644 --- a/src/gb/extra/proxy.c +++ b/src/gb/extra/proxy.c @@ -250,31 +250,21 @@ void GBVideoProxyRendererFinishScanline(struct GBVideoRenderer* renderer, int y) void GBVideoProxyRendererFinishFrame(struct GBVideoRenderer* renderer) { struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); - } if (!proxyRenderer->logger->block) { proxyRenderer->backend->finishFrame(proxyRenderer->backend); } mVideoLoggerRendererFinishFrame(proxyRenderer->logger); mVideoLoggerRendererFlush(proxyRenderer->logger); - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->unlock(proxyRenderer->logger); - } } static void GBVideoProxyRendererEnableSGBBorder(struct GBVideoRenderer* renderer, bool enable) { struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); // Insert an extra item into the queue to make sure it gets flushed mVideoLoggerRendererFlush(proxyRenderer->logger); proxyRenderer->logger->wait(proxyRenderer->logger); } proxyRenderer->backend->enableSGBBorder(proxyRenderer->backend, enable); - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->unlock(proxyRenderer->logger); - } } static void GBVideoProxyRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) { @@ -297,9 +287,6 @@ static void GBVideoProxyRendererPutPixels(struct GBVideoRenderer* renderer, size struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { proxyRenderer->logger->lock(proxyRenderer->logger); - // Insert an extra item into the queue to make sure it gets flushed - mVideoLoggerRendererFlush(proxyRenderer->logger); - proxyRenderer->logger->wait(proxyRenderer->logger); } proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { diff --git a/src/gb/gb.c b/src/gb/gb.c index fc3c174b0..5f5178a90 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -723,7 +723,7 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle void GBHalt(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; - if (!(gb->memory.ie & gb->memory.io[REG_IF])) { + if (!(gb->memory.ie & gb->memory.io[REG_IF] & 0x1F)) { cpu->cycles = cpu->nextEvent; cpu->halted = true; } else if (gb->model < GB_MODEL_CGB) { diff --git a/src/gba/audio.c b/src/gba/audio.c index c0ed7cf4c..d2650d6e8 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -14,6 +14,8 @@ #include #include +#define MP2K_LOCK_MAX 8 + #ifdef _3DS #define blip_add_delta blip_add_delta_fast #endif @@ -24,7 +26,7 @@ const unsigned GBA_AUDIO_SAMPLES = 2048; const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t); const int GBA_AUDIO_VOLUME_MAX = 0x100; -static const int CLOCKS_PER_FRAME = 0x400; +static const int CLOCKS_PER_FRAME = 0x800; static int _applyBias(struct GBAAudio* audio, int sample); static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate); @@ -49,9 +51,11 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); + audio->externalMixing = false; audio->forceDisableChA = false; audio->forceDisableChB = false; audio->masterVolume = GBA_AUDIO_VOLUME_MAX; + audio->mixer = NULL; } void GBAAudioReset(struct GBAAudio* audio) { @@ -111,6 +115,20 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } + uint32_t source = info->source; + uint32_t magic[2] = { + audio->p->cpu->memory.load32(audio->p->cpu, source - 0x350, NULL), + audio->p->cpu->memory.load32(audio->p->cpu, source - 0x980, NULL) + }; + if (audio->mixer) { + if (magic[0] - MP2K_MAGIC <= MP2K_LOCK_MAX) { + audio->mixer->engage(audio->mixer, source - 0x350); + } else if (magic[1] - MP2K_MAGIC <= MP2K_LOCK_MAX) { + audio->mixer->engage(audio->mixer, source - 0x980); + } else { + audio->externalMixing = false; + } + } info->reg = GBADMARegisterSetDestControl(info->reg, GBA_DMA_FIXED); info->reg = GBADMARegisterSetWidth(info->reg, 1); } @@ -265,23 +283,28 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { sampleLeft >>= psgShift; sampleRight >>= psgShift; - if (!audio->forceDisableChA) { - if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; - } - - if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; - } + if (audio->mixer) { + audio->mixer->step(audio->mixer); } + if (!audio->externalMixing) { + if (!audio->forceDisableChA) { + if (audio->chALeft) { + sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + } - if (!audio->forceDisableChB) { - if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + if (audio->chARight) { + sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + } } - if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + if (!audio->forceDisableChB) { + if (audio->chBLeft) { + sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + } + + if (audio->chBRight) { + sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + } } } diff --git a/src/gba/core.c b/src/gba/core.c index 50e6ab375..2ddb592e5 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #ifndef DISABLE_THREADING @@ -127,6 +128,9 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { }; struct mVideoLogContext; + +#define CPU_COMPONENT_AUDIO_MIXER CPU_COMPONENT_MISC_1 + struct GBACore { struct mCore d; struct GBAVideoSoftwareRenderer renderer; @@ -144,6 +148,7 @@ struct GBACore { const struct Configuration* overrides; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; + struct GBAAudioMixer* audioMixer; }; static bool _GBACoreInit(struct mCore* core) { @@ -166,6 +171,7 @@ static bool _GBACoreInit(struct mCore* core) { gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; gbacore->logContext = NULL; + gbacore->audioMixer = NULL; GBACreate(gba); // TODO: Restore cheats @@ -217,6 +223,7 @@ static void _GBACoreDeinit(struct mCore* core) { mCheatDeviceDestroy(gbacore->cheatDevice); } free(gbacore->cheatDevice); + free(gbacore->audioMixer); mCoreConfigFreeOpts(&core->opts); free(core); } @@ -280,6 +287,7 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections"); mCoreConfigCopyValue(&core->config, config, "gba.bios"); + mCoreConfigCopyValue(&core->config, config, "gba.audioHle"); #ifndef DISABLE_THREADING mCoreConfigCopyValue(&core->config, config, "threadedVideo"); @@ -475,6 +483,16 @@ static void _GBACoreReset(struct mCore* core) { GBAVideoAssociateRenderer(&gba->video, renderer); } +#ifndef MINIMAL_CORE + int useAudioMixer; + if (!gbacore->audioMixer && mCoreConfigGetIntValue(&core->config, "gba.audioHle", &useAudioMixer) && useAudioMixer) { + gbacore->audioMixer = malloc(sizeof(*gbacore->audioMixer)); + GBAAudioMixerCreate(gbacore->audioMixer); + ((struct ARMCore*) core->cpu)->components[CPU_COMPONENT_AUDIO_MIXER] = &gbacore->audioMixer->d; + ARMHotplugAttach(core->cpu, CPU_COMPONENT_AUDIO_MIXER); + } +#endif + GBAOverrideApplyDefaults(gba, gbacore->overrides); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 @@ -521,7 +539,7 @@ static void _GBACoreReset(struct mCore* core) { #endif ARMReset(core->cpu); - if (core->opts.skipBios && gba->isPristine) { + if (core->opts.skipBios && (gba->romVf || gba->memory.rom)) { GBASkipBIOS(core->board); } } diff --git a/src/gba/extra/audio-mixer.c b/src/gba/extra/audio-mixer.c new file mode 100644 index 000000000..85941536e --- /dev/null +++ b/src/gba/extra/audio-mixer.c @@ -0,0 +1,307 @@ +/* Copyright (c) 2013-2017 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 + +#include +#include +#include + +#define OVERSAMPLE 2 + +static void _mp2kInit(void* cpu, struct mCPUComponent* component); +static void _mp2kDeinit(struct mCPUComponent* component); + +static bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address); +static void _mp2kVblank(struct GBAAudioMixer* mixer); +static void _mp2kStep(struct GBAAudioMixer* mixer); + +void GBAAudioMixerCreate(struct GBAAudioMixer* mixer) { + mixer->d.init = _mp2kInit; + mixer->d.deinit = _mp2kDeinit; + mixer->engage = _mp2kEngage; + mixer->vblank = _mp2kVblank; + mixer->step = _mp2kStep; +} + +void _mp2kInit(void* cpu, struct mCPUComponent* component) { + struct ARMCore* arm = cpu; + struct GBA* gba = (struct GBA*) arm->master; + struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component; + gba->audio.mixer = mixer; + mixer->p = &gba->audio; + mixer->contextAddress = 0; + mixer->tempo = 120.0 / 75.0; + mixer->frame = 0; + mixer->last.left = 0; + mixer->last.right = 0; + memset(&mixer->context, 0, sizeof(mixer->context)); + memset(&mixer->activeTracks, 0, sizeof(mixer->activeTracks)); + + size_t i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + mixer->activeTracks[i].channel = &mixer->context.chans[i]; + CircleBufferInit(&mixer->activeTracks[i].buffer, 0x10000); + } +} + +void _mp2kDeinit(struct mCPUComponent* component) { + struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component; + size_t i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + CircleBufferDeinit(&mixer->activeTracks[i].buffer); + } +} + +static void _loadInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint32_t base) { + struct ARMMemory* memory = &cpu->memory; + instrument->type = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, type), 0); + instrument->key = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, key), 0); + instrument->length = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, length), 0); + instrument->ps.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, ps.pan), 0); + if (instrument->type == 0x40 || instrument->type == 0x80) { + instrument->data.subTable = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.subTable), 0); + instrument->extInfo.map = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.map), 0); + } else { + instrument->data.waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.waveData), 0); + instrument->extInfo.adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.attack), 0); + instrument->extInfo.adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.decay), 0); + instrument->extInfo.adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.sustain), 0); + instrument->extInfo.adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.release), 0); + } +} + +static void _lookupInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint8_t key) { + struct ARMMemory* memory = &cpu->memory; + if (instrument->type == 0x40) { + uint32_t subInstrumentBase = instrument->data.subTable; + uint32_t keyTable = instrument->extInfo.map; + uint8_t id = memory->load8(cpu, keyTable + key, 0); + subInstrumentBase += 12 * id; + _loadInstrument(cpu, instrument, subInstrumentBase); + } + if (instrument->type == 0x80) { + uint32_t subInstrumentBase = instrument->data.subTable; + subInstrumentBase += 12 * key; + _loadInstrument(cpu, instrument, subInstrumentBase); + } +} + +static void _stepSample(struct GBAAudioMixer* mixer, struct GBAMP2kTrack* track) { + struct ARMCore* cpu = mixer->p->p->cpu; + struct ARMMemory* memory = &cpu->memory; + uint32_t headerAddress; + struct GBAMP2kInstrument instrument = track->track.instrument; + + uint8_t note = track->track.key; + _lookupInstrument(cpu, &instrument, note); + double freq; + + switch (instrument.type) { + case 0x00: + case 0x08: + case 0x40: + case 0x80: + freq = GBA_ARM7TDMI_FREQUENCY / (double) track->channel->freq; + break; + default: + // We don't care about PSG channels + return; + } + headerAddress = instrument.data.waveData; + if (headerAddress < 0x20) { + mLOG(GBA_AUDIO, ERROR, "Audio track has invalid instrument"); + return; + } + uint32_t loopOffset = memory->load32(cpu, headerAddress + 0x8, 0); + uint32_t endOffset = memory->load32(cpu, headerAddress + 0xC, 0); + uint32_t sampleBase = headerAddress + 0x10; + uint32_t sampleI = track->samplePlaying; + double sampleOffset = track->currentOffset; + double updates = VIDEO_TOTAL_LENGTH / (mixer->tempo * mixer->p->sampleInterval / OVERSAMPLE); + int nSample; + for (nSample = 0; nSample < updates; ++nSample) { + int8_t sample = memory->load8(cpu, sampleBase + sampleI, 0); + + struct GBAStereoSample stereo = { + (sample * track->channel->leftVolume * track->channel->envelopeV) >> 9, + (sample * track->channel->rightVolume * track->channel->envelopeV) >> 9 + }; + + CircleBufferWrite16(&track->buffer, stereo.left); + CircleBufferWrite16(&track->buffer, stereo.right); + + sampleOffset += mixer->p->sampleInterval / OVERSAMPLE; + while (sampleOffset > freq) { + sampleOffset -= freq; + ++sampleI; + if (sampleI >= endOffset) { + sampleI = loopOffset; + } + } + } + + track->samplePlaying = sampleI; + track->currentOffset = sampleOffset; +} + +static void _mp2kReload(struct GBAAudioMixer* mixer) { + struct ARMCore* cpu = mixer->p->p->cpu; + struct ARMMemory* memory = &cpu->memory; + mixer->context.magic = memory->load32(cpu, mixer->contextAddress + offsetof(struct GBAMP2kContext, magic), 0); + int i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + struct GBAMP2kSoundChannel* ch = &mixer->context.chans[i]; + struct GBAMP2kTrack* track = &mixer->activeTracks[i]; + track->waiting = false; + uint32_t base = mixer->contextAddress + offsetof(struct GBAMP2kContext, chans[i]); + + ch->status = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, status), 0); + ch->type = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, type), 0); + ch->rightVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rightVolume), 0); + ch->leftVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, leftVolume), 0); + ch->adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.attack), 0); + ch->adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.decay), 0); + ch->adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.sustain), 0); + ch->adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.release), 0); + ch->ky = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ky), 0); + ch->envelopeV = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeV), 0); + ch->envelopeRight = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeRight), 0); + ch->envelopeLeft = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeLeft), 0); + ch->echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoVolume), 0); + ch->echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoLength), 0); + ch->d1 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d1), 0); + ch->d2 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d2), 0); + ch->gt = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, gt), 0); + ch->midiKey = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, midiKey), 0); + ch->ve = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ve), 0); + ch->pr = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, pr), 0); + ch->rp = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rp), 0); + ch->d3[0] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[0]), 0); + ch->d3[1] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[1]), 0); + ch->d3[2] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[2]), 0); + ch->ct = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, ct), 0); + ch->fw = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, fw), 0); + ch->freq = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, freq), 0); + ch->waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, waveData), 0); + ch->cp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, cp), 0); + ch->track = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, track), 0); + ch->pp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, pp), 0); + ch->np = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, np), 0); + ch->d4 = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, d4), 0); + ch->xpi = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpi), 0); + ch->xpc = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpc), 0); + + base = ch->track; + if (base) { + track->track.flags = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, flags), 0); + track->track.wait = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, wait), 0); + track->track.patternLevel = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternLevel), 0); + track->track.repN = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, repN), 0); + track->track.gateTime = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, gateTime), 0); + track->track.key = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, key), 0); + track->track.velocity = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, velocity), 0); + track->track.runningStatus = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, runningStatus), 0); + track->track.keyM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyM), 0); + track->track.pitM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitM), 0); + track->track.keyShift = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShift), 0); + track->track.keyShiftX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShiftX), 0); + track->track.tune = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, tune), 0); + track->track.pitX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitX), 0); + track->track.bend = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bend), 0); + track->track.bendRange = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bendRange), 0); + track->track.volMR = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volMR), 0); + track->track.volML = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volML), 0); + track->track.vol = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, vol), 0); + track->track.volX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volX), 0); + track->track.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pan), 0); + track->track.panX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, panX), 0); + track->track.modM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modM), 0); + track->track.mod = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, mod), 0); + track->track.modT = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modT), 0); + track->track.lfoSpeed = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeed), 0); + track->track.lfoSpeedC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeedC), 0); + track->track.lfoDelay = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelay), 0); + track->track.lfoDelayC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelayC), 0); + track->track.priority = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, priority), 0); + track->track.echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoVolume), 0); + track->track.echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoLength), 0); + track->track.chan = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, chan), 0); + _loadInstrument(cpu, &track->track.instrument, base + offsetof(struct GBAMP2kMusicPlayerTrack, instrument)); + track->track.cmdPtr = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, cmdPtr), 0); + track->track.patternStack[0] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[0]), 0); + track->track.patternStack[1] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[1]), 0); + track->track.patternStack[2] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[2]), 0); + } else { + memset(&track->track, 0, sizeof(track->track)); + } + if (track->track.runningStatus == 0xCD) { + // XCMD isn't supported + mixer->p->externalMixing = false; + } + } +} + +bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) { + if (address < BASE_WORKING_RAM) { + return false; + } + if (address != mixer->contextAddress) { + mixer->contextAddress = address; + mixer->p->externalMixing = true; + _mp2kReload(mixer); + } + return true; +} + +void _mp2kStep(struct GBAAudioMixer* mixer) { + mixer->frame += mixer->p->sampleInterval; + + while (mixer->frame >= VIDEO_TOTAL_LENGTH / mixer->tempo) { + int i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + struct GBAMP2kTrack* track = &mixer->activeTracks[i]; + if (track->channel->status > 0) { + _stepSample(mixer, track); + } else { + track->currentOffset = 0; + track->samplePlaying = 0; + CircleBufferClear(&track->buffer); + } + } + mixer->frame -= VIDEO_TOTAL_LENGTH / mixer->tempo; + } + + uint32_t interval = mixer->p->sampleInterval / OVERSAMPLE; + int i; + for (i = 0; i < OVERSAMPLE; ++i) { + struct GBAStereoSample sample = {0}; + size_t track; + for (track = 0; track < MP2K_MAX_SOUND_CHANNELS; ++track) { + if (!mixer->activeTracks[track].channel->status) { + continue; + } + int16_t value; + CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); + sample.left += value; + CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); + sample.right += value; + } + if (mixer->p->externalMixing) { + blip_add_delta(mixer->p->psg.left, mixer->p->clock + i * interval, sample.left - mixer->last.left); + blip_add_delta(mixer->p->psg.right, mixer->p->clock + i * interval, sample.left - mixer->last.left); + } + mixer->last = sample; + } +} + +void _mp2kVblank(struct GBAAudioMixer* mixer) { + if (!mixer->contextAddress) { + return; + } + mLOG(GBA_AUDIO, DEBUG, "Frame"); + mixer->p->externalMixing = true; + _mp2kReload(mixer); +} diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c index 59c270224..ae16d184a 100644 --- a/src/gba/extra/proxy.c +++ b/src/gba/extra/proxy.c @@ -147,6 +147,7 @@ void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) { proxyRenderer->backend->deinit(proxyRenderer->backend); } else { proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_DEINIT); + mVideoLoggerRendererFlush(proxyRenderer->logger); } mVideoLoggerRendererDeinit(proxyRenderer->logger); @@ -307,28 +308,20 @@ void GBAVideoProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); - } if (!proxyRenderer->logger->block) { proxyRenderer->backend->finishFrame(proxyRenderer->backend); } mVideoLoggerRendererFinishFrame(proxyRenderer->logger); mVideoLoggerRendererFlush(proxyRenderer->logger); - if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->unlock(proxyRenderer->logger); - } } static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { - proxyRenderer->logger->lock(proxyRenderer->logger); // Insert an extra item into the queue to make sure it gets flushed mVideoLoggerRendererFlush(proxyRenderer->logger); proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_GET_PIXELS); mVideoLoggerRendererFlush(proxyRenderer->logger); - proxyRenderer->logger->unlock(proxyRenderer->logger); *pixels = proxyRenderer->logger->pixelBuffer; *stride = proxyRenderer->logger->pixelStride; } else { @@ -340,8 +333,6 @@ static void GBAVideoProxyRendererPutPixels(struct GBAVideoRenderer* renderer, si struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { proxyRenderer->logger->lock(proxyRenderer->logger); - // Insert an extra item into the queue to make sure it gets flushed - mVideoLoggerRendererFlush(proxyRenderer->logger); } proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { diff --git a/src/gba/gba.c b/src/gba/gba.c index 0c445495e..dddf03a00 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -241,10 +241,6 @@ void GBAReset(struct ARMCore* cpu) { if (gba->pristineRomSize > SIZE_CART0) { GBAMatrixReset(gba); } - - if (!gba->romVf && gba->memory.rom) { - GBASkipBIOS(gba); - } } void GBASkipBIOS(struct GBA* gba) { @@ -788,6 +784,10 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { void GBAFrameStarted(struct GBA* gba) { GBATestKeypadIRQ(gba); + if (gba->audio.mixer) { + gba->audio.mixer->vblank(gba->audio.mixer); + } + size_t c; for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) { struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c); diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 58248ca89..e613535aa 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -13,6 +13,8 @@ #include #include +#define FLAG_CONST "const vec4 flagCoeff = vec4(64., 32., 16., 1.);\n" + static void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer); static void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer); @@ -41,15 +43,20 @@ static void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* ren static void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y); static void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y); -static void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y); +static void _cleanRegister(struct GBAVideoGLRenderer* renderer, int address, uint16_t value); +static void _drawScanlines(struct GBAVideoGLRenderer* renderer, int lastY); +static void _finalizeLayers(struct GBAVideoGLRenderer* renderer); -#define TEST_LAYER_ENABLED(X) !renderer->disableBG[X] && glRenderer->bg[X].enabled == 4 +#define TEST_LAYER_ENABLED(X) !glRenderer->d.disableBG[X] && glRenderer->bg[X].enabled == 4 struct GBAVideoGLUniform { const char* name; int type; }; +static const GLchar* const _gles3Header = + "#version 300\n"; + static const GLchar* const _gl3Header = "#version 130\n"; @@ -102,6 +109,7 @@ static const struct GBAVideoGLUniform _uniformsMode0[] = { { "size", GBA_GL_BG_SIZE, }, { "offset", GBA_GL_BG_OFFSET, }, { "inflags", GBA_GL_BG_INFLAGS, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; @@ -114,14 +122,22 @@ static const char* const _renderMode0 = "uniform int size;\n" "uniform ivec2 offset;\n" "uniform ivec4 inflags;\n" + "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + FLAG_CONST "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" "void main() {\n" - " ivec2 coord = ivec2(texCoord) + offset;\n" + " ivec2 coord = ivec2(texCoord);\n" + " if (mosaic.x > 1) {\n" + " coord.x -= int(mod(coord.x, mosaic.x));\n" + " }\n" + " if (mosaic.y > 1) {\n" + " coord.y -= int(mod(coord.y, mosaic.y));\n" + " }\n" + " coord += offset;\n" " if ((size & 1) == 1) {\n" " coord.y += coord.x & 256;\n" " }\n" @@ -168,9 +184,79 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = { { "inflags", GBA_GL_BG_INFLAGS, }, { "offset", GBA_GL_BG_OFFSET, }, { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, { 0 } }; +static const char* const _interpolate = + "vec2 interpolate(ivec2 arr[4], float x) {\n" + " float x1m = 1. - x;\n" + " return x1m * x1m * x1m * arr[0] +" + " 3 * x1m * x1m * x * arr[1] +" + " 3 * x1m * x * x * arr[2] +" + " x * x * x * arr[3];\n" + "}\n" + + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]) {\n" + " int start = max(range.x, y - 3);\n" + " ivec4 splitOffset[4];\n" + + " mat[0] = texelFetch(transform, ivec2(0, start), 0).xz;\n" + " mat[1] = texelFetch(transform, ivec2(0, start + 1), 0).xz;\n" + " mat[2] = texelFetch(transform, ivec2(0, start + 2), 0).xz;\n" + " mat[3] = texelFetch(transform, ivec2(0, start + 3), 0).xz;\n" + + " splitOffset[0] = texelFetch(transform, ivec2(1, start + 0), 0);\n" + " splitOffset[1] = texelFetch(transform, ivec2(1, start + 1), 0);\n" + " splitOffset[2] = texelFetch(transform, ivec2(1, start + 2), 0);\n" + " splitOffset[3] = texelFetch(transform, ivec2(1, start + 3), 0);\n" + + " aff[0] = (splitOffset[0].xz & 0xFFFF) + (splitOffset[0].yw << 16);\n" + " aff[1] = (splitOffset[1].xz & 0xFFFF) + (splitOffset[1].yw << 16);\n" + " aff[2] = (splitOffset[2].xz & 0xFFFF) + (splitOffset[2].yw << 16);\n" + " aff[3] = (splitOffset[3].xz & 0xFFFF) + (splitOffset[3].yw << 16);\n" + + " if (y - 3 < range.x) {\n" + " ivec2 tempMat[3];\n" + " ivec2 tempAff[3];\n" + " tempMat[0] = ivec2(interpolate(mat, -0.75));\n" + " tempMat[1] = ivec2(interpolate(mat, -0.5));\n" + " tempMat[2] = ivec2(interpolate(mat, -0.25));\n" + " tempAff[0] = ivec2(interpolate(aff, -0.75));\n" + " tempAff[1] = ivec2(interpolate(aff, -0.5));\n" + " tempAff[2] = ivec2(interpolate(aff, -0.25));\n" + " if (range.x == y) {\n" + " mat[3] = mat[0];\n" + " mat[2] = tempMat[2];\n" + " mat[1] = tempMat[1];\n" + " mat[0] = tempMat[0];\n" + " aff[3] = aff[0];\n" + " aff[2] = tempAff[2];\n" + " aff[1] = tempAff[1];\n" + " aff[0] = tempAff[0];\n" + " } else if (range.x == y - 1) {\n" + " mat[3] = mat[1];\n" + " mat[2] = mat[0];\n" + " mat[1] = tempMat[2];\n" + " mat[0] = tempMat[1];\n" + " aff[3] = aff[1];\n" + " aff[2] = aff[0];\n" + " aff[1] = tempAff[2];\n" + " aff[0] = tempAff[1];\n" + " } else if (range.x == y - 2) {\n" + " mat[3] = mat[2];\n" + " mat[2] = mat[1];\n" + " mat[1] = mat[0];\n" + " mat[0] = tempMat[0];\n" + " aff[3] = aff[2];\n" + " aff[2] = aff[1];\n" + " aff[1] = aff[0];\n" + " aff[0] = tempAff[0];\n" + " }\n" + " }\n" + "}\n"; + static const char* const _renderMode2 = "in vec2 texCoord;\n" "uniform sampler2D vram;\n" @@ -179,15 +265,18 @@ static const char* const _renderMode2 = "uniform int charBase;\n" "uniform int size;\n" "uniform ivec4 inflags;\n" - "uniform ivec2[4] offset;\n" - "uniform ivec2[4] transform;\n" + "uniform isampler2D transform;\n" + "uniform ivec2 range;\n" + "uniform ivec2 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + FLAG_CONST "precision highp float;\n" "precision highp int;\n" "vec4 fetchTile(ivec2 coord);\n" + "vec2 interpolate(ivec2 arr[4], float x);\n" + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" "vec4 renderTile(ivec2 coord) {\n" " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n" @@ -206,20 +295,147 @@ static const char* const _renderMode2 = " return color;\n" "}\n" - "vec2 interpolate(ivec2 arr[4], float x) {\n" - " float x1m = 1. - x;\n" - " return x1m * x1m * x1m * arr[0] +" - " 3 * x1m * x1m * x * arr[1] +" - " 3 * x1m * x * x * arr[2] +" - " x * x * x * arr[3];\n" - "}\n" + "void main() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " loadAffine(int(texCoord.y), mat, offset);\n" + " vec2 coord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " coord.x -= mod(coord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " coord.y -= mod(coord.y, mosaic.y);\n" + " }\n" + " float y = fract(coord.y);\n" + " float lin = 0.75 + y * 0.25;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" + " vec2 mixedOffset = interpolate(offset, lin);\n" + " color = fetchTile(ivec2(mixedTransform * coord.x + mixedOffset));\n" + " flags = inflags / flagCoeff;\n" + "}"; + +static const struct GBAVideoGLUniform _uniformsMode35[] = { + { "loc", GBA_GL_VS_LOC, }, + { "maxPos", GBA_GL_VS_MAXPOS, }, + { "vram", GBA_GL_BG_VRAM, }, + { "charBase", GBA_GL_BG_CHARBASE, }, + { "size", GBA_GL_BG_SIZE, }, + { "inflags", GBA_GL_BG_INFLAGS, }, + { "offset", GBA_GL_BG_OFFSET, }, + { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, + { 0 } +}; + +static const char* const _renderMode35 = + "in vec2 texCoord;\n" + "uniform sampler2D vram;\n" + "uniform int charBase;\n" + "uniform ivec2 size;\n" + "uniform ivec4 inflags;\n" + "uniform isampler2D transform;\n" + "uniform ivec2 range;\n" + "uniform ivec2 mosaic;\n" + "out vec4 color;\n" + "out vec4 flags;\n" + FLAG_CONST + "precision highp float;\n" + "precision highp int;\n" + + "vec2 interpolate(ivec2 arr[4], float x);\n" + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" "void main() {\n" - " float y = fract(texCoord.y);\n" - " float lin = 0.5 - y / ceil(y) * 0.25;\n" - " vec2 mixedTransform = interpolate(transform, lin);\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " loadAffine(int(texCoord.y), mat, offset);\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x -= mod(incoord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y -= mod(incoord.y, mosaic.y);\n" + " }\n" + " float y = fract(incoord.y);\n" + " float lin = 0.75 + y * 0.25;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" - " color = fetchTile(ivec2(mixedTransform * texCoord.x + mixedOffset));\n" + " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" + " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" + " discard;\n" + " }\n" + " if (coord.y < 0 || coord.y >= (size.y << 8)) {\n" + " discard;\n" + " }\n" + " int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n" + " ivec4 entry = ivec4(texelFetch(vram, ivec2(address & 255, address >> 8), 0) * 15.9);\n" + " int sixteen = (entry.x << 12) | (entry.y << 8) | (entry.z << 4) | entry.w;\n" + " color = vec4((sixteen & 0x1F) / 31., ((sixteen >> 5) & 0x1F) / 31., ((sixteen >> 10) & 0x1F) / 31., 1.);\n" + " flags = inflags / flagCoeff;\n" + "}"; + +static const struct GBAVideoGLUniform _uniformsMode4[] = { + { "loc", GBA_GL_VS_LOC, }, + { "maxPos", GBA_GL_VS_MAXPOS, }, + { "vram", GBA_GL_BG_VRAM, }, + { "palette", GBA_GL_BG_PALETTE, }, + { "charBase", GBA_GL_BG_CHARBASE, }, + { "size", GBA_GL_BG_SIZE, }, + { "inflags", GBA_GL_BG_INFLAGS, }, + { "offset", GBA_GL_BG_OFFSET, }, + { "transform", GBA_GL_BG_TRANSFORM, }, + { "range", GBA_GL_BG_RANGE, }, + { "mosaic", GBA_GL_BG_MOSAIC, }, + { 0 } +}; + +static const char* const _renderMode4 = + "in vec2 texCoord;\n" + "uniform sampler2D vram;\n" + "uniform sampler2D palette;\n" + "uniform int charBase;\n" + "uniform ivec2 size;\n" + "uniform ivec4 inflags;\n" + "uniform isampler2D transform;\n" + "uniform ivec2 range;\n" + "uniform ivec2 mosaic;\n" + "out vec4 color;\n" + "out vec4 flags;\n" + FLAG_CONST + "precision highp float;\n" + "precision highp int;\n" + + "vec2 interpolate(ivec2 arr[4], float x);\n" + "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + + "void main() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " loadAffine(int(texCoord.y), mat, offset);\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x -= mod(incoord.x, mosaic.x);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y -= mod(incoord.y, mosaic.y);\n" + " }\n" + " float y = fract(incoord.y);\n" + " float lin = 0.75 + y * 0.25;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" + " vec2 mixedOffset = interpolate(offset, lin);\n" + " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" + " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" + " discard;\n" + " }\n" + " if (coord.y < 0 || coord.y >= (size.y << 8)) {\n" + " discard;\n" + " }\n" + " int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n" + " vec4 twoEntries = texelFetch(vram, ivec2((address >> 1) & 255, address >> 9), 0);\n" + " ivec2 entry = ivec2(twoEntries[3 - 2 * (address & 1)] * 15.9, twoEntries[2 - 2 * (address & 1)] * 15.9);\n" + " color = texelFetch(palette, entry, 0);\n" + " color.a = 1;\n" " flags = inflags / flagCoeff;\n" "}"; @@ -235,6 +451,7 @@ static const struct GBAVideoGLUniform _uniformsObj[] = { { "transform", GBA_GL_OBJ_TRANSFORM, }, { "dims", GBA_GL_OBJ_DIMS, }, { "objwin", GBA_GL_OBJ_OBJWIN, }, + { "mosaic", GBA_GL_OBJ_MOSAIC, }, { 0 } }; @@ -248,16 +465,29 @@ static const char* const _renderObj = "uniform ivec4 inflags;\n" "uniform mat2x2 transform;\n" "uniform ivec4 dims;\n" - "uniform vec3 objwin;\n" + "uniform vec4 objwin;\n" + "uniform ivec4 mosaic;\n" "out vec4 color;\n" "out vec4 flags;\n" - "out vec2 window;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + "out vec3 window;\n" + FLAG_CONST "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n" "void main() {\n" - " ivec2 coord = ivec2(transform * (texCoord - dims.zw / 2) + dims.xy / 2);\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " int x = int(incoord.x);\n" + " incoord.x = clamp(x - int(mod(mosaic.z + x, mosaic.x)), 0, dims.z - 1);\n" + " } else if (mosaic.x < -1) {\n" + " int x = dims.z - int(incoord.x) - 1;\n" + " incoord.x = clamp(dims.z - x + int(mod(mosaic.z + x, -mosaic.x)) - 1, 0, dims.z - 1);\n" + " }\n" + " if (mosaic.y > 1) {\n" + " int y = int(incoord.y);\n" + " incoord.y = clamp(y - int(mod(mosaic.w + y, mosaic.y)), 0, dims.w - 1);\n" + " }\n" + " ivec2 coord = ivec2(transform * (incoord - dims.zw / 2) + dims.xy / 2);\n" " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n" " discard;\n" " }\n" @@ -267,7 +497,7 @@ static const char* const _renderObj = " }\n" " color = pix;\n" " flags = inflags / flagCoeff;\n" - " window = objwin.yz;\n" + " window = objwin.yzw;\n" "}"; static const struct GBAVideoGLUniform _uniformsFinalize[] = { @@ -288,9 +518,9 @@ static const char* const _finalize = "uniform sampler2D layers[5];\n" "uniform sampler2D flags[5];\n" "uniform sampler2D window;\n" - "uniform vec4 backdrop;\n" - "uniform vec4 backdropFlags;\n" - "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n" + "uniform sampler2D backdrop;\n" + "uniform sampler2D backdropFlags;\n" + FLAG_CONST "out vec4 color;\n" "void composite(vec4 pixel, ivec4 flags, inout vec4 topPixel, inout ivec4 topFlags, inout vec4 bottomPixel, inout ivec4 bottomFlags) {\n" @@ -312,49 +542,47 @@ static const char* const _finalize = "}\n" "void main() {\n" - " ivec2 windowFlags = ivec2(texelFetch(window, ivec2(texCoord * scale), 0).xy * 32);\n" - " int layerWindow = windowFlags.x | (windowFlags.y << 4);\n" - " vec4 topPixel = backdrop;\n" - " vec4 bottomPixel = backdrop;\n" - " ivec4 topFlags = ivec4(backdropFlags * flagCoeff);\n" - " ivec4 bottomFlags = ivec4(backdropFlags * flagCoeff);\n" + " vec4 topPixel = texelFetch(backdrop, ivec2(0, texCoord.y), 0);\n" + " vec4 bottomPixel = topPixel;\n" + " ivec4 topFlags = ivec4(texelFetch(backdropFlags, ivec2(0, texCoord.y), 0) * flagCoeff);\n" + " ivec4 bottomFlags = topFlags;\n" + " vec4 windowFlags = texelFetch(window, ivec2(texCoord * scale), 0);\n" + " int layerWindow = int(windowFlags.x * 128);\n" " if ((layerWindow & 1) == 0) {\n" " vec4 pix = texelFetch(layers[0], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[0], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[0], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" " if ((layerWindow & 2) == 0) {\n" " vec4 pix = texelFetch(layers[1], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[1], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[1], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" " if ((layerWindow & 4) == 0) {\n" " vec4 pix = texelFetch(layers[2], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[2], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[2], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" " if ((layerWindow & 8) == 0) {\n" " vec4 pix = texelFetch(layers[3], ivec2(texCoord * scale), 0);\n" - " ivec4 inflags = ivec4(texelFetch(flags[3], ivec2(texCoord * scale), 0) * flagCoeff);\n" + " ivec4 inflags = ivec4(texelFetch(flags[3], ivec2(texCoord * scale), 0).xyz * flagCoeff.xyz, 0);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" - " if ((layerWindow & 32) != 0) {\n" - " topFlags.y &= ~1;\n" - " }\n" " if ((layerWindow & 16) == 0) {\n" " vec4 pix = texelFetch(layers[4], ivec2(texCoord * scale), 0);\n" " ivec4 inflags = ivec4(texelFetch(flags[4], ivec2(texCoord * scale), 0) * flagCoeff);\n" " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n" " }\n" - " if ((topFlags.y & 13) == 5) {\n" - " if ((bottomFlags.y & 2) == 2) {\n" - " topPixel *= topFlags.z / 16.;\n" - " topPixel += bottomPixel * bottomFlags.w / 16.;\n" - " }\n" + " if ((layerWindow & 32) != 0) {\n" + " topFlags.y &= ~1;\n" + " }\n" + " if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n" + " topPixel *= topFlags.z / 16.;\n" + " topPixel += bottomPixel * windowFlags.y;\n" " } else if ((topFlags.y & 13) == 9) {\n" - " topPixel += (1. - topPixel) * topFlags.z / 16.;\n" + " topPixel += (1. - topPixel) * windowFlags.z;\n" " } else if ((topFlags.y & 13) == 13) {\n" - " topPixel -= topPixel * topFlags.z / 16.;\n" + " topPixel -= topPixel * windowFlags.z;\n" " }\n" " color = topPixel;\n" "}"; @@ -388,7 +616,7 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) { renderer->scale = 1; } -void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) { +static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) { GLuint program = glCreateProgram(); shader->program = program; @@ -416,6 +644,7 @@ void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShad glBindVertexArray(shader->vao); glBindBuffer(GL_ARRAY_BUFFER, glRenderer->vbo); GLuint positionLocation = glGetAttribLocation(program, "position"); + glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL); size_t i; @@ -424,13 +653,18 @@ void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShad } } +static void _deleteShader(struct GBAVideoGLShader* shader) { + glDeleteProgram(shader->program); + glDeleteVertexArrays(1, &shader->vao); +} + static void _initFramebufferTexture(GLuint tex, GLenum format, GLenum attachment, int scale) { glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, format, GBA_VIDEO_HORIZONTAL_PIXELS * scale, GBA_VIDEO_VERTICAL_PIXELS * scale, 0, format, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, format, scale > 0 ? GBA_VIDEO_HORIZONTAL_PIXELS * scale : 1, GBA_VIDEO_VERTICAL_PIXELS * (scale > 0 ? scale : 1), 0, format, GL_UNSIGNED_BYTE, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex, 0); } @@ -452,12 +686,30 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, 256, 192, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0); + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_2]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, NULL); + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_3]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, NULL); + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_COLOR], GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale); _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGBA, GL_COLOR_ATTACHMENT2, glRenderer->scale); + + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_BACKDROP_COLOR], GL_RGB, GL_COLOR_ATTACHMENT0, 0); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_BACKDROP_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, 0); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]); - _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RG, GL_COLOR_ATTACHMENT0, glRenderer->scale); + _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OUTPUT]); _initFramebufferTexture(glRenderer->outputTex, GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale); @@ -486,24 +738,29 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { bg->y = 0; bg->refx = 0; bg->refy = 0; - bg->affine[0].dx = 256; - bg->affine[0].dmx = 0; - bg->affine[0].dy = 0; - bg->affine[0].dmy = 256; - bg->affine[0].sx = 0; - bg->affine[0].sy = 0; + bg->affine.dx = 256; + bg->affine.dmx = 0; + bg->affine.dy = 0; + bg->affine.dmy = 256; + bg->affine.sx = 0; + bg->affine.sy = 0; glGenFramebuffers(1, &bg->fbo); glGenTextures(1, &bg->tex); glGenTextures(1, &bg->flags); glBindFramebuffer(GL_FRAMEBUFFER, bg->fbo); _initFramebufferTexture(bg->tex, GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale); - _initFramebufferTexture(bg->flags, GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale); + _initFramebufferTexture(bg->flags, GL_RGB, GL_COLOR_ATTACHMENT1, glRenderer->scale); glBindFramebuffer(GL_FRAMEBUFFER, 0); } char log[1024]; - const GLchar* shaderBuffer[8]; - shaderBuffer[0] = _gl3Header; + const GLchar* shaderBuffer[4]; + const GLubyte* version = glGetString(GL_VERSION); + if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) { + shaderBuffer[0] = _gl3Header; + } else { + shaderBuffer[0] = _gles3Header; + } GLuint vs = glCreateShader(GL_VERTEX_SHADER); shaderBuffer[1] = _vertexShader; @@ -523,12 +780,21 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { _compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, log); shaderBuffer[1] = _renderMode2; + shaderBuffer[2] = _interpolate; - shaderBuffer[2] = _fetchTileOverflow; - _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 3, vs, _uniformsMode2, log); + shaderBuffer[3] = _fetchTileOverflow; + _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 4, vs, _uniformsMode2, log); - shaderBuffer[2] = _fetchTileNoOverflow; - _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 3, vs, _uniformsMode2, log); + shaderBuffer[3] = _fetchTileNoOverflow; + _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 4, vs, _uniformsMode2, log); + + shaderBuffer[1] = _renderMode4; + shaderBuffer[2] = _interpolate; + _compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, log); + + shaderBuffer[1] = _renderMode35; + shaderBuffer[2] = _interpolate; + _compileShader(glRenderer, &glRenderer->bgShader[5], shaderBuffer, 3, vs, _uniformsMode35, log); shaderBuffer[1] = _renderObj; @@ -562,6 +828,22 @@ void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer) { glDeleteTextures(GBA_GL_TEX_MAX, glRenderer->layers); glDeleteTextures(1, &glRenderer->paletteTex); glDeleteTextures(1, &glRenderer->vramTex); + + _deleteShader(&glRenderer->bgShader[0]); + _deleteShader(&glRenderer->bgShader[1]); + _deleteShader(&glRenderer->bgShader[2]); + _deleteShader(&glRenderer->bgShader[3]); + _deleteShader(&glRenderer->objShader[0]); + _deleteShader(&glRenderer->objShader[1]); + _deleteShader(&glRenderer->finalizeShader); + + int i; + for (i = 0; i < 4; ++i) { + struct GBAVideoGLBackground* bg = &glRenderer->bg[i]; + glDeleteFramebuffers(1, &bg->fbo); + glDeleteTextures(1, &bg->tex); + glDeleteTextures(1, &bg->flags); + } } void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) { @@ -570,6 +852,9 @@ void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) { glRenderer->paletteDirty = true; glRenderer->vramDirty = 0xFFFFFF; glRenderer->firstAffine = -1; + glRenderer->firstY = -1; + glRenderer->dispcnt = 0; + glRenderer->mosaic = 0; } void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { @@ -600,108 +885,160 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, GBAVideoCacheWriteVideoRegister(renderer->cache, address, value); } + bool dirty = true; switch (address) { case REG_DISPCNT: value &= 0xFFF7; + break; + case REG_BG0CNT: + case REG_BG1CNT: + value &= 0xDFFF; + break; + case REG_BG0HOFS: + case REG_BG0VOFS: + case REG_BG1HOFS: + case REG_BG1VOFS: + case REG_BG2HOFS: + case REG_BG2VOFS: + case REG_BG3HOFS: + case REG_BG3VOFS: + value &= 0x01FF; + break; + case REG_BG2PA: + glRenderer->bg[2].affine.dx = value; + dirty = false; + break; + case REG_BG2PB: + glRenderer->bg[2].affine.dmx = value; + dirty = false; + break; + case REG_BG2PC: + glRenderer->bg[2].affine.dy = value; + dirty = false; + break; + case REG_BG2PD: + glRenderer->bg[2].affine.dmy = value; + dirty = false; + break; + case REG_BG2X_LO: + GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG2X_HI: + GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG2Y_LO: + GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG2Y_HI: + GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[2], value); + dirty = false; + break; + case REG_BG3PA: + glRenderer->bg[3].affine.dx = value; + dirty = false; + break; + case REG_BG3PB: + glRenderer->bg[3].affine.dmx = value; + dirty = false; + break; + case REG_BG3PC: + glRenderer->bg[3].affine.dy = value; + dirty = false; + break; + case REG_BG3PD: + glRenderer->bg[3].affine.dmy = value; + dirty = false; + break; + case REG_BG3X_LO: + GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BG3X_HI: + GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BG3Y_LO: + GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BG3Y_HI: + GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[3], value); + dirty = false; + break; + case REG_BLDALPHA: + value &= 0x1F1F; + break; + case REG_BLDY: + value &= 0x1F; + if (value > 0x10) { + value = 0x10; + } + break; + case REG_WININ: + value &= 0x3F3F; + break; + case REG_WINOUT: + value &= 0x3F3F; + break; + default: + break; + } + if (glRenderer->shadowRegs[address >> 1] == value) { + dirty = false; + } else { + glRenderer->shadowRegs[address >> 1] = value; + } + if (dirty) { + glRenderer->regsDirty |= 1ULL << (address >> 1); + } + return value; +} + +void _cleanRegister(struct GBAVideoGLRenderer* glRenderer, int address, uint16_t value) { + switch (address) { + case REG_DISPCNT: glRenderer->dispcnt = value; GBAVideoGLRendererUpdateDISPCNT(glRenderer); break; case REG_BG0CNT: - value &= 0xDFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[0], value); break; case REG_BG1CNT: - value &= 0xDFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[1], value); break; case REG_BG2CNT: - value &= 0xFFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[2], value); break; case REG_BG3CNT: - value &= 0xFFFF; GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[3], value); break; case REG_BG0HOFS: - value &= 0x01FF; glRenderer->bg[0].x = value; break; case REG_BG0VOFS: - value &= 0x01FF; glRenderer->bg[0].y = value; break; case REG_BG1HOFS: - value &= 0x01FF; glRenderer->bg[1].x = value; break; case REG_BG1VOFS: - value &= 0x01FF; glRenderer->bg[1].y = value; break; case REG_BG2HOFS: - value &= 0x01FF; glRenderer->bg[2].x = value; break; case REG_BG2VOFS: - value &= 0x01FF; glRenderer->bg[2].y = value; break; case REG_BG3HOFS: - value &= 0x01FF; glRenderer->bg[3].x = value; break; case REG_BG3VOFS: - value &= 0x01FF; glRenderer->bg[3].y = value; break; - case REG_BG2PA: - glRenderer->bg[2].affine[0].dx = value; - break; - case REG_BG2PB: - glRenderer->bg[2].affine[0].dmx = value; - break; - case REG_BG2PC: - glRenderer->bg[2].affine[0].dy = value; - break; - case REG_BG2PD: - glRenderer->bg[2].affine[0].dmy = value; - break; - case REG_BG2X_LO: - GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[2], value); - break; - case REG_BG2X_HI: - GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[2], value); - break; - case REG_BG2Y_LO: - GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[2], value); - break; - case REG_BG2Y_HI: - GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[2], value); - break; - case REG_BG3PA: - glRenderer->bg[3].affine[0].dx = value; - break; - case REG_BG3PB: - glRenderer->bg[3].affine[0].dmx = value; - break; - case REG_BG3PC: - glRenderer->bg[3].affine[0].dy = value; - break; - case REG_BG3PD: - glRenderer->bg[3].affine[0].dmy = value; - break; - case REG_BG3X_LO: - GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[3], value); - break; - case REG_BG3X_HI: - GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[3], value); - break; - case REG_BG3Y_LO: - GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[3], value); - break; - case REG_BG3Y_HI: - GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[3], value); - break; case REG_BLDCNT: GBAVideoGLRendererWriteBLDCNT(glRenderer, value); value &= 0x3FFF; @@ -718,35 +1055,31 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, value &= 0x1F1F; break; case REG_BLDY: - value &= 0x1F; - if (value > 0x10) { - value = 0x10; - } glRenderer->bldy = value; break; case REG_WIN0H: - glRenderer->winN[0].h.end = value; - glRenderer->winN[0].h.start = value >> 8; - if (glRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[0].h.start > glRenderer->winN[0].h.end) { - glRenderer->winN[0].h.start = 0; + glRenderer->winN[0].h[0].end = value; + glRenderer->winN[0].h[0].start = value >> 8; + if (glRenderer->winN[0].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[0].h[0].start > glRenderer->winN[0].h[0].end) { + glRenderer->winN[0].h[0].start = 0; } - if (glRenderer->winN[0].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[0].h.end = GBA_VIDEO_HORIZONTAL_PIXELS; - if (glRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[0].h.start = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[0].h[0].end > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[0].h[0].end = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[0].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[0].h[0].start = GBA_VIDEO_HORIZONTAL_PIXELS; } } break; case REG_WIN1H: - glRenderer->winN[1].h.end = value; - glRenderer->winN[1].h.start = value >> 8; - if (glRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[1].h.start > glRenderer->winN[1].h.end) { - glRenderer->winN[1].h.start = 0; + glRenderer->winN[1].h[0].end = value; + glRenderer->winN[1].h[0].start = value >> 8; + if (glRenderer->winN[1].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[1].h[0].start > glRenderer->winN[1].h[0].end) { + glRenderer->winN[1].h[0].start = 0; } - if (glRenderer->winN[1].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[1].h.end = GBA_VIDEO_HORIZONTAL_PIXELS; - if (glRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) { - glRenderer->winN[1].h.start = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[1].h[0].end > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[1].h[0].end = GBA_VIDEO_HORIZONTAL_PIXELS; + if (glRenderer->winN[1].h[0].start > GBA_VIDEO_HORIZONTAL_PIXELS) { + glRenderer->winN[1].h[0].start = GBA_VIDEO_HORIZONTAL_PIXELS; } } break; @@ -777,12 +1110,10 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, } break; case REG_WININ: - value &= 0x3F3F; glRenderer->winN[0].control = value; glRenderer->winN[1].control = value >> 8; break; case REG_WINOUT: - value &= 0x3F3F; glRenderer->winout = value; glRenderer->objwin = value >> 8; break; @@ -792,11 +1123,42 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, default: break; } - return value; } void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; + + memcpy(&glRenderer->affine[0][y], &glRenderer->bg[2].affine, sizeof(struct GBAVideoGLAffine)); + memcpy(&glRenderer->affine[1][y], &glRenderer->bg[3].affine, sizeof(struct GBAVideoGLAffine)); + if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { + if (glRenderer->firstAffine < 0) { + glRenderer->firstAffine = y; + } + } else { + glRenderer->firstAffine = -1; + } + + if (glRenderer->paletteDirty || glRenderer->vramDirty || glRenderer->oamDirty || glRenderer->regsDirty) { + if (glRenderer->firstY >= 0) { + _drawScanlines(glRenderer, y - 1); + } + } + if (glRenderer->firstY < 0) { + glRenderer->firstY = y; + } + + memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); + memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); + + int i; + for (i = 0; i < 0x30; ++i) { + if (!(glRenderer->regsDirty & (1ULL << i))) { + continue; + } + _cleanRegister(glRenderer, i << 1, glRenderer->shadowRegs[i]); + } + glRenderer->regsDirty = 0; + if (glRenderer->paletteDirty) { glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex); #ifdef BUILD_GLES3 @@ -806,26 +1168,30 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { #endif glRenderer->paletteDirty = false; } - int i; - for (i = 0; i < 16; ++i) { + + int first = -1; + glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex); + for (i = 0; i < 25; ++i) { if (!(glRenderer->vramDirty & (1 << i))) { - continue; + if (first >= 0) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * first, 256, 8 * (i - first), GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vramBG[2048 * first]); + first = -1; + } + } else if (first < 0) { + first = i; } - // TODO: PBOs - glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * i, 256, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vramBG[2048 * i]); - } - for (i = 16; i < 24; ++i) { - if (!(glRenderer->vramDirty & (1 << i))) { - continue; - } - // TODO: PBOs - glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * i, 256, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vramOBJ[2048 * (i - 16)]); } glRenderer->vramDirty = 0; + if (glRenderer->oamDirty) { + glRenderer->oamMax = GBAVideoRendererCleanOAM(glRenderer->d.oam->obj, glRenderer->sprites, 0, GBA_VIDEO_VERTICAL_PIXELS, false); + glRenderer->oamDirty = false; + } + if (y == 0) { + memcpy(&glRenderer->winN[0].h[1], &glRenderer->winN[0].h[0], sizeof(struct GBAVideoWindowRegion)); + memcpy(&glRenderer->winN[1].h[1], &glRenderer->winN[1].h[0], sizeof(struct GBAVideoWindowRegion)); + glDisable(GL_SCISSOR_TEST); glClearColor(0, 0, 0, 0); glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]); @@ -837,37 +1203,43 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); glClear(GL_COLOR_BUFFER_BIT); } - glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); + } + + if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { + glRenderer->bg[2].affine.sx += glRenderer->bg[2].affine.dmx; + glRenderer->bg[2].affine.sy += glRenderer->bg[2].affine.dmy; + glRenderer->bg[3].affine.sx += glRenderer->bg[3].affine.dmx; + glRenderer->bg[3].affine.sy += glRenderer->bg[3].affine.dmy; + } +} + +void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) { + if (glRenderer->firstAffine >= 0) { + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_2]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, glRenderer->affine[0]); + glBindTexture(GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_AFFINE_3]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16I, 2, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGBA_INTEGER, GL_SHORT, glRenderer->affine[1]); } glEnable(GL_SCISSOR_TEST); - if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { - if (glRenderer->firstAffine < 0) { - memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - glRenderer->firstAffine = y; - } else if (y - glRenderer->firstAffine == 1) { - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - } - } else { - glRenderer->firstAffine = -1; - } + uint32_t backdrop = M_RGB5_TO_RGB8(glRenderer->d.palette[0]); + glViewport(0, 0, 1, GBA_VIDEO_VERTICAL_PIXELS); + glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1); + glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); + glClearColor(((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); + glClear(GL_COLOR_BUFFER_BIT); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT1 }); + glClearColor(1, (glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4)) / 32.f, glRenderer->blda / 16.f, 0); + glClear(GL_COLOR_BUFFER_BIT); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); GBAVideoGLRendererDrawWindow(glRenderer, y); if (GBARegisterDISPCNTIsObjEnable(glRenderer->dispcnt) && !glRenderer->d.disableOBJ) { - if (glRenderer->oamDirty) { - glRenderer->oamMax = GBAVideoRendererCleanOAM(glRenderer->d.oam->obj, glRenderer->sprites, 0, GBA_VIDEO_VERTICAL_PIXELS, false); - glRenderer->oamDirty = false; - } int i; for (i = glRenderer->oamMax; i--;) { struct GBAVideoRendererSprite* sprite = &glRenderer->sprites[i]; - if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { + if ((y < sprite->y && (sprite->endY - 256 < 0 || glRenderer->firstY >= sprite->endY - 256)) || glRenderer->firstY >= sprite->endY) { continue; } @@ -891,13 +1263,13 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { GBAVideoGLRendererDrawBackgroundMode2(glRenderer, &glRenderer->bg[2], y); break; case 3: - //GBAVideoGLRendererDrawBackgroundMode3(glRenderer, &glRenderer->bg[2], y); + GBAVideoGLRendererDrawBackgroundMode3(glRenderer, &glRenderer->bg[2], y); break; case 4: - //GBAVideoGLRendererDrawBackgroundMode4(glRenderer, &glRenderer->bg[2], y); + GBAVideoGLRendererDrawBackgroundMode4(glRenderer, &glRenderer->bg[2], y); break; case 5: - //GBAVideoGLRendererDrawBackgroundMode5(glRenderer, &glRenderer->bg[2], y); + GBAVideoGLRendererDrawBackgroundMode5(glRenderer, &glRenderer->bg[2], y); break; } } @@ -911,31 +1283,20 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { break; } } - _finalizeLayers(glRenderer, y); - - if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) { - memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[2], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[2], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[1], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[1], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine)); - memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine)); - - glRenderer->bg[2].affine[0].sx += glRenderer->bg[2].affine[0].dmx; - glRenderer->bg[2].affine[0].sy += glRenderer->bg[2].affine[0].dmy; - glRenderer->bg[3].affine[0].sx += glRenderer->bg[3].affine[0].dmx; - glRenderer->bg[3].affine[0].sy += glRenderer->bg[3].affine[0].dmy; - } + glRenderer->firstY = -1; } void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) { struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer; + _drawScanlines(glRenderer, GBA_VIDEO_VERTICAL_PIXELS - 1); + _finalizeLayers(glRenderer); + glBindVertexArray(0); glRenderer->firstAffine = -1; - glRenderer->bg[2].affine[0].sx = glRenderer->bg[2].refx; - glRenderer->bg[2].affine[0].sy = glRenderer->bg[2].refy; - glRenderer->bg[3].affine[0].sx = glRenderer->bg[3].refx; - glRenderer->bg[3].affine[0].sy = glRenderer->bg[3].refy; - glFlush(); + glRenderer->firstY = -1; + glRenderer->bg[2].affine.sx = glRenderer->bg[2].refx; + glRenderer->bg[2].affine.sy = glRenderer->bg[2].refy; + glRenderer->bg[3].affine.sx = glRenderer->bg[3].refx; + glRenderer->bg[3].affine.sy = glRenderer->bg[3].refy; } void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { @@ -990,26 +1351,26 @@ static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16 static void GBAVideoGLRendererWriteBGX_LO(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refx = (bg->refx & 0xFFFF0000) | value; - bg->affine[0].sx = bg->refx; + bg->affine.sx = bg->refx; } static void GBAVideoGLRendererWriteBGX_HI(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refx = (bg->refx & 0x0000FFFF) | (value << 16); bg->refx <<= 4; bg->refx >>= 4; - bg->affine[0].sx = bg->refx; + bg->affine.sx = bg->refx; } static void GBAVideoGLRendererWriteBGY_LO(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refy = (bg->refy & 0xFFFF0000) | value; - bg->affine[0].sy = bg->refy; + bg->affine.sy = bg->refy; } static void GBAVideoGLRendererWriteBGY_HI(struct GBAVideoGLBackground* bg, uint16_t value) { bg->refy = (bg->refy & 0x0000FFFF) | (value << 16); bg->refy <<= 4; bg->refy >>= 4; - bg->affine[0].sy = bg->refy; + bg->affine.sy = bg->refy; } static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, uint16_t value) { @@ -1029,11 +1390,11 @@ static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, u renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value); } -void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y) { +void _finalizeLayers(struct GBAVideoGLRenderer* renderer) { const GLuint* uniforms = renderer->finalizeShader.uniforms; glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OUTPUT]); glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); - glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); + glScissor(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); glUseProgram(renderer->finalizeShader.program); glBindVertexArray(renderer->finalizeShader.vao); glActiveTexture(GL_TEXTURE0); @@ -1058,18 +1419,20 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y) { glBindTexture(GL_TEXTURE_2D, renderer->bg[3].tex); glActiveTexture(GL_TEXTURE0 + 10); glBindTexture(GL_TEXTURE_2D, renderer->bg[3].flags); + glActiveTexture(GL_TEXTURE0 + 11); + glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_COLOR]); + glActiveTexture(GL_TEXTURE0 + 12); + glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_FLAGS]); - uint32_t backdrop = M_RGB5_TO_RGB8(renderer->d.palette[0]); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); + glUniform2i(uniforms[GBA_GL_VS_LOC], GBA_VIDEO_VERTICAL_PIXELS, 0); glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glUniform1i(uniforms[GBA_GL_FINALIZE_SCALE], renderer->scale); glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 5, 7, 9, 1 }); glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 }); glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f); - glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f, - (renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy) / 16.f, renderer->bldb / 16.f); - glEnableVertexAttribArray(0); + glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0); + glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 11); + glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 12); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -1088,10 +1451,6 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB spriteY -= 256; } - if (!GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesBIsVFlip(sprite->b)) { - spriteY = (y - height) + (y - spriteY) + 1; - } - int totalWidth = width; int totalHeight = height; if (GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesAIsDoubleSize(sprite->a)) { @@ -1099,29 +1458,27 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB totalHeight <<= 1; } - enum GBAVideoBlendEffect blendEffect = GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT ? BLEND_ALPHA : renderer->blendEffect; - const struct GBAVideoGLShader* shader = &renderer->objShader[GBAObjAttributesAGet256Color(sprite->a)]; const GLuint* uniforms = shader->uniforms; glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OBJ]); glViewport(x * renderer->scale, spriteY * renderer->scale, totalWidth * renderer->scale, totalHeight * renderer->scale); - glScissor(x * renderer->scale, y * renderer->scale, totalWidth * renderer->scale, renderer->scale); + glScissor(x * renderer->scale, renderer->firstY * renderer->scale, totalWidth * renderer->scale, (y - renderer->firstY + 1) * renderer->scale); glUseProgram(shader->program); glBindVertexArray(shader->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, renderer->vramTex); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y - spriteY); - glUniform2i(uniforms[GBA_GL_VS_MAXPOS], (GBAObjAttributesBIsHFlip(sprite->b) && !GBAObjAttributesAIsTransformed(sprite->a)) ? -totalWidth : totalWidth, totalHeight); + glUniform2i(uniforms[GBA_GL_VS_LOC], totalHeight, 0); + glUniform2i(uniforms[GBA_GL_VS_MAXPOS], totalWidth, totalHeight); glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0); glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1); glUniform1i(uniforms[GBA_GL_OBJ_CHARBASE], charBase); glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride); glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c)); glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c) << 3, - (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (blendEffect * 4), - blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); + (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (renderer->blendEffect * 4), + renderer->blda, GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); if (GBAObjAttributesAIsTransformed(sprite->a)) { struct GBAOAMMatrix mat; LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); @@ -1131,133 +1488,242 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { mat.a / 256.f, mat.c / 256.f, mat.b / 256.f, mat.d / 256.f }); } else { - glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { 1.f, 0, 0, 1.f }); + int flipX = 1; + int flipY = 1; + if (GBAObjAttributesBIsHFlip(sprite->b)) { + flipX = -1; + } + if (GBAObjAttributesBIsVFlip(sprite->b)) { + flipY = -1; + } + glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { flipX, 0, 0, flipY }); } glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) { - int window = ~renderer->objwin & 0xFF; - glUniform3f(uniforms[GBA_GL_OBJ_OBJWIN], 1, (window & 0xF) / 32.f, (window >> 4) / 32.f); + int window = ~renderer->objwin & 0x3F; + glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 1, window / 128.f, renderer->bldb / 16.f, renderer->bldy / 16.f); glDrawBuffers(3, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }); } else { - glUniform3f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0); + glUniform4f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0, 0); glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); } - glEnableVertexAttribArray(0); + if (GBAObjAttributesAIsMosaic(sprite->a) && GBAObjAttributesAGetMode(sprite->a) != OBJ_MODE_OBJWIN) { + int mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; + if (GBAObjAttributesBIsHFlip(sprite->b)) { + mosaicH = -mosaicH; + } + glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], mosaicH, GBAMosaicControlGetObjV(renderer->mosaic) + 1, x, spriteY); + } else { + glUniform4i(uniforms[GBA_GL_OBJ_MOSAIC], 0, 0, 0, 0); + } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } -void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { - int inY = y + background->y; - int yBase = inY & 0xFF; - if (background->size == 2) { - yBase += inY & 0x100; - } else if (background->size == 3) { - yBase += (inY & 0x100) << 1; - } - - const struct GBAVideoGLShader* shader = &renderer->bgShader[background->multipalette ? 1 : 0]; - const GLuint* uniforms = shader->uniforms; +void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms) { glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); - glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); - glUseProgram(shader->program); - glBindVertexArray(shader->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, renderer->vramTex); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); + if (background->mosaic) { + glUniform2i(uniforms[GBA_GL_BG_MOSAIC], GBAMosaicControlGetBgV(renderer->mosaic) + 1, GBAMosaicControlGetBgH(renderer->mosaic) + 1); + } else { + glUniform2i(uniforms[GBA_GL_BG_MOSAIC], 0, 0); + } + glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, + background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), + renderer->blda, 0); + glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); +} + +void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + int inY0 = renderer->firstY + background->y; + int yDiv = (((y + background->y) & ~0xFF) - background->y) & 0xFF; + int inY1 = yDiv + background->y; + int yBase0 = inY0 & 0xFF; + int yBase1 = inY1 & 0xFF; + if (background->size == 2) { + yBase0 += inY0 & 0x100; + yBase1 += inY1 & 0x100; + } else if (background->size == 3) { + yBase0 += (inY0 & 0x100) << 1; + yBase1 += (inY1 & 0x100) << 1; + } + + const struct GBAVideoGLShader* shader = &renderer->bgShader[background->multipalette ? 1 : 0]; + const GLuint* uniforms = shader->uniforms; + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareBackground(renderer, background, uniforms); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); - glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y); - glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, - background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); - glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); - glEnableVertexAttribArray(0); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + if (yDiv > renderer->firstY) { + int end = yDiv - 1; + if (end > y) { + end = y; + } + glScissor(0, renderer->firstY * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, (end - renderer->firstY + 1) * renderer->scale); + glUniform2i(uniforms[GBA_GL_VS_LOC], end - renderer->firstY + 1, renderer->firstY); + glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase0 - renderer->firstY); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + + if (y >= yDiv) { + int start = yDiv; + if (yDiv < renderer->firstY) { + start = renderer->firstY; + } + glScissor(0, start * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, (y - start + 1) * renderer->scale); + glUniform2i(uniforms[GBA_GL_VS_LOC], y - start + 1, start); + glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase1 - yDiv); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } +void _prepareTransform(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, const GLuint* uniforms, int y) { + glScissor(0, renderer->firstY * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale * (y - renderer->firstY + 1)); + glUniform2i(uniforms[GBA_GL_VS_LOC], y - renderer->firstY + 1, renderer->firstY); + glUniform2i(uniforms[GBA_GL_BG_RANGE], renderer->firstAffine, y); + + glActiveTexture(GL_TEXTURE0 + 2); + glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_AFFINE_2 + background->index - 2]); + glUniform1i(uniforms[GBA_GL_BG_TRANSFORM], 2); + _prepareBackground(renderer, background, uniforms); +} + void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { const struct GBAVideoGLShader* shader = &renderer->bgShader[background->overflow ? 2 : 3]; const GLuint* uniforms = shader->uniforms; - glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); - glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); - glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale); glUseProgram(shader->program); glBindVertexArray(shader->vao); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, renderer->vramTex); - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, renderer->paletteTex); - glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y); - glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - glUniform1i(uniforms[GBA_GL_BG_VRAM], 0); - glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1); + _prepareTransform(renderer, background, uniforms, y); glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase); glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase); glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size); - glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1, - background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4), - renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb); - if (renderer->scale > 1) { - glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { - background->affine[0].sx, background->affine[0].sy, - background->affine[1].sx, background->affine[1].sy, - background->affine[2].sx, background->affine[2].sy, - background->affine[3].sx, background->affine[3].sy, - }); - glUniform2iv(uniforms[GBA_GL_BG_TRANSFORM], 4, (GLint[]) { - background->affine[0].dx, background->affine[0].dy, - background->affine[1].dx, background->affine[1].dy, - background->affine[2].dx, background->affine[2].dy, - background->affine[3].dx, background->affine[3].dy, - }); - } else { - glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) { - background->affine[0].sx, background->affine[0].sy, - background->affine[0].sx, background->affine[0].sy, - background->affine[0].sx, background->affine[0].sy, - background->affine[0].sx, background->affine[0].sy, - }); - glUniform2iv(uniforms[GBA_GL_BG_TRANSFORM], 4, (GLint[]) { - background->affine[0].dx, background->affine[0].dy, - background->affine[0].dx, background->affine[0].dy, - background->affine[0].dx, background->affine[0].dy, - background->affine[0].dx, background->affine[0].dy, - }); - } - glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }); - glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); } -static void _clearWindow(GBAWindowControl window, int start, int end, int y, int scale) { - glScissor(start, y, end - start, scale); - window = ~window & 0xFF; - glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); +void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[5]; + const GLuint* uniforms = shader->uniforms; + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], 0); + glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); +} + +void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[4]; + const GLuint* uniforms = shader->uniforms; + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0xA000 : 0); + glUniform2i(uniforms[GBA_GL_BG_SIZE], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); +} + +void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) { + const struct GBAVideoGLShader* shader = &renderer->bgShader[5]; + const GLuint* uniforms = shader->uniforms; + glBindFramebuffer(GL_FRAMEBUFFER, background->fbo); + glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale); + glUseProgram(shader->program); + glBindVertexArray(shader->vao); + _prepareTransform(renderer, background, uniforms, y); + glUniform1i(uniforms[GBA_GL_BG_CHARBASE], GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt) ? 0x5000 : 0); + glUniform2i(uniforms[GBA_GL_BG_SIZE], 160, 128); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 }); +} + +static void _scissorWindow(int start, int end, int y, int lines, int scale) { + if (start > end) { + _scissorWindow(start, GBA_VIDEO_HORIZONTAL_PIXELS * scale, y, lines, scale); + _scissorWindow(0, end, y, lines, scale); + return; + } + glScissor(start, y, end - start, lines); + glClear(GL_COLOR_BUFFER_BIT); +} + +static void _scissorWindowN(const struct GBAVideoWindowRegion* region, const struct GBAVideoWindowRegion* v, const struct GBAVideoWindowRegion* y, int scale) { + int sdelta = region[0].start - region[1].start; + int edelta = region[0].end - region[1].end; + int maxDelta = 0; + if (sdelta > maxDelta) { + maxDelta = sdelta; + } else if (-sdelta > maxDelta) { + maxDelta = -sdelta; + } + if (edelta > maxDelta) { + maxDelta = edelta; + } else if (-edelta > maxDelta) { + maxDelta = -edelta; + } + int startY = y->start; + int endY = y->end; + if (startY < v->start) { + startY = v->start; + } + if (endY >= v->end) { + endY = v->end - 1; + } + if (!(sdelta | edelta) || maxDelta >= GBA_VIDEO_VERTICAL_PIXELS / 2) { + _scissorWindow(region[0].start * scale, region[0].end * scale, startY * scale, (endY - startY + 1) * scale, scale); + } else { + int i; + for (i = 0; i < scale * (endY - startY + 1); ++i) { + int start = region[1].start * scale + sdelta * i; + int end = region[1].end * scale + edelta * i; + _scissorWindow(start, end, startY * scale + i, 1, scale); + } + } +} + +static void _clearWindow(GBAWindowControl window, int bldb, int bldy) { + window = ~window & 0x3F; + glClearColor(window / 128.f, bldb / 16.f, bldy / 16.f, 0); } void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) { glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_WINDOW]); int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20; if (!(renderer->dispcnt & 0xE000)) { - _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale); + _clearWindow(dispcnt, renderer->bldb, renderer->bldy); + _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->firstY * renderer->scale, (y - renderer->firstY + 1) * renderer->scale, renderer->scale); } else { - _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale); - if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) { - _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start * renderer->scale, renderer->winN[1].h.end * renderer->scale, y * renderer->scale, renderer->scale); + _clearWindow(renderer->winout & dispcnt, renderer->bldb, renderer->bldy); + _scissorWindow(0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->firstY * renderer->scale, (y - renderer->firstY + 1) * renderer->scale, renderer->scale); + struct GBAVideoWindowRegion yRegion = { + y, + renderer->firstY + }; + if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && renderer->firstY < renderer->winN[1].v.end) { + _clearWindow(renderer->winN[1].control & dispcnt, renderer->bldb, renderer->bldy); + _scissorWindowN(renderer->winN[1].h, &renderer->winN[1].v, &yRegion, renderer->scale); } - if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) { - _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start * renderer->scale, renderer->winN[0].h.end * renderer->scale, y * renderer->scale, renderer->scale); + if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && renderer->firstY < renderer->winN[0].v.end) { + _clearWindow(renderer->winN[0].control & dispcnt, renderer->bldb, renderer->bldy); + _scissorWindowN(renderer->winN[0].h, &renderer->winN[0].v, &yRegion, renderer->scale); } } } diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index b4573483c..1578bffdb 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -17,19 +17,12 @@ #define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \ SPRITE_YBASE_ ## DEPTH(inY); \ unsigned tileData; \ - if (outX % mosaicH) { \ - if (!inX && xOffset > 0) { \ - inX = mosaicH - (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } else if (inX == width - xOffset) { \ - inX = mosaicH + (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } \ - } \ for (; outX < condition; ++outX, inX += xOffset) { \ int localX = inX - xOffset * (outX % mosaicH); \ - if (localX < 0 || localX > width - 1) { \ - continue; \ + if (localX < 0) { \ + localX = 0; \ + } else if (localX > width - 1) {\ + localX = width - 1; \ } \ SPRITE_XBASE_ ## DEPTH(localX); \ SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ @@ -55,6 +48,31 @@ SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ } +#define SPRITE_TRANSFORMED_MOSAIC_LOOP(DEPTH, TYPE) \ + unsigned tileData; \ + unsigned widthMask = ~(width - 1); \ + unsigned heightMask = ~(height - 1); \ + int localX = xAccum >> 8; \ + int localY = yAccum >> 8; \ + for (; outX < condition; ++outX, ++inX) { \ + renderer->spriteCyclesRemaining -= 2; \ + xAccum += mat.a; \ + yAccum += mat.c; \ + \ + if (outX % mosaicH == 0) { \ + localX = xAccum >> 8; \ + localY = yAccum >> 8; \ + } \ + \ + if (localX & widthMask || localY & heightMask) { \ + continue; \ + } \ + \ + SPRITE_YBASE_ ## DEPTH(localY); \ + SPRITE_XBASE_ ## DEPTH(localX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ + } + #define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2); #define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 4; @@ -281,6 +299,13 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (end < condition) { condition = end; } + int mosaicH = 1; + if (GBAObjAttributesAIsMosaic(sprite->a)) { + mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; + if (condition % mosaicH) { + condition += mosaicH - (condition % mosaicH); + } + } int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)) + (width << 7); int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)) + (height << 7); @@ -340,6 +365,13 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(16, OBJWIN); + } else if (mosaicH > 1) { + if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL_OBJWIN); + } else { + SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL); + } } else if (objwinSlowPath) { objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN); @@ -358,6 +390,12 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 8]; if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(256, OBJWIN); + } else if (mosaicH > 1) { + if (objwinSlowPath) { + SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL_OBJWIN); + } else { + SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL); + } } else if (objwinSlowPath) { objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 8]; SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN); diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 5c639840b..acb05e52c 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -942,11 +942,17 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere struct GBAVideoRendererSprite* sprite = &renderer->sprites[i]; int localY = y; renderer->end = 0; + if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) { + continue; + } if (GBAObjAttributesAIsMosaic(sprite->obj.a)) { localY = mosaicY; - } - if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) { - continue; + if (localY < sprite->y) { + localY = sprite->y; + } + if (localY >= sprite->endY) { + localY = sprite->endY - 1; + } } for (w = 0; w < renderer->nWindows; ++w) { if (renderer->spriteCyclesRemaining <= 0) { diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index 6f4b8cb4c..527c3ccdc 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -16,6 +16,7 @@ static int cheatsSetup(void** state) { struct mCore* core = GBACoreCreate(); core->init(core); + mCoreInitConfig(core, NULL); core->cheatDevice(core); *state = core; return 0; @@ -26,6 +27,7 @@ static int cheatsTeardown(void** state) { return 0; } struct mCore* core = *state; + mCoreConfigDeinit(&core->config); core->deinit(core); return 0; } diff --git a/src/gba/test/core.c b/src/gba/test/core.c index acb0165af..9313010b6 100644 --- a/src/gba/test/core.c +++ b/src/gba/test/core.c @@ -27,7 +27,9 @@ M_TEST_DEFINE(reset) { struct mCore* core = GBACoreCreate(); assert_non_null(core); assert_true(core->init(core)); + mCoreInitConfig(core, NULL); core->reset(core); + mCoreConfigDeinit(&core->config); core->deinit(core); } @@ -36,7 +38,9 @@ M_TEST_DEFINE(loadNullROM) { assert_non_null(core); assert_true(core->init(core)); assert_false(core->loadROM(core, NULL)); + mCoreInitConfig(core, NULL); core->reset(core); + mCoreConfigDeinit(&core->config); core->deinit(core); } diff --git a/src/gba/video.c b/src/gba/video.c index 90a61e1fa..cf356f551 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -110,8 +110,7 @@ void GBAVideoReset(struct GBAVideo* video) { memset(video->palette, 0, sizeof(video->palette)); memset(video->oam.raw, 0, sizeof(video->oam.raw)); - video->renderer->deinit(video->renderer); - video->renderer->init(video->renderer); + video->renderer->reset(video->renderer); } void GBAVideoDeinit(struct GBAVideo* video) { diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 35e7ac9e0..168858241 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -136,9 +136,11 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { glBindVertexArray(context->initialShader.vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); + glEnableVertexAttribArray(context->initialShader.positionLocation); glVertexAttribPointer(context->initialShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glBindVertexArray(context->finalShader.vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); + glEnableVertexAttribArray(context->finalShader.positionLocation); glVertexAttribPointer(context->finalShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glBindVertexArray(0); @@ -246,7 +248,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glBindTexture(GL_TEXTURE_2D, oldTex); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); @@ -303,7 +305,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { break; } } - glEnableVertexAttribArray(shader->positionLocation); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, shader->tex); } @@ -327,6 +328,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { _drawShader(context, &context->finalShader); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); + glBindVertexArray(0); } void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { @@ -463,8 +465,8 @@ void mGLES2ShaderAttach(struct mGLES2Context* context, struct mGLES2Shader* shad glBindVertexArray(context->shaders[i].vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); - glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(context->shaders[i].positionLocation); + glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); } glBindVertexArray(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); diff --git a/src/platform/qt/Action.cpp b/src/platform/qt/Action.cpp index 0fe4be787..e5d6600d4 100644 --- a/src/platform/qt/Action.cpp +++ b/src/platform/qt/Action.cpp @@ -67,6 +67,10 @@ void Action::trigger(bool active) { return; } + if (m_exclusive && !m_booleanFunction) { + active = true; + } + if (m_function && active) { m_function(); } diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 53ee76478..23197be86 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -291,6 +291,7 @@ if(QT_STATIC) if(WIN32) list(APPEND QT_LIBRARIES qwindows dwmapi imm32 uxtheme Qt5EventDispatcherSupport Qt5FontDatabaseSupport Qt5ThemeSupport Qt5WindowsUIAutomationSupport) set_target_properties(Qt5::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32") + set_target_properties(Qt5::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) elseif(APPLE) find_package(Cups) find_package(Qt5PrintSupport) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 582c15f61..ac0c20682 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -86,6 +86,7 @@ CoreController::CoreController(mCore* core, QObject* parent) context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer->data()), controller->screenDimensions().width()); } + QMetaObject::invokeMethod(controller, "didReset"); controller->finishFrame(); }; @@ -196,9 +197,6 @@ CoreController::~CoreController() { mCacheSetDeinit(m_cacheSet.get()); m_cacheSet.reset(); } - - mCoreConfigDeinit(&m_threadContext.core->config); - m_threadContext.core->deinit(m_threadContext.core); } const color_t* CoreController::drawContext() { @@ -368,7 +366,6 @@ void CoreController::stop() { #endif setPaused(false); mCoreThreadEnd(&m_threadContext); - emit stopping(); } void CoreController::reset() { @@ -445,13 +442,21 @@ void CoreController::rewind(int states) { } void CoreController::setFastForward(bool enable) { + if (m_fastForward == enable) { + return; + } m_fastForward = enable; updateFastForward(); + emit fastForwardChanged(enable); } void CoreController::forceFastForward(bool enable) { + if (m_fastForwardForced == enable) { + return; + } m_fastForwardForced = enable; updateFastForward(); + emit fastForwardChanged(enable || m_fastForward); } void CoreController::loadState(int slot) { diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 657688ce0..d6c7609ee 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -99,6 +99,9 @@ public: void setInputController(InputController*); void setLogger(LogController*); + bool audioSync() const { return m_audioSync; } + bool videoSync() const { return m_videoSync; } + public slots: void start(); void stop(); @@ -167,6 +170,7 @@ signals: void crashed(const QString& errorMessage); void failed(); void frameAvailable(); + void didReset(); void stateLoaded(); void rewound(); diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index b82f0d6b4..3988804f0 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -20,7 +20,7 @@ Display* Display::create(QWidget* parent) { #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) QSurfaceFormat format; format.setSwapInterval(1); - format.setSwapBehavior(QSurfaceFormat::TripleBuffer); + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); #endif switch (s_driver) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 7da5fae79..63809526c 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2019 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -34,17 +34,32 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) : Display(parent) , m_gl(nullptr) { + setAttribute(Qt::WA_NativeWindow); + windowHandle()->create(); + // This can spontaneously re-enter into this->resizeEvent before creation is done, so we // need to make sure it's initialized to nullptr before we assign the new object to it m_gl = new QOpenGLContext; m_gl->setFormat(format); m_gl->create(); - setAttribute(Qt::WA_NativeWindow); + + m_gl->makeCurrent(windowHandle()); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + int majorVersion = m_gl->format().majorVersion(); + QStringList extensions = QString(reinterpret_cast(glGetString(GL_EXTENSIONS))).split(' '); + m_gl->doneCurrent(); + + if (majorVersion == 2 && !extensions.contains("GL_ARB_framebuffer_object")) { + QSurfaceFormat newFormat(format); + newFormat.setVersion(1, 4); + m_gl->setFormat(newFormat); + m_gl->create(); + } + m_painter = new PainterGL(&m_videoProxy, windowHandle(), m_gl); setUpdatesEnabled(false); // Prevent paint events, which can cause race conditions - - connect(&m_videoProxy, &VideoProxy::dataAvailable, &m_videoProxy, &VideoProxy::processData); - connect(&m_videoProxy, &VideoProxy::eventPosted, &m_videoProxy, &VideoProxy::handleEvent); } DisplayGL::~DisplayGL() { @@ -98,6 +113,7 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); #endif resizePainter(); + connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext); } void DisplayGL::stopDrawing() { @@ -249,10 +265,9 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent #endif m_backend->swap = [](VideoBackend* v) { PainterGL* painter = static_cast(v->user); - if (!painter->m_gl->isValid()) { - return; + if (!painter->m_swapTimer.isActive()) { + QMetaObject::invokeMethod(&painter->m_swapTimer, "start"); } - painter->m_gl->swapBuffers(painter->m_gl->surface()); }; m_backend->init(m_backend, 0); @@ -270,6 +285,10 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent for (int i = 0; i < 2; ++i) { m_free.append(new uint32_t[256 * 512]); } + + m_swapTimer.setInterval(16); + m_swapTimer.setSingleShot(true); + connect(&m_swapTimer, &QTimer::timeout, this, &PainterGL::swap); } PainterGL::~PainterGL() { @@ -305,18 +324,8 @@ void PainterGL::resizeContext() { return; } - if (!m_active) { - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - } - QSize size = m_context->screenDimensions(); m_backend->setDimensions(m_backend, size.width(), size.height()); - if (!m_active) { - m_gl->doneCurrent(); - } } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { @@ -368,27 +377,22 @@ void PainterGL::draw() { return; } + if (m_needsUnlock) { + QTimer::singleShot(0, this, &PainterGL::draw); + return; + } + if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) { dequeue(); - mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); - m_painter.begin(m_window); - performDraw(); - m_painter.end(); - m_backend->swap(m_backend); - if (!m_delayTimer.isValid()) { - m_delayTimer.start(); - } else if (m_gl->format().swapInterval()) { - while (m_delayTimer.elapsed() < 15) { - QThread::usleep(100); - } - m_delayTimer.restart(); + forceDraw(); + if (m_context->thread()->impl->sync.videoFrameWait) { + m_needsUnlock = true; + } else { + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); } } else { mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); } - if (!m_queue.isEmpty()) { - QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); - } } void PainterGL::forceDraw() { @@ -404,6 +408,9 @@ void PainterGL::stop() { dequeueAll(); m_backend->clear(m_backend); m_backend->swap(m_backend); + if (m_videoProxy) { + m_videoProxy->reset(); + } m_gl->doneCurrent(); m_gl->moveToThread(m_surface->thread()); m_context.reset(); @@ -428,6 +435,30 @@ void PainterGL::performDraw() { if (m_messagePainter) { m_messagePainter->paint(&m_painter); } + m_frameReady = true; +} + +void PainterGL::swap() { + if (!m_gl->isValid()) { + return; + } + if (m_frameReady) { + m_gl->swapBuffers(m_surface); + m_gl->makeCurrent(m_surface); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif + m_frameReady = false; + } + if (m_needsUnlock) { + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); + m_needsUnlock = false; + } + if (!m_queue.isEmpty()) { + QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); + } else { + m_swapTimer.start(); + } } void PainterGL::enqueue(const uint32_t* backing) { @@ -480,12 +511,6 @@ void PainterGL::setShaders(struct VDir* dir) { return; } #ifdef BUILD_GLES2 - if (!m_active) { - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - } if (m_shader.passes) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); @@ -494,9 +519,6 @@ void PainterGL::setShaders(struct VDir* dir) { if (m_started) { mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); } - if (!m_active) { - m_gl->doneCurrent(); - } #endif } @@ -505,19 +527,10 @@ void PainterGL::clearShaders() { return; } #ifdef BUILD_GLES2 - if (!m_active) { - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - } if (m_shader.passes) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); } - if (!m_active) { - m_gl->doneCurrent(); - } #endif } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 2dbd1f2f0..2c54934f4 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -16,13 +16,13 @@ #endif #endif -#include #include #include #include #include #include #include +#include #include "VideoProxy.h" @@ -105,6 +105,9 @@ public slots: int glTex(); +private slots: + void swap(); + private: void performDraw(); void dequeue(); @@ -125,7 +128,9 @@ private: VideoBackend* m_backend = nullptr; QSize m_size; MessagePainter* m_messagePainter = nullptr; - QElapsedTimer m_delayTimer; + QTimer m_swapTimer{this}; + bool m_needsUnlock = false; + bool m_frameReady = false; VideoProxy* m_videoProxy; }; diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index e6f80e0b1..dfd472c2f 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/platform/qt/LogController.cpp @@ -127,6 +127,9 @@ void LogController::logToStdout(bool log) { void LogController::setLogFile(const QString& file) { m_logStream.reset(); + if (file.isEmpty()) { + return; + } m_logFile = std::make_unique(file); m_logFile->open(QIODevice::Append | QIODevice::Text); m_logStream = std::make_unique(m_logFile.get()); diff --git a/src/platform/qt/MapView.ui b/src/platform/qt/MapView.ui index 15a1f5f74..d07a832b5 100644 --- a/src/platform/qt/MapView.ui +++ b/src/platform/qt/MapView.ui @@ -104,7 +104,7 @@ 1 - 4 + 8 diff --git a/src/platform/qt/ObjView.ui b/src/platform/qt/ObjView.ui index 7f33eaeb0..df0a003a9 100644 --- a/src/platform/qt/ObjView.ui +++ b/src/platform/qt/ObjView.ui @@ -59,7 +59,7 @@ 1 - 6 + 8 diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 4b5133b60..efb560068 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -57,6 +57,10 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC } }); + connect(m_ui.nativeGB, &QAbstractButton::pressed, [this]() { + m_ui.fpsTarget->setValue(double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH)); + }); + if (m_ui.savegamePath->text().isEmpty()) { m_ui.savegameSameDir->setChecked(true); } @@ -364,7 +368,6 @@ void SettingsView::updateConfig() { saveSetting("videoSync", m_ui.videoSync); saveSetting("audioSync", m_ui.audioSync); saveSetting("frameskip", m_ui.frameskip); - saveSetting("fpsTarget", m_ui.fpsTarget); saveSetting("autofireThreshold", m_ui.autofireThreshold); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); @@ -395,7 +398,7 @@ void SettingsView::updateConfig() { saveSetting("logToStdout", m_ui.logToStdout); saveSetting("logFile", m_ui.logFile); saveSetting("useDiscordPresence", m_ui.useDiscordPresence); - saveSetting("audioHle", m_ui.audioHle); + saveSetting("gba.audioHle", m_ui.audioHle); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -403,6 +406,13 @@ void SettingsView::updateConfig() { saveSetting("fastForwardRatio", m_ui.fastForwardRatio); } + double nativeFps = double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH); + if (nativeFps - m_ui.fpsTarget->value() < 0.0001) { + m_controller->setOption("fpsTarget", QVariant(nativeFps)); + } else { + saveSetting("fpsTarget", m_ui.fpsTarget); + } + switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) { case IDLE_LOOP_IGNORE: saveSetting("idleOptimization", "ignore"); @@ -549,7 +559,7 @@ void SettingsView::reloadConfig() { loadSetting("logToStdout", m_ui.logToStdout); loadSetting("logFile", m_ui.logFile); loadSetting("useDiscordPresence", m_ui.useDiscordPresence); - loadSetting("audioHle", m_ui.audioHle); + loadSetting("gba.audioHle", m_ui.audioHle); loadSetting("videoScale", m_ui.videoScale, 1); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 84c8a4e38..77b7cbe96 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -6,8 +6,8 @@ 0 0 - 790 - 686 + 849 + 753 @@ -40,7 +40,7 @@ - 200 + 180 16777215 @@ -399,21 +399,21 @@ - + Qt::Horizontal - + Sync: - + @@ -431,27 +431,34 @@ - + Lock aspect ratio - + Force integer scaling - + Bilinear filtering + + + + Native (59.7275) + + + @@ -894,7 +901,7 @@ 1 - 13 + 16 @@ -903,9 +910,6 @@ - - false - XQ GBA audio (experimental) diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui index 02c4c533f..4f027a493 100644 --- a/src/platform/qt/TileView.ui +++ b/src/platform/qt/TileView.ui @@ -126,7 +126,7 @@ 1 - 4 + 8 diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp index a6c673207..1eb8fa933 100644 --- a/src/platform/qt/VideoProxy.cpp +++ b/src/platform/qt/VideoProxy.cpp @@ -24,6 +24,9 @@ VideoProxy::VideoProxy() { m_logger.d.writeData = &callback::func<&VideoProxy::writeData>; m_logger.d.readData = &callback::func<&VideoProxy::readData>; m_logger.d.postEvent = &callback::func<&VideoProxy::postEvent>; + + connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData); + connect(this, &VideoProxy::eventPosted, this, &VideoProxy::handleEvent); } void VideoProxy::attach(CoreController* controller) { @@ -41,7 +44,10 @@ void VideoProxy::init() { } void VideoProxy::reset() { + m_mutex.lock(); RingFIFOClear(&m_dirtyQueue); + m_toThreadCond.wakeAll(); + m_mutex.unlock(); } void VideoProxy::deinit() { @@ -92,11 +98,13 @@ void VideoProxy::unlock() { } void VideoProxy::wait() { + m_mutex.lock(); while (RingFIFOSize(&m_dirtyQueue)) { emit dataAvailable(); m_toThreadCond.wakeAll(); m_fromThreadCond.wait(&m_mutex, 1); } + m_mutex.unlock(); } void VideoProxy::wake(int y) { diff --git a/src/platform/qt/VideoProxy.h b/src/platform/qt/VideoProxy.h index 7f778a605..e62facca1 100644 --- a/src/platform/qt/VideoProxy.h +++ b/src/platform/qt/VideoProxy.h @@ -30,11 +30,11 @@ signals: public slots: void processData(); + void reset(); void handleEvent(int); private: void init(); - void reset(); void deinit(); bool writeData(const void* data, size_t length); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 72ac72dad..c16ca9b4d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -648,8 +648,12 @@ void Window::closeEvent(QCloseEvent* event) { m_config->setOption("width", GBA_VIDEO_HORIZONTAL_PIXELS * m_savedScale); } saveConfig(); - m_display.reset(); - QMainWindow::closeEvent(event); + if (m_controller) { + event->ignore(); + m_pendingClose = true; + } else { + m_display.reset(); + } } void Window::focusInEvent(QFocusEvent*) { @@ -784,7 +788,6 @@ void Window::gameStarted() { } attachWidget(m_display.get()); setMouseTracking(true); - m_display->setMinimumSize(size); setFocus(); #ifndef Q_OS_MAC @@ -792,7 +795,6 @@ void Window::gameStarted() { menuBar()->hide(); } #endif - m_display->startDrawing(m_controller); reloadAudioDriver(); multiplayerChanged(); @@ -843,6 +845,11 @@ void Window::gameStarted() { void Window::gameStopped() { m_controller.reset(); + m_display->stopDrawing(); + if (m_pendingClose) { + m_display.reset(); + close(); + } for (Action* action : m_platformActions) { action->setEnabled(true); } @@ -929,7 +936,6 @@ void Window::reloadDisplayDriver() { m_shaderView = std::make_unique(m_display.get(), m_config); #endif - connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing); connect(m_display.get(), &Display::hideCursor, [this]() { if (static_cast(m_screenWidget->layout())->currentWidget() == m_display.get()) { m_screenWidget->setCursor(Qt::BlankCursor); @@ -954,8 +960,6 @@ void Window::reloadDisplayDriver() { #endif if (m_controller) { - m_display->setMinimumSize(m_controller->screenDimensions()); - connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); @@ -966,13 +970,12 @@ void Window::reloadDisplayDriver() { attachWidget(m_display.get()); m_display->startDrawing(m_controller); - } else { -#ifdef M_CORE_GB - m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS); -#elif defined(M_CORE_GBA) - m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); -#endif } +#ifdef M_CORE_GB + m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS); +#elif defined(M_CORE_GBA) + m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); +#endif } void Window::reloadAudioDriver() { @@ -991,6 +994,7 @@ void Window::reloadAudioDriver() { m_audioProcessor->requestSampleRate(opts->sampleRate); m_audioProcessor->start(); connect(m_controller.get(), &CoreController::stopping, m_audioProcessor.get(), &AudioProcessor::stop); + connect(m_controller.get(), &CoreController::fastForwardChanged, m_audioProcessor.get(), &AudioProcessor::inputParametersChanged); } void Window::tryMakePortable() { @@ -1372,7 +1376,7 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addMenu(tr("Audio/&Video"), "av"); m_actions.addMenu(tr("Frame size"), "frame", "av"); - for (int i = 1; i <= 6; ++i) { + for (int i = 1; i <= 8; ++i) { Action* setSize = m_actions.addAction(tr("%1×").arg(QString::number(i)), QString("frame.%1x").arg(QString::number(i)), [this, i]() { Action* setSize = m_frameSizes[i]; showNormal(); @@ -1739,6 +1743,9 @@ void Window::setController(CoreController* controller, const QString& fname) { if (!controller) { return; } + if (m_pendingClose) { + return; + } if (m_controller) { m_controller->stop(); @@ -1771,12 +1778,14 @@ void Window::setController(CoreController* controller, const QString& fname) { m_inputController.recalibrateAxes(); m_controller->setInputController(&m_inputController); m_controller->setLogger(&m_log); + m_display->startDrawing(m_controller); connect(this, &Window::shutdown, [this]() { if (!m_controller) { return; } m_controller->stop(); + disconnect(m_controller.get(), &CoreController::started, this, &Window::gameStarted); }); connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted); @@ -1804,7 +1813,6 @@ void Window::setController(CoreController* controller, const QString& fname) { emit paused(false); }); - connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext); connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index b935ad086..4980a9314 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -207,6 +207,7 @@ private: QString m_pendingPatch; QString m_pendingState; bool m_pendingPause = false; + bool m_pendingClose = false; bool m_hitUnimplementedBiosCall; diff --git a/src/platform/qt/ts/medusa-emu-es.ts b/src/platform/qt/ts/medusa-emu-es.ts index cae9fd4f7..2e950a8df 100644 --- a/src/platform/qt/ts/medusa-emu-es.ts +++ b/src/platform/qt/ts/medusa-emu-es.ts @@ -30,9 +30,15 @@ + © 2013 – 2019 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 +Game Boy Advance is a registered trademark of Nintendo Co., Ltd. + © 2013 – 2019 Jeffrey Pfau, licenciado bajo la Mozilla Public License, versión 2.0 +Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + + © 2013 – 2018 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 Game Boy Advance is a registered trademark of Nintendo Co., Ltd. - © 2013 – 2018 Jeffrey Pfau, licenciado bajo la Mozilla Public License, versión 2.0 + © 2013 – 2018 Jeffrey Pfau, licenciado bajo la Mozilla Public License, versión 2.0 Game Boy Advance es una marca registrada de Nintendo Co., Ltd. @@ -125,6 +131,84 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. 0x00 (00) + + BattleChipView + + + BattleChip Gate + BattleChip Gate + + + + Chip name + Nombre del chip + + + + Insert + Insertar + + + + Save + Guardar + + + + Load + Cargar + + + + Add + Agregar + + + + Remove + Quitar + + + + Gate type + Tipo de Gate + + + + Ba&ttleChip Gate + Ba&ttleChip Gate + + + + Progress &Gate + Progress &Gate + + + + Beast &Link Gate + Beast &Link Gate + + + + Inserted + Insertado + + + + Chip ID + ID de chip + + + + Update Chip data + Actualizar datos del chip + + + + Show advanced + Mostrar ajustes avanzados + + CheatsView @@ -201,12 +285,12 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Frameskip - Salto de cuadros + Salto Frame delay (ms) - Retraso entre cuadros (ms) + Retraso (ms) @@ -219,7 +303,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. I/O Viewer - Visor de I/O + Visor de E/S @@ -417,7 +501,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Debug - Depuración (Debug) + Debug @@ -427,12 +511,12 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Info - Información (Info) + Info Warning - Advertencia (Warning) + Warning @@ -457,7 +541,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Max Lines - Máximo de líneas + Líneas max. @@ -631,57 +715,69 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Alinear a: - 1 Byte - 1 byte + 1 byte + + + 2 Bytes + 2 bytes + + + 4 Bytes + 4 bytes + + + + &1 Byte + - 2 Bytes - 2 bytes + &2 Bytes + - 4 Bytes - 4 bytes + &4 Bytes + - + Unsigned Integer: Entero sin signo: - + Signed Integer: Entero con signo: - + String: - Cadena: + Cadena de texto: - + Load TBL Cargar TBL - + Copy Selection Copiar selección - + Paste Pegar - + Save Selection Guardar selección - + Load Cargar @@ -717,7 +813,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Transform - Transformación + Transform @@ -936,7 +1032,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Super Game Boy (SGB) - + Super Game Boy (SGB) @@ -1203,6 +1299,35 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. No se puede iniciar un procesador de audio sin entrada + + QGBA::BattleChipView + + + BattleChip data missing + Datos del BattleChip no encontrados + + + + BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? + Faltan los datos de BattleChip. Las BattleChip Gates seguirán funcionando, pero faltarán algunos gráficos. ¿Quieres descargar los datos ahora? + + + + + Select deck file + Elegir archivo de baraja + + + + Incompatible deck + Baraja incompatible + + + + The selected deck is not compatible with this Chip Gate + La baraja seleccionada no es compatible con esta Chip Gate + + QGBA::CheatsModel @@ -1249,22 +1374,22 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Error al abrir el archivo de guardado: %1 - + Failed to open game file: %1 Error al abrir el archivo del juego: %1 - + Failed to open snapshot file for reading: %1 Error al leer del archivo de captura: %1 - + Failed to open snapshot file for writing: %1 Error al escribir al archivo de captura: %1 @@ -1277,12 +1402,20 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Error al abrir el archivo del juego: %1 + + QGBA::GBAApp + + + Enable Discord Rich Presence + Habilitar Rich Presence en Discord + + QGBA::GBAKeyEditor Clear Button - Limpiar botón + Limpiar botones @@ -2789,40 +2922,94 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Espacio %1 + + QGBA::LogConfigModel + + + + Default + Por defecto + + + + Fatal + Fatal + + + + Error + Error + + + + Warning + Advertencia (Warning) + + + + Info + Información (Info) + + + + Debug + Depuración (Debug) + + + + Stub + Stub + + + + Game Error + Error de juego + + QGBA::LogController - + + [%1] %2: %3 + [%1] %2: %3 + + + + An error occurred + Ocurrió un error + + + DEBUG DEPURACIÓN - + STUB STUB - + INFO INFORMACIÓN - + WARN ADVERTENCIA - + ERROR ERROR - + FATAL FATAL - + GAME ERROR ERROR DE JUEGO @@ -2830,47 +3017,47 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::MapView - + Map Addr. Dir de mapa - + Mirror Espejar - + None Ninguno - + Both Ambos - + Horizontal Horizontal - + Vertical Vertical - + Export map Exportar mapa - + Portable Network Graphics (*.png) Gráficos de red portátiles (*.png) - + Failed to open output PNG file: %1 Error al abrir el archivo PNG de salida: %1 @@ -2976,54 +3163,54 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::ObjView - - + + 0x%0 0x%0 - + Off No - + Normal Normal - + Trans Trans - + OBJWIN OBJWIN - + Invalid Inválido - - + + N/A n/d - + Export sprite Exportar sprite - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - + Failed to open output PNG file: %1 Error al abrir el archivo PNG de salida: %1 @@ -3107,59 +3294,59 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::SettingsView - - + + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (forzar versión 1.x) - + None (Still Image) Nada (imagen estática) - + Keyboard Teclado - + Controllers Controladores - + Shortcuts Atajos de teclado - - + + Shaders Shaders - + Select BIOS Seleccionar BIOS @@ -3200,17 +3387,32 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::ShortcutController - + Action + Acción + + + Keyboard + Teclado + + + Gamepad + Mando + + + + QGBA::ShortcutModel + + Action Acción - + Keyboard Teclado - + Gamepad Mando @@ -3236,108 +3438,108 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::Window - + Game Boy Advance ROMs (%1) ROMs de Game Boy Advance (%1) - + Game Boy ROMs (%1) ROMs de Game Boy (%1) - + All ROMs (%1) Todas las ROMs (%1) - + %1 Video Logs (*.mvl) Video-registros de %1 (*.mvl) - + Archives (%1) Contenedores (%1) - - - + + + Select ROM Seleccionar ROM - + Select folder Seleccionar carpeta - + Game Boy Advance save files (%1) Archivos de guardado de Game Boy Advance (%1) - - - + + + Select save Seleccionar guardado - + mGBA savestate files (%1) Archivos de estado de guardado de mGBA (%1) - - + + Select savestate Elegir estado de guardado - + Select patch Seleccionar parche - + Patches (*.ips *.ups *.bps) Parches (*.ips *.ups *.bps) - + Select image Seleccionar imagen - + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) Archivo de imagen (*.png *.gif *.jpg *.jpeg);;Todos los archivos (*) - - + + GameShark saves (*.sps *.xps) Guardados de GameShark (*.sps *.xps) - + Select video log Seleccionar video-registro - + Video logs (*.mvl) Video-registros (*.mvl) - + Crash Error fatal - + The game has crashed with the following error: %1 @@ -3346,433 +3548,424 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. %1 - + Couldn't Load No se pudo cargar - + Could not load game. Are you sure it's in the correct format? No se pudo cargar el juego. ¿Estás seguro de que está en el formato correcto? - + Unimplemented BIOS call Llamada a BIOS no implementada - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Este juego utiliza una llamada al BIOS que no se ha implementado. Utiliza el BIOS oficial para obtener la mejor experiencia. - + Really make portable? ¿Hacer "portable"? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Esto hará que el emulador cargue su configuración desde el mismo directorio que el ejecutable. ¿Quieres continuar? - + Restart needed Reinicio necesario - + Some changes will not take effect until the emulator is restarted. Algunos cambios no surtirán efecto hasta que se reinicie el emulador. - + - Player %1 of %2 - Jugador %1 de %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 fps) - %4 - + &File &Archivo - + Load &ROM... Cargar &ROM... - + Load ROM in archive... Cargar ROM desde contenedor... - + Add folder to library... Agregar carpeta a la biblioteca... - + Load alternate save... Cargar guardado alternativo... - + Load temporary save... Cargar guardado temporal... - + Load &patch... Cargar &parche... - + Boot BIOS Arrancar BIOS - + Replace ROM... Reemplazar ROM... - + ROM &info... &Información de la ROM... - + Recent Recientes - + Make portable Hacer "portable" - + &Load state Ca&rgar estado - - F10 - F10 + + About... + Acerca de... - + F10 + F10 + + + Load state file... Cargar archivo de estado... - + &Save state Guardar e&stado - Shift+F10 - Shift+F10 + Shift+F10 - + Save state file... Guardar archivo de estado... - + Quick load Cargado rápido - + Quick save Guardado rápido - + Load recent Cargar reciente - + Save recent Guardar reciente - + Undo load state Deshacer cargar estado - F11 - F11 + F11 - + Undo save state Deshacer guardar estado - Shift+F11 - Shift+F11 + Shift+F11 - - + + State &%1 Estado &%1 - F%1 - F%1 + F%1 + + + Shift+F%1 + Shift+F%1 - Shift+F%1 - Shift+F%1 - - - Load camera image... Cargar imagen para la cámara... - + Import GameShark Save Importar guardado de GameShark - + Export GameShark Save Exportar guardado de GameShark - + New multiplayer window Nueva ventana multijugador - About - Acerca de + Acerca de - + E&xit Salir (&X) - + &Emulation &Emulación - + &Reset &Reinicializar - Ctrl+R - Ctrl+R + Ctrl+R - + Sh&utdown Apagar (&U) - + Yank game pak Tirar del cartucho - + &Pause &Pausar - Ctrl+P - Ctrl+P + Ctrl+P - + &Next frame Cuadro siguie&nte - Ctrl+N - Ctrl+N + Ctrl+N - + Fast forward (held) Avance rápido (mantener) - + &Fast forward &Avance rápido - Shift+Tab - Shift+Tab + Shift+Tab - + Fast forward speed Velocidad de avance rápido - + Unbounded Sin límite - + %0x %0x - + Rewind (held) Rebobinar (mantener) - + Re&wind Re&bobinar - ~ - ~ + ~ - + Step backwards Paso hacia atrás - Ctrl+B - Ctrl+B + Ctrl+B - + Sync to &video Sincronizar a &video - + Sync to &audio Sincronizar a au&dio - + Solar sensor Sensor solar - + Increase solar level Subir nivel - + Decrease solar level Bajar nivel - + Brightest solar level Más claro - + Darkest solar level Más oscuro - + Brightness %1 Brillo %1 - + Audio/&Video Audio/&video - + Frame size Tamaño del cuadro - %1x - %1x + %1x - + Toggle fullscreen Pantalla completa - + Lock aspect ratio Bloquear proporción de aspecto - + Force integer scaling Forzar escala a enteros - + Bilinear filtering Filtro bilineal - + Frame&skip &Salto de cuadros - + Mute Silenciar - + FPS target Objetivo de FPS - + Native (59.7275) Nativo (59,7275) @@ -3809,192 +4002,214 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. 240 - + Take &screenshot Tomar pan&tallazo - + F12 F12 - Record output... - Grabar salida... + Grabar salida... - + Record GIF... Grabar GIF... - Record video log... - Grabar video-registro... + Grabar video-registro... - Stop video log - Detener video-registro + Detener video-registro - + Game Boy Printer... Game Boy Printer... - + + BattleChip Gate... + BattleChip Gate... + + + + %1× + %1× + + + + Record A/V... + Grabar A/V... + + + Video layers Capas de video - + Audio channels Canales de audio - + Adjust layer placement... Ajustar ubicación de capas... - + &Tools Herramien&tas - + View &logs... Ver re&gistros... - + Game &overrides... Ajustes específic&os por juego... - + Game &Pak sensors... Sensores del Game &Pak... - + &Cheats... Tru&cos... - + Settings... Ajustes... - + Open debugger console... Abrir consola de depuración... - + Start &GDB server... Iniciar servidor &GDB... - + View &palette... Ver &paleta... - + View &sprites... Ver &sprites... - + View &tiles... Ver &tiles... - + View &map... Ver &mapa... - + View memory... Ver memoria... - + Search memory... Buscar memoria... - + View &I/O registers... Ver registros &I/O... - + + Record debug video log... + Grabar registro de depuración de video... + + + + Stop debug video log + Detener registro de depuración de video + + + Exit fullscreen Salir de pantalla completa - + GameShark Button (held) Botón GameShark (mantener) - + Autofire Disparo automático - + Autofire A Disparo automático A - + Autofire B Disparo automático B - + Autofire L Disparo automático L - + Autofire R Disparo automático R - + Autofire Start Disparo automático Start - + Autofire Select Disparo automático Select - + Autofire Up Disparo automático Arriba - + Autofire Right Disparo automático Derecha - + Autofire Down Disparo automático Abajo - + Autofire Left Disparo automático Izquierda @@ -4158,505 +4373,588 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Ajustes - + Audio/Video Audio/video - + Interface Interfaz - + Emulation Emulación - + + Enhancements + Mejoras + + + BIOS BIOS - + Paths Rutas - + + Logging + Registros + + + Game Boy Game Boy - + Audio driver: Sistema de audio: - + Audio buffer: Búfer de audio: - - + + 1536 1536 - + 512 512 - + 768 768 - + 1024 1024 - + 2048 2048 - + 3072 3072 - + 4096 4096 - + samples muestras - + Sample rate: Tasa de muestreo: - - + + 44100 44100 - + 22050 22050 - + 32000 32000 - + 48000 48000 - + Hz Hz - + Volume: Volumen: - - + + Mute Silenciar - + Fast forward volume: Vol. durante av. rápido: - + Display driver: Sistema de video: - + Frameskip: Salto de cuadros: - + Skip every Saltar cada - - + + frames cuadros - + FPS target: Objetivo de FPS: - + frames per second cuadros por segundo - + Sync: Sincronizar con: - + Video Video - + Audio Audio - + Lock aspect ratio Bloquear proporción de aspecto - + Bilinear filtering Filtro bilineal - - Force integer scaling - Forzar escalado de enteros + + Log to file + Guardar a archivo - + + Log to console + Guardar a consola + + + + Select Log File + Seleccionar + + + + Game Boy model: + Modelo de Game Boy: + + + + Super Game Boy model: + Modelo de Super Game Boy: + + + + Game Boy Color model: + Modelo de Game Boy Color: + + + + Use GBC colors in GB games + Usar colores de GBC en juegos GB + + + + Camera: + Cámara: + + + + Force integer scaling + Forzar escalado a valores enteros + + + Language Idioma - + English English - + Library: Biblioteca: - + List view Lista - + Tree view Árbol - + Show when no game open Mostrar cuando no haya un juego abierto - + Clear cache Limpiar caché - + Allow opposing input directions Permitir direcciones opuestas al mismo tiempo - + Suspend screensaver Suspender protector de pantalla - + Pause when inactive Pausar al no estar activo - + Show FPS in title bar Mostrar FPS en la barra de título - + Automatically save cheats Guardar trucos automáticamente - + Automatically load cheats Cargar trucos automáticamente - + Automatically save state Guardar estado automáticamente - + Automatically load state Cargar estado automáticamente - - Fast forward speed: - Velocidad de avance rápido: + + Enable Discord Rich Presence + Hablitar Rich Presence en Discord - + + Fast forward speed: + Avance rápido: + + + + × × - + Unbounded Sin límite - + Enable rewind Habilitar el rebobinar - + Rewind history: - Historial de rebobinado: + Hist. de rebobinado: - + Idle loops: Bucles inactivos: - + Run all Ejecutarlos todos - + Remove known Eliminar los conocidos - + Detect and remove Detectar y eliminar - + Savestate extra data: - Guardar datos extra con el estado: + Guardar datos extra: - - + + Screenshot Pantallazo - - + + Save data Datos de guardado - - + + Cheat codes Trucos - + Load extra data: - Cargar datos extra con el estado: + Cargar datos extra: Rewind affects save data El rebobinar afecta los datos de guardado - + Preload entire ROM into memory Cargar ROM completa a la memoria - + Autofire interval: - Intervalo de disparo automático: + Intervalo de turbo: - + + Video renderer: + Renderizador de video: + + + + Software + Software + + + + OpenGL + OpenGL + + + + OpenGL enhancements + Mejoras para OpenGL + + + + High-resolution scale: + Escala de alta resolución: + + + + XQ GBA audio (experimental) + Mejorar audio GBA (experimental) + + + GB BIOS file: Archivo BIOS GB: - - - - - - - - - + + + + + + + + + Browse Examinar - + Use BIOS file if found Usar archivo BIOS si fue encontrado - + Skip BIOS intro Saltar animación de entrada del BIOS - + GBA BIOS file: Archivo BIOS GBA: - + GBC BIOS file: Archivo BIOS GBC: - + SGB BIOS file: - SGB BIOS file: + Archivo BIOS SGB: - + Save games Datos de guardado - - - - - + + + + + Same directory as the ROM Al mismo directorio que la ROM - + Save states Estados de guardado - + Screenshots Pantallazos - + Patches Parches - + Cheats Trucos - Game Boy model - Modelo de Game Boy + Modelo de Game Boy - - - + + + Autodetect Detección automática - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - Super Game Boy model - Modelo de Super Game Boy + Modelo de Super Game Boy - Game Boy Color model - Modelo de Game Boy Color + Modelo de Game Boy Color - + Default BG colors: Colores de fondo por defecto: - + Super Game Boy borders Bordes de Super Game Boy - + Camera driver: Controlador de cámara: - + Default sprite colors 1: Colores de sprite 1 por defecto: - + Default sprite colors 2: Colores de sprite 2 por defecto: @@ -4730,20 +5028,30 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Tiles - + 256 colors 256 colores - + × × - + Magnification Ampliación + + + Tiles per row + Tiles por fila + + + + Fit to window + Ajustar a ventana + VideoView @@ -4882,86 +5190,91 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + HEVC (NVENC) + HEVC (NVENC) + + + VP8 VP8 - + VP9 VP9 - + FFV1 FFV1 - + FLAC FLAC - + Opus Opus - + Vorbis Vorbis - + MP3 MP3 - + AAC AAC - + Uncompressed Sin comprimir - + Bitrate (kbps) Tasa de bits (kbps) - + VBR VBR - + ABR ABR - + Dimensions Dimensiones - + : : - + × × - + Lock aspect ratio Bloquear proporción de aspecto - + Show advanced Mostrar ajustes avanzados diff --git a/src/platform/qt/ts/mgba-zh_CN.ts b/src/platform/qt/ts/mgba-zh_CN.ts index b60a570ad..faafd280a 100644 --- a/src/platform/qt/ts/mgba-zh_CN.ts +++ b/src/platform/qt/ts/mgba-zh_CN.ts @@ -4243,21 +4243,26 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 + Enhancements + 增强 + + + BIOS BIOS - + Paths 路径 - + Logging 日志记录 - + Game Boy Game Boy @@ -4272,491 +4277,522 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 音频缓冲: - - + + 1536 1536 - + 512 512 - + 768 768 - + 1024 1024 - + 2048 2048 - + 3072 3072 - + 4096 4096 - + samples 采样 - + Sample rate: 采样率: - - + + 44100 44100 - + 22050 22050 - + 32000 32000 - + 48000 48000 - + Hz Hz - + Volume: 音量: - - + + Mute 静音 - + Fast forward volume: 快进音量: - + Display driver: 显示驱动: - + Frameskip: 跳帧: - + Skip every 每间隔 - - + + frames - + FPS target: 目标 FPS: - + frames per second 帧每秒 - + Sync: 同步: - + Video 视频 - + Audio 音频 - + Lock aspect ratio 锁定纵横比 - + Force integer scaling 强制整数缩放 - + Bilinear filtering 双线性过滤 - + Language 语言 - + English 英语 - + Library: 库: - + List view 列表查看 - + Tree view 树状查看 - + Show when no game open 未打开游戏时显示 - + Clear cache 清除缓存 - + Allow opposing input directions 允许逆向输入 - + Suspend screensaver 停用屏幕保护程序 - + Pause when inactive 非活动时暂停 - + Show FPS in title bar 在标题栏显示 FPS - + Automatically save cheats 自动保存作弊码 - + Automatically load cheats 自动载入作弊码 - + Automatically save state 自动存档 - + Automatically load state 自动读档 - - + + Enable Discord Rich Presence 启用 Enable Discord Rich Presence - + Fast forward speed: 快进速度: - + + × × - + Unbounded 不限制 - + Autofire interval: 连发间隔: - + Enable rewind 启用回退 - + Rewind history: 回退历史: - + Idle loops: 空循环: - + Run all 运行所有 - + Remove known 移除选定 - + Detect and remove 检测并移除 - + Preload entire ROM into memory 将整个 ROM 预加载到内存中 - + Savestate extra data: 即时存档额外数据: - - + + Screenshot 截图 - - + + Save data 保存数据 - - + + Cheat codes 作弊码 - + Load extra data: - 读档时载入额外数据: + 载入额外数据: - - GB BIOS file: - GB BIOS 文件: + + Video renderer: + 视频渲染器: - - - - - - - - - + + Software + 软件 + + + + OpenGL + OpenGL + + + + OpenGL enhancements + OpenGL 增强 + + + + High-resolution scale: + 高分辨率比例: + + + + XQ GBA audio (experimental) + XQ GBA 音频 (实验) + + + + + + + + + + + Browse 浏览 - + + GB BIOS file: + GB BIOS 文件: + + + Use BIOS file if found 当可用时使用 BIOS 文件 - + Skip BIOS intro 跳过 BIOS 启动画面 - + GBA BIOS file: GBA BIOS 文件: - + GBC BIOS file: GBC BIOS 文件: - + SGB BIOS file: SGB BIOS 文件: - + Save games - 已保存的游戏 + 游戏存档 - - - - - + + + + + Same directory as the ROM 保存在 ROM 所在目录 Save states - 保存即时存档 + 即时存档 - + Screenshots 截图 - + Patches 补丁 - + Cheats 作弊码 - + Log to file 记录日志到文件 - + Log to console 记录日志到控制台 - + Select Log File 选择日志文件 - - Game Boy model - Game Boy 模型 + + Game Boy model: + Game Boy 模型: - - - + + + Autodetect 自动检测 - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model: Super Game Boy 模型: - + Game Boy Color model: Game Boy Color 模型: - + Default BG colors: 默认背景颜色: - + Super Game Boy borders Super Game Boy 边框 - + Camera driver: 相机驱动: - + Default sprite colors 1: 默认精灵图颜色 1: - + Default sprite colors 2: 默认精灵图颜色 2: - + Use GBC colors in GB games 在 GB 游戏中使用 GBC 颜色 - + Camera: 相机 diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index e4d400b29..d9c62c9a5 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -51,9 +51,9 @@ void mSDLGLCommonInit(struct mSDLRenderer* renderer) { #else SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); #ifdef COLOR_16_BIT - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #else - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #endif SDL_WM_SetCaption(projectName, ""); #endif diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index 675a1139c..1cedc5ad5 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -13,7 +13,7 @@ #include #include -#ifndef __APPLE__ +#ifdef __linux__ #include #endif @@ -37,7 +37,7 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { size_t size = renderer->width * renderer->height * BYTES_PER_PIXEL; #ifdef _WIN32 renderer->outputBuffer = _aligned_malloc(size, 16); -#elif !defined(__APPLE__) +#elif defined(__linux__) renderer->outputBuffer = memalign(16, size); #else posix_memalign((void**) &renderer->outputBuffer, 16, size); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 13ea14fd3..76ffe85ca 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -131,12 +131,8 @@ int main(int argc, char** argv) { renderer.viewportWidth = renderer.core->opts.width; renderer.viewportHeight = renderer.core->opts.height; -#if SDL_VERSION_ATLEAST(2, 0, 0) renderer.player.fullscreen = renderer.core->opts.fullscreen; renderer.player.windowUpdated = 0; -#else - renderer.fullscreen = renderer.core->opts.fullscreen; -#endif renderer.lockAspectRatio = renderer.core->opts.lockAspectRatio; renderer.lockIntegerScaling = renderer.core->opts.lockIntegerScaling; diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index bd4f2cecc..5f286d5fd 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -54,8 +54,6 @@ struct mSDLRenderer { SDL_Texture* sdlTex; SDL_Renderer* sdlRenderer; SDL_GLContext* glCtx; -#else - bool fullscreen; #endif unsigned width; diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index 7df0672c3..8cb1aede2 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -63,10 +63,10 @@ struct mSDLPlayer { size_t playerId; struct mInputMap* bindings; struct SDL_JoystickCombo* joystick; + int fullscreen; int windowUpdated; #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Window* window; - int fullscreen; struct mSDLRumble { struct mRumble d; diff --git a/src/platform/sdl/sw-sdl1.c b/src/platform/sdl/sw-sdl1.c index c8002fd24..90a9d2499 100644 --- a/src/platform/sdl/sw-sdl1.c +++ b/src/platform/sdl/sw-sdl1.c @@ -22,9 +22,9 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) { bool mSDLSWInit(struct mSDLRenderer* renderer) { #ifdef COLOR_16_BIT - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #else - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->fullscreen)); + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->player.fullscreen)); #endif SDL_WM_SetCaption(projectName, "");