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, "");