diff --git a/CHANGES b/CHANGES index 540ad89d1..77643b674 100644 --- a/CHANGES +++ b/CHANGES @@ -27,8 +27,11 @@ Features: - Undo-able savestate loading and saving - Controller profiles now store shortcut settings - Default controller profiles for several common controllers - - Libretro now supports BIOS and rumble + - Libretro now supports BIOS, rumble and solar sensor - Implement BIOS call Stop, for sleep mode + - Automatically load patches, if found + - Improved video synchronization + - Configurable audio output sample rate Bugfixes: - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit @@ -64,6 +67,11 @@ Bugfixes: - Qt: Fix a missing va_end call in the log handler lambda within the GameController constructor - GBA Cheats: Fix Pro Action Replay and GameShark issues when used together - Qt: Fix analog buttons not getting unmapped + - GBA Video: Prevent tiles < 512 from being used in modes 3 - 5 + - Qt: Fix passing command line options + - Qt: Fix crashes on Windows by using using QMetaObject to do cross-thread calls + - GBA Video: Fix timing on first scanline + - GBA: Ensure cycles never go negative Misc: - Qt: Handle saving input settings better - Debugger: Free watchpoints in addition to breakpoints @@ -109,6 +117,12 @@ Misc: - GBA Input: Allow axes and buttons to be mapped to the same key - GBA BIOS: Stub out SoundBias - Qt: Gamepads can now have both buttons and analog axes mapped to the same key + - Qt: Increase usability of key mapper + - Qt: Show checkmark for window sizes + - Qt: Set window path to loaded ROM + - GBA Memory: Run multiple DMAs in a tight loop if they all occur before present + - GBA Audio: Process multiple audio events at once, if necessary + - GBA: Process multiple timer events at once, if necessary 0.2.1: (2015-05-13) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 765dc4266..e0fb81313 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_STATIC OFF CACHE BOOL "Build a static library") set(BUILD_SHARED ON CACHE BOOL "Build a shared library") set(BUILD_GL ON CACHE STRING "Build with OpenGL") +set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c) file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c) @@ -44,13 +45,17 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) endif() +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}") + +include(GNUInstallDirs) + if (NOT DEFINED LIBDIR) set(LIBDIR "lib") endif() -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}") - -include(GNUInstallDirs) +if (NOT DEFINED MANDIR) + set(MANDIR ${CMAKE_INSTALL_MANDIR}) +endif() # Function definitions include(FindPkgConfig) @@ -126,7 +131,14 @@ endif() if(BUILD_GL) find_package(OpenGL QUIET) if(NOT OPENGL_FOUND) - set(BUILD_GL OFF) + set(BUILD_GL OFF CACHE BOOL "OpenGL not found" FORCE) + endif() +endif() +if(BUILD_GLES2 AND NOT BUILD_RASPI) + find_path(OPENGLES2_INCLUDE_DIR NAMES GLES2/gl2.h) + find_library(OPENGLES2_LIBRARY NAMES GLESv2 GLESv2_CM) + if(NOT OPENGLES2_INCLUDE_DIR OR NOT OPENGLES2_LIBRARY) + set(BUILD_GLES2 OFF CACHE BOOL "OpenGL|ES 2 not found" FORCE) endif() endif() find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") @@ -177,6 +189,10 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA) endif() endif() +if(BUILD_RASPI) + set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE) +endif() + if(BUILD_PANDORA) add_definitions(-DBUILD_PANDORA) endif() @@ -371,6 +387,7 @@ endif() if(BUILD_SHARED) add_library(${BINARY_NAME} SHARED ${SRC}) + set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") @@ -384,7 +401,7 @@ endif() add_dependencies(${BINARY_NAME} version-info) target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB}) -install(TARGETS ${BINARY_NAME} DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) +install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) if(UNIX AND NOT APPLE) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) @@ -402,6 +419,10 @@ if(BUILD_GL) add_definitions(-DBUILD_GL) endif() +if(BUILD_GLES2) + add_definitions(-DBUILD_GLES2) +endif() + if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c) add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) diff --git a/PORTING.md b/PORTING.md index baa6f3712..ef7216c21 100644 --- a/PORTING.md +++ b/PORTING.md @@ -30,13 +30,12 @@ The ports are vaguely usable, but by no means should be considered stable. ### PS Vita (port/psp2) * Add menu -* Add audio +* Fix audio * Make it faster * Threaded renderer shim * Hardware acceleration ### Wii (port/wii) * Add menu -* Add audio * Thread support * Clean up video detection diff --git a/README.md b/README.md index be79140fc..eb96a9f70 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,6 @@ Footnotes - 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)) - BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141)) -- Game Pak prefetch ([Bug #195](http://mgba.io/b/195)) -- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199)) [2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. diff --git a/doc/mgba-qt.6 b/doc/mgba-qt.6 new file mode 100644 index 000000000..256384721 --- /dev/null +++ b/doc/mgba-qt.6 @@ -0,0 +1,118 @@ +.\" Copyright (c) 2015 Anthony J. Bentley +.\" +.\" 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 https://mozilla.org/MPL/2.0/. +.Dd July 29, 2015 +.Dt MGBA-QT 6 +.Os +.Sh NAME +.Nm mgba-qt +.Nd Game Boy Advance emulator +.Sh SYNOPSIS +.Nm mgba-qt +.Op Fl b Ar biosfile +.Op Fl l Ar loglevel +.Op Fl p Ar patchfile +.Op Fl s Ar n +.Ar file +.Sh DESCRIPTION +.Nm +is a Game Boy Advance emulator. +The options are as follows: +.Bl -tag -width Ds +.It Fl b Ar biosfile , Fl -bios Ar biosfile +Specify a BIOS file to use during boot. +If this flag is omitted, +.Nm +will use the BIOS specified in the configuration file, +or a high\(hylevel emulated BIOS if none is specified. +.It Fl l Ar loglevel +Log messages during emulation. +.Ar loglevel +is a bitmask defining which types of messages to log: +.Bl -bullet -compact +.It +1 \(en fatal errors +.It +2 \(en errors +.It +4 \(en warnings +.It +8 \(en informative messages +.It +16 \(en debugging messages +.It +32 \(en stub messages for unimplemented features +.It +256 \(en in\(hygame errors +.It +512 \(en software interrupts +.It +1024 \(en emulator status messages +.It +2048 \(en serial I/O messages +.El +The default is to log warnings, errors, fatal errors, and status messages. +.It Fl p Ar patchfile , Fl -patch Ar patchfile +Specify a patch file in BPS, IPS, or UPS format. +.It Fl s Ar n , Fl -frameskip Ar n +Skip every +.Ar n +frames. +.El +.Sh CONTROLS +The default controls are as follows: +.Bl -hang -width "Frame advance" -compact +.It A +.Cm x +.It B +.Cm z +.It L +.Cm a +.It R +.Cm s +.It Start +.Aq Cm Enter +.It Select +.Aq Cm Backspace +.It Load state +.Cm F1 Ns \(en Ns Cm F9 +.It Save state +.Ao Cm Shift Ac Ns \(hy Ns Cm F1 Ns \(en Ns Cm F9 +.It Frame advance +.Ao Cm Ctrl Ac Ns \(hy Ns Cm n +.El +.Sh FILES +.Bl -tag -width ~/.config/mgba/config.ini -compact +.It Pa ~/.config/mgba/config.ini +Default +.Xr mgba 6 +configuration file. +.It Pa ~/.config/mgba/qt.ini +Default +.Nm mgba-qt +configuration file. +.It Pa portable.ini +If this file exists in the current directory, +.Nm +will read +.Pa config.ini +and +.Pa qt.ini +from the current directory instead of +.Pa ~/.config/mgba . +.El +.Sh AUTHORS +.An Jeffrey Pfau Aq Mt jeffrey@endrift.com +.Sh HOMEPAGE +.Bl -bullet +.It +.Lk https://mgba.io/ "mGBA homepage" +.It +.Lk https://github.com/mgba-emu/mgba "Development repository" +.It +.Lk https://github.com/mgba-emu/mgba/issues "Bug tracker" +.It +.Lk https://forums.mgba.io/ "Message board" +.El diff --git a/doc/mgba.6 b/doc/mgba.6 new file mode 100644 index 000000000..d2a4cb05b --- /dev/null +++ b/doc/mgba.6 @@ -0,0 +1,259 @@ +.\" Copyright (c) 2015 Anthony J. Bentley +.\" +.\" 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 https://mozilla.org/MPL/2.0/. +.Dd July 29, 2015 +.Dt MGBA 6 +.Os +.Sh NAME +.Nm mgba +.Nd Game Boy Advance emulator +.Sh SYNOPSIS +.Nm mgba +.Op Fl 123456dfg +.Op Fl b Ar biosfile +.Op Fl c Ar cheatfile +.Op Fl l Ar loglevel +.Op Fl p Ar patchfile +.Op Fl s Ar n +.Op Fl v Ar moviefile +.Ar file +.Sh DESCRIPTION +.Nm +is a Game Boy Advance emulator. +The options are as follows: +.Bl -tag -width Ds +.It Fl 1 +Scale the window 1\(mu. +.It Fl 2 +Scale the window 2\(mu. +.It Fl 3 +Scale the window 3\(mu. +.It Fl 4 +Scale the window 4\(mu. +.It Fl 5 +Scale the window 5\(mu. +.It Fl 6 +Scale the window 6\(mu. +.It Fl b Ar biosfile , Fl -bios Ar biosfile +Specify a BIOS file to use during boot. +If this flag is omitted, +.Nm +will use the BIOS specified in the configuration file, +or a high\(hylevel emulated BIOS if none is specified. +.It Fl c Ar cheatfile , Fl -cheats Ar cheatfile +Apply cheat codes from +.Ar cheatfile . +.It Fl d +Start emulating via the command\(hyline debugger. +.It Fl f +Start the emulator full\(hyscreen. +.It Fl g +Start a +.Xr gdb 1 +session. +By default the session starts on port 2345. +.It Fl l Ar loglevel +Log messages during emulation to +.Dv stdout . +.Ar loglevel +is a bitmask defining which types of messages to log: +.Bl -bullet -compact +.It +1 \(en fatal errors +.It +2 \(en errors +.It +4 \(en warnings +.It +8 \(en informative messages +.It +16 \(en debugging messages +.It +32 \(en stub messages for unimplemented features +.It +256 \(en in\(hygame errors +.It +512 \(en software interrupts +.It +1024 \(en emulator status messages +.It +2048 \(en serial I/O messages +.El +The default is to log warnings, errors, fatal errors, and status messages. +.It Fl p Ar patchfile , Fl -patch Ar patchfile +Specify a patch file in BPS, IPS, or UPS format. +.It Fl s Ar n , Fl -frameskip Ar n +Skip every +.Ar n +frames. +.It Fl v Ar moviefile , Fl -movie Ar moviefile +Play back a movie of recording input from +.Ar moviefile . +.El +.Sh CONTROLS +The default controls are as follows: +.Bl -hang -width "Frame advance" -compact +.It A +.Cm x +.It B +.Cm z +.It L +.Cm a +.It R +.Cm s +.It Start +.Aq Cm Enter +.It Select +.Aq Cm Backspace +.It Load state +.Cm F1 Ns \(en Ns Cm F9 +.It Save state +.Ao Cm Shift Ac Ns \(hy Ns Cm F1 Ns \(en Ns Cm F9 +.It Frame advance +.Ao Cm Ctrl Ac Ns \(hy Ns Cm n +.El +.Sh DEBUGGER +When +.Nm +is run with the +.Fl d +option, the command\(hyline debugger is enabled. +It supports the following commands: +.Pp +.Bl -tag -compact -width 1 +.It Cm b Ns Oo Cm reak Oc Ar address +.It Cm b Ns Oo Cm reak Oc Ns Cm /a Ar address +.It Cm b Ns Oo Cm reak Oc Ns Cm /t Ar address +Set a breakpoint \(en ARM +.Pq Ql /a , +Thumb +.Pq Ql /t , +or the current CPU mode \(en at +.Ar address . +.It Cm c Ns Op Cm ontinue +Continue execution. +.It Cm d Ns Oo Cm elete Oc Ar address +Delete a breakpoint at +.Ar address . +.It Cm dis Ns Oo Cm asm Oc Op Ar address Op Ar count +.It Cm dis Ns Oo Cm asm Oc Ns Cm /a Op Ar address Op Ar count +.It Cm dis Ns Oo Cm asm Oc Ns Cm /t Op Ar address Op Ar count +.It Cm dis Ns Oo Cm assemble Oc Op Ar address Op Ar count +.It Cm dis Ns Oo Cm assemble Oc Ns Cm /a Op Ar address Op Ar count +.It Cm dis Ns Oo Cm assemble Oc Ns Cm /t Op Ar address Op Ar count +Disassemble +.Ar count +instructions starting at +.Ar address , +as ARM +.Pq Ql /a , +Thumb +.Pq Ql /t , +or the current CPU mode. +If +.Ar count +is not specified, only disassemble the instruction at +.Ar address . +If +.Ar address +is not specified, only disassemble the current address. +.It Cm h Ns Op Cm elp +Print help. +.It Cm i Ns Op Cm nfo +.It Cm status +Print the current contents of general\(hypurpose registers and the current +program state register, and disassemble the current instruction. +.It Cm n Ns Op Cm ext +Execute the next instruction. +.It Cm p Ns Oo Cm rint Oc Ar value ... +.It Cm p Ns Oo Cm rint Oc Ns Cm /t Ar value ... +.It Cm p Ns Oo Cm rint Oc Ns Cm /x Ar value ... +Print one or more +.Ar value Ns s +as binary +.Pq Ql /t , +hexadecimal +.Pq Ql /x , +or decimal. +.It Cm q Ns Op Cm uit +Quit the emulator. +.It Cm reset +Reset the emulation. +.It Cm r/1 Ar address +.It Cm r/2 Ar address +.It Cm r/4 Ar address +Read a byte +.Pq Ql /1 , +halfword +.Pq Ql /2 , +or word +.Pq Ql /4 +from +.Ar address . +.It Cm w Ns Oo Cm atch Oc Ar address +Set a watchpoint at +.Ar address . +.It Cm w/1 Ar address data +.It Cm w/2 Ar address data +.It Cm w/4 Ar address data +Write +.Ar data +as a byte +.Pq Ql /1 , +halfword +.Pq Ql /2 , +or word +.Pq Ql /4 +to +.Ar address . +.It Cm w/r Ar register data +Write +.Ar data +as a word to +.Ar register . +.It Cm x/1 Ar address Op Ar count +.It Cm x/2 Ar address Op Ar count +.It Cm x/4 Ar address Op Ar count +Examine +.Ar count +bytes +.Pq Ql /1 , +halfwords +.Pq Ql /2 , +or words +.Pq Ql /4 +from +.Ar address . +If +.Ar count +is not specified, examine 16 bytes, 8 halfwords, or 4 words. +.El +.Sh FILES +.Bl -tag -width ~/.config/mgba/config.ini -compact +.It Pa ~/.config/mgba/config.ini +Default +.Nm +configuration file. +.It Pa portable.ini +If this file exists in the current directory, +.Nm +will read +.Pa config.ini +from the current directory instead of +.Pa ~/.config/mgba . +.El +.Sh AUTHORS +.An Jeffrey Pfau Aq Mt jeffrey@endrift.com +.Sh HOMEPAGE +.Bl -bullet +.It +.Lk https://mgba.io/ "mGBA homepage" +.It +.Lk https://github.com/mgba-emu/mgba "Development repository" +.It +.Lk https://github.com/mgba-emu/mgba/issues "Bug tracker" +.It +.Lk https://forums.mgba.io/ "Message board" +.El diff --git a/res/keymap.qpic b/res/keymap.qpic index ffb7f8b7d..782d0c7f2 100644 Binary files a/res/keymap.qpic and b/res/keymap.qpic differ diff --git a/res/keymap.svg b/res/keymap.svg index 5ec9d5099..9b37a3aa4 100644 --- a/res/keymap.svg +++ b/res/keymap.svg @@ -7,130 +7,266 @@ - - - + + + + + + + + - - - - - - + - + - - - + + + - + - + - + - - + + - + - + - - + + - - + + - + - - + + - - + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/mgba-1024.png b/res/mgba-1024.png index 4ef829578..2afd2aa8b 100644 Binary files a/res/mgba-1024.png and b/res/mgba-1024.png differ diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 90d657af1..c3f0c9de1 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -18,7 +18,9 @@ static const char* ERROR_OVERFLOW = "Arguments overflow"; static struct CLIDebugger* _activeDebugger; +#ifndef NDEBUG static void _breakInto(struct CLIDebugger*, struct CLIDebugVector*); +#endif static void _continue(struct CLIDebugger*, struct CLIDebugVector*); static void _disassemble(struct CLIDebugger*, struct CLIDebugVector*); static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*); @@ -56,6 +58,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, { "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, { "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, + { "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, + { "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, { "c", _continue, 0, "Continue execution" }, { "continue", _continue, 0, "Continue execution" }, { "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" }, @@ -97,7 +101,9 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" }, { "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" }, { "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" }, +#ifndef NDEBUG { "!", _breakInto, 0, "Break into attached debugger (for developers)" }, +#endif { 0, 0, 0, 0 } }; @@ -112,6 +118,7 @@ static inline void _printPSR(union PSR psr) { psr.t ? 'T' : '-'); } +#ifndef NDEBUG static void _handleDeath(int sig) { UNUSED(sig); printf("No debugger attached!\n"); @@ -133,6 +140,7 @@ static void _breakInto(struct CLIDebugger* debugger, struct CLIDebugVector* dv) #endif sigaction(SIGTRAP, &osa, 0); } +#endif static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); diff --git a/src/gba/audio.c b/src/gba/audio.c index 372c7ef25..6fac940b3 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -167,7 +167,7 @@ void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { int32_t GBAAudioProcessEvents(struct GBAAudio* audio, int32_t cycles) { audio->nextEvent -= cycles; audio->eventDiff += cycles; - if (audio->nextEvent <= 0) { + while (audio->nextEvent <= 0) { audio->nextEvent = INT_MAX; if (audio->enable) { if (audio->playingCh1 && !audio->ch1.envelope.dead) { diff --git a/src/gba/gba.c b/src/gba/gba.c index 732da9eed..a7356ad3a 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -94,6 +94,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) { gba->idleDetectionFailures = 0; gba->realisticTiming = true; + gba->hardCrash = true; gba->performingDMA = false; } @@ -183,6 +184,11 @@ static void GBAProcessEvents(struct ARMCore* cpu) { int32_t cycles = cpu->nextEvent; int32_t nextEvent = INT_MAX; int32_t testEvent; +#ifndef NDEBUG + if (cycles < 0) { + GBALog(gba, GBA_LOG_FATAL, "Negative cycles passed: %i", cycles); + } +#endif gba->bus = cpu->prefetch[1]; if (cpu->executionMode == MODE_THUMB) { @@ -238,7 +244,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) { if (timer->enable) { timer->nextEvent -= cycles; timer->lastEvent -= cycles; - if (timer->nextEvent <= 0) { + while (timer->nextEvent <= 0) { timer->lastEvent = timer->nextEvent; timer->nextEvent += timer->overflowInterval; gba->memory.io[REG_TM0CNT_LO >> 1] = timer->reload; diff --git a/src/gba/gba.h b/src/gba/gba.h index 46eea057c..e472a14ac 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -124,6 +124,7 @@ struct GBA { bool taintedRegisters[16]; bool realisticTiming; + bool hardCrash; }; struct GBACartridge { diff --git a/src/gba/hardware.c b/src/gba/hardware.c index bec3fa398..4f2276ba4 100644 --- a/src/gba/hardware.c +++ b/src/gba/hardware.c @@ -9,6 +9,8 @@ #include "gba/serialize.h" #include "util/hash.h" +const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; + static void _readPins(struct GBACartridgeHardware* hw); static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins); @@ -610,6 +612,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer hw->readWrite = state->hw.readWrite; hw->pinState = state->hw.pinState; hw->direction = state->hw.pinDirection; + hw->devices = state->hw.devices; hw->rtc = state->hw.rtc; hw->gyroSample = state->hw.gyroSample; hw->gyroEdge = state->hw.gyroEdge; @@ -622,4 +625,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer hw->gbpInputsPosted = state->hw.gbpInputsPosted; hw->gbpTxPosition = state->hw.gbpTxPosition; hw->gbpNextEvent = state->hw.gbpNextEvent; + if (hw->devices & HW_GB_PLAYER) { + GBASIOSetDriver(&hw->p->sio, &hw->gbpDriver.d, SIO_NORMAL_32); + } } diff --git a/src/gba/input.c b/src/gba/input.c index a204741a2..6ae95844c 100644 --- a/src/gba/input.c +++ b/src/gba/input.c @@ -433,8 +433,9 @@ int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, in void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) { struct GBAInputMapImpl* impl = _guaranteeMap(map, type); - TableEnumerate(&impl->axes, _unbindAxis, &description->highDirection); - TableEnumerate(&impl->axes, _unbindAxis, &description->lowDirection); + struct GBAAxis d2 = *description; + TableEnumerate(&impl->axes, _unbindAxis, &d2.highDirection); + TableEnumerate(&impl->axes, _unbindAxis, &d2.lowDirection); struct GBAAxis* dup = malloc(sizeof(struct GBAAxis)); *dup = *description; TableInsert(&impl->axes, axis, dup); diff --git a/src/gba/interface.h b/src/gba/interface.h index 3daf0dcb8..396f4b549 100644 --- a/src/gba/interface.h +++ b/src/gba/interface.h @@ -79,6 +79,8 @@ struct GBARotationSource { int32_t (*readGyroZ)(struct GBARotationSource*); }; +extern const int GBA_LUX_LEVELS[10]; + struct GBALuminanceSource { void (*sample)(struct GBALuminanceSource*); diff --git a/src/gba/memory.c b/src/gba/memory.c index 9e7a478bb..9da33f31b 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -278,9 +278,11 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { memory->activeRegion = -1; cpu->memory.activeRegion = _deadbeef; cpu->memory.activeMask = 0; - if (!gba->yankedRomSize) { - GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address"); + enum GBALogLevel errorLevel = GBA_LOG_FATAL; + if (gba->yankedRomSize || !gba->hardCrash) { + errorLevel = GBA_LOG_GAME_ERROR; } + GBALog(gba, errorLevel, "Jumped to invalid address: %08X", address); return; } cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion]; @@ -634,8 +636,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { #define STORE_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \ STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ } else { \ STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \ + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \ } \ wait += waitstatesRegion[REGION_VRAM]; @@ -728,8 +734,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); } else { STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE); } break; case REGION_OAM: @@ -794,8 +802,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OBJ: 0x%08X", address); break; } - ((int8_t*) gba->video.renderer->vram)[address & 0x1FFFE] = value; - ((int8_t*) gba->video.renderer->vram)[(address & 0x1FFFE) | 1] = value; + gba->video.renderer->vram[(address & 0x1FFFE) >> 1] = ((uint8_t) value) | (value << 8); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); break; case REGION_OAM: GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OAM: 0x%08X", address); @@ -1397,7 +1405,7 @@ int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles) { } memory->nextDMA -= cycles; memory->eventDiff += cycles; - if (memory->nextDMA <= 0) { + while (memory->nextDMA <= 0) { struct GBADMA* dma = &memory->dma[memory->activeDMA]; GBAMemoryServiceDMA(gba, memory->activeDMA, dma); GBAMemoryUpdateDMAs(gba, memory->eventDiff); diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 2d13b7340..eb793db23 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -116,6 +116,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re x >>= 23; uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; + if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) { + return 0; + } int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { int target2 = renderer->target2Bd << 4; diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index f2b7d842c..7dae88af6 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -13,6 +13,7 @@ static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer); +static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address); static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); @@ -46,6 +47,7 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) { renderer->d.reset = GBAVideoSoftwareRendererReset; renderer->d.deinit = GBAVideoSoftwareRendererDeinit; renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister; + renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM; renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM; renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette; renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline; @@ -327,6 +329,11 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender return value; } +static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { + UNUSED(renderer); + UNUSED(address); +} + static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer; softwareRenderer->oamDirty = 1; diff --git a/src/gba/serialize.c b/src/gba/serialize.c index e7aa56fac..96e7c0daa 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -87,6 +87,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); error = true; } + if (state->cpu.nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: Next event is negative"); + error = true; + } if (state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative"); error = true; @@ -99,6 +103,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); error = true; } + if (state->timers[0].nextEvent < 0 || state->timers[1].nextEvent < 0 || state->timers[2].nextEvent < 0 || state->timers[3].nextEvent < 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: timer nextEvent is negative"); + error = true; + } if (state->audio.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative"); error = true; diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c index 19e5c2bd6..ecf175dbb 100644 --- a/src/gba/supervisor/config.c +++ b/src/gba/supervisor/config.c @@ -247,6 +247,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { opts->audioBuffers = audioBuffers; } + _lookupUIntValue(config, "sampleRate", &opts->sampleRate); int fakeBool; if (_lookupIntValue(config, "useBios", &fakeBool)) { @@ -305,6 +306,7 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); + ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate); ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync); ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync); ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen); diff --git a/src/gba/supervisor/config.h b/src/gba/supervisor/config.h index 07c824cd2..35d822838 100644 --- a/src/gba/supervisor/config.h +++ b/src/gba/supervisor/config.h @@ -29,6 +29,7 @@ struct GBAOptions { int rewindBufferInterval; float fpsTarget; size_t audioBuffers; + unsigned sampleRate; int fullscreen; int width; diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 08b5b7402..94eff4a61 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -419,6 +419,16 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR); + if (!threadContext->patch) { + threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ups", O_RDONLY); + } + if (!threadContext->patch) { + threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ips", O_RDONLY); + } + if (!threadContext->patch) { + threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".bps", O_RDONLY); + } + MutexInit(&threadContext->stateMutex); ConditionInit(&threadContext->stateCond); diff --git a/src/gba/video.c b/src/gba/video.c index 66ebedd34..fef6e1b54 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -17,6 +17,7 @@ static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer); static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer); static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); +static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address); static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y); @@ -47,6 +48,7 @@ static struct GBAVideoRenderer dummyRenderer = { .reset = GBAVideoDummyRendererReset, .deinit = GBAVideoDummyRendererDeinit, .writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister, + .writeVRAM = GBAVideoDummyRendererWriteVRAM, .writePalette = GBAVideoDummyRendererWritePalette, .writeOAM = GBAVideoDummyRendererWriteOAM, .drawScanline = GBAVideoDummyRendererDrawScanline, @@ -65,7 +67,7 @@ void GBAVideoReset(struct GBAVideo* video) { video->lastHblank = 0; video->nextHblank = VIDEO_HDRAW_LENGTH; video->nextEvent = video->nextHblank; - video->eventDiff = video->nextEvent; + video->eventDiff = 0; video->nextHblankIRQ = 0; video->nextVblankIRQ = 0; @@ -222,6 +224,12 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* return value; } +static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { + UNUSED(renderer); + UNUSED(address); + // Nothing to do +} + static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { UNUSED(renderer); UNUSED(address); diff --git a/src/gba/video.h b/src/gba/video.h index a94235238..6713a8f0b 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -161,6 +161,7 @@ struct GBAVideoRenderer { void (*deinit)(struct GBAVideoRenderer* renderer); uint16_t (*writeVideoRegister)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); + void (*writeVRAM)(struct GBAVideoRenderer* renderer, uint32_t address); void (*writePalette)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); void (*writeOAM)(struct GBAVideoRenderer* renderer, uint32_t oam); void (*drawScanline)(struct GBAVideoRenderer* renderer, int y); diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 1ae1a9fee..9a986dd0e 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -8,6 +8,7 @@ #include "util/common.h" #include "gba/gba.h" +#include "gba/interface.h" #include "gba/renderers/video-software.h" #include "gba/serialize.h" #include "gba/supervisor/overrides.h" @@ -18,6 +19,8 @@ #define SAMPLES 1024 #define RUMBLE_PWM 35 +#define SOLAR_SENSOR_LEVEL "mgba_solar_sensor_level" + static retro_environment_t environCallback; static retro_video_refresh_t videoCallback; static retro_audio_sample_batch_t audioCallback; @@ -31,6 +34,8 @@ static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio); static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); static void _setRumble(struct GBARumble* rumble, int enable); +static uint8_t _readLux(struct GBALuminanceSource* lux); +static void _updateLux(struct GBALuminanceSource* lux); static struct GBA gba; static struct ARMCore cpu; @@ -44,6 +49,8 @@ static struct GBAAVStream stream; static int rumbleLevel; static struct CircleBuffer rumbleHistory; static struct GBARumble rumble; +static struct GBALuminanceSource lux; +static int luxLevel; unsigned retro_api_version(void) { return RETRO_API_VERSION; @@ -51,6 +58,13 @@ unsigned retro_api_version(void) { void retro_set_environment(retro_environment_t env) { environCallback = env; + + struct retro_variable vars[] = { + { SOLAR_SENSOR_LEVEL, "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" }, + { 0, 0 } + }; + + environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars); } void retro_set_video_refresh(retro_video_refresh_t video) { @@ -115,7 +129,9 @@ void retro_init(void) { { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" }, - { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" } + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" } }; environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors); @@ -130,6 +146,11 @@ void retro_init(void) { rumbleCallback = 0; } + luxLevel = 0; + lux.readLuminance = _readLux; + lux.sample = _updateLux; + _updateLux(&lux); + struct retro_log_callback log; if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) { logCallback = log.log; @@ -151,6 +172,7 @@ void retro_init(void) { if (rumbleCallback) { gba.rumble = &rumble; } + gba.luminanceSource = &lux; rom = 0; const char* sysDir = 0; @@ -201,6 +223,26 @@ void retro_run(void) { keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8; keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9; + static bool wasAdjustingLux = false; + if (wasAdjustingLux) { + wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) || + inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3); + } else { + if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) { + ++luxLevel; + if (luxLevel > 10) { + luxLevel = 10; + } + wasAdjustingLux = true; + } else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) { + --luxLevel; + if (luxLevel < 0) { + luxLevel = 0; + } + wasAdjustingLux = true; + } + } + int frameCount = gba.video.frameCounter; while (gba.video.frameCounter == frameCount) { ARMRunLoop(&cpu); @@ -401,3 +443,40 @@ static void _setRumble(struct GBARumble* rumble, int enable) { CircleBufferWrite8(&rumbleHistory, enable); rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM); } + +static void _updateLux(struct GBALuminanceSource* lux) { + UNUSED(lux); + struct retro_variable var = { + .key = SOLAR_SENSOR_LEVEL, + .value = 0 + }; + + bool updated = false; + if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) { + return; + } + if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) { + return; + } + + char* end; + int newLuxLevel = strtol(var.value, &end, 10); + if (!*end) { + if (newLuxLevel > 10) { + luxLevel = 10; + } else if (newLuxLevel < 0) { + luxLevel = 0; + } else { + luxLevel = newLuxLevel; + } + } +} + +static uint8_t _readLux(struct GBALuminanceSource* lux) { + UNUSED(lux); + int value = 0x16; + if (luxLevel > 0) { + value += GBA_LUX_LEVELS[luxLevel - 1]; + } + return 0xFF - value; +} diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c new file mode 100644 index 000000000..a667cf8e8 --- /dev/null +++ b/src/platform/opengl/gles2.c @@ -0,0 +1,136 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gles2.h" + +#include "gba/video.h" + +static const char* const _vertexShader = + "attribute vec4 position;\n" + "varying vec2 texCoord;\n" + + "void main() {\n" + " gl_Position = position;\n" + " texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n" + "}"; + +static const char* const _fragmentShader = + "varying vec2 texCoord;\n" + "uniform sampler2D tex;\n" + + "void main() {\n" + " vec4 color = texture2D(tex, texCoord);\n" + " color.a = 1.;\n" + " gl_FragColor = color;" + "}"; + +static const GLfloat _vertices[] = { + -1.f, -1.f, + -1.f, 1.f, + 1.f, 1.f, + 1.f, -1.f, +}; + +static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) { + UNUSED(handle); + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glGenTextures(1, &context->tex); + glBindTexture(GL_TEXTURE_2D, context->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); + +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif + + glShaderSource(context->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0); + glShaderSource(context->vertexShader, 1, (const GLchar**) &_vertexShader, 0); + glAttachShader(context->program, context->vertexShader); + glAttachShader(context->program, context->fragmentShader); + char log[1024]; + glCompileShader(context->fragmentShader); + glCompileShader(context->vertexShader); + glGetShaderInfoLog(context->fragmentShader, 1024, 0, log); + glGetShaderInfoLog(context->vertexShader, 1024, 0, log); + glLinkProgram(context->program); + glGetProgramInfoLog(context->program, 1024, 0, log); + printf("%s\n", log); + context->texLocation = glGetUniformLocation(context->program, "tex"); + context->positionLocation = glGetAttribLocation(context->program, "position"); + glClearColor(0.f, 0.f, 0.f, 1.f); +} + +static void GBAGLES2ContextDeinit(struct VideoBackend* v) { + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glDeleteTextures(1, &context->tex); +} + +static void GBAGLES2ContextResized(struct VideoBackend* v, int w, int h) { + int drawW = w; + int drawH = h; + if (v->lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + glViewport(0, 0, 240, 160); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); +} + +static void GBAGLES2ContextClear(struct VideoBackend* v) { + UNUSED(v); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); +} + +void GBAGLES2ContextDrawFrame(struct VideoBackend* v) { + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glUseProgram(context->program); + glUniform1i(context->texLocation, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, context->tex); + glVertexAttribPointer(context->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); + glEnableVertexAttribArray(context->positionLocation); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glUseProgram(0); +} + +void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { + struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; + glBindTexture(GL_TEXTURE_2D, context->tex); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); +#endif +} + +void GBAGLES2ContextCreate(struct GBAGLES2Context* context) { + context->d.init = GBAGLES2ContextInit; + context->d.deinit = GBAGLES2ContextDeinit; + context->d.resized = GBAGLES2ContextResized; + context->d.swap = 0; + context->d.clear = GBAGLES2ContextClear; + context->d.postFrame = GBAGLES2ContextPostFrame; + context->d.drawFrame = GBAGLES2ContextDrawFrame; + context->d.setMessage = 0; + context->d.clearMessage = 0; +} diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h new file mode 100644 index 000000000..c1f97bc27 --- /dev/null +++ b/src/platform/opengl/gles2.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GLES2_H +#define GLES2_H + +#include + +#include "platform/video-backend.h" + +struct GBAGLES2Context { + struct VideoBackend d; + + GLuint tex; + GLuint fragmentShader; + GLuint vertexShader; + GLuint program; + GLuint bufferObject; + GLuint texLocation; + GLuint positionLocation; +}; + +void GBAGLES2ContextCreate(struct GBAGLES2Context*); + +#endif diff --git a/src/platform/perf-main.c b/src/platform/perf-main.c index 564765314..942dd57d9 100644 --- a/src/platform/perf-main.c +++ b/src/platform/perf-main.c @@ -186,7 +186,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) } } GBASyncWaitFrameEnd(&context->sync); - if (*frames == duration) { + if (duration > 0 && *frames == duration) { _GBAPerfShutdown(0); } if (_dispatchExiting) { diff --git a/src/platform/posix/threading.h b/src/platform/posix/threading.h index 7ca05ce15..71015526a 100644 --- a/src/platform/posix/threading.h +++ b/src/platform/posix/threading.h @@ -10,7 +10,7 @@ #include #include -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__OpenBSD__) #include #endif @@ -79,7 +79,7 @@ static inline int ThreadJoin(Thread thread) { static inline int ThreadSetName(const char* name) { #ifdef __APPLE__ return pthread_setname_np(name); -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); return 0; #else diff --git a/src/platform/qt/AboutScreen.cpp b/src/platform/qt/AboutScreen.cpp new file mode 100644 index 000000000..ca53d334a --- /dev/null +++ b/src/platform/qt/AboutScreen.cpp @@ -0,0 +1,36 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "AboutScreen.h" + +#include "util/version.h" + +#include + +using namespace QGBA; + +AboutScreen::AboutScreen(QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + QPixmap logo(":/res/mgba-1024.png"); + logo = logo.scaled(m_ui.logo->minimumSize() * devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + logo.setDevicePixelRatio(devicePixelRatio()); + m_ui.logo->setPixmap(logo); + + m_ui.projectName->setText(QLatin1String(projectName)); + m_ui.projectVersion->setText(QLatin1String(projectVersion)); + QString gitInfo = m_ui.gitInfo->text(); + gitInfo.replace("{gitBranch}", QLatin1String(gitBranch)); + gitInfo.replace("{gitCommit}", QLatin1String(gitCommit)); + m_ui.gitInfo->setText(gitInfo); + QString description = m_ui.description->text(); + description.replace("{projectName}", QLatin1String(projectName)); + m_ui.description->setText(description); + QString extraLinks = m_ui.extraLinks->text(); + extraLinks.replace("{gitBranch}", QLatin1String(gitBranch)); + m_ui.extraLinks->setText(extraLinks); +} diff --git a/src/platform/qt/AboutScreen.h b/src/platform/qt/AboutScreen.h new file mode 100644 index 000000000..aebf1bdc1 --- /dev/null +++ b/src/platform/qt/AboutScreen.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2013-2014 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_ABOUT_SCREEN +#define QGBA_ABOUT_SCREEN + +#include + +#include "ui_AboutScreen.h" + +namespace QGBA { + +class AboutScreen : public QDialog { +Q_OBJECT + +public: + AboutScreen(QWidget* parent = nullptr); + +private: + Ui::AboutScreen m_ui; +}; + +} + +#endif diff --git a/src/platform/qt/AboutScreen.ui b/src/platform/qt/AboutScreen.ui new file mode 100644 index 000000000..6d2662c45 --- /dev/null +++ b/src/platform/qt/AboutScreen.ui @@ -0,0 +1,177 @@ + + + AboutScreen + + + + 0 + 0 + 565 + 268 + + + + About + + + + QLayout::SetFixedSize + + + + + + 36 + 75 + true + + + + {projectName} + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 10 + + + + © 2013 – 2015 Jeffrey Pfau — Game Boy Advance is a registered trademark of Nintendo Co., Ltd. + + + Qt::AlignCenter + + + true + + + + + + + + 75 + true + + + + {projectVersion} + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + 8 + + + 16 + + + 12 + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 256 + 192 + + + + {logo} + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + {projectName} is an open-source Game Boy Advance emulator + + + Qt::AlignCenter + + + true + + + + + + + <a href="http://mgba.io/">Website</a> • <a href="https://forums.mgba.io/">Forums / Support</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Source</a> • <a href="https://github.com/mgba-emu/mgba/blob/{gitBranch}/LICENSE">License</a> + + + Qt::AlignCenter + + + true + + + + + + + + 10 + + + + Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt> + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + diff --git a/src/platform/qt/AudioProcessor.cpp b/src/platform/qt/AudioProcessor.cpp index 6c58e2c39..df4e269de 100644 --- a/src/platform/qt/AudioProcessor.cpp +++ b/src/platform/qt/AudioProcessor.cpp @@ -38,10 +38,10 @@ AudioProcessor* AudioProcessor::create() { #endif default: -#ifdef BUILD_QT_MULTIMEDIA - return new AudioProcessorQt(); -#else +#ifdef BUILD_SDL return new AudioProcessorSDL(); +#else + return new AudioProcessorQt(); #endif } } diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h index abbca9395..74d76a345 100644 --- a/src/platform/qt/AudioProcessor.h +++ b/src/platform/qt/AudioProcessor.h @@ -31,6 +31,7 @@ public: virtual void setInput(GBAThread* input); int getBufferSamples() const { return m_samples; } + virtual unsigned sampleRate() const = 0; public slots: virtual void start() = 0; @@ -39,7 +40,7 @@ public slots: virtual void setBufferSamples(int samples) = 0; virtual void inputParametersChanged() = 0; - virtual unsigned sampleRate() const = 0; + virtual void requestSampleRate(unsigned) = 0; protected: GBAThread* input() { return m_context; } diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp index 281ca6bba..c28d934b9 100644 --- a/src/platform/qt/AudioProcessorQt.cpp +++ b/src/platform/qt/AudioProcessorQt.cpp @@ -20,6 +20,7 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent) : AudioProcessor(parent) , m_audioOutput(nullptr) , m_device(nullptr) + , m_sampleRate(44100) { } @@ -45,7 +46,7 @@ void AudioProcessorQt::start() { if (!m_audioOutput) { QAudioFormat format; - format.setSampleRate(44100); + format.setSampleRate(m_sampleRate); format.setChannelCount(2); format.setSampleSize(16); format.setCodec("audio/pcm"); @@ -84,6 +85,15 @@ void AudioProcessorQt::inputParametersChanged() { } } +void AudioProcessorQt::requestSampleRate(unsigned rate) { + m_sampleRate = rate; + if (m_device) { + QAudioFormat format(m_audioOutput->format()); + format.setSampleRate(rate); + m_device->setFormat(format); + } +} + unsigned AudioProcessorQt::sampleRate() const { if (!m_audioOutput) { return 0; diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index ee9dde24d..52d7b606c 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -20,6 +20,7 @@ public: AudioProcessorQt(QObject* parent = nullptr); virtual void setInput(GBAThread* input); + virtual unsigned sampleRate() const override; public slots: virtual void start(); @@ -28,11 +29,12 @@ public slots: virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); - virtual unsigned sampleRate() const override; + virtual void requestSampleRate(unsigned) override; private: QAudioOutput* m_audioOutput; AudioDevice* m_device; + unsigned m_sampleRate; }; } diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp index 0caa9765c..2db807f3f 100644 --- a/src/platform/qt/AudioProcessorSDL.cpp +++ b/src/platform/qt/AudioProcessorSDL.cpp @@ -15,7 +15,7 @@ using namespace QGBA; AudioProcessorSDL::AudioProcessorSDL(QObject* parent) : AudioProcessor(parent) - , m_audio() + , m_audio{ 2048, 44100 } { } @@ -55,6 +55,18 @@ void AudioProcessorSDL::setBufferSamples(int samples) { void AudioProcessorSDL::inputParametersChanged() { } -unsigned AudioProcessorSDL::sampleRate() const { - return m_audio.obtainedSpec.freq; +void AudioProcessorSDL::requestSampleRate(unsigned rate) { + m_audio.sampleRate = rate; + if (m_audio.thread) { + GBASDLDeinitAudio(&m_audio); + GBASDLInitAudio(&m_audio, input()); + } +} + +unsigned AudioProcessorSDL::sampleRate() const { + if (m_audio.thread) { + return m_audio.obtainedSpec.freq; + } else { + return 0; + } } diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index 002dcd839..c98d00473 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/platform/qt/AudioProcessorSDL.h @@ -22,6 +22,8 @@ public: AudioProcessorSDL(QObject* parent = nullptr); ~AudioProcessorSDL(); + virtual unsigned sampleRate() const override; + public slots: virtual void start(); virtual void pause(); @@ -29,7 +31,7 @@ public slots: virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); - virtual unsigned sampleRate() const override; + virtual void requestSampleRate(unsigned) override; private: GBASDLAudio m_audio; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index e7db28908..3c1de1151 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -28,17 +28,31 @@ endif() set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) -find_package(Qt5Multimedia) +if(NOT WIN32 OR NOT BUILD_SDL) + find_package(Qt5Multimedia) +endif() find_package(Qt5OpenGL) find_package(Qt5Widgets) -if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND) +if(NOT BUILD_GL AND NOT BUILD_GLES2) + message(WARNING "OpenGL is required to build the Qt port") + set(BUILD_QT OFF PARENT_SCOPE) + return() +endif() + +if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND) message(WARNING "Cannot find Qt modules") set(BUILD_QT OFF PARENT_SCOPE) return() endif() +if(BUILD_GL) list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) +endif() + +if(BUILD_GLES2) +list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c) +endif() get_target_property(QT_TYPE Qt5::Core TYPE) if(QT_TYPE STREQUAL STATIC_LIBRARY) @@ -47,6 +61,7 @@ if(QT_TYPE STREQUAL STATIC_LIBRARY) endif() set(SOURCE_FILES + AboutScreen.cpp AudioProcessor.cpp CheatsModel.cpp CheatsView.cpp @@ -83,6 +98,7 @@ set(SOURCE_FILES VideoView.cpp) qt5_wrap_ui(UI_FILES + AboutScreen.ui CheatsView.ui GIFView.ui LoadSaveState.ui @@ -142,7 +158,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}") list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) -target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) +target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE) install(TARGETS ${BINARY_NAME}-qt @@ -154,6 +170,9 @@ if(UNIX AND NOT APPLE) install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/applications/\")") endif() endif() +if(UNIX) + install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba-qt.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-qt) +endif() if(APPLE OR WIN32) set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) endif() diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index c39ad398b..357a0c788 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -99,19 +99,29 @@ void CheatsView::removeSet() { } void CheatsView::enterCheat(std::function callback) { - GBACheatSet* set; + GBACheatSet* set = nullptr; QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes(); - if (selection.count() != 1) { - return; + QModelIndex index; + if (selection.count() == 0) { + set = new GBACheatSet; + GBACheatSetInit(set, nullptr); + } else if (selection.count() == 1) { + index = selection[0]; + set = m_model.itemAt(index); } - set = m_model.itemAt(selection[0]); + if (!set) { return; } m_controller->threadInterrupt(); + if (selection.count() == 0) { + m_model.addSet(set); + index = m_model.index(m_model.rowCount() - 1, 0, QModelIndex()); + m_ui.cheatList->selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + } QStringList cheats = m_ui.codeEntry->toPlainText().split('\n', QString::SkipEmptyParts); for (const QString& string : cheats) { - m_model.beginAppendRow(selection[0]); + m_model.beginAppendRow(index); callback(set, string.toUtf8().constData()); m_model.endAppendRow(); } diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index a5859fc83..021908680 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -107,7 +107,7 @@ ConfigController::ConfigController(QObject* parent) m_opts.audioSync = GameController::AUDIO_SYNC; m_opts.videoSync = GameController::VIDEO_SYNC; m_opts.fpsTarget = 60; - m_opts.audioBuffers = 2048; + m_opts.audioBuffers = 1536; m_opts.volume = GBA_AUDIO_VOLUME_MAX; m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS; m_opts.rewindEnable = false; @@ -115,8 +115,8 @@ ConfigController::ConfigController(QObject* parent) m_opts.rewindBufferCapacity = 0; m_opts.useBios = true; m_opts.suspendScreensaver = true; - GBAConfigLoadDefaults(&m_config, &m_opts); GBAConfigLoad(&m_config); + GBAConfigLoadDefaults(&m_config, &m_opts); GBAConfigMap(&m_config, &m_opts); } @@ -126,7 +126,11 @@ ConfigController::~ConfigController() { } bool ConfigController::parseArguments(GBAArguments* args, int argc, char* argv[]) { - return ::parseArguments(args, &m_config, argc, argv, 0); + if (::parseArguments(args, &m_config, argc, argv, 0)) { + GBAConfigMap(&m_config, &m_opts); + return true; + } + return false; } ConfigOption* ConfigController::addOption(const char* key) { diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 6a11a5c64..3766baf7b 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -22,7 +22,7 @@ Display::Driver Display::s_driver = Display::Driver::QT; Display* Display::create(QWidget* parent) { #ifdef BUILD_GL - QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); + QGLFormat format(QGLFormat(QGL::Rgba | QGL::SingleBuffer)); format.setSwapInterval(1); #endif @@ -72,6 +72,9 @@ void Display::filter(bool filter) { void Display::showMessage(const QString& message) { m_messagePainter.showMessage(message); + if (!isDrawing()) { + forceDraw(); + } } void Display::mouseMoveEvent(QMouseEvent*) { diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index cd3f1fd2a..3e6d738f2 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -33,6 +33,8 @@ public: bool isAspectRatioLocked() const { return m_lockAspectRatio; } bool isFiltered() const { return m_filter; } + virtual bool isDrawing() const = 0; + signals: void showCursor(); void hideCursor(); diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index ca41f0f46..d0a5f4db3 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -16,6 +16,7 @@ using namespace QGBA; DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) : Display(parent) + , m_isDrawing(false) , m_gl(new EmptyGLWidget(format, this)) , m_painter(new PainterGL(m_gl)) , m_drawThread(nullptr) @@ -33,6 +34,7 @@ void DisplayGL::startDrawing(GBAThread* thread) { if (m_drawThread) { return; } + m_isDrawing = true; m_painter->setContext(thread); m_painter->setMessagePainter(messagePainter()); m_context = thread; @@ -55,6 +57,7 @@ void DisplayGL::startDrawing(GBAThread* thread) { void DisplayGL::stopDrawing() { if (m_drawThread) { + m_isDrawing = false; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); } @@ -69,6 +72,7 @@ void DisplayGL::stopDrawing() { void DisplayGL::pauseDrawing() { if (m_drawThread) { + m_isDrawing = false; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); } @@ -81,6 +85,7 @@ void DisplayGL::pauseDrawing() { void DisplayGL::unpauseDrawing() { if (m_drawThread) { + m_isDrawing = true; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); } @@ -113,7 +118,8 @@ void DisplayGL::filter(bool filter) { void DisplayGL::framePosted(const uint32_t* buffer) { if (m_drawThread && buffer) { - QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer)); + m_painter->enqueue(buffer); + QMetaObject::invokeMethod(m_painter, "draw"); } } @@ -135,7 +141,11 @@ PainterGL::PainterGL(QGLWidget* parent) , m_context(nullptr) , m_messagePainter(nullptr) { +#ifdef BUILD_GL GBAGLContextCreate(&m_backend); +#elif defined(BUILD_GLES2) + GBAGLES2ContextCreate(&m_backend); +#endif m_backend.d.swap = [](VideoBackend* v) { PainterGL* painter = static_cast(v->user); painter->m_gl->swapBuffers(); @@ -143,6 +153,19 @@ PainterGL::PainterGL(QGLWidget* parent) m_backend.d.user = this; m_backend.d.filter = false; m_backend.d.lockAspectRatio = false; + + for (int i = 0; i < 2; ++i) { + m_free.append(new uint32_t[256 * 256]); + } +} + +PainterGL::~PainterGL() { + while (!m_queue.isEmpty()) { + delete[] m_queue.dequeue(); + } + for (auto item : m_free) { + delete[] item; + } } void PainterGL::setContext(GBAThread* context) { @@ -153,15 +176,6 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { m_messagePainter = messagePainter; } -void PainterGL::setBacking(const uint32_t* backing) { - m_gl->makeCurrent(); - m_backend.d.postFrame(&m_backend.d, backing); - if (m_active) { - draw(); - } - m_gl->doneCurrent(); -} - void PainterGL::resize(const QSize& size) { m_size = size; if (m_active) { @@ -191,7 +205,11 @@ void PainterGL::start() { } void PainterGL::draw() { - if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) { + if (m_queue.isEmpty()) { + return; + } + if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip) || !m_queue.isEmpty()) { + dequeue(); m_painter.begin(m_gl->context()->device()); performDraw(); m_painter.end(); @@ -200,6 +218,9 @@ void PainterGL::draw() { } else { GBASyncWaitFrameEnd(&m_context->sync); } + if (!m_queue.isEmpty()) { + QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); + } } void PainterGL::forceDraw() { @@ -212,6 +233,7 @@ void PainterGL::forceDraw() { void PainterGL::stop() { m_active = false; m_gl->makeCurrent(); + dequeueAll(); m_backend.d.clear(&m_backend.d); m_backend.d.swap(&m_backend.d); m_backend.d.deinit(&m_backend.d); @@ -222,9 +244,6 @@ void PainterGL::stop() { void PainterGL::pause() { m_active = false; - // Make sure both buffers are filled - forceDraw(); - forceDraw(); } void PainterGL::unpause() { @@ -241,3 +260,41 @@ void PainterGL::performDraw() { m_messagePainter->paint(&m_painter); } } + +void PainterGL::enqueue(const uint32_t* backing) { + m_mutex.lock(); + uint32_t* buffer; + if (m_free.isEmpty()) { + buffer = m_queue.dequeue(); + } else { + buffer = m_free.takeLast(); + } + memcpy(buffer, backing, 256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + m_queue.enqueue(buffer); + m_mutex.unlock(); +} + +void PainterGL::dequeue() { + m_mutex.lock(); + if (m_queue.isEmpty()) { + m_mutex.unlock(); + return; + } + uint32_t* buffer = m_queue.dequeue(); + m_backend.d.postFrame(&m_backend.d, buffer); + m_free.append(buffer); + m_mutex.unlock(); +} + +void PainterGL::dequeueAll() { + uint32_t* buffer = 0; + m_mutex.lock(); + while (!m_queue.isEmpty()) { + buffer = m_queue.dequeue(); + m_free.append(buffer); + } + if (buffer) { + m_backend.d.postFrame(&m_backend.d, buffer); + } + m_mutex.unlock(); +} diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index fc4cb500f..bad1b252a 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -9,12 +9,18 @@ #include "Display.h" #include +#include #include +#include #include #include extern "C" { +#ifdef BUILD_GL #include "platform/opengl/gl.h" +#elif defined(BUILD_GLES2) +#include "platform/opengl/gles2.h" +#endif } struct GBAThread; @@ -39,6 +45,8 @@ public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); + bool isDrawing() const override { return m_isDrawing; } + public slots: void startDrawing(GBAThread* context) override; void stopDrawing() override; @@ -56,6 +64,7 @@ protected: private: void resizePainter(); + bool m_isDrawing; QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread; @@ -67,12 +76,13 @@ Q_OBJECT public: PainterGL(QGLWidget* parent); + ~PainterGL(); void setContext(GBAThread*); void setMessagePainter(MessagePainter*); + void enqueue(const uint32_t* backing); public slots: - void setBacking(const uint32_t*); void forceDraw(); void draw(); void start(); @@ -85,12 +95,21 @@ public slots: private: void performDraw(); + void dequeue(); + void dequeueAll(); + QList m_free; + QQueue m_queue; QPainter m_painter; + QMutex m_mutex; QGLWidget* m_gl; bool m_active; GBAThread* m_context; +#ifdef BUILD_GL GBAGLContext m_backend; +#elif defined(BUILD_GLES2) + GBAGLES2Context m_backend; +#endif QSize m_size; MessagePainter* m_messagePainter; }; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 182ecd2bc..71e684055 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -15,11 +15,13 @@ using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) + , m_isDrawing(false) , m_backing(nullptr) { } void DisplayQt::startDrawing(GBAThread*) { + m_isDrawing = true; } void DisplayQt::lockAspectRatio(bool lock) { diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index f7fefb0ee..1d5acc5b4 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -21,11 +21,13 @@ Q_OBJECT public: DisplayQt(QWidget* parent = nullptr); + bool isDrawing() const override { return m_isDrawing; } + public slots: void startDrawing(GBAThread* context) override; - void stopDrawing() override {} - void pauseDrawing() override {} - void unpauseDrawing() override {} + void stopDrawing() override { m_isDrawing = false; } + void pauseDrawing() override { m_isDrawing = false; } + void unpauseDrawing() override { m_isDrawing = true; } void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; void filter(bool filter) override; @@ -35,6 +37,7 @@ protected: virtual void paintEvent(QPaintEvent*) override; private: + bool m_isDrawing; QImage m_backing; }; diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index c40eb42ba..dc0399273 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -15,6 +15,7 @@ #include extern "C" { +#include "gba/supervisor/thread.h" #include "platform/commandline.h" #include "util/socket.h" } @@ -33,10 +34,13 @@ GBAApp::GBAApp(int& argc, char* argv[]) SDL_Init(SDL_INIT_NOPARACHUTE); #endif +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/res/mgba-1024.png")); +#endif SocketSubsystemInit(); qRegisterMetaType("const uint32_t*"); + qRegisterMetaType("GBAThread*"); QApplication::setApplicationName(projectName); QApplication::setApplicationVersion(projectVersion); @@ -45,31 +49,30 @@ GBAApp::GBAApp(int& argc, char* argv[]) Display::setDriver(static_cast(m_configController.getQtOption("displayDriver").toInt())); } + GBAArguments args; + bool loaded = m_configController.parseArguments(&args, argc, argv); + if (loaded && args.showHelp) { + usage(argv[0], 0); + ::exit(0); + return; + } + + AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt())); Window* w = new Window(&m_configController); connect(w, &Window::destroyed, [this]() { m_windows[0] = nullptr; }); m_windows[0] = w; -#ifndef Q_OS_MAC - w->show(); -#endif - - GBAArguments args; - if (m_configController.parseArguments(&args, argc, argv)) { + if (loaded) { w->argumentsPassed(&args); } else { w->loadConfig(); } freeArguments(&args); - - AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt())); - w->controller()->reloadAudioDriver(); + w->show(); w->controller()->setMultiplayerController(&m_multiplayer); -#ifdef Q_OS_MAC - w->show(); -#endif } bool GBAApp::event(QEvent* event) { @@ -91,14 +94,9 @@ Window* GBAApp::newWindow() { }); m_windows[windowId] = w; w->setAttribute(Qt::WA_DeleteOnClose); -#ifndef Q_OS_MAC - w->show(); -#endif w->loadConfig(); - w->controller()->setMultiplayerController(&m_multiplayer); -#ifdef Q_OS_MAC w->show(); -#endif + w->controller()->setMultiplayerController(&m_multiplayer); return w; } diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index c86ed2bab..73e0f6ac6 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -18,9 +18,9 @@ using namespace QGBA; const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247; -const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431; -const qreal GBAKeyEditor::DPAD_WIDTH = 0.1; -const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1; +const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432; +const qreal GBAKeyEditor::DPAD_WIDTH = 0.12; +const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12; GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent) : QWidget(parent) @@ -148,8 +148,8 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) { setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y); setLocation(m_keySelect, 0.415, 0.93); setLocation(m_keyStart, 0.585, 0.93); - setLocation(m_keyA, 0.826, 0.451); - setLocation(m_keyB, 0.667, 0.490); + setLocation(m_keyA, 0.826, 0.475); + setLocation(m_keyB, 0.667, 0.514); setLocation(m_keyL, 0.1, 0.1); setLocation(m_keyR, 0.9, 0.1); diff --git a/src/platform/qt/GDBWindow.cpp b/src/platform/qt/GDBWindow.cpp index 26191b6e0..10e4b55d8 100644 --- a/src/platform/qt/GDBWindow.cpp +++ b/src/platform/qt/GDBWindow.cpp @@ -18,7 +18,7 @@ using namespace QGBA; GDBWindow::GDBWindow(GDBController* controller, QWidget* parent) - : QWidget(parent) + : QDialog(parent) , m_gdbController(controller) { setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint); diff --git a/src/platform/qt/GDBWindow.h b/src/platform/qt/GDBWindow.h index 683e7c632..9759df05b 100644 --- a/src/platform/qt/GDBWindow.h +++ b/src/platform/qt/GDBWindow.h @@ -6,7 +6,7 @@ #ifndef QGBA_GDB_WINDOW #define QGBA_GDB_WINDOW -#include +#include class QLineEdit; class QPushButton; @@ -15,7 +15,7 @@ namespace QGBA { class GDBController; -class GDBWindow : public QWidget { +class GDBWindow : public QDialog { Q_OBJECT public: diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 32c5aa41d..57216ddb3 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -29,11 +29,10 @@ extern "C" { using namespace QGBA; using namespace std; -const int GameController::LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; - GameController::GameController(QObject* parent) : QObject(parent) - , m_drawContext(new uint32_t[256 * 256]) + , m_drawContext(new uint32_t[256 * VIDEO_HORIZONTAL_PIXELS]) + , m_frontBuffer(new uint32_t[256 * 256]) , m_threadContext() , m_activeKeys(0) , m_inactiveKeys(0) @@ -49,6 +48,8 @@ GameController::GameController(QObject* parent) , m_turboForced(false) , m_turboSpeed(-1) , m_wasPaused(false) + , m_audioChannels{ true, true, true, true, true, true } + , m_videoLayers{ true, true, true, true, true } , m_inputController(nullptr) , m_multiplayer(nullptr) , m_stateSlot(1) @@ -86,12 +87,25 @@ GameController::GameController(QObject* parent) m_threadContext.startCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); - controller->m_audioProcessor->setInput(context); + if (controller->m_audioProcessor) { + controller->m_audioProcessor->setInput(context); + } context->gba->luminanceSource = &controller->m_lux; GBARTCGenericSourceInit(&controller->m_rtc, context->gba); context->gba->rtcSource = &controller->m_rtc.d; context->gba->rumble = controller->m_inputController->rumble(); context->gba->rotationSource = controller->m_inputController->rotationSource(); + context->gba->audio.forceDisableCh[0] = !controller->m_audioChannels[0]; + context->gba->audio.forceDisableCh[1] = !controller->m_audioChannels[1]; + context->gba->audio.forceDisableCh[2] = !controller->m_audioChannels[2]; + context->gba->audio.forceDisableCh[3] = !controller->m_audioChannels[3]; + context->gba->audio.forceDisableChA = !controller->m_audioChannels[4]; + context->gba->audio.forceDisableChB = !controller->m_audioChannels[5]; + context->gba->video.renderer->disableBG[0] = !controller->m_videoLayers[0]; + context->gba->video.renderer->disableBG[1] = !controller->m_videoLayers[1]; + context->gba->video.renderer->disableBG[2] = !controller->m_videoLayers[2]; + context->gba->video.renderer->disableBG[3] = !controller->m_videoLayers[3]; + context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4]; controller->m_fpsTarget = context->fpsTarget; if (GBALoadState(context, context->stateDir, 0)) { @@ -100,24 +114,25 @@ GameController::GameController(QObject* parent) vf->truncate(vf, 0); } } - controller->gameStarted(context); + QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(GBAThread*, context)); }; m_threadContext.cleanCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); - controller->gameStopped(context); + QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(GBAThread*, context)); }; m_threadContext.frameCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); + if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { + memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); + QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); + } else { + QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, nullptr)); + } if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { GBAThreadPauseFromThread(context); - controller->gamePaused(&controller->m_threadContext); - } - if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { - controller->frameAvailable(controller->m_drawContext); - } else { - controller->frameAvailable(nullptr); + QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(GBAThread*, context)); } }; @@ -146,7 +161,7 @@ GameController::GameController(QObject* parent) va_copy(argc, args); int immediate = va_arg(argc, int); va_end(argc); - controller->unimplementedBiosCall(immediate); + QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate)); } else if (level == GBA_LOG_STATUS) { // Slot 0 is reserved for suspend points if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { @@ -174,9 +189,9 @@ GameController::GameController(QObject* parent) } QString message(QString().vsprintf(format, args)); if (level == GBA_LOG_STATUS) { - controller->statusPosted(message); + QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message)); } - controller->postLog(level, message); + QMetaObject::invokeMethod(controller, "postLog", Q_ARG(int, level), Q_ARG(const QString&, message)); }; connect(&m_rewindTimer, &QTimer::timeout, [this]() { @@ -205,6 +220,7 @@ GameController::~GameController() { GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; + delete[] m_frontBuffer; delete m_backupLoadState; } @@ -328,6 +344,7 @@ void GameController::openGame(bool biosOnly) { } m_inputController->recalibrateAxes(); + memset(m_drawContext, 0xF8, 1024 * VIDEO_HORIZONTAL_PIXELS); if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false; @@ -453,8 +470,7 @@ void GameController::setPaused(bool paused) { return; } if (paused) { - GBAThreadPause(&m_threadContext); - emit gamePaused(&m_threadContext); + m_pauseAfterFrame.testAndSetRelaxed(false, true); } else { GBAThreadUnpause(&m_threadContext); emit gameUnpaused(&m_threadContext); @@ -519,9 +535,9 @@ void GameController::startRewinding() { return; } m_wasPaused = isPaused(); - bool signalsBlocked = blockSignals(true); - setPaused(true); - blockSignals(signalsBlocked); + if (!GBAThreadIsPaused(&m_threadContext)) { + GBAThreadPause(&m_threadContext); + } m_rewindTimer.start(); } @@ -574,10 +590,64 @@ void GameController::clearKeys() { } void GameController::setAudioBufferSamples(int samples) { - threadInterrupt(); - redoSamples(samples); - threadContinue(); - QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); + if (m_audioProcessor) { + threadInterrupt(); + redoSamples(samples); + threadContinue(); + QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); + } +} + +void GameController::setAudioSampleRate(unsigned rate) { + if (m_audioProcessor) { + threadInterrupt(); + redoSamples(m_audioProcessor->getBufferSamples()); + threadContinue(); + QMetaObject::invokeMethod(m_audioProcessor, "requestSampleRate", Q_ARG(unsigned, rate)); + } +} + +void GameController::setAudioChannelEnabled(int channel, bool enable) { + if (channel > 5 || channel < 0) { + return; + } + m_audioChannels[channel] = enable; + if (m_gameOpen) { + switch (channel) { + case 0: + case 1: + case 2: + case 3: + m_threadContext.gba->audio.forceDisableCh[channel] = !enable; + break; + case 4: + m_threadContext.gba->audio.forceDisableChA = !enable; + break; + case 5: + m_threadContext.gba->audio.forceDisableChB = !enable; + break; + } + } +} + +void GameController::setVideoLayerEnabled(int layer, bool enable) { + if (layer > 4 || layer < 0) { + return; + } + m_videoLayers[layer] = enable; + if (m_gameOpen) { + switch (layer) { + case 0: + case 1: + case 2: + case 3: + m_threadContext.gba->video.renderer->disableBG[layer] = !enable; + break; + case 4: + m_threadContext.gba->video.renderer->disableOBJ = !enable; + break; + } + } } void GameController::setFPSTarget(float fps) { @@ -587,7 +657,9 @@ void GameController::setFPSTarget(float fps) { if (m_turbo && m_turboSpeed > 0) { m_threadContext.fpsTarget *= m_turboSpeed; } - redoSamples(m_audioProcessor->getBufferSamples()); + if (m_audioProcessor) { + redoSamples(m_audioProcessor->getBufferSamples()); + } threadContinue(); } @@ -598,9 +670,14 @@ void GameController::setSkipBIOS(bool set) { } void GameController::setUseBIOS(bool use) { - threadInterrupt(); + if (use == m_useBios) { + return; + } m_useBios = use; - threadContinue(); + if (m_gameOpen) { + closeGame(); + openGame(); + } } void GameController::loadState(int slot) { @@ -744,7 +821,9 @@ void GameController::enableTurbo() { m_threadContext.sync.audioWait = true; m_threadContext.sync.videoFrameWait = false; } - redoSamples(m_audioProcessor->getBufferSamples()); + if (m_audioProcessor) { + redoSamples(m_audioProcessor->getBufferSamples()); + } threadContinue(); } @@ -773,11 +852,21 @@ void GameController::screenshot() { #endif void GameController::reloadAudioDriver() { - QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); - int samples = m_audioProcessor->getBufferSamples(); - delete m_audioProcessor; + int samples = 0; + unsigned sampleRate = 0; + if (m_audioProcessor) { + QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); + samples = m_audioProcessor->getBufferSamples(); + sampleRate = m_audioProcessor->sampleRate(); + delete m_audioProcessor; + } m_audioProcessor = AudioProcessor::create(); - m_audioProcessor->setBufferSamples(samples); + if (samples) { + m_audioProcessor->setBufferSamples(samples); + } + if (sampleRate) { + m_audioProcessor->requestSampleRate(sampleRate); + } m_audioProcessor->moveToThread(m_audioThread); connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start())); connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause())); @@ -794,7 +883,7 @@ void GameController::setLuminanceValue(uint8_t value) { value = std::max(value - 0x16, 0); m_luxLevel = 10; for (int i = 0; i < 10; ++i) { - if (value < LUX_LEVELS[i]) { + if (value < GBA_LUX_LEVELS[i]) { m_luxLevel = i; break; } @@ -806,7 +895,7 @@ void GameController::setLuminanceLevel(int level) { int value = 0x16; level = std::max(0, std::min(10, level)); if (level > 0) { - value += LUX_LEVELS[level - 1]; + value += GBA_LUX_LEVELS[level - 1]; } setLuminanceValue(value); } diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index bbd5c2171..a4de17fff 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -72,6 +72,8 @@ public: void setOptions(const GBAOptions*); + int stateSlot() const { return m_stateSlot; } + #ifdef USE_GDB_STUB ARMDebugger* debugger(); void setDebugger(ARMDebugger*); @@ -117,6 +119,9 @@ public slots: void keyReleased(int key); void clearKeys(); void setAudioBufferSamples(int samples); + void setAudioSampleRate(unsigned rate); + void setAudioChannelEnabled(int channel, bool enable = true); + void setVideoLayerEnabled(int layer, bool enable = true); void setFPSTarget(float fps); void loadState(int slot = 0); void saveState(int slot = 0); @@ -163,6 +168,7 @@ private: void enableTurbo(); uint32_t* m_drawContext; + uint32_t* m_frontBuffer; GBAThread m_threadContext; GBAVideoSoftwareRenderer* m_renderer; GBACheatDevice m_cheatDevice; @@ -193,6 +199,9 @@ private: QTimer m_rewindTimer; bool m_wasPaused; + bool m_audioChannels[6]; + bool m_videoLayers[5]; + int m_stateSlot; GBASerializedState* m_backupLoadState; QByteArray m_backupSaveState; @@ -207,8 +216,6 @@ private: uint8_t m_luxValue; int m_luxLevel; - static const int LUX_LEVELS[10]; - GBARTCGenericSource m_rtc; }; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index ebc73e267..85bf49b1c 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -208,9 +208,11 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device) GBARumble* InputController::rumble() { #ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) if (m_playerAttached) { return &m_sdlPlayer.rumble.d; } +#endif #endif return nullptr; } @@ -497,19 +499,29 @@ bool InputController::hasPendingEvent(GBAKey key) const { return m_pendingEvents.contains(key); } -#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) void InputController::suspendScreensaver() { +#ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) GBASDLSuspendScreensaver(&s_sdlEvents); +#endif +#endif } void InputController::resumeScreensaver() { +#ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) GBASDLResumeScreensaver(&s_sdlEvents); +#endif +#endif } void InputController::setScreensaverSuspendable(bool suspendable) { +#ifdef BUILD_SDL +#if SDL_VERSION_ATLEAST(2, 0, 0) GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable); -} #endif +#endif +} void InputController::stealFocus(QWidget* focus) { m_focusParent = focus; diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index b0e25e92f..f73524bad 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -87,12 +87,10 @@ signals: public slots: void testGamepad(int type); -#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0) // TODO: Move these to somewhere that makes sense void suspendScreensaver(); void resumeScreensaver(); void setScreensaverSuspendable(bool); -#endif private: void postPendingEvent(GBAKey); diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 15796b67e..c14f7ccd7 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -23,9 +23,10 @@ using namespace QGBA; LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) : QWidget(parent) , m_controller(controller) - , m_currentFocus(0) + , m_currentFocus(controller->stateSlot() - 1) , m_mode(LoadSave::LOAD) { + setAttribute(Qt::WA_TranslucentBackground); m_ui.setupUi(this); m_slots[0] = m_ui.state1; @@ -45,6 +46,13 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); }); } + if (m_currentFocus >= 9) { + m_currentFocus = 0; + } + if (m_currentFocus < 0) { + m_currentFocus = 0; + } + QAction* escape = new QAction(this); escape->connect(escape, SIGNAL(triggered()), this, SLOT(close())); escape->setShortcut(QKeySequence("Esc")); @@ -201,6 +209,5 @@ void LoadSaveState::showEvent(QShowEvent* event) { void LoadSaveState::paintEvent(QPaintEvent*) { QPainter painter(this); QRect full(QPoint(), size()); - painter.drawPixmap(full, m_currentImage); painter.fillRect(full, QColor(0, 0, 0, 128)); } diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 738760684..b836f7ce0 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -15,7 +15,7 @@ extern "C" { using namespace QGBA; OverrideView::OverrideView(GameController* controller, ConfigController* config, QWidget* parent) - : QWidget(parent) + : QDialog(parent) , m_controller(controller) , m_config(config) { diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h index 6a217edd5..3ed5d3e20 100644 --- a/src/platform/qt/OverrideView.h +++ b/src/platform/qt/OverrideView.h @@ -6,7 +6,7 @@ #ifndef QGBA_OVERRIDE_VIEW #define QGBA_OVERRIDE_VIEW -#include +#include #include "ui_OverrideView.h" @@ -21,7 +21,7 @@ namespace QGBA { class ConfigController; class GameController; -class OverrideView : public QWidget { +class OverrideView : public QDialog { Q_OBJECT public: diff --git a/src/platform/qt/SavestateButton.cpp b/src/platform/qt/SavestateButton.cpp index c9f6560b0..d99ac34f6 100644 --- a/src/platform/qt/SavestateButton.cpp +++ b/src/platform/qt/SavestateButton.cpp @@ -13,7 +13,7 @@ using namespace QGBA; SavestateButton::SavestateButton(QWidget* parent) : QAbstractButton(parent) { - // Nothing to do + setAttribute(Qt::WA_TranslucentBackground); } void SavestateButton::paintEvent(QPaintEvent*) { diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index 501b05fd9..9a2edd6e4 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -12,7 +12,7 @@ using namespace QGBA; SensorView::SensorView(GameController* controller, InputController* input, QWidget* parent) - : QWidget(parent) + : QDialog(parent) , m_controller(controller) , m_input(input) , m_rotation(input->rotationSource()) diff --git a/src/platform/qt/SensorView.h b/src/platform/qt/SensorView.h index a37fb6e65..50c96ecf1 100644 --- a/src/platform/qt/SensorView.h +++ b/src/platform/qt/SensorView.h @@ -7,7 +7,7 @@ #define QGBA_SENSOR_VIEW #include -#include +#include #include @@ -22,7 +22,7 @@ class GameController; class GamepadAxisEvent; class InputController; -class SensorView : public QWidget { +class SensorView : public QDialog { Q_OBJECT public: diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index cc343cd29..f60a43cd3 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -13,8 +13,8 @@ using namespace QGBA; SettingsView::SettingsView(ConfigController* controller, QWidget* parent) - : QWidget(parent) - , m_controller(controller) + : QDialog(parent) + , m_controller(controller) { m_ui.setupUi(this); @@ -22,6 +22,7 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent) loadSetting("useBios", m_ui.useBios); loadSetting("skipBios", m_ui.skipBios); loadSetting("audioBuffers", m_ui.audioBufferSize); + loadSetting("sampleRate", m_ui.sampleRate); loadSetting("videoSync", m_ui.videoSync); loadSetting("audioSync", m_ui.audioSync); loadSetting("frameskip", m_ui.frameskip); @@ -102,6 +103,7 @@ void SettingsView::updateConfig() { saveSetting("useBios", m_ui.useBios); saveSetting("skipBios", m_ui.skipBios); saveSetting("audioBuffers", m_ui.audioBufferSize); + saveSetting("sampleRate", m_ui.sampleRate); saveSetting("videoSync", m_ui.videoSync); saveSetting("audioSync", m_ui.audioSync); saveSetting("frameskip", m_ui.frameskip); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 782e20e63..e47cda889 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -6,7 +6,7 @@ #ifndef QGBA_SETTINGS_VIEW #define QGBA_SETTINGS_VIEW -#include +#include #include "ui_SettingsView.h" @@ -14,7 +14,7 @@ namespace QGBA { class ConfigController; -class SettingsView : public QWidget { +class SettingsView : public QDialog { Q_OBJECT public: diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index bfd7af451..f9302a87c 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -62,26 +62,41 @@ true - 2048 + 1536 - 2 + 3 512 + + + 768 + + 1024 + + + 1536 + + 2048 + + + 3072 + + 4096 @@ -99,13 +114,20 @@ + + + Sample rate: + + + + Volume: - + @@ -132,28 +154,38 @@ - + Qt::Horizontal - + Display driver: - + + + + + 0 + 0 + + + + + Frameskip: - + @@ -174,14 +206,14 @@ - + FPS target: - + @@ -205,21 +237,21 @@ - + Qt::Horizontal - + Sync: - + @@ -237,29 +269,63 @@ - + Lock aspect ratio - + Resample video - - - - - 0 - 0 - - - + + + + + + true + + + 44100 + + + 2 + + + + 22050 + + + + + 32000 + + + + + 44100 + + + + + 48000 + + + + + + + + Hz + + + + diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index 5cc6f06a4..811a34f34 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -103,6 +103,8 @@ public: const QKeySequence& shortcut, const QString& visibleName, const QString& name); void addMenu(QMenu* menu, QMenu* parent = nullptr); + QAction* getAction(const QString& name); + QKeySequence shortcutAt(const QModelIndex& index) const; bool isMenuAt(const QModelIndex& index) const; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 29f800c0f..6307fdf12 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -13,6 +13,7 @@ #include #include +#include "AboutScreen.h" #include "CheatsView.h" #include "ConfigController.h" #include "Display.h" @@ -40,8 +41,8 @@ extern "C" { using namespace QGBA; -#ifdef __WIN32 -// This is a macro everywhere except MinGW, it seems +#if defined(__WIN32) || defined(__OpenBSD__) +// This is a macro everywhere except MinGW and OpenBSD, it seems using std::isnan; #endif @@ -99,6 +100,14 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(m_controller, SIGNAL(gameStopped(GBAThread*)), &m_inputController, SLOT(resumeScreensaver())); connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw())); connect(m_controller, SIGNAL(rewound(GBAThread*)), m_display, SLOT(forceDraw())); + connect(m_controller, &GameController::gamePaused, [this]() { + QImage currentImage(reinterpret_cast(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS, + VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGBX8888); + QPixmap pixmap; + pixmap.convertFromImage(currentImage); + m_screenWidget->setPixmap(pixmap); + m_screenWidget->setLockAspectRatio(3, 2); + }); connect(m_controller, SIGNAL(gamePaused(GBAThread*)), m_display, SLOT(pauseDrawing())); #ifndef Q_OS_MAC connect(m_controller, SIGNAL(gamePaused(GBAThread*)), menuBar(), SLOT(show())); @@ -126,15 +135,16 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame())); connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide())); connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int))); + connect(this, SIGNAL(sampleRateChanged(unsigned)), m_controller, SLOT(setAudioSampleRate(unsigned))); connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); connect(m_display, &Display::hideCursor, [this]() { if (static_cast(m_screenWidget->layout())->currentWidget() == m_display) { - setCursor(Qt::BlankCursor); + m_screenWidget->setCursor(Qt::BlankCursor); } }); connect(m_display, &Display::showCursor, [this]() { - unsetCursor(); + m_screenWidget->unsetCursor(); }); connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_shortcutController, SLOT(loadProfile(const QString&))); @@ -171,6 +181,7 @@ void Window::argumentsPassed(GBAArguments* args) { void Window::resizeFrame(int width, int height) { QSize newSize(width, height); + m_screenWidget->setSizeHint(newSize); newSize -= m_screenWidget->size(); newSize += size(); resize(newSize); @@ -193,6 +204,7 @@ void Window::loadConfig() { m_controller->loadBIOS(opts->bios); } + // TODO: Move these to ConfigController if (opts->fpsTarget) { emit fpsTargetChanged(opts->fpsTarget); } @@ -201,6 +213,10 @@ void Window::loadConfig() { emit audioBufferSamplesChanged(opts->audioBuffers); } + if (opts->sampleRate) { + emit sampleRateChanged(opts->sampleRate); + } + if (opts->width && opts->height) { resizeFrame(opts->width, opts->height); } @@ -344,6 +360,11 @@ void Window::openMemoryWindow() { openView(memoryWindow); } +void Window::openAboutScreen() { + AboutScreen* about = new AboutScreen(); + openView(about); +} + #ifdef BUILD_SDL void Window::openGamepadWindow() { const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON); @@ -386,9 +407,7 @@ void Window::gdbOpen() { m_gdbController = new GDBController(m_controller, this); } GDBWindow* window = new GDBWindow(m_gdbController); - connect(this, SIGNAL(shutdown()), window, SLOT(close())); - window->setAttribute(Qt::WA_DeleteOnClose); - window->show(); + openView(window); } #endif @@ -420,14 +439,34 @@ void Window::keyReleaseEvent(QKeyEvent* event) { event->accept(); } -void Window::resizeEvent(QResizeEvent*) { +void Window::resizeEvent(QResizeEvent* event) { if (!isFullScreen()) { m_config->setOption("height", m_screenWidget->height()); m_config->setOption("width", m_screenWidget->width()); } + + int factor = 0; + if (event->size().width() % VIDEO_HORIZONTAL_PIXELS == 0 && event->size().height() % VIDEO_VERTICAL_PIXELS == 0 && + event->size().width() / VIDEO_HORIZONTAL_PIXELS == event->size().height() / VIDEO_VERTICAL_PIXELS) { + factor = event->size().width() / VIDEO_HORIZONTAL_PIXELS; + } + for (QMap::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) { + bool enableSignals = iter.value()->blockSignals(true); + if (iter.key() == factor) { + iter.value()->setChecked(true); + } else { + iter.value()->setChecked(false); + } + iter.value()->blockSignals(enableSignals); + } + m_config->setOption("fullscreen", isFullScreen()); } +void Window::showEvent(QShowEvent* event) { + resizeFrame(m_screenWidget->sizeHint().width(), m_screenWidget->sizeHint().height()); +} + void Window::closeEvent(QCloseEvent* event) { emit shutdown(); m_config->setQtOption("windowPos", pos()); @@ -435,6 +474,10 @@ void Window::closeEvent(QCloseEvent* event) { QMainWindow::closeEvent(event); } +void Window::focusInEvent(QFocusEvent*) { + m_display->forceDraw(); +} + void Window::focusOutEvent(QFocusEvent*) { m_controller->setTurbo(false, false); m_controller->stopRewinding(); @@ -475,7 +518,6 @@ void Window::enterFullScreen() { return; } showFullScreen(); - setCursor(Qt::BlankCursor); #ifndef Q_OS_MAC if (m_controller->isLoaded() && !m_controller->isPaused()) { menuBar()->hide(); @@ -487,7 +529,7 @@ void Window::exitFullScreen() { if (!isFullScreen()) { return; } - unsetCursor(); + m_screenWidget->unsetCursor(); menuBar()->show(); showNormal(); } @@ -515,6 +557,7 @@ void Window::gameStarted(GBAThread* context) { action->setDisabled(false); } if (context->fname) { + setWindowFilePath(context->fname); appendMRU(context->fname); } updateTitle(); @@ -534,10 +577,12 @@ void Window::gameStopped() { foreach (QAction* action, m_gameActions) { action->setDisabled(true); } + setWindowFilePath(QString()); updateTitle(); detachWidget(m_display); m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); m_screenWidget->setPixmap(m_logo); + m_screenWidget->unsetCursor(); m_fpsTimer.stop(); } @@ -640,7 +685,7 @@ void Window::openStateWindow(LoadSave ls) { connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(close())); connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_stateWindow, SLOT(close())); connect(m_stateWindow, &LoadSaveState::closed, [this]() { - m_screenWidget->layout()->removeWidget(m_stateWindow); + detachWidget(m_stateWindow); m_stateWindow = nullptr; QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection); }); @@ -752,6 +797,14 @@ void Window::setupMenu(QMenuBar* menubar) { }); addControlledAction(fileMenu, multiWindow, "multiWindow"); +#ifndef Q_OS_MAC + fileMenu->addSeparator(); +#endif + + QAction* about = new QAction(tr("About"), fileMenu); + connect(about, SIGNAL(triggered()), this, SLOT(openAboutScreen())); + fileMenu->addAction(about); + #ifndef Q_OS_MAC addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif @@ -782,13 +835,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(pause, SIGNAL(triggered(bool)), m_controller, SLOT(setPaused(bool))); connect(m_controller, &GameController::gamePaused, [this, pause]() { pause->setChecked(true); - - QImage currentImage(reinterpret_cast(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS, - VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGB32); - QPixmap pixmap; - pixmap.convertFromImage(currentImage.rgbSwapped()); - m_screenWidget->setPixmap(pixmap); - m_screenWidget->setLockAspectRatio(3, 2); }); connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); }); m_gameActions.append(pause); @@ -897,10 +943,15 @@ void Window::setupMenu(QMenuBar* menubar) { m_shortcutController->addMenu(frameMenu, avMenu); for (int i = 1; i <= 6; ++i) { QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu); - connect(setSize, &QAction::triggered, [this, i]() { + setSize->setCheckable(true); + connect(setSize, &QAction::triggered, [this, i, setSize]() { showNormal(); resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i); + bool enableSignals = setSize->blockSignals(true); + setSize->setChecked(true); + setSize->blockSignals(enableSignals); }); + m_frameSizes[i] = setSize; addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i))); } QKeySequence fullscreenKeys; @@ -937,20 +988,6 @@ void Window::setupMenu(QMenuBar* menubar) { avMenu->addSeparator(); - QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size")); - ConfigOption* buffers = m_config->addOption("audioBuffers"); - buffers->connect([this](const QVariant& value) { - emit audioBufferSamplesChanged(value.toInt()); - }, this); - buffers->addValue(tr("512"), 512, buffersMenu); - buffers->addValue(tr("768"), 768, buffersMenu); - buffers->addValue(tr("1024"), 1024, buffersMenu); - buffers->addValue(tr("2048"), 2048, buffersMenu); - buffers->addValue(tr("4096"), 4096, buffersMenu); - m_config->updateOption("audioBuffers"); - - avMenu->addSeparator(); - QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); fpsTargetOption->connect([this](const QVariant& value) { @@ -997,16 +1034,14 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers); enableBg->setCheckable(true); enableBg->setChecked(true); - connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; }); - m_gameActions.append(enableBg); + connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->setVideoLayerEnabled(i, enable); }); addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i)); } QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers); enableObj->setCheckable(true); enableObj->setChecked(true); - connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; }); - m_gameActions.append(enableObj); + connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); }); addControlledAction(videoLayers, enableObj, "enableOBJ"); QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); @@ -1015,23 +1050,20 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels); enableCh->setCheckable(true); enableCh->setChecked(true); - connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; }); - m_gameActions.append(enableCh); + connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(i, enable); }); addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1)); } QAction* enableChA = new QAction(tr("Channel A"), audioChannels); enableChA->setCheckable(true); enableChA->setChecked(true); - connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; }); - m_gameActions.append(enableChA); + connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(4, enable); }); addControlledAction(audioChannels, enableChA, QString("enableChA")); QAction* enableChB = new QAction(tr("Channel B"), audioChannels); enableChB->setCheckable(true); enableChB->setChecked(true); - connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; }); - m_gameActions.append(enableChB); + connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); }); addControlledAction(audioChannels, enableChB, QString("enableChB")); QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); @@ -1091,6 +1123,21 @@ void Window::setupMenu(QMenuBar* menubar) { m_controller->setSkipBIOS(value.toBool()); }, this); + ConfigOption* useBios = m_config->addOption("useBios"); + useBios->connect([this](const QVariant& value) { + m_controller->setUseBIOS(value.toBool()); + }, this); + + ConfigOption* buffers = m_config->addOption("audioBuffers"); + buffers->connect([this](const QVariant& value) { + emit audioBufferSamplesChanged(value.toInt()); + }, this); + + ConfigOption* sampleRate = m_config->addOption("sampleRate"); + sampleRate->connect([this](const QVariant& value) { + emit sampleRateChanged(value.toUInt()); + }, this); + ConfigOption* volume = m_config->addOption("volume"); volume->connect([this](const QVariant& value) { m_controller->setVolume(value.toInt()); @@ -1133,7 +1180,7 @@ void Window::setupMenu(QMenuBar* menubar) { void Window::attachWidget(QWidget* widget) { m_screenWidget->layout()->addWidget(widget); - unsetCursor(); + m_screenWidget->unsetCursor(); static_cast(m_screenWidget->layout())->setCurrentWidget(widget); } @@ -1215,10 +1262,10 @@ void WindowBackground::paintEvent(QPaintEvent*) { painter.fillRect(QRect(QPoint(), size()), Qt::black); QSize s = size(); QSize ds = s; - if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) { - ds.setWidth(s.height() * m_aspectWidth / m_aspectHeight); - } else if (s.width() * m_aspectHeight < s.height() * m_aspectWidth) { - ds.setHeight(s.width() * m_aspectHeight / m_aspectWidth); + if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) { + ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight); + } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) { + ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth); } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 7db12500c..ebc923776 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -54,6 +54,7 @@ signals: void startDrawing(GBAThread*); void shutdown(); void audioBufferSamplesChanged(int samples); + void sampleRateChanged(unsigned samples); void fpsTargetChanged(float target); public slots: @@ -82,6 +83,8 @@ public slots: void openPaletteWindow(); void openMemoryWindow(); + void openAboutScreen(); + #ifdef BUILD_SDL void openGamepadWindow(); #endif @@ -102,7 +105,9 @@ protected: virtual void keyPressEvent(QKeyEvent* event) override; virtual void keyReleaseEvent(QKeyEvent* event) override; virtual void resizeEvent(QResizeEvent*) override; + virtual void showEvent(QShowEvent*) override; virtual void closeEvent(QCloseEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; virtual void focusOutEvent(QFocusEvent*) override; virtual void dragEnterEvent(QDragEnterEvent*) override; virtual void dropEvent(QDropEvent*) override; @@ -144,6 +149,7 @@ private: GameController* m_controller; Display* m_display; QList m_gameActions; + QMap m_frameSizes; LogController m_log; LogView* m_logView; LoadSaveState* m_stateWindow; diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index 8152cf3a2..3dfca440b 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -10,9 +10,11 @@ #include #ifdef _WIN32 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#ifdef BUILD_QT_MULTIMEDIA Q_IMPORT_PLUGIN(QWindowsAudioPlugin); #endif #endif +#endif int main(int argc, char* argv[]) { QGBA::GBAApp application(argc, argv); diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index 88510b37b..3368a15ce 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -51,13 +51,16 @@ set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c) if(BUILD_RASPI) add_definitions(-DBUILD_RASPI) - set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-sdl.c) - set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host") + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c) + set(OPENGLES2_LIBRARY "-lEGL -lGLESv2 -lbcm_host") + set(BUILD_GLES2 ON CACHE BOOL "Using OpenGL|ES 2" FORCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline") - add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC}) + add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") - target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY}) + target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGLES2_LIBRARY}) install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi) + unset(OPENGLES2_INCLUDE_DIR} CACHE) # Clear NOTFOUND endif() if(BUILD_PANDORA) @@ -66,13 +69,21 @@ else() list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c) if(BUILD_GL) list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c) - list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) include_directories(${OPENGL_INCLUDE_DIR}) endif() + if(BUILD_GLES2) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c) + list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) + include_directories(${OPENGLES2_INCLUDE_DIR}) + endif() endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") -target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY}) +target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl) +if(UNIX) + install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl) +endif() diff --git a/src/platform/sdl/egl-sdl.c b/src/platform/sdl/egl-sdl.c deleted file mode 100644 index e14b753a2..000000000 --- a/src/platform/sdl/egl-sdl.c +++ /dev/null @@ -1,166 +0,0 @@ -/* Copyright (c) 2013-2014 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 "main.h" - -static const char* _vertexShader = - "attribute vec4 position;\n" - "varying vec2 texCoord;\n" - - "void main() {\n" - " gl_Position = position;\n" - " texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n" - "}"; - -static const char* _fragmentShader = - "varying vec2 texCoord;\n" - "uniform sampler2D tex;\n" - - "void main() {\n" - " vec4 color = texture2D(tex, texCoord);\n" - " color.a = 1.;\n" - " gl_FragColor = color;" - "}"; - -static const GLfloat _vertices[] = { - -1.f, -1.f, - -1.f, 1.f, - 1.f, 1.f, - 1.f, -1.f, -}; - -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { - bcm_host_init(); - renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - int major, minor; - if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) { - printf("Failed to initialize EGL"); - return false; - } - - if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) { - printf("Failed to get GLES API"); - return false; - } - - const EGLint requestConfig[] = { - EGL_RED_SIZE, 5, - EGL_GREEN_SIZE, 5, - EGL_BLUE_SIZE, 5, - EGL_ALPHA_SIZE, 1, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_NONE - }; - - EGLConfig config; - EGLint numConfigs; - - if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) { - printf("Failed to choose EGL config\n"); - return false; - } - - const EGLint contextAttributes[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - int dispWidth = 240, dispHeight = 160, adjWidth; - renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes); - graphics_get_display_size(0, &dispWidth, &dispHeight); - adjWidth = dispHeight / 2 * 3; - - DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0); - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); - - VC_RECT_T destRect = { - .x = (dispWidth - adjWidth) / 2, - .y = 0, - .width = adjWidth, - .height = dispHeight - }; - - VC_RECT_T srcRect = { - .x = 0, - .y = 0, - .width = 240 << 16, - .height = 160 << 16 - }; - - DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0); - vc_dispmanx_update_submit_sync(update); - - renderer->window.element = element; - renderer->window.width = dispWidth; - renderer->window.height = dispHeight; - - renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0); - if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) { - return false; - } - - renderer->d.outputBuffer = memalign(16, 256 * 256 * 4); - renderer->d.outputBufferStride = 256; - glGenTextures(1, &renderer->tex); - glBindTexture(GL_TEXTURE_2D, renderer->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, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - renderer->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - renderer->vertexShader = glCreateShader(GL_VERTEX_SHADER); - renderer->program = glCreateProgram(); - - glShaderSource(renderer->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0); - glShaderSource(renderer->vertexShader, 1, (const GLchar**) &_vertexShader, 0); - glAttachShader(renderer->program, renderer->vertexShader); - glAttachShader(renderer->program, renderer->fragmentShader); - char log[1024]; - glCompileShader(renderer->fragmentShader); - glCompileShader(renderer->vertexShader); - glGetShaderInfoLog(renderer->fragmentShader, 1024, 0, log); - glGetShaderInfoLog(renderer->vertexShader, 1024, 0, log); - glLinkProgram(renderer->program); - glGetProgramInfoLog(renderer->program, 1024, 0, log); - printf("%s\n", log); - renderer->texLocation = glGetUniformLocation(renderer->program, "tex"); - renderer->positionLocation = glGetAttribLocation(renderer->program, "position"); - glClearColor(1.f, 0.f, 0.f, 1.f); -} - -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { - SDL_Event event; - - while (context->state < THREAD_EXITING) { - while (SDL_PollEvent(&event)) { - GBASDLHandleEvent(context, &renderer->player, &event); - } - - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { - glViewport(0, 0, 240, 160); - glClear(GL_COLOR_BUFFER_BIT); - glUseProgram(renderer->program); - glUniform1i(renderer->texLocation, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, renderer->tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer); - glVertexAttribPointer(renderer->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); - glEnableVertexAttribArray(renderer->positionLocation); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glUseProgram(0); - eglSwapBuffers(renderer->display, renderer->surface); - } - GBASyncWaitFrameEnd(&context->sync); - } -} - -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { - eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroySurface(renderer->display, renderer->surface); - eglDestroyContext(renderer->display, renderer->context); - eglTerminate(renderer->display); - bcm_host_deinit(); -} diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c new file mode 100644 index 000000000..30158653d --- /dev/null +++ b/src/platform/sdl/gl-common.c @@ -0,0 +1,47 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "main.h" + +void GBASDLGLCommonSwap(struct VideoBackend* context) { + struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_SwapWindow(renderer->window); +#else + UNUSED(renderer); + SDL_GL_SwapBuffers(); +#endif +} + +void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer) { +#ifndef COLOR_16_BIT + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); +#else + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); +#ifdef COLOR_5_6_5 + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); +#else + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); +#endif + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); + renderer->glCtx = SDL_GL_CreateContext(renderer->window); + SDL_GL_SetSwapInterval(1); + SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); + renderer->player.window = renderer->window; +#else + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); +#ifdef COLOR_16_BIT + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL); +#else + SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL); +#endif +#endif +} diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h new file mode 100644 index 000000000..c07fd7926 --- /dev/null +++ b/src/platform/sdl/gl-common.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef SDL_GL_COMMON_H +#define SDL_GL_COMMON_H +#include "main.h" + +void GBASDLGLCommonSwap(struct VideoBackend* context); +void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer); + +#endif diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 429d67a65..12073b12a 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -5,18 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" +#include "gl-common.h" + #include "gba/supervisor/thread.h" #include "platform/opengl/gl.h" -static void _sdlSwap(struct VideoBackend* context) { - struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user; -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GL_SwapWindow(renderer->window); -#else - SDL_GL_SwapBuffers(); -#endif -} - static void _doViewport(int w, int h, struct VideoBackend* v) { v->resized(v, w, h); v->clear(v); @@ -35,34 +28,7 @@ void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { } bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { -#ifndef COLOR_16_BIT - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); -#else - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); -#ifdef COLOR_5_6_5 - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); -#else - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); -#endif - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); -#endif - -#if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); - renderer->glCtx = SDL_GL_CreateContext(renderer->window); - SDL_GL_SetSwapInterval(1); - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); - renderer->player.window = renderer->window; -#else - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); -#ifdef COLOR_16_BIT - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL); -#else - SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL); -#endif -#endif + GBASDLGLCommonInit(renderer); renderer->d.outputBuffer = malloc(256 * 256 * BYTES_PER_PIXEL); renderer->d.outputBufferStride = 256; @@ -71,7 +37,7 @@ bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { renderer->gl.d.user = renderer; renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; renderer->gl.d.filter = renderer->filter; - renderer->gl.d.swap = _sdlSwap; + renderer->gl.d.swap = GBASDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0); _doViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c new file mode 100644 index 000000000..a3a617471 --- /dev/null +++ b/src/platform/sdl/gles2-sdl.c @@ -0,0 +1,144 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "main.h" + +#include "gl-common.h" + +#include + +static bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer); +static void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLGLES2Init; + renderer->deinit = GBASDLGLES2Deinit; + renderer->runloop = GBASDLGLES2Runloop; +} + +bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer) { +#ifdef BUILD_RASPI + bcm_host_init(); + renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + int major, minor; + if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) { + printf("Failed to initialize EGL"); + return false; + } + + if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) { + printf("Failed to get GLES API"); + return false; + } + + const EGLint requestConfig[] = { + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 5, + EGL_BLUE_SIZE, 5, + EGL_ALPHA_SIZE, 1, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + + EGLConfig config; + EGLint numConfigs; + + if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) { + printf("Failed to choose EGL config\n"); + return false; + } + + const EGLint contextAttributes[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + int dispWidth = 240, dispHeight = 160, adjWidth; + renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes); + graphics_get_display_size(0, &dispWidth, &dispHeight); + adjWidth = dispHeight / 2 * 3; + + DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0); + DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); + + VC_RECT_T destRect = { + .x = (dispWidth - adjWidth) / 2, + .y = 0, + .width = adjWidth, + .height = dispHeight + }; + + VC_RECT_T srcRect = { + .x = 0, + .y = 0, + .width = 240 << 16, + .height = 160 << 16 + }; + + DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0); + vc_dispmanx_update_submit_sync(update); + + renderer->window.element = element; + renderer->window.width = dispWidth; + renderer->window.height = dispHeight; + + renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0); + if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) { + return false; + } +#else + GBASDLGLCommonInit(renderer); +#endif + + renderer->d.outputBuffer = memalign(16, 256 * 256 * 4); + renderer->d.outputBufferStride = 256; + + GBAGLES2ContextCreate(&renderer->gl); + renderer->gl.d.user = renderer; + renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; + renderer->gl.d.filter = renderer->filter; + renderer->gl.d.swap = GBASDLGLCommonSwap; + renderer->gl.d.init(&renderer->gl.d, 0); + return true; +} + +void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { + SDL_Event event; + struct VideoBackend* v = &renderer->gl.d; + + while (context->state < THREAD_EXITING) { + while (SDL_PollEvent(&event)) { + GBASDLHandleEvent(context, &renderer->player, &event); + } + + if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + v->postFrame(v, renderer->d.outputBuffer); + } + v->drawFrame(v); + GBASyncWaitFrameEnd(&context->sync); +#ifdef BUILD_RASPI + eglSwapBuffers(renderer->display, renderer->surface); +#else + v->swap(v); +#endif + } +} + +void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer) { + if (renderer->gl.d.deinit) { + renderer->gl.d.deinit(&renderer->gl.d); + } +#ifdef BUILD_RASPI + eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(renderer->display, renderer->surface); + eglDestroyContext(renderer->display, renderer->context); + eglTerminate(renderer->display); + bcm_host_deinit(); +#elif SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_DeleteContext(renderer->glCtx); +#endif + free(renderer->d.outputBuffer); +} diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 0b3143eeb..78dda4706 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -86,6 +86,8 @@ int main(int argc, char** argv) { #ifdef BUILD_GL GBASDLGLCreate(&renderer); +#elif defined(BUILD_GLES2) + GBASDLGLES2Create(&renderer); #else GBASDLSWCreate(&renderer); #endif @@ -110,6 +112,10 @@ int main(int argc, char** argv) { bool didFail = false; renderer.audio.samples = context.audioBuffers; + renderer.audio.sampleRate = 44100; + if (opts.sampleRate) { + renderer.audio.sampleRate = opts.sampleRate; + } if (!GBASDLInitAudio(&renderer.audio, &context)) { didFail = true; } diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index 4a428c190..66ae8c75c 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -20,13 +20,16 @@ #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #include -#include #include #include #pragma GCC diagnostic pop #endif +#ifdef BUILD_GLES2 +#include "platform/opengl/gles2.h" +#endif + #ifdef USE_PIXMAN #include #endif @@ -57,6 +60,8 @@ struct SDLSoftwareRenderer { #ifdef BUILD_GL struct GBAGLContext gl; +#elif BUILD_GLES2 + struct GBAGLES2Context gl; #endif #ifdef USE_PIXMAN @@ -69,13 +74,6 @@ struct SDLSoftwareRenderer { EGLSurface surface; EGLContext context; EGL_DISPMANX_WINDOW_T window; - GLuint tex; - GLuint fragmentShader; - GLuint vertexShader; - GLuint program; - GLuint bufferObject; - GLuint texLocation; - GLuint positionLocation; #endif #ifdef BUILD_PANDORA @@ -90,4 +88,8 @@ void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer); #ifdef BUILD_GL void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer); #endif + +#ifdef BUILD_GLES2 +void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer); +#endif #endif diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c index 6e0fdebb2..321679a96 100644 --- a/src/platform/sdl/sdl-audio.c +++ b/src/platform/sdl/sdl-audio.c @@ -22,7 +22,7 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex return false; } - context->desiredSpec.freq = 44100; + context->desiredSpec.freq = context->sampleRate; context->desiredSpec.format = AUDIO_S16SYS; context->desiredSpec.channels = 2; context->desiredSpec.samples = context->samples; diff --git a/src/platform/sdl/sdl-audio.h b/src/platform/sdl/sdl-audio.h index 6730333eb..f2f0b8df2 100644 --- a/src/platform/sdl/sdl-audio.h +++ b/src/platform/sdl/sdl-audio.h @@ -15,6 +15,7 @@ struct GBASDLAudio { // Input size_t samples; + unsigned sampleRate; // State SDL_AudioSpec desiredSpec; diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index e316a7f2f..c63e5dc76 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -22,7 +22,7 @@ #endif #define GYRO_STEPS 100 -#define RUMBLE_PWM 35 +#define RUMBLE_PWM 20 #if SDL_VERSION_ATLEAST(2, 0, 0) static void _GBASDLSetRumble(struct GBARumble* rumble, int enable); diff --git a/tools/debian/changelog b/tools/debian/changelog new file mode 100644 index 000000000..18bed0d13 --- /dev/null +++ b/tools/debian/changelog @@ -0,0 +1,5 @@ +mgba (0.3.0-1) UNRELEASED; urgency=low + + * Initial release (closes: Bug#787470). + + -- Sérgio Benjamim Mon, 17 Aug 2015 18:40:00 -0300 diff --git a/tools/debian/clean b/tools/debian/clean new file mode 100644 index 000000000..d5cc2a095 --- /dev/null +++ b/tools/debian/clean @@ -0,0 +1,2 @@ +debian/libmgba.install +debian/libretro-mgba.install diff --git a/tools/debian/compat b/tools/debian/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/tools/debian/compat @@ -0,0 +1 @@ +9 diff --git a/tools/debian/control b/tools/debian/control new file mode 100644 index 000000000..79b508833 --- /dev/null +++ b/tools/debian/control @@ -0,0 +1,78 @@ +Source: mgba +Section: otherosfs +Priority: extra +Maintainer: Sérgio Benjamim +Build-Depends: cmake (>= 2.8.11), + debhelper (>= 9), + libavcodec-dev, + libavformat-dev, + libavresample-dev, + libavutil-dev, + libedit-dev, + libmagickwand-dev, + libpng-dev, + libqt5opengl5-dev, + libsdl2-dev, + libswscale-dev, + libzip-dev, + pkg-config, + qtbase5-dev, + qtmultimedia5-dev, + zlib1g-dev +Standards-Version: 3.9.6 +Homepage: http://mgba.io/ + +Package: libmgba +Architecture: any +Multi-Arch: same +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: Game Boy Advance emulator (common library for mGBA) + mGBA is a new emulator for running Game Boy Advance games. It aims to be faster + and more accurate than many existing Game Boy Advance emulators, as well as + adding features that other emulators lack. + . + This package provides the common library for mGBA. + . + Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is + not affiliated with or endorsed by any of the companies mentioned. + +Package: mgba-qt +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: Game Boy Advance emulator (Qt frontend for mGBA) + mGBA is a new emulator for running Game Boy Advance games. It aims to be faster + and more accurate than many existing Game Boy Advance emulators, as well as + adding features that other emulators lack. + . + This package provides the Qt GUI frontend for mGBA. + . + Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is + not affiliated with or endorsed by any of the companies mentioned. + +Package: mgba-sdl +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: Game Boy Advance emulator (SDL frontend for mGBA) + mGBA is a new emulator for running Game Boy Advance games. It aims to be faster + and more accurate than many existing Game Boy Advance emulators, as well as + adding features that other emulators lack. + . + This package provides the SDL UI console for mGBA. + . + Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is + not affiliated with or endorsed by any of the companies mentioned. + +Package: libretro-mgba +Architecture: any +Multi-Arch: same +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: Libretro wrapper for mGBA + This wrapper makes mGBA API compatible with libretro, thus allowing its use + with libretro frontends, such as RetroArch. + . + mGBA is a new emulator for running Game Boy Advance games. It aims to be faster + and more accurate than many existing Game Boy Advance emulators, as well as + adding features that other emulators lack. + . + Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is + not affiliated with or endorsed by any of the companies mentioned. diff --git a/tools/debian/copyright b/tools/debian/copyright new file mode 100644 index 000000000..75a308d13 --- /dev/null +++ b/tools/debian/copyright @@ -0,0 +1,472 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: mGBA +Upstream-Contact: Jeffrey Pfau (aka endrift) +Source: https://github.com/mgba-emu/mgba +Comment: This package was debianized by + Sergio Benjamim (sergio-br2) on Mon, 01 Jun 2015 18:40:00 -0300 + + +Files: * +Copyright: 2013-2015 Jeffrey Pfau +License: MPL-2.0 + +Files: src/third-party/blip_buf/* +Copyright: 2003-2009 Shay Green +License: LGPL-2.1+ + +Files: src/third-party/inih/* +Copyright: 2009 Brush Technology. All rights reserved. +License: BSD-3-clause-Brush-Technology + +Files: src/third-party/lzma/* +Copyright: 2008-2015 Igor Pavlov +License: public-domain + These files have been put in the public domain by their author + +Files: src/platform/libretro/libretro.h +Copyright: 2010-2015 The RetroArch Team +License: Expat + +Files: debian/* +Copyright: 2015 Sergio Benjamim (sergio-br2) +License: MPL-2.0 + + +License: MPL-2.0 + Mozilla Public License Version 2.0 + ================================== + . + 1. Definitions + -------------- + . + 1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + . + 1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + . + 1.3. "Contribution" + means Covered Software of a particular Contributor. + . + 1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + . + 1.5. "Incompatible With Secondary Licenses" + means + . + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + . + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + . + 1.6. "Executable Form" + means any form of the work other than Source Code Form. + . + 1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + . + 1.8. "License" + means this document. + . + 1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + . + 1.10. "Modifications" + means any of the following: + . + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + . + (b) any new file in Source Code Form that contains any Covered + Software. + . + 1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + . + 1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + . + 1.13. "Source Code Form" + means the form of the work preferred for making modifications. + . + 1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + . + 2. License Grants and Conditions + -------------------------------- + . + 2.1. Grants + . + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + . + (a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + . + (b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + . + 2.2. Effective Date + . + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + . + 2.3. Limitations on Grant Scope + . + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + . + (a) for any code that a Contributor has removed from Covered Software; + or + . + (b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + . + (c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + . + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + . + 2.4. Subsequent Licenses + . + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + . + 2.5. Representation + . + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights + to grant the rights to its Contributions conveyed by this License. + . + 2.6. Fair Use + . + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + . + 2.7. Conditions + . + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted + in Section 2.1. + . + 3. Responsibilities + ------------------- + . + 3.1. Distribution of Source Form + . + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + . + 3.2. Distribution of Executable Form + . + If You distribute Covered Software in Executable Form then: + . + (a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + . + (b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + . + 3.3. Distribution of a Larger Work + . + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + . + 3.4. Notices + . + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, + or limitations of liability) contained within the Source Code Form of + the Covered Software, except that You may alter any license notices to + the extent required to remedy known factual inaccuracies. + . + 3.5. Application of Additional Terms + . + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + . + 4. Inability to Comply Due to Statute or Regulation + --------------------------------------------------- + . + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Software due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description must + be placed in a text file included with all distributions of the Covered + Software under this License. Except to the extent prohibited by statute + or regulation, such description must be sufficiently detailed for a + recipient of ordinary skill to be able to understand it. + . + 5. Termination + -------------- + . + 5.1. The rights granted under this License will terminate automatically + if You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, this is the + first time You have received notice of non-compliance with this License + from such Contributor, and You become compliant prior to 30 days after + Your receipt of the notice. + . + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + . + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all + end user license agreements (excluding distributors and resellers) which + have been validly granted by You or Your distributors under this License + prior to termination shall survive termination. + . + ************************************************************************ + * * + * 6. Disclaimer of Warranty * + * ------------------------- * + * * + * Covered Software is provided under this License on an "as is" * + * basis, without warranty of any kind, either expressed, implied, or * + * statutory, including, without limitation, warranties that the * + * Covered Software is free of defects, merchantable, fit for a * + * particular purpose or non-infringing. The entire risk as to the * + * quality and performance of the Covered Software is with You. * + * Should any Covered Software prove defective in any respect, You * + * (not any Contributor) assume the cost of any necessary servicing, * + * repair, or correction. This disclaimer of warranty constitutes an * + * essential part of this License. No use of any Covered Software is * + * authorized under this License except under this disclaimer. * + * * + ************************************************************************ + . + ************************************************************************ + * * + * 7. Limitation of Liability * + * -------------------------- * + * * + * Under no circumstances and under no legal theory, whether tort * + * (including negligence), contract, or otherwise, shall any * + * Contributor, or anyone who distributes Covered Software as * + * permitted above, be liable to You for any direct, indirect, * + * special, incidental, or consequential damages of any character * + * including, without limitation, damages for lost profits, loss of * + * goodwill, work stoppage, computer failure or malfunction, or any * + * and all other commercial damages or losses, even if such party * + * shall have been informed of the possibility of such damages. This * + * limitation of liability shall not apply to liability for death or * + * personal injury resulting from such party's negligence to the * + * extent applicable law prohibits such limitation. Some * + * jurisdictions do not allow the exclusion or limitation of * + * incidental or consequential damages, so this exclusion and * + * limitation may not apply to You. * + * * + ************************************************************************ + . + 8. Litigation + ------------- + . + Any litigation relating to this License may be brought only in the + courts of a jurisdiction where the defendant maintains its principal + place of business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. + Nothing in this Section shall prevent a party's ability to bring + cross-claims or counter-claims. + . + 9. Miscellaneous + ---------------- + . + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides + that the language of a contract shall be construed against the drafter + shall not be used to construe this License against a Contributor. + . + 10. Versions of the License + --------------------------- + . + 10.1. New Versions + . + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + . + 10.2. Effect of New Versions + . + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + . + 10.3. Modified Versions + . + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + . + 10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses + . + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + . + Exhibit A - Source Code Form License Notice + ------------------------------------------- + . + 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/. + . + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to look + for such a notice. + . + You may add additional accurate notices of copyright ownership. + . + Exhibit B - "Incompatible With Secondary Licenses" Notice + --------------------------------------------------------- + . + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + + +License: BSD-3-clause-Brush-Technology + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Brush Technology nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +License: LGPL-2.1+ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + . + On Debian systems, the full text of the GNU Lesser General Public + License version 2.1 can be found in the file + `/usr/share/common-licenses/LGPL-2.1'. diff --git a/tools/debian/docs b/tools/debian/docs new file mode 100644 index 000000000..77001cf9b --- /dev/null +++ b/tools/debian/docs @@ -0,0 +1,2 @@ +README.md +CHANGES diff --git a/tools/debian/libmgba.install.in b/tools/debian/libmgba.install.in new file mode 100644 index 000000000..f29fc8c85 --- /dev/null +++ b/tools/debian/libmgba.install.in @@ -0,0 +1 @@ +obj/libmgba.so* usr/lib/@DEB_HOST_MULTIARCH@/ diff --git a/tools/debian/libretro-mgba.install.in b/tools/debian/libretro-mgba.install.in new file mode 100644 index 000000000..4f15c2df0 --- /dev/null +++ b/tools/debian/libretro-mgba.install.in @@ -0,0 +1 @@ +obj/mgba_libretro.so usr/lib/@DEB_HOST_MULTIARCH@/libretro diff --git a/tools/debian/mgba-qt.install b/tools/debian/mgba-qt.install new file mode 100644 index 000000000..7216bba6f --- /dev/null +++ b/tools/debian/mgba-qt.install @@ -0,0 +1,3 @@ +usr/bin/mgba-qt +res/mgba-qt.desktop usr/share/applications +usr/share/icons diff --git a/tools/debian/mgba-qt.manpages b/tools/debian/mgba-qt.manpages new file mode 100644 index 000000000..2c3d1c5ed --- /dev/null +++ b/tools/debian/mgba-qt.manpages @@ -0,0 +1 @@ +doc/mgba-qt.6 diff --git a/tools/debian/mgba-sdl.install b/tools/debian/mgba-sdl.install new file mode 100644 index 000000000..104a1b436 --- /dev/null +++ b/tools/debian/mgba-sdl.install @@ -0,0 +1 @@ +usr/bin/mgba diff --git a/tools/debian/mgba-sdl.manpages b/tools/debian/mgba-sdl.manpages new file mode 100644 index 000000000..96e337fb7 --- /dev/null +++ b/tools/debian/mgba-sdl.manpages @@ -0,0 +1 @@ +doc/mgba.6 diff --git a/tools/debian/rules b/tools/debian/rules new file mode 100755 index 000000000..ebe066d37 --- /dev/null +++ b/tools/debian/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f + +# Copyright (C) 2015 Sergio Benjamim + +# 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/. */ + +DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) +ARCH=$(shell dpkg-architecture -qDEB_HOST_ARCH) + +ifeq ($(ARCH),armhf) + ARM=-DBUILD_GL=OFF -DBUILD_GLES2=ON +endif + +%: + dh $@ --buildsystem=cmake --builddirectory=obj --parallel + +override_dh_auto_configure: + dh_auto_configure -- -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_SKIP_RPATH=ON -DBUILD_LIBRETRO=ON $(ARM) + sed 's/@DEB_HOST_MULTIARCH@/$(DEB_HOST_MULTIARCH)/g' \ + debian/libretro-mgba.install.in > debian/libretro-mgba.install + sed 's/@DEB_HOST_MULTIARCH@/$(DEB_HOST_MULTIARCH)/g' \ + debian/libmgba.install.in > debian/libmgba.install + +override_dh_installchangelogs: + dh_installchangelogs -k CHANGES diff --git a/tools/debian/source/format b/tools/debian/source/format new file mode 100644 index 000000000..163aaf8d8 --- /dev/null +++ b/tools/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/tools/debian/watch b/tools/debian/watch new file mode 100644 index 000000000..90bf499e4 --- /dev/null +++ b/tools/debian/watch @@ -0,0 +1,3 @@ +version=3 +opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/mgba-$1\.tar\.gz/ \ + https://github.com/mgba-emu/mgba/tags .*/v?(\d\S*)\.tar\.gz diff --git a/tools/sanitize-deb.sh b/tools/sanitize-deb.sh index e1c523fb8..c63cd5bc3 100755 --- a/tools/sanitize-deb.sh +++ b/tools/sanitize-deb.sh @@ -56,8 +56,8 @@ while [ $# -gt 0 ]; do sed -i~ "s/,$//g" deb-temp/DEBIAN/control sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control rm deb-temp/DEBIAN/control~ - chown -R 0:0 deb-temp - chmod 600 deb-temp/DEBIAN/md5sums + chmod 644 deb-temp/DEBIAN/md5sums + chown -R root:root deb-temp dpkg-deb -b deb-temp $DEB rm -rf deb-temp shift