diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index b2dda12d..f47b3a4a 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -9,6 +9,11 @@ on: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build-macos: strategy: @@ -28,12 +33,13 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da + vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4 - name: Build uses: lukka/run-cmake@v10 with: configurePreset: release-mac-${{ matrix.arch }} buildPreset: release-mac-${{ matrix.arch }} + configurePresetAdditionalArgs: "['-DMELONDS_EMBED_BUILD_INFO=ON']" - name: Compress app bundle shell: bash run: | diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 1bcf2f66..1104142d 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -8,6 +8,11 @@ on: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build-x86_64: name: x86_64 @@ -23,7 +28,7 @@ jobs: sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \ qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 - name: Configure - run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr + run: cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON - name: Build run: | cmake --build build @@ -74,7 +79,7 @@ jobs: -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \ -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \ -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \ - -DUSE_QT6=ON + -DMELONDS_EMBED_BUILD_INFO=ON - name: Build shell: bash run: | diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 30dbd2a8..c3350b4d 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -9,6 +9,11 @@ on: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build: runs-on: windows-latest @@ -27,9 +32,9 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da + vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4 - name: Configure - run: cmake --preset=release-mingw-x86_64 + run: cmake --preset=release-mingw-x86_64 -DMELONDS_EMBED_BUILD_INFO=ON - name: Build run: cmake --build --preset=release-mingw-x86_64 - uses: actions/upload-artifact@v4 diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..51bc4cac --- /dev/null +++ b/BUILD.md @@ -0,0 +1,81 @@ +# Building melonDS + +* [Linux](#linux) +* [Windows](#windows) +* [macOS](#macos) + +## Linux +1. Install dependencies: + * Ubuntu: + * All versions: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev libarchive-dev libenet-dev libzstd-dev` + * 24.04: `sudo apt install qt6-{base,base-private,multimedia,svg}-dev` + * 22.04: `sudo apt install qtbase6-dev qtbase6-private-dev qtmultimedia6-dev libqt6svg6-dev` + * Older versions: `sudo apt install qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev` + Also add `-DUSE_QT6=OFF` to the first CMake command below. + * Fedora: `sudo dnf install gcc-c++ cmake extra-cmake-modules SDL2-devel libarchive-devel enet-devel libzstd-devel qt6-{qtbase,qtmultimedia,qtsvg}-devel wayland-devel` + * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt6-{base,multimedia,svg} libarchive enet zstd` +2. Download the melonDS repository and prepare: + ```bash + git clone https://github.com/melonDS-emu/melonDS + cd melonDS + ``` +3. Compile: + ```bash + cmake -B build + cmake --build build -j$(nproc --all) + ``` + +## Windows +1. Install [MSYS2](https://www.msys2.org/) +2. Open the MSYS2 terminal from the Start menu: + * For x64 systems (most common), use **MSYS2 UCRT64** + * For ARM64 systems, use **MSYS2 CLANGARM64** +3. Update the packages using `pacman -Syu` and reopen the same terminal if it asks you to +4. Install git and clone the repository + ```bash + pacman -S git + git clone https://github.com/melonDS-emu/melonDS + cd melonDS + ``` +5. Install dependencies: + Replace `` below with `mingw-w64-ucrt-x86_64` on x64 systems, or `mingw-w64-clang-aarch64` on ARM64 systems. + ```bash + pacman -S -{toolchain,cmake,SDL2,libarchive,enet,zstd}` + ``` +6. Install Qt and configure the build directory + * Dynamic builds (with DLLs) + 1. Install Qt: `pacman -S -{qt6-base,qt6-svg,qt6-multimedia,qt6-svg,qt6-tools}` + 2. Set up the build directory with `cmake -B build` + * Static builds (without DLLs, standalone executable) + 1. Install Qt: `pacman -S -qt5-static` + (Note: As of writing, the `qt6-static` package does not work.) + 2. Set up the build directory with `cmake -B build -DBUILD_STATIC=ON -DUSE_QT6=OFF -DCMAKE_PREFIX_PATH=$MSYSTEM_PREFIX/qt5-static` +7. Compile: `cmake --build build` + +If everything went well, melonDS should now be in the `build` folder. For dynamic builds, you may need to run melonDS from the MSYS2 terminal in order for it to find the required DLLs. + +## macOS +1. Install the [Homebrew Package Manager](https://brew.sh) +2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd` +3. Download the melonDS repository and prepare: + ```zsh + git clone https://github.com/melonDS-emu/melonDS + cd melonDS + ``` +4. Compile: + ```zsh + cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" + cmake --build build -j$(sysctl -n hw.logicalcpu) + ``` +If everything went well, melonDS.app should now be in the `build` directory. + +### Self-contained app bundle +If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run ` +../tools/mac-libs.rb .` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command. + +## Nix (macOS/Linux) + +melonDS provides a Nix flake with support for both macOS and Linux. The [Nix package manager](https://nixos.org) needs to be installed to use it. + +* To run melonDS, just type `nix run github:melonDS-emu/melonDS`. +* To get a shell for development, clone the melonDS repository and type `nix develop` in its directory. \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 40583949..55bf825f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ include(CMakeDependentOption) include(CheckIPOSupported) include(SetupCCache) +include(Sanitizers) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") diff --git a/CMakePresets.json b/CMakePresets.json index 2144417b..b506b88a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -27,10 +27,6 @@ "binaryDir": "${sourceDir}/build/release-mingw-x86_64", "generator": "Ninja", "cacheVariables": { - "USE_QT6": { - "type": "BOOL", - "value": "ON" - }, "BUILD_STATIC": { "type": "BOOL", "value": "ON" diff --git a/README.md b/README.md index eb8b1358..2cf42c2d 100644 --- a/README.md +++ b/README.md @@ -32,75 +32,7 @@ DS BIOS dumps from a DSi or 3DS can be used with no compatibility issues. DSi BI As for the rest, the interface should be pretty straightforward. If you have a question, don't hesitate to ask, though! ## How to build - -### Linux -1. Install dependencies: - * Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev` - * Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev` - * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia qt5-svg libarchive enet zstd` -3. Download the melonDS repository and prepare: - ```bash - git clone https://github.com/melonDS-emu/melonDS - cd melonDS - ``` - -3. Compile: - ```bash - cmake -B build - cmake --build build -j$(nproc --all) - ``` - -### Windows -1. Install [MSYS2](https://www.msys2.org/) -2. Open the **MSYS2 MinGW 64-bit** terminal -3. Update the packages using `pacman -Syu` and reopen the terminal if it asks you to -4. Install git to clone the repository - ```bash - pacman -S git - ``` -5. Download the melonDS repository and prepare: - ```bash - git clone https://github.com/melonDS-emu/melonDS - cd melonDS - ``` -#### Dynamic builds (with DLLs) -5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,qt5-svg,qt5-tools,libarchive,enet,zstd}` -6. Compile: - ```bash - cmake -B build - cmake --build build - cd build - ../tools/msys-dist.sh - ``` -If everything went well, melonDS and the libraries it needs should now be in the `dist` folder. - -#### Static builds (without DLLs, standalone executable) -5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libarchive,enet,zstd}` -6. Compile: - ```bash - cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static - cmake --build build - ``` -If everything went well, melonDS should now be in the `build` folder. - -### macOS -1. Install the [Homebrew Package Manager](https://brew.sh) -2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd` -3. Download the melonDS repository and prepare: - ```zsh - git clone https://github.com/melonDS-emu/melonDS - cd melonDS - ``` -4. Compile: - ```zsh - cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" - cmake --build build -j$(sysctl -n hw.logicalcpu) - ``` -If everything went well, melonDS.app should now be in the `build` directory. - -#### Self-contained app bundle -If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run ` -../tools/mac-libs.rb .` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command. +See [BUILD.md](./BUILD.md) for build instructions. ## TODO LIST diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index f8c33fd4..3fb0786f 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -9,7 +9,7 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") endif() FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2024.08.23 + GIT_TAG 2024.10.21 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() @@ -19,11 +19,7 @@ set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/cmake/overlay-triplets") option(USE_RECOMMENDED_TRIPLETS "Use the recommended triplets that are used for official builds" ON) # Duplicated here because it needs to be set before project() -if (NOT WIN32) - option(USE_QT6 "Build using Qt 6 instead of 5" ON) -else() - option(USE_QT6 "Build using Qt 6 instead of 5" OFF) -endif() +option(USE_QT6 "Use Qt 6 instead of Qt 5" ON) # Since the Linux build pulls in glib anyway, we can just use upstream libslirp if (UNIX AND NOT APPLE) diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 00000000..9c09da28 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,8 @@ +set(SANITIZE "" CACHE STRING "Sanitizers to enable.") + +string(REGEX MATCHALL "[^,]+" ENABLED_SANITIZERS "${SANITIZE}") + +foreach(SANITIZER ${ENABLED_SANITIZERS}) + add_compile_options("-fsanitize=${SANITIZER}") + add_link_options("-fsanitize=${SANITIZER}") +endforeach() \ No newline at end of file diff --git a/flake.lock b/flake.lock index 9f0fe239..d7dd5bc6 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725432240, - "narHash": "sha256-+yj+xgsfZaErbfYM3T+QvEE2hU7UuE+Jf0fJCJ8uPS0=", + "lastModified": 1730531603, + "narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ad416d066ca1222956472ab7d0555a6946746a80", + "rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8236ccd3..a7768e4e 100644 --- a/flake.nix +++ b/flake.nix @@ -12,13 +12,16 @@ inherit (pkgs.lib) cmakeBool optionals makeLibraryPath; inherit (pkgs.stdenv) isLinux isDarwin; - versionSuffix = with self; if sourceInfo?dirtyShortRev + revision = with self; if sourceInfo?dirtyRev + then sourceInfo.dirtyRev + else sourceInfo.rev; + shortRevision = with self; if sourceInfo?dirtyShortRev then sourceInfo.dirtyShortRev else sourceInfo.shortRev; melonDS = pkgs.stdenv.mkDerivation { pname = "melonDS"; - version = "0.9.5-${versionSuffix}"; + version = "0.9.5-${shortRevision}"; src = ./.; nativeBuildInputs = with pkgs; [ @@ -46,8 +49,13 @@ cmakeFlags = [ (cmakeBool "USE_QT6" true) (cmakeBool "USE_SYSTEM_LIBSLIRP" true) + (cmakeBool "MELONDS_EMBED_BUILD_INFO" true) ]; + env.MELONDS_GIT_HASH = revision; + env.MELONDS_GIT_BRANCH = "(unknown)"; + env.MELONDS_BUILD_PROVIDER = "Nix"; + qtWrapperArgs = optionals isLinux [ "--prefix LD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap pkgs.wayland ]}" ] ++ optionals isDarwin [ @@ -68,6 +76,9 @@ devShells = { default = pkgs.mkShell { inputsFrom = [ self.packages.${system}.default ]; + packages = with pkgs; [ + qt6.qttools + ]; }; # Shell for building static melonDS release builds with vcpkg diff --git a/res/melon.qrc b/res/melon.qrc index 38915bbf..3c5824d6 100644 --- a/res/melon.qrc +++ b/res/melon.qrc @@ -2,5 +2,6 @@ icon/melon_256x256.png + melon.svg diff --git a/src/ARM.cpp b/src/ARM.cpp index 6ac989af..682ce9ff 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -110,6 +110,7 @@ const u32 ARM::ConditionTable[16] = ARM::ARM(u32 num, bool jit, std::optional gdb, melonDS::NDS& nds) : #ifdef GDBSTUB_ENABLED GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0), + BreakOnStartup(gdb ? (num ? gdb->ARM7BreakOnStartup : gdb->ARM9BreakOnStartup) : false), #endif Num(num), // well uh NDS(nds) diff --git a/src/ARMInterpreter_LoadStore.cpp b/src/ARMInterpreter_LoadStore.cpp index 84203310..9dc14ea4 100644 --- a/src/ARMInterpreter_LoadStore.cpp +++ b/src/ARMInterpreter_LoadStore.cpp @@ -543,8 +543,8 @@ void A_LDM(ARM* cpu) } } - u32 pc; - if ((cpu->CurInstr & (1<<15))) + u32 pc = 0; + if (cpu->CurInstr & (1<<15)) { if (preinc) base += 4; dabort |= !(first ? cpu->DataRead32 (base, &pc) @@ -556,13 +556,12 @@ void A_LDM(ARM* cpu) pc &= ~0x1; } - // switch back to previous regs - if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15))) - cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true); - // handle data aborts if (dabort) [[unlikely]] { + if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15))) + cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true); + cpu->AddCycles_CDI(); ((ARMv5*)cpu)->DataAbort(); return; @@ -587,7 +586,10 @@ void A_LDM(ARM* cpu) else cpu->R[baseid] = wbbase; } - + + if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15))) + cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true); + // jump if pc got written if (cpu->CurInstr & (1<<15)) cpu->JumpTo(pc, cpu->CurInstr & (1<<22)); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e177835..1f947d11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,6 +128,17 @@ if (ENABLE_JIT) endif() set(MELONDS_VERSION_SUFFIX "$ENV{MELONDS_VERSION_SUFFIX}" CACHE STRING "Suffix to add to displayed melonDS version") +option(MELONDS_EMBED_BUILD_INFO "Embed detailed build info into the binary" OFF) +set(MELONDS_GIT_BRANCH "$ENV{MELONDS_GIT_BRANCH}" CACHE STRING "The Git branch used for this build") +set(MELONDS_GIT_HASH "$ENV{MELONDS_GIT_HASH}" CACHE STRING "The hash of the Git commit") +set(MELONDS_BUILD_PROVIDER "$ENV{MELONDS_BUILD_PROVIDER}" CACHE STRING "The name of the provider of this build") + +if (MELONDS_EMBED_BUILD_INFO) + target_compile_definitions(core PUBLIC MELONDS_EMBED_BUILD_INFO) + if (NOT MELONDS_GIT_BRANCH OR NOT MELONDS_GIT_HASH OR NOT MELONDS_BUILD_PROVIDER) + message(FATAL_ERROR "When embedding build information, all fields must be filled out. See src/CMakeLists.txt.") + endif() +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h") target_sources(core PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/version.h") diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index 792bf12d..9827bdbe 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -1445,7 +1445,6 @@ void DSi_NWifi::CheckRX() int rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); while (rxlen > 0) { - //printf("WMI packet recv %04X %04X %04X\n", *(u16*)&LANBuffer[0], *(u16*)&LANBuffer[2], *(u16*)&LANBuffer[4]); // check destination MAC if (*(u32*)&LANBuffer[0] != 0xFFFFFFFF || *(u16*)&LANBuffer[4] != 0xFFFF) { @@ -1508,6 +1507,7 @@ void DSi_NWifi::CheckRX() Mailbox[8].Write(LANBuffer[14+i]); DrainRXBuffer(); + return; } } diff --git a/src/GBACart.cpp b/src/GBACart.cpp index 4fd42894..a62aca6b 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -582,6 +582,11 @@ int CartGameSolarSensor::SetInput(int num, bool pressed) return -1; } +void CartGameSolarSensor::SetLightLevel(u8 level) noexcept +{ + LightLevel = std::clamp(level, 0, 10); +} + void CartGameSolarSensor::ProcessGPIO() { if (GPIO.data & 4) return; // Boktai chip select diff --git a/src/GBACart.h b/src/GBACart.h index f6fb95dd..726a234d 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -158,6 +158,8 @@ public: void DoSavestate(Savestate* file) override; int SetInput(int num, bool pressed) override; + void SetLightLevel(u8 level) noexcept; + [[nodiscard]] u8 GetLightLevel() const noexcept { return LightLevel; } protected: void ProcessGPIO() override; diff --git a/src/GPU.h b/src/GPU.h index 26e9d5df..5c373ca8 100644 --- a/src/GPU.h +++ b/src/GPU.h @@ -499,6 +499,17 @@ public: OAMDirty |= 1 << (addr / 1024); } + template + inline T ReadVRAMFlat_Texture(u32 addr) const + { + return *(T*)&VRAMFlat_Texture[addr & 0x7FFFF]; + } + template + inline T ReadVRAMFlat_TexPal(u32 addr) const + { + return *(T*)&VRAMFlat_TexPal[addr & 0x1FFFF]; + } + void SetPowerCnt(u32 val) noexcept; void StartFrame() noexcept; diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index 1221ed59..a9d0bd64 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -193,10 +193,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 1: // A3I5 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + ((pixel&0x1F)<<1)); *alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6); } break; @@ -204,12 +204,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 2: // 4-color { vramaddr += (((t * width) + s) >> 2); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); pixel >>= ((s & 0x3) << 1); pixel &= 0x3; texpal <<= 3; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -217,12 +217,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 3: // 16-color { vramaddr += (((t * width) + s) >> 1); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); if (s & 0x1) pixel >>= 4; else pixel &= 0xF; texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -230,10 +230,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 4: // 256-color { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -253,31 +253,31 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s val = 0; else { - val = ReadVRAM_Texture(vramaddr, gpu); + val = gpu.ReadVRAMFlat_Texture(vramaddr); val >>= (2 * (s & 0x3)); } - u16 palinfo = ReadVRAM_Texture(slot1addr, gpu); + u16 palinfo = gpu.ReadVRAMFlat_Texture(slot1addr); u32 paloffset = (palinfo & 0x3FFF) << 2; texpal <<= 4; switch (val & 0x3) { case 0: - *color = ReadVRAM_TexPal(texpal + paloffset, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); *alpha = 31; break; case 1: - *color = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); *alpha = 31; break; case 2: if ((palinfo >> 14) == 1) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -294,8 +294,8 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -311,20 +311,20 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s *color = r | g | b; } else - *color = ReadVRAM_TexPal(texpal + paloffset + 4, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 4); *alpha = 31; break; case 3: if ((palinfo >> 14) == 2) { - *color = ReadVRAM_TexPal(texpal + paloffset + 6, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 6); *alpha = 31; } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -353,10 +353,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 6: // A5I3 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + ((pixel&0x7)<<1)); *alpha = (pixel >> 3); } break; @@ -364,7 +364,7 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 7: // direct color { vramaddr += (((t * width) + s) << 1); - *color = ReadVRAM_Texture(vramaddr, gpu); + *color = gpu.ReadVRAMFlat_Texture(vramaddr); *alpha = (*color & 0x8000) ? 31 : 0; } break; @@ -1659,8 +1659,8 @@ void SoftRenderer::ClearBuffers(const GPU& gpu) { for (int x = 0; x < 256; x++) { - u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1), gpu); - u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1), gpu); + u16 val2 = gpu.ReadVRAMFlat_Texture(0x40000 + (yoff << 9) + (xoff << 1)); + u16 val3 = gpu.ReadVRAMFlat_Texture(0x60000 + (yoff << 9) + (xoff << 1)); // TODO: confirm color conversion u32 r = (val2 << 1) & 0x3E; if (r) r++; diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index 55a698b0..73d02e4f 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -430,16 +430,6 @@ private: s32 ycoverage, ycov_incr; }; - template - inline T ReadVRAM_Texture(u32 addr, const GPU& gpu) const - { - return *(T*)&gpu.VRAMFlat_Texture[addr & 0x7FFFF]; - } - template - inline T ReadVRAM_TexPal(u32 addr, const GPU& gpu) const - { - return *(T*)&gpu.VRAMFlat_TexPal[addr & 0x1FFFF]; - } u32 AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept; struct RendererPolygon diff --git a/src/GPU3D_Texcache.cpp b/src/GPU3D_Texcache.cpp index 196009e6..a6a40a04 100644 --- a/src/GPU3D_Texcache.cpp +++ b/src/GPU3D_Texcache.cpp @@ -75,11 +75,11 @@ inline u32 ConvertRGB5ToRGB6(u16 val) } template -void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData) +void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu) { for (u32 i = 0; i < width*height; i++) { - u16 value = *(u16*)&texData[i * 2]; + u16 value = gpu.ReadVRAMFlat_Texture(addr + i * 2); switch (outputFmt) { @@ -96,28 +96,28 @@ void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData) } } -template void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData); +template void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu); template -void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData) +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u32 addr, u32 addrAux, u32 palAddr, GPU& gpu) { // we process a whole block at the time for (int y = 0; y < height / 4; y++) { for (int x = 0; x < width / 4; x++) { - u32 data = ((u32*)texData)[x + y * (width / 4)]; - u16 auxData = ((u16*)texAuxData)[x + y * (width / 4)]; + u32 data = gpu.ReadVRAMFlat_Texture(addr + (x + y * (width / 4))*4); + u16 auxData = gpu.ReadVRAMFlat_Texture(addrAux + (x + y * (width / 4))*2); - u32 paletteOffset = auxData & 0x3FFF; - u16 color0 = palData[paletteOffset*2] | 0x8000; - u16 color1 = palData[paletteOffset*2+1] | 0x8000; - u16 color2, color3; + u32 paletteOffset = palAddr + (auxData & 0x3FFF) * 4; + u16 color0 = gpu.ReadVRAMFlat_TexPal(paletteOffset) | 0x8000; + u16 color1 = gpu.ReadVRAMFlat_TexPal(paletteOffset+2) | 0x8000; + u16 color2 = gpu.ReadVRAMFlat_TexPal(paletteOffset+4) | 0x8000; + u16 color3 = gpu.ReadVRAMFlat_TexPal(paletteOffset+6) | 0x8000; switch ((auxData >> 14) & 0x3) { case 0: - color2 = palData[paletteOffset*2+2] | 0x8000; color3 = 0; break; case 1: @@ -137,8 +137,6 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u color3 = 0; break; case 2: - color2 = palData[paletteOffset*2+2] | 0x8000; - color3 = palData[paletteOffset*2+3] | 0x8000; break; case 3: { @@ -179,7 +177,8 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u { for (int i = 0; i < 4; i++) { - u16 color = (packed >> 16 * (data >> 2 * (i + j * 4))) & 0xFFFF; + u32 colorIdx = 16 * ((data >> 2 * (i + j * 4)) & 0x3); + u16 color = (packed >> colorIdx) & 0xFFFF; u32 res; switch (outputFmt) { @@ -197,20 +196,20 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u } } -template void ConvertCompressedTexture(u32, u32, u32*, u8*, u8*, u16*); +template void ConvertCompressedTexture(u32, u32, u32*, u32, u32, u32, GPU&); template -void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData) +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, GPU& gpu) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - u8 val = texData[x + y * width]; + u8 val = gpu.ReadVRAMFlat_Texture(addr + x + y * width); u32 idx = val & ((1 << Y) - 1); - u16 color = palData[idx]; + u16 color = gpu.ReadVRAMFlat_TexPal(palAddr + idx * 2); u32 alpha = (val >> Y) & ((1 << X) - 1); if (X != 5) alpha = alpha * 4 + alpha / 2; @@ -228,22 +227,24 @@ void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* pa } } -template void ConvertAXIYTexture(u32, u32, u32*, u8*, u16*); -template void ConvertAXIYTexture(u32, u32, u32*, u8*, u16*); +template void ConvertAXIYTexture(u32, u32, u32*, u32, u32, GPU&); +template void ConvertAXIYTexture(u32, u32, u32*, u32, u32, GPU&); template -void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent) +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, bool color0Transparent, GPU& gpu) { for (int y = 0; y < height; y++) { - for (int x = 0; x < width / (8 / colorBits); x++) + for (int x = 0; x < width / (16 / colorBits); x++) { - u8 val = texData[x + y * (width / (8 / colorBits))]; + // smallest possible row is 8 pixels with 2bpp => fits in u16 + u16 val = gpu.ReadVRAMFlat_Texture(addr + 2 * (x + y * (width / (16 / colorBits)))); - for (int i = 0; i < 8 / colorBits; i++) + for (int i = 0; i < 16 / colorBits; i++) { - u32 index = (val >> (i * colorBits)) & ((1 << colorBits) - 1); - u16 color = palData[index]; + u32 index = val & ((1 << colorBits) - 1); + val >>= colorBits; + u16 color = gpu.ReadVRAMFlat_TexPal(palAddr + index * 2); bool transparent = color0Transparent && index == 0; u32 res; @@ -256,14 +257,14 @@ void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color) | (transparent ? 0 : 0xFF000000); break; } - output[x * (8 / colorBits) + y * width + i] = res; + output[x * (16 / colorBits) + y * width + i] = res; } } } } -template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); -template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); -template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); } \ No newline at end of file diff --git a/src/GPU3D_Texcache.h b/src/GPU3D_Texcache.h index 214c6254..f2cd6416 100644 --- a/src/GPU3D_Texcache.h +++ b/src/GPU3D_Texcache.h @@ -32,13 +32,13 @@ enum }; template -void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData); +void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu); template -void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData); +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u32 addr, u32 addrAux, u32 palAddr, GPU& gpu); template -void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData); +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, GPU& gpu); template -void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent); +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, bool color0Transparent, GPU& gpu); template class Texcache @@ -48,6 +48,50 @@ public: : TexLoader(texloader) // probably better if this would be a move constructor??? {} + u64 MaskedHash(u8* vram, u32 vramSize, u32 addr, u32 size) + { + u64 hash = 0; + + while (size > 0) + { + u32 pieceSize; + if (addr + size > vramSize) + // wraps around, only do the part inside + pieceSize = vramSize - addr; + else + // fits completely inside + pieceSize = size; + + hash = XXH64(&vram[addr], pieceSize, hash); + + addr += pieceSize; + addr &= (vramSize - 1); + assert(size >= pieceSize); + size -= pieceSize; + } + + return hash; + } + + bool CheckInvalid(u32 start, u32 size, u64 oldHash, u64* dirty, u8* vram, u32 vramSize) + { + u32 startBit = start / VRAMDirtyGranularity; + u32 bitsCount = ((start + size + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; + + u32 startEntry = startBit >> 6; + u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; + for (u32 j = startEntry; j < startEntry + entriesCount; j++) + { + if (GetRangedBitMask(j, startBit, bitsCount) & dirty[j & ((vramSize / VRAMDirtyGranularity)-1)]) + { + if (MaskedHash(vram, vramSize, start, size) != oldHash) + return true; + } + } + + return false; + } + bool Update(GPU& gpu) { auto textureDirty = gpu.VRAMDirty_Texture.DeriveState(gpu.VRAMMap_Texture, gpu); @@ -66,40 +110,21 @@ public: { for (u32 i = 0; i < 2; i++) { - u32 startBit = entry.TextureRAMStart[i] / VRAMDirtyGranularity; - u32 bitsCount = ((entry.TextureRAMStart[i] + entry.TextureRAMSize[i] + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; - - u32 startEntry = startBit >> 6; - u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; - for (u32 j = startEntry; j < startEntry + entriesCount; j++) - { - if (GetRangedBitMask(j, startBit, bitsCount) & textureDirty.Data[j]) - { - u64 newTexHash = XXH3_64bits(&gpu.VRAMFlat_Texture[entry.TextureRAMStart[i]], entry.TextureRAMSize[i]); - - if (newTexHash != entry.TextureHash[i]) - goto invalidate; - } - } + if (CheckInvalid(entry.TextureRAMStart[i], entry.TextureRAMSize[i], + entry.TextureHash[i], + textureDirty.Data, + gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture))) + goto invalidate; } } if (texPalChanged && entry.TexPalSize > 0) { - u32 startBit = entry.TexPalStart / VRAMDirtyGranularity; - u32 bitsCount = ((entry.TexPalStart + entry.TexPalSize + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; - - u32 startEntry = startBit >> 6; - u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; - for (u32 j = startEntry; j < startEntry + entriesCount; j++) - { - if (GetRangedBitMask(j, startBit, bitsCount) & texPalDirty.Data[j]) - { - u64 newPalHash = XXH3_64bits(&gpu.VRAMFlat_TexPal[entry.TexPalStart], entry.TexPalSize); - if (newPalHash != entry.TexPalHash) - goto invalidate; - } - } + if (CheckInvalid(entry.TexPalStart, entry.TexPalSize, + entry.TexPalHash, + texPalDirty.Data, + gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal))) + goto invalidate; } it++; @@ -163,17 +188,13 @@ public: { entry.TextureRAMSize[0] = width*height*2; - ConvertBitmapTexture(width, height, DecodingBuffer, &gpu.VRAMFlat_Texture[addr]); + ConvertBitmapTexture(width, height, DecodingBuffer, addr, gpu); } else if (fmt == 5) { - u8* texData = &gpu.VRAMFlat_Texture[addr]; u32 slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1); if (addr >= 0x40000) slot1addr += 0x10000; - u8* texAuxData = &gpu.VRAMFlat_Texture[slot1addr]; - - u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palBase*16); entry.TextureRAMSize[0] = width*height/16*4; entry.TextureRAMStart[1] = slot1addr; @@ -181,7 +202,7 @@ public: entry.TexPalStart = palBase*16; entry.TexPalSize = 0x10000; - ConvertCompressedTexture(width, height, DecodingBuffer, texData, texAuxData, palData); + ConvertCompressedTexture(width, height, DecodingBuffer, addr, slot1addr, entry.TexPalStart, gpu); } else { @@ -204,30 +225,29 @@ public: entry.TexPalStart = palAddr; entry.TexPalSize = numPalEntries*2; - u8* texData = &gpu.VRAMFlat_Texture[addr]; - u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palAddr); - //assert(entry.TexPalStart+entry.TexPalSize <= 128*1024*1024); bool color0Transparent = texParam & (1 << 29); switch (fmt) { - case 1: ConvertAXIYTexture(width, height, DecodingBuffer, texData, palData); break; - case 6: ConvertAXIYTexture(width, height, DecodingBuffer, texData, palData); break; - case 2: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; - case 3: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; - case 4: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; + case 1: ConvertAXIYTexture(width, height, DecodingBuffer, addr, palAddr, gpu); break; + case 6: ConvertAXIYTexture(width, height, DecodingBuffer, addr, palAddr, gpu); break; + case 2: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + case 3: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + case 4: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; } } for (int i = 0; i < 2; i++) { if (entry.TextureRAMSize[i]) - entry.TextureHash[i] = XXH3_64bits(&gpu.VRAMFlat_Texture[entry.TextureRAMStart[i]], entry.TextureRAMSize[i]); + entry.TextureHash[i] = MaskedHash(gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture), + entry.TextureRAMStart[i], entry.TextureRAMSize[i]); } if (entry.TexPalSize) - entry.TexPalHash = XXH3_64bits(&gpu.VRAMFlat_TexPal[entry.TexPalStart], entry.TexPalSize); + entry.TexPalHash = MaskedHash(gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal), + entry.TexPalStart, entry.TexPalSize); auto& texArrays = TexArrays[widthLog2][heightLog2]; auto& freeTextures = FreeTextures[widthLog2][heightLog2]; diff --git a/src/RTC.cpp b/src/RTC.cpp index fe262644..24e00de7 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -40,7 +40,7 @@ RTC::RTC(melonDS::NDS& nds) : NDS(nds) // indicate the power was off // this will be changed if a previously saved RTC state is loaded - State.StatusReg1 = 0x80; + State.StatusReg1 = 0x80 | (1<<1); } RTC::~RTC() @@ -943,4 +943,4 @@ void RTC::Write(u16 val, bool byte) IO = (IO & 0x0001) | (val & 0xFFFE); } -} \ No newline at end of file +} diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp index 14a8670a..b055794a 100644 --- a/src/debug/GdbStub.cpp +++ b/src/debug/GdbStub.cpp @@ -101,6 +101,15 @@ bool GdbStub::Init() Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n"); goto err; } + { + // Make sure the port can be reused immediately after melonDS stops and/or restarts + int enable = 1; +#ifdef _WIN32 + setsockopt(SockFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(enable)); +#else + setsockopt(SockFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#endif + } #ifndef __linux__ SocketSetBlocking(SockFd, false); #endif diff --git a/src/frontend/qt_sdl/AboutDialog.cpp b/src/frontend/qt_sdl/AboutDialog.cpp new file mode 100644 index 00000000..22022de9 --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.cpp @@ -0,0 +1,59 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "AboutDialog.h" + +#include +#include + +#include "ui_AboutDialog.h" + +#include "version.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + + ui->lblVersionInfo->setText("Version " MELONDS_VERSION); +#ifdef MELONDS_EMBED_BUILD_INFO + ui->lblBuildInfo->setText( + "Branch: " MELONDS_GIT_BRANCH "\n" + "Commit: " MELONDS_GIT_HASH "\n" + "Built by: " MELONDS_BUILD_PROVIDER + ); +#else + ui->lblBuildInfo->hide(); +#endif + adjustSize(); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} + +void AboutDialog::openWebsite() +{ + QDesktopServices::openUrl(QUrl(MELONDS_URL)); +} + +void AboutDialog::openGitHub() +{ + QDesktopServices::openUrl(QUrl("https://github.com/melonDS-emu/melonDS"));; +} diff --git a/src/frontend/qt_sdl/AboutDialog.h b/src/frontend/qt_sdl/AboutDialog.h new file mode 100644 index 00000000..eb328f5b --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.h @@ -0,0 +1,50 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MELONDS_ABOUTDIALOG_H +#define MELONDS_ABOUTDIALOG_H + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class AboutDialog; +} +QT_END_NAMESPACE + +class AboutDialog : public QDialog +{ +Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = nullptr); + + ~AboutDialog() override; + +private slots: + static void openWebsite(); + static void openGitHub(); + +private: + Ui::AboutDialog *ui; +}; + + +#endif //MELONDS_ABOUTDIALOG_H diff --git a/src/frontend/qt_sdl/AboutDialog.ui b/src/frontend/qt_sdl/AboutDialog.ui new file mode 100644 index 00000000..89ca7a36 --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.ui @@ -0,0 +1,300 @@ + + + AboutDialog + + + + 0 + 0 + 600 + 304 + + + + + 0 + 0 + + + + + 600 + 0 + + + + About melonDS + + + false + + + + 0 + + + QLayout::SizeConstraint::SetFixedSize + + + 12 + + + + + 24 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + + + true + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + + + + Qt::TextFormat::PlainText + + + :/melon-icon + + + true + + + Qt::AlignmentFlag::AlignCenter + + + 0 + + + 0 + + + + + + + 12 + + + + + true + + + + 32 + + + + melonDS + + + + + + + true + + + [VERSION INFO PLACEHOLDER] + + + Qt::TextFormat::MarkdownText + + + + + + + true + + + By Arisotura, the melonDS team <a href="https://github.com/melonDS-emu/melonDS/graphs/contributors">and contributors</a>. + + + + + + + true + + + [EMBEDDED BUILD INFO PLACEHOLDER] + + + Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByMouse + + + + + + + false + + + + 10 + + + + <html><head/><body><p>Licensed under the GNU General Public License v3 or any newer version.</p><p>melonDS is intended only for use with software that you own.</p><p>The Nintendo DS and Nintendo DSi systems are the property of Nintendo.<br />melonDS and the melonDS team are not affiliated with or endorsed by Nintendo.</p></body></html> + + + Qt::TextFormat::RichText + + + false + + + + + + + + + + + 12 + + + 12 + + + + + true + + + Qt::Orientation::Horizontal + + + + 40 + 0 + + + + + + + + true + + + Visit the &website + + + true + + + + + + + true + + + View on &GitHub + + + true + + + + + + + true + + + &Close + + + true + + + + + + + + + + + + + btnClose + clicked() + AboutDialog + accept() + + + 586 + 261 + + + 294 + 154 + + + + + btnOpenGitHub + clicked() + AboutDialog + openGitHub() + + + 449 + 243 + + + 299 + 139 + + + + + btnOpenWebsite + clicked() + AboutDialog + openWebsite() + + + 345 + 245 + + + 96 + 275 + + + + + + openWebsite() + openGitHub() + + diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index 37f856ac..5f2aef19 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -41,7 +41,7 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui( emuInstance = ((MainWindow*)parent)->getEmuInstance(); auto& cfg = emuInstance->getGlobalConfig(); auto& instcfg = emuInstance->getLocalConfig(); - bool emuActive = emuInstance->getEmuThread()->emuIsActive(); + bool emuActive = emuInstance->emuIsActive(); oldInterp = cfg.GetInt("Audio.Interpolation"); oldBitDepth = cfg.GetInt("Audio.BitDepth"); @@ -170,6 +170,12 @@ void AudioSettingsDialog::on_AudioSettingsDialog_accepted() void AudioSettingsDialog::on_AudioSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + auto& cfg = emuInstance->getGlobalConfig(); auto& instcfg = emuInstance->getLocalConfig(); cfg.SetInt("Audio.Interpolation", oldInterp); diff --git a/src/frontend/qt_sdl/CLI.cpp b/src/frontend/qt_sdl/CLI.cpp index 299ce65b..5e352cae 100644 --- a/src/frontend/qt_sdl/CLI.cpp +++ b/src/frontend/qt_sdl/CLI.cpp @@ -96,7 +96,7 @@ CommandLineOptions* ManageArgs(QApplication& melon) } else { - options->errorsToDisplay += "Option -a/--archive-file given, but no archive specified!"; + Log(LogLevel::Error, "Option -a/--archive-file given, but no archive specified!"); } } @@ -108,7 +108,7 @@ CommandLineOptions* ManageArgs(QApplication& melon) } else { - options->errorsToDisplay += "Option -A/--archive-file-gba given, but no archive specified!"; + Log(LogLevel::Error, "Option -A/--archive-file-gba given, but no archive specified!"); } } #endif diff --git a/src/frontend/qt_sdl/CLI.h b/src/frontend/qt_sdl/CLI.h index 4997e6a7..beb120bf 100644 --- a/src/frontend/qt_sdl/CLI.h +++ b/src/frontend/qt_sdl/CLI.h @@ -28,8 +28,6 @@ namespace CLI { struct CommandLineOptions { - QStringList errorsToDisplay = {}; - std::optional dsRomPath; std::optional dsRomArchivePath; std::optional gbaRomPath; diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 524fa13d..54888c49 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -37,6 +37,9 @@ set(SOURCES_QT_SDL QPathInput.h SaveManager.cpp CameraManager.cpp + AboutDialog.cpp + AboutDialog.h + AboutDialog.ui ArchiveUtil.h ArchiveUtil.cpp @@ -56,11 +59,7 @@ set(SOURCES_QT_SDL NetplayDialog.cpp ) -if (APPLE) - option(USE_QT6 "Build using Qt 6 instead of 5" ON) -else() - option(USE_QT6 "Build using Qt 6 instead of 5" OFF) -endif() +option(USE_QT6 "Use Qt 6 instead of Qt 5" ON) if (USE_QT6) find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets Svg REQUIRED) @@ -119,6 +118,10 @@ elseif (APPLE) target_sources(melonDS PRIVATE ../duckstation/gl/context_agl.mm ) + set_source_files_properties( + ../duckstation/gl/context_agl.mm + PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations" + ) else() find_package(X11 REQUIRED) find_package(EGL REQUIRED) diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp index 39c05cef..63b7a76e 100644 --- a/src/frontend/qt_sdl/CameraSettingsDialog.cpp +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -163,6 +163,12 @@ void CameraSettingsDialog::on_CameraSettingsDialog_accepted() void CameraSettingsDialog::on_CameraSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + for (int i = 0; i < 2; i++) { camManager[i]->stop(); diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 0a161b6f..02be5b65 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -80,7 +80,7 @@ RangeList IntRanges = {"3D.Renderer", {0, renderer3D_Max-1}}, {"Screen.VSyncInterval", {1, 20}}, {"3D.GL.ScaleFactor", {1, 16}}, - {"Audio.Interpolation", {0, 3}}, + {"Audio.Interpolation", {0, 4}}, {"Instance*.Audio.Volume", {0, 256}}, {"Mic.InputType", {0, micInputType_MAX-1}}, {"Instance*.Window*.ScreenRotation", {0, screenRot_MAX-1}}, @@ -99,7 +99,7 @@ DefaultList DefaultBools = {"3D.Soft.Threaded", true}, {"3D.GL.HiresCoordinates", true}, {"LimitFPS", true}, - {"Window*.ShowOSD", true}, + {"Instance*.Window*.ShowOSD", true}, {"Emu.DirectBoot", true}, {"Instance*.DS.Battery.LevelOkay", true}, {"Instance*.DSi.Battery.Charging", true}, diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp index 54bae531..2ac3f42d 100644 --- a/src/frontend/qt_sdl/EmuInstance.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -93,25 +93,25 @@ EmuInstance::EmuInstance(int inst) : deleting(false), if (val == 0.0) { Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n"); - targetFPS = 1.0 / 60.0; + targetFPS = 60.0; } - else targetFPS = 1.0 / val; + else targetFPS = val; val = globalCfg.GetDouble("FastForwardFPS"); if (val == 0.0) { Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n"); - fastForwardFPS = 1.0 / 60.0; + fastForwardFPS = 60.0; } - else fastForwardFPS = 1.0 / val; + else fastForwardFPS = val; val = globalCfg.GetDouble("SlowmoFPS"); if (val == 0.0) { Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n"); - slowmoFPS = 1.0 / 60.0; + slowmoFPS = 60.0; } - else slowmoFPS = 1.0 / val; + else slowmoFPS = val; doAudioSync = globalCfg.GetBool("AudioSync"); @@ -136,7 +136,15 @@ EmuInstance::EmuInstance(int inst) : deleting(false), createWindow(); emuThread->start(); - emuThread->emuPause(); + + // if any extra windows were saved as enabled, open them + for (int i = 1; i < kMaxWindows; i++) + { + std::string key = "Window" + std::to_string(i) + ".Enabled"; + bool enable = localCfg.GetBool(key); + if (enable) + createWindow(i); + } } EmuInstance::~EmuInstance() @@ -144,8 +152,6 @@ EmuInstance::~EmuInstance() deleting = true; deleteAllWindows(); - MPInterface::Get().End(instanceID); - emuThread->emuExit(); emuThread->wait(); delete emuThread; @@ -154,6 +160,13 @@ EmuInstance::~EmuInstance() audioDeInit(); inputDeInit(); + + NDS::Current = nullptr; + if (nds) + { + saveRTCData(); + delete nds; + } } @@ -166,7 +179,7 @@ std::string EmuInstance::instanceFileSuffix() return suffix; } -void EmuInstance::createWindow() +void EmuInstance::createWindow(int id) { if (numWindows >= kMaxWindows) { @@ -174,16 +187,20 @@ void EmuInstance::createWindow() return; } - int id = -1; - for (int i = 0; i < kMaxWindows; i++) + if (id == -1) { - if (windowList[i]) continue; - id = i; - break; + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) continue; + id = i; + break; + } } if (id == -1) return; + if (windowList[id]) + return; MainWindow* win = new MainWindow(id, this, topWindow); if (!topWindow) topWindow = win; @@ -192,6 +209,16 @@ void EmuInstance::createWindow() numWindows++; emuThread->attachWindow(win); + + // if creating a secondary window, we may need to initialize its OpenGL context here + if (win->hasOpenGL() && (id != 0)) + emuThread->initContext(id); + + bool enable = (numWindows < kMaxWindows); + doOnAllWindows([=](MainWindow* win) + { + win->actNewWindow->setEnabled(enable); + }); } void EmuInstance::deleteWindow(int id, bool close) @@ -201,12 +228,8 @@ void EmuInstance::deleteWindow(int id, bool close) MainWindow* win = windowList[id]; if (!win) return; - if (win->hasOpenGL() && win == mainWindow) - { - // we intentionally don't unpause here - emuThread->emuPause(); - emuThread->deinitContext(); - } + if (win->hasOpenGL()) + emuThread->deinitContext(id); emuThread->detachWindow(win); @@ -219,11 +242,20 @@ void EmuInstance::deleteWindow(int id, bool close) if (close) win->close(); - if ((!mainWindow) && (!deleting)) + if (numWindows == 0) { - // if we closed this instance's main window, delete the instance + // if we closed the last window, delete the instance + // if the main window is closed, Qt will take care of closing any secondary windows deleteEmuInstance(instanceID); } + else + { + bool enable = (numWindows < kMaxWindows); + doOnAllWindows([=](MainWindow* win) + { + win->actNewWindow->setEnabled(enable); + }); + } } void EmuInstance::deleteAllWindows() @@ -232,6 +264,57 @@ void EmuInstance::deleteAllWindows() deleteWindow(i, true); } +void EmuInstance::doOnAllWindows(std::function func, int exclude) +{ + for (int i = 0; i < kMaxWindows; i++) + { + if (i == exclude) continue; + if (!windowList[i]) continue; + + func(windowList[i]); + } +} + +void EmuInstance::saveEnabledWindows() +{ + doOnAllWindows([=](MainWindow* win) + { + win->saveEnabled(true); + }); +} + + +void EmuInstance::broadcastCommand(int cmd, QVariant param) +{ + broadcastInstanceCommand(cmd, param, instanceID); +} + +void EmuInstance::handleCommand(int cmd, QVariant& param) +{ + switch (cmd) + { + case InstCmd_Pause: + emuThread->emuPause(false); + break; + + case InstCmd_Unpause: + emuThread->emuUnpause(false); + break; + + case InstCmd_UpdateRecentFiles: + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->loadRecentFilesMenu(true); + } + break; + + /*case InstCmd_UpdateVideoSettings: + mainWindow->updateVideoSettings(param.value()); + break;*/ + } +} + void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...) { @@ -285,24 +368,18 @@ bool EmuInstance::usesOpenGL() (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); } -void EmuInstance::initOpenGL() +void EmuInstance::initOpenGL(int win) { - for (int i = 0; i < kMaxWindows; i++) - { - if (windowList[i]) - windowList[i]->initOpenGL(); - } + if (windowList[win]) + windowList[win]->initOpenGL(); setVSyncGL(true); } -void EmuInstance::deinitOpenGL() +void EmuInstance::deinitOpenGL(int win) { - for (int i = 0; i < kMaxWindows; i++) - { - if (windowList[i]) - windowList[i]->deinitOpenGL(); - } + if (windowList[win]) + windowList[win]->deinitOpenGL(); } void EmuInstance::setVSyncGL(bool vsync) @@ -1080,7 +1157,7 @@ std::optional EmuInstance::loadSDCard(const string& key) noexcept void EmuInstance::enableCheats(bool enable) { cheatsOn = enable; - if (cheatFile) + if (cheatsOn && cheatFile) nds->AREngine.Cheats = cheatFile->GetCodes(); else nds->AREngine.Cheats.clear(); @@ -1105,6 +1182,30 @@ void EmuInstance::setBatteryLevels() } } +void EmuInstance::loadRTCData() +{ + auto file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); + if (file) + { + RTC::StateData state; + Platform::FileRead(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + nds->RTC.SetState(state); + } +} + +void EmuInstance::saveRTCData() +{ + auto file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); + if (file) + { + RTC::StateData state; + nds->RTC.GetState(state); + Platform::FileWrite(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + } +} + void EmuInstance::setDateTime() { QDateTime hosttime = QDateTime::currentDateTime(); @@ -1116,6 +1217,9 @@ void EmuInstance::setDateTime() bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGBAArgs&& _gbaargs) noexcept { + // update the console type + consoleType = globalCfg.GetInt("Emu.ConsoleType"); + // Let's get the cart we want to use; // if we wnat to keep the cart, we'll eject it from the existing console first. std::unique_ptr nextndscart; @@ -1149,8 +1253,6 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB } - int consoletype = globalCfg.GetInt("Emu.ConsoleType"); - auto arm9bios = loadARM9BIOS(); if (!arm9bios) return false; @@ -1159,7 +1261,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB if (!arm7bios) return false; - auto firmware = loadFirmware(consoletype); + auto firmware = loadFirmware(consoleType); if (!firmware) return false; @@ -1203,7 +1305,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB NDSArgs* args = &ndsargs; std::optional dsiargs = std::nullopt; - if (consoletype == 1) + if (consoleType == 1) { ndsargs.GBAROM = nullptr; @@ -1234,19 +1336,25 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB args = &(*dsiargs); } - - if ((!nds) || (consoletype != nds->ConsoleType)) + renderLock.lock(); + if ((!nds) || (consoleType != nds->ConsoleType)) { NDS::Current = nullptr; - if (nds) delete nds; + if (nds) + { + saveRTCData(); + delete nds; + } - if (consoletype == 1) + if (consoleType == 1) nds = new DSi(std::move(dsiargs.value()), this); else nds = new NDS(std::move(ndsargs), this); NDS::Current = nds; nds->Reset(); + loadRTCData(); + //emuThread->updateVideoRenderer(); // not actually needed? } else { @@ -1260,7 +1368,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB nds->SPU.SetInterpolation(args->Interpolation); nds->SPU.SetDegrade10Bit(args->BitDepth); - if (consoletype == 1) + if (consoleType == 1) { DSi* dsi = (DSi*)nds; DSiArgs& _dsiargs = *dsiargs; @@ -1276,14 +1384,13 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB dsi->EjectGBACart(); } } + renderLock.unlock(); return true; } void EmuInstance::reset() { - consoleType = globalCfg.GetInt("Emu.ConsoleType"); - updateConsole(Keep {}, Keep {}); if (consoleType == 1) ejectGBACart(); @@ -1973,25 +2080,36 @@ bool EmuInstance::gbaCartInserted() return gbaCartType != -1; } +QString EmuInstance::gbaAddonName(int addon) +{ + switch (addon) + { + case GBAAddon_RumblePak: + return "Rumble Pak"; + case GBAAddon_RAMExpansion: + return "Memory expansion"; + } + + return "???"; +} + QString EmuInstance::gbaCartLabel() { if (consoleType == 1) return "none (DSi)"; - switch (gbaCartType) + if (gbaCartType == 0) { - case 0: - { - QString ret = QString::fromStdString(baseGBAROMName); + QString ret = QString::fromStdString(baseGBAROMName); - int maxlen = 32; - if (ret.length() > maxlen) - ret = ret.left(maxlen-6) + "..." + ret.right(3); + int maxlen = 32; + if (ret.length() > maxlen) + ret = ret.left(maxlen-6) + "..." + ret.right(3); - return ret; - } - - case GBAAddon_RAMExpansion: - return "Memory expansion"; + return ret; + } + else if (gbaCartType != -1) + { + return gbaAddonName(gbaCartType); } return "(none)"; diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index 04290f56..a135a52c 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -21,13 +21,14 @@ #include +#include "main.h" #include "NDS.h" #include "EmuThread.h" #include "Window.h" #include "Config.h" #include "SaveManager.h" -const int kMaxWindows = 16; +const int kMaxWindows = 4; enum { @@ -86,14 +87,21 @@ public: melonDS::NDS* getNDS() { return nds; } MainWindow* getMainWindow() { return mainWindow; } + int getNumWindows() { return numWindows; } MainWindow* getWindow(int id) { return windowList[id]; } + void doOnAllWindows(std::function func, int exclude = -1); + void saveEnabledWindows(); + Config::Table& getGlobalConfig() { return globalCfg; } Config::Table& getLocalConfig() { return localCfg; } + void broadcastCommand(int cmd, QVariant param = QVariant()); + void handleCommand(int cmd, QVariant& param); + std::string instanceFileSuffix(); - void createWindow(); + void createWindow(int id = -1); void deleteWindow(int id, bool close); void deleteAllWindows(); @@ -103,8 +111,8 @@ public: void emuStop(melonDS::Platform::StopReason reason); bool usesOpenGL(); - void initOpenGL(); - void deinitOpenGL(); + void initOpenGL(int win); + void deinitOpenGL(int win); void setVSyncGL(bool vsync); void makeCurrentGL(); void drawScreenGL(); @@ -139,6 +147,11 @@ public: int getJoystickID() { return joystickID; } SDL_Joystick* getJoystick() { return joystick; } + void touchScreen(int x, int y); + void releaseScreen(); + + QMutex renderLock; + private: static int lastSep(const std::string& path); std::string getAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file); @@ -168,7 +181,6 @@ private: std::optional getSDCardArgs(const std::string& key) noexcept; std::optional loadSDCard(const std::string& key) noexcept; void setBatteryLevels(); - void setDateTime(); void reset(); bool bootToMenu(); melonDS::u32 decompressROM(const melonDS::u8* inContent, const melonDS::u32 inSize, std::unique_ptr& outContent); @@ -186,6 +198,7 @@ private: void loadGBAAddon(int type); void ejectGBACart(); bool gbaCartInserted(); + QString gbaAddonName(int addon); QString gbaCartLabel(); void audioInit(); @@ -222,6 +235,10 @@ private: bool hotkeyPressed(int id) { return hotkeyPress & (1<micLock); int maxlen = sizeof(micExtBuffer) / sizeof(s16); + if ((inst->micExtBufferCount + len) > maxlen) + len = maxlen - inst->micExtBufferCount; + if ((inst->micExtBufferWritePos + len) > maxlen) { u32 len1 = maxlen - inst->micExtBufferWritePos; @@ -121,11 +125,15 @@ void EmuInstance::micCallback(void* data, Uint8* stream, int len) memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], input, len*sizeof(s16)); inst->micExtBufferWritePos += len; } + + inst->micExtBufferCount += len; + SDL_UnlockMutex(inst->micLock); } void EmuInstance::audioMute() { audioMuted = false; + if (numEmuInstances() < 2) return; switch (mpAudioMode) { @@ -134,10 +142,16 @@ void EmuInstance::audioMute() break; case 2: // only currently focused instance - //if (mainWindow != nullptr) - // audioMuted = !mainWindow->isActiveWindow(); - // TODO!! - printf("TODO!! audioMute mode 2\n"); + audioMuted = true; + for (int i = 0; i < kMaxWindows; i++) + { + if (!windowList[i]) continue; + if (windowList[i]->isFocused()) + { + audioMuted = false; + break; + } + } break; } } @@ -270,6 +284,8 @@ void EmuInstance::micLoadWav(const std::string& name) void EmuInstance::micProcess() { + SDL_LockMutex(micLock); + int type = micInputType; bool cmd = hotkeyDown(HK_Mic); @@ -278,6 +294,8 @@ void EmuInstance::micProcess() type = micInputType_Silence; } + const int kFrameLen = 735; + switch (type) { case micInputType_Silence: // no mic @@ -289,21 +307,35 @@ void EmuInstance::micProcess() case micInputType_Wav: // WAV if (micBuffer) { - if ((micBufferReadPos + 735) > micBufferLength) - { - s16 tmp[735]; - u32 len1 = micBufferLength - micBufferReadPos; - memcpy(&tmp[0], &micBuffer[micBufferReadPos], len1*sizeof(s16)); - memcpy(&tmp[len1], &micBuffer[0], (735 - len1)*sizeof(s16)); + int len = kFrameLen; + if (micExtBufferCount < len) + len = micExtBufferCount; - nds->MicInputFrame(tmp, 735); - micBufferReadPos = 735 - len1; + s16 tmp[kFrameLen]; + + if ((micBufferReadPos + len) > micBufferLength) + { + u32 part1 = micBufferLength - micBufferReadPos; + memcpy(&tmp[0], &micBuffer[micBufferReadPos], part1*sizeof(s16)); + memcpy(&tmp[part1], &micBuffer[0], (len - part1)*sizeof(s16)); + + micBufferReadPos = len - part1; } else { - nds->MicInputFrame(&micBuffer[micBufferReadPos], 735); - micBufferReadPos += 735; + memcpy(&tmp[0], &micBuffer[micBufferReadPos], len*sizeof(s16)); + + micBufferReadPos += len; } + + if (len < kFrameLen) + { + for (int i = len; i < kFrameLen; i++) + tmp[i] = tmp[len-1]; + } + nds->MicInputFrame(tmp, 735); + + micExtBufferCount -= len; } else { @@ -317,19 +349,21 @@ void EmuInstance::micProcess() int sample_len = sizeof(mic_blow) / sizeof(u16); static int sample_pos = 0; - s16 tmp[735]; + s16 tmp[kFrameLen]; - for (int i = 0; i < 735; i++) + for (int i = 0; i < kFrameLen; i++) { - tmp[i] = mic_blow[sample_pos]; + tmp[i] = mic_blow[sample_pos] ^ 0x8000; sample_pos++; if (sample_pos >= sample_len) sample_pos = 0; } - nds->MicInputFrame(tmp, 735); + nds->MicInputFrame(tmp, kFrameLen); } break; } + + SDL_UnlockMutex(micLock); } void EmuInstance::setupMicInputData() @@ -402,12 +436,15 @@ void EmuInstance::audioInit() memset(micExtBuffer, 0, sizeof(micExtBuffer)); micExtBufferWritePos = 0; + micExtBufferCount = 0; micWavBuffer = nullptr; micBuffer = nullptr; micBufferLength = 0; micBufferReadPos = 0; + micLock = SDL_CreateMutex(); + setupMicInputData(); } @@ -425,6 +462,9 @@ void EmuInstance::audioDeInit() if (micWavBuffer) delete[] micWavBuffer; micWavBuffer = nullptr; + + if (micLock) SDL_DestroyMutex(micLock); + micLock = nullptr; } void EmuInstance::audioSync() diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index bb06c242..aa1c529f 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -74,6 +74,10 @@ void EmuInstance::inputInit() hotkeyMask = 0; lastHotkeyMask = 0; + isTouching = false; + touchX = 0; + touchY = 0; + joystick = nullptr; controller = nullptr; hasRumble = false; @@ -353,3 +357,15 @@ void EmuInstance::inputProcess() hotkeyRelease = lastHotkeyMask & ~hotkeyMask; lastHotkeyMask = hotkeyMask; } + +void EmuInstance::touchScreen(int x, int y) +{ + touchX = x; + touchY = y; + isTouching = true; +} + +void EmuInstance::releaseScreen() +{ + isTouching = false; +} diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index 7a6c0f40..b37f7118 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -215,6 +215,13 @@ void EmuSettingsDialog::verifyFirmware() void EmuSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index 4ce4efda..f767c6db 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -70,41 +70,46 @@ EmuThread::EmuThread(EmuInstance* inst, QObject* parent) : QThread(parent) void EmuThread::attachWindow(MainWindow* window) { - connect(this, SIGNAL(windowUpdate()), window->panel, SLOT(repaint())); connect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); connect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); connect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); connect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); connect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); - connect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); connect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); connect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); connect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); + + if (window->winHasMenu()) + { + connect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); + } } void EmuThread::detachWindow(MainWindow* window) { - disconnect(this, SIGNAL(windowUpdate()), window->panel, SLOT(repaint())); disconnect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); disconnect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); disconnect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); disconnect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); disconnect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); - disconnect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); disconnect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); disconnect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); - disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); disconnect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); + + if (window->winHasMenu()) + { + disconnect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); + } } void EmuThread::run() { Config::Table& globalCfg = emuInstance->getGlobalConfig(); u32 mainScreenPos[3]; - Platform::FileHandle* file; - emuInstance->updateConsole(nullptr, nullptr); + //emuInstance->updateConsole(nullptr, nullptr); // No carts are inserted when melonDS first boots mainScreenPos[0] = 0; @@ -112,11 +117,11 @@ void EmuThread::run() mainScreenPos[2] = 0; autoScreenSizing = 0; - videoSettingsDirty = false; + //videoSettingsDirty = false; if (emuInstance->usesOpenGL()) { - emuInstance->initOpenGL(); + emuInstance->initOpenGL(0); useOpenGL = true; videoRenderer = globalCfg.GetInt("3D.Renderer"); @@ -127,7 +132,8 @@ void EmuThread::run() videoRenderer = 0; } - updateRenderer(); + //updateRenderer(); + videoSettingsDirty = true; u32 nframes = 0; double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); @@ -138,15 +144,6 @@ void EmuThread::run() u32 winUpdateCount = 0, winUpdateFreq = 1; u8 dsiVolumeLevel = 0x1F; - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); - if (file) - { - RTC::StateData state; - Platform::FileRead(&state, sizeof(state), 1, file); - Platform::CloseFile(file); - emuInstance->nds->RTC.SetState(state); - } - char melontitle[100]; bool fastforward = false; @@ -234,6 +231,7 @@ void EmuThread::run() // update render settings if needed if (videoSettingsDirty) { + emuInstance->renderLock.lock(); if (useOpenGL) { emuInstance->setVSyncGL(true); @@ -249,11 +247,17 @@ void EmuThread::run() updateRenderer(); videoSettingsDirty = false; + emuInstance->renderLock.unlock(); } // process input and hotkeys emuInstance->nds->SetKeyMask(emuInstance->inputMask); + if (emuInstance->isTouching) + emuInstance->nds->TouchScreen(emuInstance->touchX, emuInstance->touchY); + else + emuInstance->nds->ReleaseScreen(); + if (emuInstance->hotkeyPressed(HK_Lid)) { bool lid = !emuInstance->nds->IsLidClosed(); @@ -317,13 +321,13 @@ void EmuThread::run() if (!useOpenGL) { - FrontBufferLock.lock(); - FrontBuffer = emuInstance->nds->GPU.FrontBuffer; - FrontBufferLock.unlock(); + frontBufferLock.lock(); + frontBuffer = emuInstance->nds->GPU.FrontBuffer; + frontBufferLock.unlock(); } else { - FrontBuffer = emuInstance->nds->GPU.FrontBuffer; + frontBuffer = emuInstance->nds->GPU.FrontBuffer; emuInstance->drawScreenGL(); } @@ -362,7 +366,7 @@ void EmuThread::run() if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS; else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS; - else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0; + else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1000.0; else emuInstance->curFPS = emuInstance->targetFPS; if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1) @@ -381,16 +385,18 @@ void EmuThread::run() if (emuInstance->doAudioSync && !(fastforward || slowmo)) emuInstance->audioSync(); - double frametimeStep = nlines / (60.0 * 263.0); + double frametimeStep = nlines / (emuInstance->curFPS * 263.0); + + if (frametimeStep < 0.001) frametimeStep = 0.001; { double curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError += emuInstance->curFPS - (curtime - lastTime); - if (frameLimitError < -emuInstance->curFPS) - frameLimitError = -emuInstance->curFPS; - if (frameLimitError > emuInstance->curFPS) - frameLimitError = emuInstance->curFPS; + frameLimitError += frametimeStep - (curtime - lastTime); + if (frameLimitError < -frametimeStep) + frameLimitError = -frametimeStep; + if (frameLimitError > frametimeStep) + frameLimitError = frametimeStep; if (round(frameLimitError * 1000.0) > 0.0) { @@ -418,10 +424,11 @@ void EmuThread::run() winUpdateFreq = fps / (u32)round(fpstarget); if (winUpdateFreq < 1) winUpdateFreq = 1; - + + double actualfps = (59.8261 * 263.0) / nlines; int inst = emuInstance->instanceID; if (inst == 0) - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, actualfps); else sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); @@ -453,17 +460,6 @@ void EmuThread::run() handleMessages(); } - - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); - if (file) - { - RTC::StateData state; - emuInstance->nds->RTC.GetState(state); - Platform::FileWrite(&state, sizeof(state), 1, file); - Platform::CloseFile(file); - } - - NDS::Current = nullptr; } void EmuThread::sendMessage(Message msg) @@ -482,7 +478,8 @@ void EmuThread::waitMessage(int num) void EmuThread::waitAllMessages() { if (QThread::currentThread() == this) return; - msgSemaphore.acquire(msgSemaphore.available()); + while (!msgQueue.empty()) + msgSemaphore.acquire(); } void EmuThread::handleMessages() @@ -498,6 +495,7 @@ void EmuThread::handleMessages() emuPauseStack = emuPauseStackRunning; emuInstance->audioDisable(); + MPInterface::Get().End(emuInstance->instanceID); break; case msg_EmuRun: @@ -541,7 +539,8 @@ void EmuThread::handleMessages() break; case msg_EmuStop: - if (msg.stopExternal) emuInstance->nds->Stop(); + if (msg.param.value()) + emuInstance->nds->Stop(); emuStatus = emuStatus_Paused; emuActive = false; @@ -566,13 +565,101 @@ void EmuThread::handleMessages() break; case msg_InitGL: - emuInstance->initOpenGL(); + emuInstance->initOpenGL(msg.param.value()); useOpenGL = true; break; case msg_DeInitGL: - emuInstance->deinitOpenGL(); - useOpenGL = false; + emuInstance->deinitOpenGL(msg.param.value()); + if (msg.param.value() == 0) + useOpenGL = false; + break; + + case msg_BootROM: + msgResult = 0; + if (!emuInstance->loadROM(msg.param.value(), true)) + break; + + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); + msgResult = 1; + break; + + case msg_BootFirmware: + msgResult = 0; + if (!emuInstance->bootToMenu()) + break; + + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); + msgResult = 1; + break; + + case msg_InsertCart: + msgResult = 0; + if (!emuInstance->loadROM(msg.param.value(), false)) + break; + + msgResult = 1; + break; + + case msg_EjectCart: + emuInstance->ejectCart(); + break; + + case msg_InsertGBACart: + msgResult = 0; + if (!emuInstance->loadGBAROM(msg.param.value())) + break; + + msgResult = 1; + break; + + case msg_InsertGBAAddon: + msgResult = 0; + emuInstance->loadGBAAddon(msg.param.value()); + msgResult = 1; + break; + + case msg_EjectGBACart: + emuInstance->ejectGBACart(); + break; + + case msg_SaveState: + msgResult = emuInstance->saveState(msg.param.value().toStdString()); + break; + + case msg_LoadState: + msgResult = emuInstance->loadState(msg.param.value().toStdString()); + break; + + case msg_UndoStateLoad: + emuInstance->undoStateLoad(); + msgResult = 1; + break; + + case msg_ImportSavefile: + { + msgResult = 0; + auto f = Platform::OpenFile(msg.param.value().toStdString(), Platform::FileMode::Read); + if (!f) break; + + u32 len = FileLength(f); + + std::unique_ptr data = std::make_unique(len); + Platform::FileRewind(f); + Platform::FileRead(data.get(), len, 1, f); + + assert(emuInstance->nds != nullptr); + emuInstance->nds->SetNDSSave(data.get(), len); + + CloseFile(f); + msgResult = 1; + } + break; + + case msg_EnableCheats: + emuInstance->enableCheats(msg.param.value()); break; } @@ -586,15 +673,15 @@ void EmuThread::changeWindowTitle(char* title) emit windowTitleChange(QString(title)); } -void EmuThread::initContext() +void EmuThread::initContext(int win) { - sendMessage(msg_InitGL); + sendMessage({.type = msg_InitGL, .param = win}); waitMessage(); } -void EmuThread::deinitContext() +void EmuThread::deinitContext(int win) { - sendMessage(msg_DeInitGL); + sendMessage({.type = msg_DeInitGL, .param = win}); waitMessage(); } @@ -604,29 +691,35 @@ void EmuThread::emuRun() waitMessage(); } -void EmuThread::emuPause() +void EmuThread::emuPause(bool broadcast) { sendMessage(msg_EmuPause); waitMessage(); + + if (broadcast) + emuInstance->broadcastCommand(InstCmd_Pause); } -void EmuThread::emuUnpause() +void EmuThread::emuUnpause(bool broadcast) { sendMessage(msg_EmuUnpause); waitMessage(); + + if (broadcast) + emuInstance->broadcastCommand(InstCmd_Unpause); } -void EmuThread::emuTogglePause() +void EmuThread::emuTogglePause(bool broadcast) { if (emuStatus == emuStatus_Paused) - emuUnpause(); + emuUnpause(broadcast); else - emuPause(); + emuPause(broadcast); } void EmuThread::emuStop(bool external) { - sendMessage({.type = msg_EmuStop, .stopExternal = external}); + sendMessage({.type = msg_EmuStop, .param = external}); waitMessage(); } @@ -660,11 +753,91 @@ bool EmuThread::emuIsActive() return emuActive; } +int EmuThread::bootROM(const QStringList& filename) +{ + sendMessage({.type = msg_BootROM, .param = filename}); + waitMessage(); + if (!msgResult) + return msgResult; + + sendMessage(msg_EmuRun); + waitMessage(); + return msgResult; +} + +int EmuThread::bootFirmware() +{ + sendMessage(msg_BootFirmware); + waitMessage(); + if (!msgResult) + return msgResult; + + sendMessage(msg_EmuRun); + waitMessage(); + return msgResult; +} + +int EmuThread::insertCart(const QStringList& filename, bool gba) +{ + MessageType msgtype = gba ? msg_InsertGBACart : msg_InsertCart; + + sendMessage({.type = msgtype, .param = filename}); + waitMessage(); + return msgResult; +} + +void EmuThread::ejectCart(bool gba) +{ + sendMessage(gba ? msg_EjectGBACart : msg_EjectCart); + waitMessage(); +} + +int EmuThread::insertGBAAddon(int type) +{ + sendMessage({.type = msg_InsertGBAAddon, .param = type}); + waitMessage(); + return msgResult; +} + +int EmuThread::saveState(const QString& filename) +{ + sendMessage({.type = msg_SaveState, .param = filename}); + waitMessage(); + return msgResult; +} + +int EmuThread::loadState(const QString& filename) +{ + sendMessage({.type = msg_LoadState, .param = filename}); + waitMessage(); + return msgResult; +} + +int EmuThread::undoStateLoad() +{ + sendMessage(msg_UndoStateLoad); + waitMessage(); + return msgResult; +} + +int EmuThread::importSavefile(const QString& filename) +{ + sendMessage(msg_EmuReset); + sendMessage({.type = msg_ImportSavefile, .param = filename}); + waitMessage(2); + return msgResult; +} + +void EmuThread::enableCheats(bool enable) +{ + sendMessage({.type = msg_EnableCheats, .param = enable}); + waitMessage(); +} + void EmuThread::updateRenderer() { if (videoRenderer != lastVideoRenderer) { - printf("creating renderer %d\n", videoRenderer); switch (videoRenderer) { case renderer3D_Software: diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h index cd36eb4c..f28c0604 100644 --- a/src/frontend/qt_sdl/EmuThread.h +++ b/src/frontend/qt_sdl/EmuThread.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -68,15 +69,28 @@ public: msg_InitGL, msg_DeInitGL, + + msg_BootROM, + msg_BootFirmware, + msg_InsertCart, + msg_EjectCart, + msg_InsertGBACart, + msg_InsertGBAAddon, + msg_EjectGBACart, + + msg_LoadState, + msg_SaveState, + msg_UndoStateLoad, + + msg_ImportSavefile, + + msg_EnableCheats, }; struct Message { MessageType type; - union - { - bool stopExternal; - }; + QVariant param; }; void sendMessage(Message msg); @@ -92,23 +106,38 @@ public: // to be called from the UI thread void emuRun(); - void emuPause(); - void emuUnpause(); - void emuTogglePause(); + void emuPause(bool broadcast = true); + void emuUnpause(bool broadcast = true); + void emuTogglePause(bool broadcast = true); void emuStop(bool external); void emuExit(); void emuFrameStep(); void emuReset(); + int bootROM(const QStringList& filename); + int bootFirmware(); + int insertCart(const QStringList& filename, bool gba); + void ejectCart(bool gba); + int insertGBAAddon(int type); + + int saveState(const QString& filename); + int loadState(const QString& filename); + int undoStateLoad(); + + int importSavefile(const QString& filename); + + void enableCheats(bool enable); + bool emuIsRunning(); bool emuIsActive(); - void initContext(); - void deinitContext(); + void initContext(int win); + void deinitContext(int win); void updateVideoSettings() { videoSettingsDirty = true; } + void updateVideoRenderer() { videoSettingsDirty = true; lastVideoRenderer = -1; } - int FrontBuffer = 0; - QMutex FrontBufferLock; + int frontBuffer = 0; + QMutex frontBufferLock; signals: void windowUpdate(); @@ -152,6 +181,8 @@ private: constexpr static int emuPauseStackPauseThreshold = 1; int emuPauseStack; + int msgResult = 0; + QMutex msgMutex; QSemaphore msgSemaphore; QQueue msgQueue; diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 5d5ecd01..1f71b5b9 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -132,6 +132,13 @@ bool FirmwareSettingsDialog::verifyMAC() void FirmwareSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 2e7e75c9..bd02405e 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -104,6 +104,13 @@ void InterfaceSettingsDialog::on_pbQuarter_clicked() void InterfaceSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + if (r == QDialog::Accepted) { auto& cfg = emuInstance->getGlobalConfig(); diff --git a/src/frontend/qt_sdl/LANDialog.cpp b/src/frontend/qt_sdl/LANDialog.cpp index 32539e3f..bec9c48f 100644 --- a/src/frontend/qt_sdl/LANDialog.cpp +++ b/src/frontend/qt_sdl/LANDialog.cpp @@ -65,6 +65,12 @@ LANStartHostDialog::~LANStartHostDialog() void LANStartHostDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { if (ui->txtPlayerName->text().trimmed().isEmpty()) @@ -186,6 +192,12 @@ void LANStartClientDialog::onDirectConnect() void LANStartClientDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { if (ui->txtPlayerName->text().trimmed().isEmpty()) @@ -313,6 +325,12 @@ void LANDialog::on_btnLeaveGame_clicked() void LANDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + bool showwarning = true; if (lan().GetNumPlayers() < 2) showwarning = false; diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp index e241ba3d..54c35d15 100644 --- a/src/frontend/qt_sdl/MPSettingsDialog.cpp +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -59,6 +59,13 @@ MPSettingsDialog::~MPSettingsDialog() void MPSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + if (r == QDialog::Accepted) { auto& cfg = emuInstance->getGlobalConfig(); diff --git a/src/frontend/qt_sdl/NetplayDialog.cpp b/src/frontend/qt_sdl/NetplayDialog.cpp index e9ed6022..d7b7cf81 100644 --- a/src/frontend/qt_sdl/NetplayDialog.cpp +++ b/src/frontend/qt_sdl/NetplayDialog.cpp @@ -63,6 +63,12 @@ NetplayStartHostDialog::~NetplayStartHostDialog() void NetplayStartHostDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { std::string player = ui->txtPlayerName->text().toStdString(); @@ -94,6 +100,12 @@ NetplayStartClientDialog::~NetplayStartClientDialog() void NetplayStartClientDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { std::string player = ui->txtPlayerName->text().toStdString(); diff --git a/src/frontend/qt_sdl/OSD_shaders.h b/src/frontend/qt_sdl/OSD_shaders.h index a2a6af70..253fcdc5 100644 --- a/src/frontend/qt_sdl/OSD_shaders.h +++ b/src/frontend/qt_sdl/OSD_shaders.h @@ -26,6 +26,7 @@ uniform vec2 uScreenSize; uniform ivec2 uOSDPos; uniform ivec2 uOSDSize; uniform float uScaleFactor; +uniform float uTexScale; in vec2 vPosition; @@ -35,8 +36,8 @@ void main() { vec4 fpos; - vec2 osdpos = (vPosition * vec2(uOSDSize * uScaleFactor)); - fTexcoord = osdpos; + vec2 osdpos = (vPosition * vec2(uOSDSize)); + fTexcoord = osdpos * uTexScale; osdpos += uOSDPos; fpos.xy = ((osdpos * 2.0) / uScreenSize * uScaleFactor) - 1.0; diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index b1bc8301..f3a453d1 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -72,6 +72,13 @@ PathSettingsDialog::~PathSettingsDialog() void PathSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index 425d99b5..10abe1ce 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -46,11 +46,13 @@ #include "main_shaders.h" #include "OSD_shaders.h" #include "font.h" +#include "version.h" using namespace melonDS; const u32 kOSDMargin = 6; +const int kLogoWidth = 192; ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) @@ -80,6 +82,29 @@ ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) loadConfig(); setFilter(mainWindow->getWindowConfig().GetBool("ScreenFilter")); + + splashLogo = QPixmap(":/melon-logo"); + + strncpy(splashText[0].text, "File->Open ROM...", 256); + splashText[0].id = 0x80000000; + splashText[0].color = 0; + splashText[0].rendered = false; + splashText[0].rainbowstart = -1; + + strncpy(splashText[1].text, "to get started", 256); + splashText[1].id = 0x80000001; + splashText[1].color = 0; + splashText[1].rendered = false; + splashText[1].rainbowstart = -1; + + std::string url = MELONDS_URL; + int urlpos = url.find("://"); + urlpos = (urlpos == std::string::npos) ? 0 : urlpos+3; + strncpy(splashText[2].text, url.c_str() + urlpos, 256); + splashText[2].id = 0x80000002; + splashText[2].color = 0; + splashText[2].rendered = false; + splashText[2].rainbowstart = -1; } ScreenPanel::~ScreenPanel() @@ -150,6 +175,8 @@ void ScreenPanel::setupScreenLayout() aspectBot); numScreens = layout.GetScreenTransforms(screenMatrix[0], screenKind); + + calcSplashLayout(); } QSize ScreenPanel::screenGetMinSize(int factor = 1) @@ -222,6 +249,7 @@ void ScreenPanel::resizeEvent(QResizeEvent* event) void ScreenPanel::mousePressEvent(QMouseEvent* event) { event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } if (event->button() != Qt::LeftButton) return; int x = event->pos().x(); @@ -230,21 +258,20 @@ void ScreenPanel::mousePressEvent(QMouseEvent* event) if (layout.GetTouchCoords(x, y, false)) { touching = true; - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) { event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } if (event->button() != Qt::LeftButton) return; if (touching) { touching = false; - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->ReleaseScreen(); + emuInstance->releaseScreen(); } } @@ -254,6 +281,7 @@ void ScreenPanel::mouseMoveEvent(QMouseEvent* event) showCursor(); + if (!emuInstance->emuIsActive()) return; //if (!(event->buttons() & Qt::LeftButton)) return; if (!touching) return; @@ -262,14 +290,14 @@ void ScreenPanel::mouseMoveEvent(QMouseEvent* event) if (layout.GetTouchCoords(x, y, true)) { - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } void ScreenPanel::tabletEvent(QTabletEvent* event) { event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } switch(event->type()) { @@ -287,16 +315,14 @@ void ScreenPanel::tabletEvent(QTabletEvent* event) if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) { touching = true; - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } break; case QEvent::TabletRelease: if (touching) { - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->ReleaseScreen(); + emuInstance->releaseScreen(); touching = false; } break; @@ -313,6 +339,7 @@ void ScreenPanel::touchEvent(QTouchEvent* event) #endif event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } switch(event->type()) { @@ -333,16 +360,14 @@ void ScreenPanel::touchEvent(QTouchEvent* event) if (layout.GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) { touching = true; - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } break; case QEvent::TouchEnd: if (touching) { - assert(emuInstance->getNDS() != nullptr); - emuInstance->getNDS()->ReleaseScreen(); + emuInstance->releaseScreen(); touching = false; } break; @@ -360,6 +385,10 @@ bool ScreenPanel::event(QEvent* event) touchEvent((QTouchEvent*)event); return true; } + else if (event->type() == QEvent::FocusIn) + mainWindow->onFocusIn(); + else if (event->type() == QEvent::FocusOut) + mainWindow->onFocusOut(); return QWidget::event(event); } @@ -478,8 +507,14 @@ void ScreenPanel::osdRenderItem(OSDItem* item) u32 color = item->color; bool rainbow = (color == 0); - u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch(); - u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + u32 rainbowinc; + if (item->rainbowstart == -1) + { + u32 ticks = (u32) QDateTime::currentMSecsSinceEpoch(); + rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + } + else + rainbowinc = (u32)item->rainbowstart; color |= 0xFF000000; const u32 shadow = 0xE0000000; @@ -577,6 +612,8 @@ void ScreenPanel::osdRenderItem(OSDItem* item) bitmap[(y * w) + x] = shadow; } } + + item->rainbowend = (int)rainbowinc; } void ScreenPanel::osdDeleteItem(OSDItem* item) @@ -598,11 +635,12 @@ void ScreenPanel::osdAddMessage(unsigned int color, const char* text) OSDItem item; - item.id = osdID++; + item.id = (osdID++) & 0x7FFFFFFF; item.timestamp = QDateTime::currentMSecsSinceEpoch(); strncpy(item.text, text, 255); item.text[255] = '\0'; item.color = color; item.rendered = false; + item.rainbowstart = -1; osdItems.push_back(item); @@ -636,6 +674,73 @@ void ScreenPanel::osdUpdate() it++; } + // render splashscreen text items if needed + + int rainbowinc = -1; + bool needrecalc = false; + + for (int i = 0; i < 3; i++) + { + if (!splashText[i].rendered) + { + splashText[i].rainbowstart = rainbowinc; + osdRenderItem(&splashText[i]); + splashText[i].rendered = true; + rainbowinc = splashText[i].rainbowend; + needrecalc = true; + } + } + + osdMutex.unlock(); + + if (needrecalc) + calcSplashLayout(); +} + +void ScreenPanel::calcSplashLayout() +{ + if (!splashText[0].rendered) + return; + + osdMutex.lock(); + + int w = width(); + int h = height(); + + int xlogo = (w - kLogoWidth) / 2; + int ylogo = (h - kLogoWidth) / 2; + + // top text + int totalwidth = splashText[0].bitmap.width() + 6 + splashText[1].bitmap.width(); + if (totalwidth >= w) + { + // stacked vertically + splashPos[0].setX((width() - splashText[0].bitmap.width()) / 2); + splashPos[1].setX((width() - splashText[1].bitmap.width()) / 2); + + int basey = ylogo / 2; + splashPos[0].setY(basey - splashText[0].bitmap.height() - 1); + splashPos[1].setY(basey + 1); + } + else + { + // horizontal + splashPos[0].setX((w - totalwidth) / 2); + splashPos[1].setX(splashPos[0].x() + splashText[0].bitmap.width() + 6); + + int basey = (ylogo - splashText[0].bitmap.height()) / 2; + splashPos[0].setY(basey); + splashPos[1].setY(basey); + } + + // bottom text + splashPos[2].setX((w - splashText[2].bitmap.width()) / 2); + splashPos[2].setY(ylogo + kLogoWidth + ((ylogo - splashText[2].bitmap.height()) / 2)); + + // logo + splashPos[3].setX(xlogo); + splashPos[3].setY(ylogo); + osdMutex.unlock(); } @@ -678,20 +783,21 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) if (emuThread->emuIsActive()) { + emuInstance->renderLock.lock(); auto nds = emuInstance->getNDS(); assert(nds != nullptr); - emuThread->FrontBufferLock.lock(); - int frontbuf = emuThread->FrontBuffer; + emuThread->frontBufferLock.lock(); + int frontbuf = emuThread->frontBuffer; if (!nds->GPU.Framebuffer[frontbuf][0] || !nds->GPU.Framebuffer[frontbuf][1]) { - emuThread->FrontBufferLock.unlock(); + emuThread->frontBufferLock.unlock(); return; } memcpy(screen[0].scanLine(0), nds->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); memcpy(screen[1].scanLine(0), nds->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); - emuThread->FrontBufferLock.unlock(); + emuThread->frontBufferLock.unlock(); QRect screenrc(0, 0, 256, 192); @@ -700,9 +806,24 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) painter.setTransform(screenTrans[i]); painter.drawImage(screenrc, screen[screenKind[i]]); } + emuInstance->renderLock.unlock(); } osdUpdate(); + + if (!emuThread->emuIsActive()) + { + // splashscreen + osdMutex.lock(); + + painter.drawPixmap(QRect(splashPos[3], QSize(kLogoWidth, kLogoWidth)), splashLogo); + + for (int i = 0; i < 3; i++) + painter.drawImage(splashPos[i], splashText[i].bitmap); + + osdMutex.unlock(); + } + if (osdEnabled) { osdMutex.lock(); @@ -736,6 +857,8 @@ ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) setAttribute(Qt::WA_KeyCompression, false); setFocusPolicy(Qt::StrongFocus); setMinimumSize(screenGetMinSize()); + + glInited = false; } ScreenPanelGL::~ScreenPanelGL() @@ -747,14 +870,14 @@ bool ScreenPanelGL::createContext() // if our parent window is parented to another window, we will // share our OpenGL context with that window + MainWindow* ourwin = (MainWindow*)parentWidget(); MainWindow* parentwin = (MainWindow*)parentWidget()->parentWidget(); - if (parentwin) + //if (parentwin) + if (ourwin->getWindowID() != 0) { if (windowinfo.has_value()) - { - glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo); - glContext->DoneCurrent(); - } + if (glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo)) + glContext->DoneCurrent(); } else { @@ -762,10 +885,8 @@ bool ScreenPanelGL::createContext() GL::Context::Version{GL::Context::Profile::Core, 4, 3}, GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; if (windowinfo.has_value()) - { - glContext = GL::Context::Create(*windowinfo, versionsToTry); - glContext->DoneCurrent(); - } + if (glContext = GL::Context::Create(*windowinfo, versionsToTry)) + glContext->DoneCurrent(); } return glContext != nullptr; @@ -781,6 +902,7 @@ void ScreenPanelGL::setSwapInterval(int intv) void ScreenPanelGL::initOpenGL() { if (!glContext) return; + if (glInited) return; glContext->MakeCurrent(); @@ -856,6 +978,7 @@ void ScreenPanelGL::initOpenGL() osdPosULoc = glGetUniformLocation(osdShader, "uOSDPos"); osdSizeULoc = glGetUniformLocation(osdShader, "uOSDSize"); osdScaleFactorULoc = glGetUniformLocation(osdShader, "uScaleFactor"); + osdTexScaleULoc = glGetUniformLocation(osdShader, "uTexScale"); const float osdvertices[6*2] = { @@ -876,12 +999,26 @@ void ScreenPanelGL::initOpenGL() glEnableVertexAttribArray(0); // position glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); + // splash logo texture + QImage logo = splashLogo.scaled(kLogoWidth*2, kLogoWidth*2).toImage(); + GLuint tex; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, logo.width(), logo.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, logo.bits()); + logoTexture = tex; + transferLayout(); + glInited = true; } void ScreenPanelGL::deinitOpenGL() { if (!glContext) return; + if (!glInited) return; glDeleteTextures(1, &screenTexture); @@ -900,12 +1037,15 @@ void ScreenPanelGL::deinitOpenGL() glDeleteVertexArrays(1, &osdVertexArray); glDeleteBuffers(1, &osdVertexBuffer); + glDeleteTextures(1, &logoTexture); + glDeleteProgram(osdShader); glContext->DoneCurrent(); lastScreenWidth = lastScreenHeight = -1; + glInited = false; } void ScreenPanelGL::makeCurrentGL() @@ -947,9 +1087,6 @@ void ScreenPanelGL::drawScreenGL() { if (!glContext) return; - auto nds = emuInstance->getNDS(); - if (!nds) return; - auto emuThread = emuInstance->getEmuThread(); glContext->MakeCurrent(); @@ -968,51 +1105,101 @@ void ScreenPanelGL::drawScreenGL() glViewport(0, 0, w, h); - glUseProgram(screenShaderProgram); - glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + if (emuThread->emuIsActive()) + { + auto nds = emuInstance->getNDS(); - int frontbuf = emuThread->FrontBuffer; - glActiveTexture(GL_TEXTURE0); + glUseProgram(screenShaderProgram); + glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + + int frontbuf = emuThread->frontBuffer; + glActiveTexture(GL_TEXTURE0); #ifdef OGLRENDERER_ENABLED - if (nds->GPU.GetRenderer3D().Accelerated) - { - // hardware-accelerated render - nds->GPU.GetRenderer3D().BindOutputTexture(frontbuf); - } - else -#endif - { - // regular render - glBindTexture(GL_TEXTURE_2D, screenTexture); - - if (nds->GPU.Framebuffer[frontbuf][0] && nds->GPU.Framebuffer[frontbuf][1]) + if (nds->GPU.GetRenderer3D().Accelerated) { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][0].get()); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][1].get()); + // hardware-accelerated render + nds->GPU.GetRenderer3D().BindOutputTexture(frontbuf); + } else +#endif + { + // regular render + glBindTexture(GL_TEXTURE_2D, screenTexture); + + if (nds->GPU.Framebuffer[frontbuf][0] && nds->GPU.Framebuffer[frontbuf][1]) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][0].get()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192 + 2, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][1].get()); + } } + + screenSettingsLock.lock(); + + GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + + glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); + glBindVertexArray(screenVertexArray); + + for (int i = 0; i < numScreens; i++) + { + glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); + glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2 * 3, 2 * 3); + } + + screenSettingsLock.unlock(); } - screenSettingsLock.lock(); - - GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - - glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); - glBindVertexArray(screenVertexArray); - - for (int i = 0; i < numScreens; i++) - { - glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); - glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3); - } - - screenSettingsLock.unlock(); - osdUpdate(); + + if (!emuThread->emuIsActive()) + { + // splashscreen + osdMutex.lock(); + + glUseProgram(osdShader); + + glUniform2f(osdScreenSizeULoc, w, h); + glUniform1f(osdScaleFactorULoc, factor); + glUniform1f(osdTexScaleULoc, 2.0); + + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBindVertexArray(osdVertexArray); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glBindTexture(GL_TEXTURE_2D, logoTexture); + glUniform2i(osdPosULoc, splashPos[3].x(), splashPos[3].y()); + glUniform2i(osdSizeULoc, kLogoWidth, kLogoWidth); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + + glUniform1f(osdTexScaleULoc, 1.0); + + for (int i = 0; i < 3; i++) + { + OSDItem& item = splashText[i]; + + if (!osdTextures.count(item.id)) + continue; + + glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]); + glUniform2i(osdPosULoc, splashPos[i].x(), splashPos[i].y()); + glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height()); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + } + + glDisable(GL_BLEND); + glUseProgram(0); + + osdMutex.unlock(); + } + if (osdEnabled) { osdMutex.lock(); @@ -1023,6 +1210,7 @@ void ScreenPanelGL::drawScreenGL() glUniform2f(osdScreenSizeULoc, w, h); glUniform1f(osdScaleFactorULoc, factor); + glUniform1f(osdTexScaleULoc, 1.0); glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); glBindVertexArray(osdVertexArray); diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h index f3662d8d..a988815e 100644 --- a/src/frontend/qt_sdl/Screen.h +++ b/src/frontend/qt_sdl/Screen.h @@ -110,6 +110,9 @@ protected: bool rendered; QImage bitmap; + + int rainbowstart; + int rainbowend; }; QMutex osdMutex; @@ -117,6 +120,10 @@ protected: unsigned int osdID; std::deque osdItems; + QPixmap splashLogo; + OSDItem splashText[3]; + QPoint splashPos[4]; + void loadConfig(); virtual void setupScreenLayout(); @@ -141,6 +148,8 @@ protected: virtual void osdDeleteItem(OSDItem* item); void osdUpdate(); + + void calcSplashLayout(); }; @@ -197,11 +206,12 @@ private: void setupScreenLayout() override; std::unique_ptr glContext; + bool glInited; GLuint screenVertexBuffer, screenVertexArray; GLuint screenTexture; GLuint screenShaderProgram; - GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; + GLint screenShaderTransformULoc, screenShaderScreenSizeULoc; QMutex screenSettingsLock; WindowInfo windowInfo; @@ -210,11 +220,14 @@ private: GLuint osdShader; GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc; - GLfloat osdScaleFactorULoc; + GLint osdScaleFactorULoc; + GLint osdTexScaleULoc; GLuint osdVertexArray; GLuint osdVertexBuffer; std::map osdTextures; + GLuint logoTexture; + void osdRenderItem(OSDItem* item) override; void osdDeleteItem(OSDItem* item) override; }; diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp index 7eebf5a4..619ecda3 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp +++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp @@ -121,6 +121,12 @@ void VideoSettingsDialog::on_VideoSettingsDialog_accepted() void VideoSettingsDialog::on_VideoSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + bool old_gl = UsesGL(); auto& cfg = emuInstance->getGlobalConfig(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index c3f988b1..e0954c83 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -94,6 +94,13 @@ WifiSettingsDialog::~WifiSettingsDialog() void WifiSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 3020defd..596a0f5c 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -81,6 +81,8 @@ #include "EmuInstance.h" #include "ArchiveUtil.h" #include "CameraManager.h" +#include "Window.h" +#include "AboutDialog.h" using namespace melonDS; @@ -232,7 +234,9 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : globalCfg(inst->globalCfg), localCfg(inst->localCfg), windowCfg(localCfg.GetTable("Window"+std::to_string(id), "Window0")), - emuThread(inst->getEmuThread()) + emuThread(inst->getEmuThread()), + enabledSaved(false), + focused(true) { #ifndef _WIN32 if (!parent) @@ -262,395 +266,416 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : setAcceptDrops(true); setFocusPolicy(Qt::ClickFocus); - QMenuBar* menubar = new QMenuBar(); - { - QMenu* menu = menubar->addMenu("File"); - - actOpenROM = menu->addAction("Open ROM..."); - connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); - actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - - /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); - connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); - actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - - recentMenu = menu->addMenu("Open recent"); - Config::Array recentROMs = globalCfg.GetArray("RecentROM"); - int numrecent = std::min(kMaxRecentROMs, (int)recentROMs.Size()); - for (int i = 0; i < numrecent; ++i) - { - std::string item = recentROMs.GetString(i); - if (!item.empty()) - recentFileList.push_back(QString::fromStdString(item)); - } - updateRecentFilesMenu(); - - //actBootFirmware = menu->addAction("Launch DS menu"); - actBootFirmware = menu->addAction("Boot firmware"); - connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); - - menu->addSeparator(); - - actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel()); - actCurrentCart->setEnabled(false); - - actInsertCart = menu->addAction("Insert cart..."); - connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); - - actEjectCart = menu->addAction("Eject cart"); - connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); - - menu->addSeparator(); - - actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel()); - actCurrentGBACart->setEnabled(false); - - actInsertGBACart = menu->addAction("Insert ROM cart..."); - connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - - { - QMenu* submenu = menu->addMenu("Insert add-on cart"); - - actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); - actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); - connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - - actInsertGBAAddon[1] = submenu->addAction("Rumble Pak"); - actInsertGBAAddon[1]->setData(QVariant(GBAAddon_RumblePak)); - connect(actInsertGBAAddon[1], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - } - - actEjectGBACart = menu->addAction("Eject cart"); - connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); - - menu->addSeparator(); - - actImportSavefile = menu->addAction("Import savefile"); - connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); - - menu->addSeparator(); - - { - QMenu* submenu = menu->addMenu("Save state"); - - for (int i = 1; i < 9; i++) - { - actSaveState[i] = submenu->addAction(QString("%1").arg(i)); - actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1))); - actSaveState[i]->setData(QVariant(i)); - connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); - } - - actSaveState[0] = submenu->addAction("File..."); - actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); - actSaveState[0]->setData(QVariant(0)); - connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); - } - { - QMenu* submenu = menu->addMenu("Load state"); - - for (int i = 1; i < 9; i++) - { - actLoadState[i] = submenu->addAction(QString("%1").arg(i)); - actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1)); - actLoadState[i]->setData(QVariant(i)); - connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actLoadState[0] = submenu->addAction("File..."); - actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); - actLoadState[0]->setData(QVariant(0)); - connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actUndoStateLoad = menu->addAction("Undo state load"); - actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); - connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); - - menu->addSeparator(); - actOpenConfig = menu->addAction("Open melonDS directory"); - connect(actOpenConfig, &QAction::triggered, this, [&]() { - QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory)); - }); - - menu->addSeparator(); - - actQuit = menu->addAction("Quit"); - connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); - actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); - } - { - QMenu* menu = menubar->addMenu("System"); - - actPause = menu->addAction("Pause"); - actPause->setCheckable(true); - connect(actPause, &QAction::triggered, this, &MainWindow::onPause); - - actReset = menu->addAction("Reset"); - connect(actReset, &QAction::triggered, this, &MainWindow::onReset); - - actStop = menu->addAction("Stop"); - connect(actStop, &QAction::triggered, this, &MainWindow::onStop); - - actFrameStep = menu->addAction("Frame step"); - connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); - - menu->addSeparator(); - - actPowerManagement = menu->addAction("Power management"); - connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); - - actDateTime = menu->addAction("Date and time"); - connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); - - menu->addSeparator(); - - actEnableCheats = menu->addAction("Enable cheats"); - actEnableCheats->setCheckable(true); - connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - - //if (inst == 0) - { - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); - - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); - } - - { - menu->addSeparator(); - QMenu* submenu = menu->addMenu("Multiplayer"); - - actMPNewInstance = submenu->addAction("Launch new instance"); - connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); - - submenu->addSeparator(); - - actLANStartHost = submenu->addAction("Host LAN game"); - connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost); - - actLANStartClient = submenu->addAction("Join LAN game"); - connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient); - - /*submenu->addSeparator(); - - actNPStartHost = submenu->addAction("NETPLAY HOST"); - connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost); - - actNPStartClient = submenu->addAction("NETPLAY CLIENT"); - connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient); - - actNPTest = submenu->addAction("NETPLAY GO"); - connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/ - } - } - { - QMenu* menu = menubar->addMenu("Config"); - - actEmuSettings = menu->addAction("Emu settings"); - connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - -#ifdef __APPLE__ - actPreferences = menu->addAction("Preferences..."); - connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - actPreferences->setMenuRole(QAction::PreferencesRole); +#if QT_VERSION_MAJOR == 6 && WIN32 + // The "windows11" theme has pretty massive padding around menubar items, this makes Config and Help not fit in a window at 1x screen sizing + // So let's reduce the padding a bit. + if (QApplication::style()->name() == "windows11") + setStyleSheet("QMenuBar::item { padding: 4px 8px; }"); #endif - actInputConfig = menu->addAction("Input and hotkeys"); - connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); - - actVideoSettings = menu->addAction("Video settings"); - connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); - - actCameraSettings = menu->addAction("Camera settings"); - connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); - - actAudioSettings = menu->addAction("Audio settings"); - connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); - - actMPSettings = menu->addAction("Multiplayer settings"); - connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); - - actWifiSettings = menu->addAction("Wifi settings"); - connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - - actFirmwareSettings = menu->addAction("Firmware settings"); - connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); - - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - - actPathSettings = menu->addAction("Path settings"); - connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); + //hasMenu = (!parent); + hasMenu = true; + if (hasMenu) + { + QMenuBar * menubar = new QMenuBar(); { - QMenu* submenu = menu->addMenu("Savestate settings"); + QMenu * menu = menubar->addMenu("File"); - actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); - actSavestateSRAMReloc->setCheckable(true); - connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); - } + actOpenROM = menu->addAction("Open ROM..."); + connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); + actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - menu->addSeparator(); + /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); + connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); + actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - { - QMenu* submenu = menu->addMenu("Screen size"); + recentMenu = menu->addMenu("Open recent"); + loadRecentFilesMenu(true); + + //actBootFirmware = menu->addAction("Launch DS menu"); + actBootFirmware = menu->addAction("Boot firmware"); + connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); + + menu->addSeparator(); + + actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel()); + actCurrentCart->setEnabled(false); + + actInsertCart = menu->addAction("Insert cart..."); + connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); + + actEjectCart = menu->addAction("Eject cart"); + connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); + + menu->addSeparator(); + + actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel()); + actCurrentGBACart->setEnabled(false); + + actInsertGBACart = menu->addAction("Insert ROM cart..."); + connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - for (int i = 0; i < 4; i++) { - int data = i+1; - actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); - actScreenSize[i]->setData(QVariant(data)); - connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); - } - } - { - QMenu* submenu = menu->addMenu("Screen rotation"); - grpScreenRotation = new QActionGroup(submenu); + QMenu * submenu = menu->addMenu("Insert add-on cart"); + QAction *act; - for (int i = 0; i < screenRot_MAX; i++) - { - int data = i*90; - actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); - actScreenRotation[i]->setActionGroup(grpScreenRotation); - actScreenRotation[i]->setData(QVariant(i)); - actScreenRotation[i]->setCheckable(true); - } - - connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); - } - { - QMenu* submenu = menu->addMenu("Screen gap"); - grpScreenGap = new QActionGroup(submenu); - - const int screengap[] = {0, 1, 8, 64, 90, 128}; - - for (int i = 0; i < 6; i++) - { - int data = screengap[i]; - actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); - actScreenGap[i]->setActionGroup(grpScreenGap); - actScreenGap[i]->setData(QVariant(data)); - actScreenGap[i]->setCheckable(true); - } - - connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); - } - { - QMenu* submenu = menu->addMenu("Screen layout"); - grpScreenLayout = new QActionGroup(submenu); - - const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; - - for (int i = 0; i < screenLayout_MAX; i++) - { - actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); - actScreenLayout[i]->setActionGroup(grpScreenLayout); - actScreenLayout[i]->setData(QVariant(i)); - actScreenLayout[i]->setCheckable(true); - } - - connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); - - submenu->addSeparator(); - - actScreenSwap = submenu->addAction("Swap screens"); - actScreenSwap->setCheckable(true); - connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); - } - { - QMenu* submenu = menu->addMenu("Screen sizing"); - grpScreenSizing = new QActionGroup(submenu); - - const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - - for (int i = 0; i < screenSizing_MAX; i++) - { - actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); - actScreenSizing[i]->setActionGroup(grpScreenSizing); - actScreenSizing[i]->setData(QVariant(i)); - actScreenSizing[i]->setCheckable(true); - } - - connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); - - submenu->addSeparator(); - - actIntegerScaling = submenu->addAction("Force integer scaling"); - actIntegerScaling->setCheckable(true); - connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); - } - { - QMenu* submenu = menu->addMenu("Aspect ratio"); - grpScreenAspectTop = new QActionGroup(submenu); - grpScreenAspectBot = new QActionGroup(submenu); - actScreenAspectTop = new QAction*[AspectRatiosNum]; - actScreenAspectBot = new QAction*[AspectRatiosNum]; - - for (int i = 0; i < 2; i++) - { - QActionGroup* group = grpScreenAspectTop; - QAction** actions = actScreenAspectTop; - - if (i == 1) + int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, -1}; + for (int i = 0; addons[i] != -1; i++) { - group = grpScreenAspectBot; - submenu->addSeparator(); - actions = actScreenAspectBot; + int addon = addons[i]; + act = submenu->addAction(emuInstance->gbaAddonName(addon)); + act->setData(QVariant(addon)); + connect(act, &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + actInsertGBAAddon.append(act); + } + } + + actEjectGBACart = menu->addAction("Eject cart"); + connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); + + menu->addSeparator(); + + actImportSavefile = menu->addAction("Import savefile"); + connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); + + menu->addSeparator(); + + { + QMenu * submenu = menu->addMenu("Save state"); + + for (int i = 1; i < 9; i++) + { + actSaveState[i] = submenu->addAction(QString("%1").arg(i)); + actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1 + i - 1))); + actSaveState[i]->setData(QVariant(i)); + connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); } - for (int j = 0; j < AspectRatiosNum; j++) + actSaveState[0] = submenu->addAction("File..."); + actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); + actSaveState[0]->setData(QVariant(0)); + connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); + } + { + QMenu * submenu = menu->addMenu("Load state"); + + for (int i = 1; i < 9; i++) { - auto ratio = aspectRatios[j]; - QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); - actions[j] = submenu->addAction(label); - actions[j]->setActionGroup(group); - actions[j]->setData(QVariant(ratio.id)); - actions[j]->setCheckable(true); + actLoadState[i] = submenu->addAction(QString("%1").arg(i)); + actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1 + i - 1)); + actLoadState[i]->setData(QVariant(i)); + connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); } - connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + actLoadState[0] = submenu->addAction("File..."); + actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); + actLoadState[0]->setData(QVariant(0)); + connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); + } + + actUndoStateLoad = menu->addAction("Undo state load"); + actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); + connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); + + menu->addSeparator(); + actOpenConfig = menu->addAction("Open melonDS directory"); + connect(actOpenConfig, &QAction::triggered, this, [&]() + { + QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory)); + }); + + menu->addSeparator(); + + actQuit = menu->addAction("Quit"); + connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); + actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); + } + { + QMenu * menu = menubar->addMenu("System"); + + actPause = menu->addAction("Pause"); + actPause->setCheckable(true); + connect(actPause, &QAction::triggered, this, &MainWindow::onPause); + + actReset = menu->addAction("Reset"); + connect(actReset, &QAction::triggered, this, &MainWindow::onReset); + + actStop = menu->addAction("Stop"); + connect(actStop, &QAction::triggered, this, &MainWindow::onStop); + + actFrameStep = menu->addAction("Frame step"); + connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); + + menu->addSeparator(); + + actPowerManagement = menu->addAction("Power management"); + connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + + actDateTime = menu->addAction("Date and time"); + connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); + + menu->addSeparator(); + + actEnableCheats = menu->addAction("Enable cheats"); + actEnableCheats->setCheckable(true); + connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); + + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } + + { + menu->addSeparator(); + QMenu * submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + + submenu->addSeparator(); + + actLANStartHost = submenu->addAction("Host LAN game"); + connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost); + + actLANStartClient = submenu->addAction("Join LAN game"); + connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient); + + /*submenu->addSeparator(); + + actNPStartHost = submenu->addAction("NETPLAY HOST"); + connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost); + + actNPStartClient = submenu->addAction("NETPLAY CLIENT"); + connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient); + + actNPTest = submenu->addAction("NETPLAY GO"); + connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/ } } - - if (parentWidget() != nullptr) // TEST { - QMenu* menu = menubar->addMenu("Test"); + QMenu * menu = menubar->addMenu("View"); - menu->addAction("Test"); + { + QMenu * submenu = menu->addMenu("Screen size"); + + for (int i = 0; i < 4; i++) + { + int data = i + 1; + actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); + actScreenSize[i]->setData(QVariant(data)); + connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); + } + } + { + QMenu * submenu = menu->addMenu("Screen rotation"); + grpScreenRotation = new QActionGroup(submenu); + + for (int i = 0; i < screenRot_MAX; i++) + { + int data = i * 90; + actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); + actScreenRotation[i]->setActionGroup(grpScreenRotation); + actScreenRotation[i]->setData(QVariant(i)); + actScreenRotation[i]->setCheckable(true); + } + + connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); + } + { + QMenu * submenu = menu->addMenu("Screen gap"); + grpScreenGap = new QActionGroup(submenu); + + const int screengap[] = {0, 1, 8, 64, 90, 128}; + + for (int i = 0; i < 6; i++) + { + int data = screengap[i]; + actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); + actScreenGap[i]->setActionGroup(grpScreenGap); + actScreenGap[i]->setData(QVariant(data)); + actScreenGap[i]->setCheckable(true); + } + + connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); + } + { + QMenu * submenu = menu->addMenu("Screen layout"); + grpScreenLayout = new QActionGroup(submenu); + + const char *screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; + + for (int i = 0; i < screenLayout_MAX; i++) + { + actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); + actScreenLayout[i]->setActionGroup(grpScreenLayout); + actScreenLayout[i]->setData(QVariant(i)); + actScreenLayout[i]->setCheckable(true); + } + + connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); + + submenu->addSeparator(); + + actScreenSwap = submenu->addAction("Swap screens"); + actScreenSwap->setCheckable(true); + connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); + } + { + QMenu * submenu = menu->addMenu("Screen sizing"); + grpScreenSizing = new QActionGroup(submenu); + + const char *screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", + "Bottom only"}; + + for (int i = 0; i < screenSizing_MAX; i++) + { + actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); + actScreenSizing[i]->setActionGroup(grpScreenSizing); + actScreenSizing[i]->setData(QVariant(i)); + actScreenSizing[i]->setCheckable(true); + } + + connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); + + submenu->addSeparator(); + + actIntegerScaling = submenu->addAction("Force integer scaling"); + actIntegerScaling->setCheckable(true); + connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); + } + { + QMenu * submenu = menu->addMenu("Aspect ratio"); + grpScreenAspectTop = new QActionGroup(submenu); + grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction *[AspectRatiosNum]; + actScreenAspectBot = new QAction *[AspectRatiosNum]; + + for (int i = 0; i < 2; i++) + { + QActionGroup * group = grpScreenAspectTop; + QAction **actions = actScreenAspectTop; + + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } + + for (int j = 0; j < AspectRatiosNum; j++) + { + auto ratio = aspectRatios[j]; + QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); + actions[j] = submenu->addAction(label); + actions[j]->setActionGroup(group); + actions[j]->setData(QVariant(ratio.id)); + actions[j]->setCheckable(true); + } + + connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + } + } + + menu->addSeparator(); + + actNewWindow = menu->addAction("Open new window"); + connect(actNewWindow, &QAction::triggered, this, &MainWindow::onOpenNewWindow); + + menu->addSeparator(); + + actScreenFiltering = menu->addAction("Screen filtering"); + actScreenFiltering->setCheckable(true); + connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + + actShowOSD = menu->addAction("Show OSD"); + actShowOSD->setCheckable(true); + connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); + } + { + QMenu * menu = menubar->addMenu("Config"); + + actEmuSettings = menu->addAction("Emu settings"); + connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + +#ifdef __APPLE__ + actPreferences = menu->addAction("Preferences..."); + connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + actPreferences->setMenuRole(QAction::PreferencesRole); +#endif + + actInputConfig = menu->addAction("Input and hotkeys"); + connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); + + actVideoSettings = menu->addAction("Video settings"); + connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); + + actCameraSettings = menu->addAction("Camera settings"); + connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); + + actAudioSettings = menu->addAction("Audio settings"); + connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + + actWifiSettings = menu->addAction("Wifi settings"); + connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); + + actFirmwareSettings = menu->addAction("Firmware settings"); + connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + + actPathSettings = menu->addAction("Path settings"); + connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); + + { + QMenu * submenu = menu->addMenu("Savestate settings"); + + actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); + actSavestateSRAMReloc->setCheckable(true); + connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); + } + + menu->addSeparator(); + + actLimitFramerate = menu->addAction("Limit framerate"); + actLimitFramerate->setCheckable(true); + connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); + + actAudioSync = menu->addAction("Audio sync"); + actAudioSync->setCheckable(true); + connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); + } + { + QMenu * menu = menubar->addMenu("Help"); + actAbout = menu->addAction("About..."); + connect(actAbout, &QAction::triggered, this, [&] + { + auto dialog = AboutDialog(this); + dialog.exec(); + }); } - actScreenFiltering = menu->addAction("Screen filtering"); - actScreenFiltering->setCheckable(true); - connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + setMenuBar(menubar); - actShowOSD = menu->addAction("Show OSD"); - actShowOSD->setCheckable(true); - connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); - - menu->addSeparator(); - - actLimitFramerate = menu->addAction("Limit framerate"); - actLimitFramerate->setCheckable(true); - connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); - - actAudioSync = menu->addAction("Audio sync"); - actAudioSync->setCheckable(true); - connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); + if (localCfg.GetString("Firmware.Username") == "Arisotura") + actMPNewInstance->setText("Fart"); } - setMenuBar(menubar); - - if (localCfg.GetString("Firmware.Username") == "Arisotura") - actMPNewInstance->setText("Fart"); #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); @@ -666,92 +691,102 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : QByteArray dec = QByteArray::fromBase64(raw, QByteArray::Base64Encoding | QByteArray::AbortOnBase64DecodingErrors); if (!dec.isEmpty()) restoreGeometry(dec); + // if the window was closed in fullscreen do not restore this + setWindowState(windowState() & ~Qt::WindowFullScreen); } show(); + panel = nullptr; createScreenPanel(); - actEjectCart->setEnabled(false); - actEjectGBACart->setEnabled(false); - - if (globalCfg.GetInt("Emu.ConsoleType") == 1) + if (hasMenu) { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - } + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - actImportSavefile->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); - - actEnableCheats->setChecked(localCfg.GetBool("EnableCheats")); - - actROMInfo->setEnabled(false); - actRAMInfo->setEnabled(false); - - actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM")); - - actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true); - - int screenGap = windowCfg.GetInt("ScreenGap"); - for (int i = 0; i < 6; i++) - { - if (actScreenGap[i]->data().toInt() == screenGap) + if (globalCfg.GetInt("Emu.ConsoleType") == 1) { - actScreenGap[i]->setChecked(true); - break; + actInsertGBACart->setEnabled(false); + for (auto act: actInsertGBAAddon) + act->setEnabled(false); } - } - actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true); - actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true); - actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling")); + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + actImportSavefile->setEnabled(false); - actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap")); + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); - int aspectTop = windowCfg.GetInt("ScreenAspectTop"); - int aspectBot = windowCfg.GetInt("ScreenAspectBot"); - for (int i = 0; i < AspectRatiosNum; i++) - { - if (aspectTop == aspectRatios[i].id) - actScreenAspectTop[i]->setChecked(true); - if (aspectBot == aspectRatios[i].id) - actScreenAspectBot[i]->setChecked(true); - } + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); - actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter")); - actShowOSD->setChecked(showOSD); + actEnableCheats->setEnabled(false); + actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); - actLimitFramerate->setChecked(emuInstance->doLimitFPS); - actAudioSync->setChecked(emuInstance->doAudioSync); + actEnableCheats->setChecked(localCfg.GetBool("EnableCheats")); - if (emuInstance->instanceID > 0) - { - actEmuSettings->setEnabled(false); - actVideoSettings->setEnabled(false); - actMPSettings->setEnabled(false); - actWifiSettings->setEnabled(false); - actInterfaceSettings->setEnabled(false); + actROMInfo->setEnabled(false); + actRAMInfo->setEnabled(false); + + actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM")); + + actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true); + + int screenGap = windowCfg.GetInt("ScreenGap"); + for (int i = 0; i < 6; i++) + { + if (actScreenGap[i]->data().toInt() == screenGap) + { + actScreenGap[i]->setChecked(true); + break; + } + } + + actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true); + actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true); + actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling")); + + actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap")); + + int aspectTop = windowCfg.GetInt("ScreenAspectTop"); + int aspectBot = windowCfg.GetInt("ScreenAspectBot"); + for (int i = 0; i < AspectRatiosNum; i++) + { + if (aspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (aspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } + + actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter")); + actShowOSD->setChecked(showOSD); + + actLimitFramerate->setChecked(emuInstance->doLimitFPS); + actAudioSync->setChecked(emuInstance->doAudioSync); + + if (emuInstance->instanceID > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); #ifdef __APPLE__ - actPreferences->setEnabled(false); + actPreferences->setEnabled(false); #endif // __APPLE__ + } + + if (emuThread->emuIsActive()) + onEmuStart(); } QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged); @@ -762,8 +797,11 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : MainWindow::~MainWindow() { - delete[] actScreenAspectTop; - delete[] actScreenAspectBot; + if (hasMenu) + { + delete[] actScreenAspectTop; + delete[] actScreenAspectBot; + } } void MainWindow::osdAddMessage(unsigned int color, const char* msg) @@ -772,8 +810,20 @@ void MainWindow::osdAddMessage(unsigned int color, const char* msg) panel->osdAddMessage(color, msg); } +void MainWindow::saveEnabled(bool enabled) +{ + if (enabledSaved) return; + windowCfg.SetBool("Enabled", enabled); + enabledSaved = true; +} + void MainWindow::closeEvent(QCloseEvent* event) { + if (windowID == 0) + emuInstance->saveEnabledWindows(); + else + saveEnabled(false); + QByteArray geom = saveGeometry(); QByteArray enc = geom.toBase64(QByteArray::Base64Encoding); windowCfg.SetString("Geometry", enc.toStdString()); @@ -789,6 +839,9 @@ void MainWindow::closeEvent(QCloseEvent* event) void MainWindow::createScreenPanel() { + if (panel) delete panel; + panel = nullptr; + hasOGL = globalCfg.GetBool("Screen.UseGL") || (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); @@ -799,7 +852,15 @@ void MainWindow::createScreenPanel() panel = panelGL; - panelGL->createContext(); + // Check that creating the context hasn't failed + if (panelGL->createContext() == false) + { + Log(LogLevel::Error, "Failed to create OpenGL context, falling back to Software Renderer.\n"); + hasOGL = false; + + globalCfg.SetBool("Screen.UseGL", false); + globalCfg.SetInt("3D.Renderer", renderer3D_Software); + } } if (!hasOGL) @@ -810,9 +871,12 @@ void MainWindow::createScreenPanel() } setCentralWidget(panel); - actScreenFiltering->setEnabled(hasOGL); + if (hasMenu) + actScreenFiltering->setEnabled(hasOGL); panel->osdSetEnabled(showOSD); + connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); + connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); } @@ -903,20 +967,12 @@ void MainWindow::dropEvent(QDropEvent* event) QList urls = event->mimeData()->urls(); if (urls.count() > 1) return; // not handling more than one file at once - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } const QString filename = file.last(); const bool romInsideArchive = file.size() > 1; @@ -930,9 +986,8 @@ void MainWindow::dropEvent(QDropEvent* event) if (isNdsRom) { - if (!emuInstance->loadROM(file, true)) + if (!emuThread->bootROM(file)) { - emuThread->emuUnpause(); return; } @@ -941,41 +996,46 @@ void MainWindow::dropEvent(QDropEvent* event) recentFileList.prepend(barredFilename); updateRecentFilesMenu(); - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); - updateCartInserted(false); } else if (isGbaRom) { - if (!emuInstance->loadGBAROM(file)) + if (!emuThread->insertCart(file, true)) { - emuThread->emuUnpause(); return; } - emuThread->emuUnpause(); - updateCartInserted(true); } else { QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); - emuThread->emuUnpause(); return; } } void MainWindow::focusInEvent(QFocusEvent* event) { - emuInstance->audioMute(); + onFocusIn(); } void MainWindow::focusOutEvent(QFocusEvent* event) +{ + onFocusOut(); +} + +void MainWindow::onFocusIn() +{ + focused = true; + if (emuInstance) + emuInstance->audioMute(); +} + +void MainWindow::onFocusOut() { // focusOutEvent is called through the window close event handler // prevent use after free + focused = false; if (emuInstance) emuInstance->audioMute(); } @@ -1009,6 +1069,9 @@ bool MainWindow::verifySetup() bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) { + if (file.isEmpty() && gbafile.isEmpty()) + return false; + if (!verifySetup()) { return false; @@ -1017,7 +1080,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool gbaloaded = false; if (!gbafile.isEmpty()) { - if (!emuInstance->loadGBAROM(gbafile)) return false; + if (!emuThread->insertCart(gbafile, true)) + return false; gbaloaded = true; } @@ -1025,33 +1089,31 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool ndsloaded = false; if (!file.isEmpty()) { - if (!emuInstance->loadROM(file, true)) return false; + if (boot) + { + if (!emuThread->bootROM(file)) + return false; + } + else + { + if (!emuThread->insertCart(file, false)) + return false; + } recentFileList.removeAll(file.join("|")); recentFileList.prepend(file.join("|")); updateRecentFilesMenu(); ndsloaded = true; } - - if (boot) + else if (boot) { - if (ndsloaded) - { - emuInstance->nds->Start(); - emuThread->emuRun(); - } - else - { - onBootFirmware(); - } + if (!emuThread->bootFirmware()) + return false; } updateCartInserted(false); - if (gbaloaded) - { updateCartInserted(true); - } return true; } @@ -1162,6 +1224,8 @@ QString MainWindow::pickFileFromArchive(QString archiveFileName) QStringList MainWindow::pickROM(bool gba) { + emuThread->emuPause(); + const QString console = gba ? "GBA" : "DS"; const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; @@ -1186,53 +1250,64 @@ QStringList MainWindow::pickROM(bool gba) "All supported files (*" + allROMs + ")" + extraFilters ); - if (filename.isEmpty()) return {}; + if (filename.isEmpty()) + { + emuThread->emuUnpause(); + return {}; + } globalCfg.SetQString("LastROMFolder", QFileInfo(filename).dir().path()); - return splitArchivePath(filename, false); + auto ret = splitArchivePath(filename, false); + emuThread->emuUnpause(); + return ret; } void MainWindow::updateCartInserted(bool gba) { bool inserted; + QString label; if (gba) { - inserted = emuInstance->gbaCartInserted() && (globalCfg.GetInt("Emu.ConsoleType") == 0); - actCurrentGBACart->setText("GBA slot: " + emuInstance->gbaCartLabel()); - actEjectGBACart->setEnabled(inserted); + inserted = emuInstance->gbaCartInserted() && (emuInstance->getConsoleType() == 0); + label = "GBA slot: " + emuInstance->gbaCartLabel(); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + if (!win->hasMenu) return; + win->actCurrentGBACart->setText(label); + win->actEjectGBACart->setEnabled(inserted); + }); } else { inserted = emuInstance->cartInserted(); - actCurrentCart->setText("DS slot: " + emuInstance->cartLabel()); - actEjectCart->setEnabled(inserted); - actImportSavefile->setEnabled(inserted); - actSetupCheats->setEnabled(inserted); - actROMInfo->setEnabled(inserted); - actRAMInfo->setEnabled(inserted); + label = "DS slot: " + emuInstance->cartLabel(); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + if (!win->hasMenu) return; + win->actCurrentCart->setText(label); + win->actEjectCart->setEnabled(inserted); + win->actImportSavefile->setEnabled(inserted); + win->actEnableCheats->setEnabled(inserted); + win->actSetupCheats->setEnabled(inserted); + win->actROMInfo->setEnabled(inserted); + win->actRAMInfo->setEnabled(inserted); + }); } } void MainWindow::onOpenFile() { - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } QStringList file = pickROM(false); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } - - if (!emuInstance->loadROM(file, true)) + + if (!emuThread->bootROM(file)) { - emuThread->emuUnpause(); return; } @@ -1241,10 +1316,6 @@ void MainWindow::onOpenFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); - updateCartInserted(false); } @@ -1255,12 +1326,23 @@ void MainWindow::onClearRecentFiles() updateRecentFilesMenu(); } -void MainWindow::updateRecentFilesMenu() +void MainWindow::loadRecentFilesMenu(bool loadcfg) { - recentMenu->clear(); + if (loadcfg) + { + recentFileList.clear(); - Config::Array recentroms = globalCfg.GetArray("RecentROM"); - recentroms.Clear(); + Config::Array recentROMs = globalCfg.GetArray("RecentROM"); + int numrecent = std::min(kMaxRecentROMs, (int) recentROMs.Size()); + for (int i = 0; i < numrecent; ++i) + { + std::string item = recentROMs.GetString(i); + if (!item.empty()) + recentFileList.push_back(QString::fromStdString(item)); + } + } + + recentMenu->clear(); for (int i = 0; i < recentFileList.size(); ++i) { @@ -1291,8 +1373,6 @@ void MainWindow::updateRecentFilesMenu() QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); actRecentFile_i->setData(item_full); connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - - recentroms.SetQString(i, recentFileList.at(i)); } while (recentFileList.size() > 10) @@ -1305,8 +1385,24 @@ void MainWindow::updateRecentFilesMenu() if (recentFileList.empty()) actClearRecentList->setEnabled(false); +} + +void MainWindow::updateRecentFilesMenu() +{ + Config::Array recentroms = globalCfg.GetArray("RecentROM"); + recentroms.Clear(); + + for (int i = 0; i < recentFileList.size(); ++i) + { + if (i >= kMaxRecentROMs) break; + + recentroms.SetQString(i, recentFileList.at(i)); + } Config::Save(); + loadRecentFilesMenu(false); + + emuInstance->broadcastCommand(InstCmd_UpdateRecentFiles); } void MainWindow::onClickRecentFile() @@ -1314,24 +1410,15 @@ void MainWindow::onClickRecentFile() QAction *act = (QAction *)sender(); QString filename = act->data().toString(); - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } const QStringList file = splitArchivePath(filename, true); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } - - if (!emuInstance->loadROM(file, true)) + + if (!emuThread->bootROM(file)) { - emuThread->emuUnpause(); return; } @@ -1339,88 +1426,52 @@ void MainWindow::onClickRecentFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); - updateCartInserted(false); } void MainWindow::onBootFirmware() { - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } - if (!emuInstance->bootToMenu()) + if (!emuThread->bootFirmware()) { - // TODO: better error reporting? QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); - emuThread->emuUnpause(); return; } - - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); } void MainWindow::onInsertCart() { - emuThread->emuPause(); - QStringList file = pickROM(false); if (file.isEmpty()) + return; + + if (!emuThread->insertCart(file, false)) { - emuThread->emuUnpause(); return; } - if (!emuInstance->loadROM(file, false)) - { - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - updateCartInserted(false); } void MainWindow::onEjectCart() { - emuThread->emuPause(); - - emuInstance->ejectCart(); - - emuThread->emuUnpause(); - + emuThread->ejectCart(false); updateCartInserted(false); } void MainWindow::onInsertGBACart() { - emuThread->emuPause(); - QStringList file = pickROM(true); if (file.isEmpty()) + return; + + if (!emuThread->insertCart(file, true)) { - emuThread->emuUnpause(); return; } - if (!emuInstance->loadGBAROM(file)) - { - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - updateCartInserted(true); } @@ -1429,23 +1480,13 @@ void MainWindow::onInsertGBAAddon() QAction* act = (QAction*)sender(); int type = act->data().toInt(); - emuThread->emuPause(); - - emuInstance->loadGBAAddon(type); - - emuThread->emuUnpause(); - + emuThread->insertGBAAddon(type); updateCartInserted(true); } void MainWindow::onEjectGBACart() { - emuThread->emuPause(); - - emuInstance->ejectGBACart(); - - emuThread->emuUnpause(); - + emuThread->ejectCart(true); updateCartInserted(true); } @@ -1453,30 +1494,25 @@ void MainWindow::onSaveState() { int slot = ((QAction*)sender())->data().toInt(); - emuThread->emuPause(); - - std::string filename; + QString filename; if (slot > 0) { - filename = emuInstance->getSavestateName(slot); + filename = QString::fromStdString(emuInstance->getSavestateName(slot)); } else { // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getSaveFileName(this, + emuThread->emuPause(); + filename = QFileDialog::getSaveFileName(this, "Save state", globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.mln);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); + emuThread->emuUnpause(); + if (filename.isEmpty()) return; - } - - filename = qfilename.toStdString(); } - if (emuInstance->saveState(filename)) + if (emuThread->saveState(filename)) { if (slot > 0) emuInstance->osdAddMessage(0, "State saved to slot %d", slot); else emuInstance->osdAddMessage(0, "State saved to file"); @@ -1487,47 +1523,39 @@ void MainWindow::onSaveState() { emuInstance->osdAddMessage(0xFFA0A0, "State save failed"); } - - emuThread->emuUnpause(); } void MainWindow::onLoadState() { int slot = ((QAction*)sender())->data().toInt(); - emuThread->emuPause(); - - std::string filename; + QString filename; if (slot > 0) { - filename = emuInstance->getSavestateName(slot); + filename = QString::fromStdString(emuInstance->getSavestateName(slot)); } else { // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getOpenFileName(this, + emuThread->emuPause(); + filename = QFileDialog::getOpenFileName(this, "Load state", globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.ml*);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); + emuThread->emuUnpause(); + if (filename.isEmpty()) return; - } - - filename = qfilename.toStdString(); } - if (!Platform::FileExists(filename)) + if (!Platform::FileExists(filename.toStdString())) { if (slot > 0) emuInstance->osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); else emuInstance->osdAddMessage(0xFFA0A0, "State file does not exist"); - emuThread->emuUnpause(); return; } - if (emuInstance->loadState(filename)) + if (emuThread->loadState(filename)) { if (slot > 0) emuInstance->osdAddMessage(0, "State loaded from slot %d", slot); else emuInstance->osdAddMessage(0, "State loaded from file"); @@ -1538,38 +1566,28 @@ void MainWindow::onLoadState() { emuInstance->osdAddMessage(0xFFA0A0, "State load failed"); } - - emuThread->emuUnpause(); } void MainWindow::onUndoStateLoad() { - emuThread->emuPause(); - emuInstance->undoStateLoad(); - emuThread->emuUnpause(); + emuThread->undoStateLoad(); emuInstance->osdAddMessage(0, "State load undone"); } void MainWindow::onImportSavefile() { - emuThread->emuPause(); QString path = QFileDialog::getOpenFileName(this, "Select savefile", globalCfg.GetQString("LastROMFolder"), "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); if (path.isEmpty()) - { - emuThread->emuUnpause(); return; - } - Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); - if (!f) + if (!Platform::FileExists(path.toStdString())) { QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); - emuThread->emuUnpause(); return; } @@ -1580,24 +1598,15 @@ void MainWindow::onImportSavefile() "The emulation will be reset and the current savefile overwritten.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) { - emuThread->emuUnpause(); return; } - - emuInstance->reset(); } - u32 len = FileLength(f); - - std::unique_ptr data = std::make_unique(len); - Platform::FileRewind(f); - Platform::FileRead(data.get(), len, 1, f); - - assert(emuInstance->nds != nullptr); - emuInstance->nds->SetNDSSave(data.get(), len); - - CloseFile(f); - emuThread->emuUnpause(); + if (!emuThread->importSavefile(path)) + { + QMessageBox::critical(this, "melonDS", "Could not import the given savefile."); + return; + } } void MainWindow::onQuit() @@ -1660,7 +1669,12 @@ void MainWindow::onOpenPowerManagement() void MainWindow::onEnableCheats(bool checked) { localCfg.SetBool("EnableCheats", checked); - emuInstance->enableCheats(checked); + emuThread->enableCheats(checked); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + win->actEnableCheats->setChecked(checked); + }, windowID); } void MainWindow::onSetupCheats() @@ -1730,6 +1744,8 @@ void MainWindow::onNPTest() void MainWindow::updateMPInterface(MPInterfaceType type) { + if (!hasMenu) return; + // MP interface was changed, reflect it in the UI bool enable = (type == MPInterface_Local); @@ -1772,15 +1788,15 @@ void MainWindow::onEmuSettingsDialogFinished(int res) if (globalCfg.GetInt("Emu.ConsoleType") == 1) { actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); + for (auto act : actInsertGBAAddon) + act->setEnabled(false); actEjectGBACart->setEnabled(false); } else { actInsertGBACart->setEnabled(true); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(true); + for (auto act : actInsertGBAAddon) + act->setEnabled(true); actEjectGBACart->setEnabled(emuInstance->gbaCartInserted()); } @@ -1885,6 +1901,7 @@ void MainWindow::onUpdateAudioVolume(int vol, int dsisync) void MainWindow::onUpdateAudioSettings() { + if (!emuThread->emuIsActive()) return; assert(emuInstance->nds != nullptr); int interp = globalCfg.GetInt("Audio.Interpolation"); @@ -1946,9 +1963,9 @@ void MainWindow::onOpenInterfaceSettings() void MainWindow::onUpdateInterfaceSettings() { pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus"); - emuInstance->targetFPS = 1.0 / globalCfg.GetDouble("TargetFPS"); - emuInstance->fastForwardFPS = 1.0 / globalCfg.GetDouble("FastForwardFPS"); - emuInstance->slowmoFPS = 1.0 / globalCfg.GetDouble("SlowmoFPS"); + emuInstance->targetFPS = globalCfg.GetDouble("TargetFPS"); + emuInstance->fastForwardFPS = globalCfg.GetDouble("FastForwardFPS"); + emuInstance->slowmoFPS = globalCfg.GetDouble("SlowmoFPS"); panel->setMouseHide(globalCfg.GetBool("MouseHide"), globalCfg.GetInt("MouseHideSeconds")*1000); } @@ -2051,6 +2068,11 @@ void MainWindow::onChangeIntegerScaling(bool checked) emit screenLayoutChange(); } +void MainWindow::onOpenNewWindow() +{ + emuInstance->createWindow(); +} + void MainWindow::onChangeScreenFiltering(bool checked) { windowCfg.SetBool("ScreenFilter", checked); @@ -2084,24 +2106,28 @@ void MainWindow::onTitleUpdate(QString title) setWindowTitle(title); } -void ToggleFullscreen(MainWindow* mainWindow) +void MainWindow::toggleFullscreen() { - if (!mainWindow->isFullScreen()) + if (!isFullScreen()) { - mainWindow->showFullScreen(); - mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working + showFullScreen(); + if (hasMenu) + menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working } else { - mainWindow->showNormal(); - int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); - mainWindow->menuBar()->setFixedHeight(menuBarHeight); + showNormal(); + if (hasMenu) + { + int menuBarHeight = menuBar()->sizeHint().height(); + menuBar()->setFixedHeight(menuBarHeight); + } } } void MainWindow::onFullscreenToggled() { - ToggleFullscreen(this); + toggleFullscreen(); } void MainWindow::onScreenEmphasisToggled() @@ -2122,6 +2148,8 @@ void MainWindow::onScreenEmphasisToggled() void MainWindow::onEmuStart() { + if (!hasMenu) return; + for (int i = 1; i < 9; i++) { actSaveState[i]->setEnabled(true); @@ -2145,6 +2173,8 @@ void MainWindow::onEmuStart() void MainWindow::onEmuStop() { + if (!hasMenu) return; + for (int i = 0; i < 9; i++) { actSaveState[i]->setEnabled(false); @@ -2165,31 +2195,69 @@ void MainWindow::onEmuStop() void MainWindow::onEmuPause(bool pause) { + if (!hasMenu) return; + actPause->setChecked(pause); } void MainWindow::onEmuReset() { + if (!hasMenu) return; + actUndoStateLoad->setEnabled(false); } void MainWindow::onUpdateVideoSettings(bool glchange) { + if (!emuInstance) return; + + // if we have a parent window: pass the message over to the parent + // the topmost parent takes care of updating all the windows + MainWindow* parentwin = (MainWindow*)parentWidget(); + if (parentwin) + return parentwin->onUpdateVideoSettings(glchange); + + bool hadOGL = hasOGL; if (glchange) { emuThread->emuPause(); - if (hasOGL) emuThread->deinitContext(); + if (hadOGL) emuThread->deinitContext(windowID); - delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); } emuThread->updateVideoSettings(); if (glchange) { - if (hasOGL) emuThread->initContext(); + if (hasOGL) emuThread->initContext(windowID); + } + + // update any child windows we have + auto childwins = findChildren(nullptr, Qt::FindDirectChildrenOnly); + for (auto child: childwins) + { + // child windows may belong to a different instance + // in that case we need to signal their thread appropriately + auto thread = child->getEmuInstance()->getEmuThread(); + + if (glchange) + { + if (hadOGL) thread->deinitContext(child->windowID); + child->createScreenPanel(); + } + + if (child->getWindowID() == 0) + thread->updateVideoSettings(); + + if (glchange) + { + if (hasOGL) thread->initContext(child->windowID); + } + } + + if (glchange) + { emuThread->emuUnpause(); } } diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index 30d97b17..9f652f54 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -110,6 +110,13 @@ public: EmuInstance* getEmuInstance() { return emuInstance; } Config::Table& getWindowConfig() { return windowCfg; } + int getWindowID() { return windowID; } + + bool winHasMenu() { return hasMenu; } + + void saveEnabled(bool enabled); + + void toggleFullscreen(); bool hasOpenGL() { return hasOGL; } GL::Context* getOGLContext(); @@ -124,11 +131,18 @@ public: void onAppStateChanged(Qt::ApplicationState state); + void onFocusIn(); + void onFocusOut(); + bool isFocused() { return focused; } + void osdAddMessage(unsigned int color, const char* msg); // called when the MP interface is changed void updateMPInterface(melonDS::MPInterfaceType type); + void loadRecentFilesMenu(bool loadcfg); + //void updateVideoSettings(bool glchange); + protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; @@ -208,6 +222,7 @@ private slots: void onChangeScreenSizing(QAction* act); void onChangeScreenAspect(QAction* act); void onChangeIntegerScaling(bool checked); + void onOpenNewWindow(); void onChangeScreenFiltering(bool checked); void onChangeShowOSD(bool checked); void onChangeLimitFramerate(bool checked); @@ -251,6 +266,9 @@ private: bool pausedManually; int windowID; + bool enabledSaved; + + bool focused; EmuInstance* emuInstance; EmuThread* emuThread; @@ -262,6 +280,8 @@ private: public: ScreenPanel* panel; + bool hasMenu; + QAction* actOpenROM; QAction* actBootFirmware; QAction* actCurrentCart; @@ -269,7 +289,7 @@ public: QAction* actEjectCart; QAction* actCurrentGBACart; QAction* actInsertGBACart; - QAction* actInsertGBAAddon[2]; + QList actInsertGBAAddon; QAction* actEjectGBACart; QAction* actImportSavefile; QAction* actSaveState[9]; @@ -325,12 +345,13 @@ public: QAction** actScreenAspectTop; QActionGroup* grpScreenAspectBot; QAction** actScreenAspectBot; + QAction* actNewWindow; QAction* actScreenFiltering; QAction* actShowOSD; QAction* actLimitFramerate; QAction* actAudioSync; + + QAction* actAbout; }; -void ToggleFullscreen(MainWindow* mainWindow); - #endif // WINDOW_H diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 9a9c93cb..d940340a 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -168,6 +168,18 @@ int numEmuInstances() } +void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst) +{ + for (int i = 0; i < kMaxEmuInstances; i++) + { + if (i == sourceinst) continue; + if (!emuInstances[i]) continue; + + emuInstances[i]->handleCommand(cmd, param); + } +} + + void pathInit() { // First, check for the portable directory next to the executable. @@ -250,10 +262,8 @@ bool MelonApplication::event(QEvent *event) MainWindow* win = inst->getMainWindow(); QFileOpenEvent *openEvent = static_cast(event); - inst->getEmuThread()->emuPause(); const QStringList file = win->splitArchivePath(openEvent->file(), true); - if (!win->preloadROMs(file, {}, true)) - inst->getEmuThread()->emuUnpause(); + win->preloadROMs(file, {}, true); } return QApplication::event(event); @@ -364,6 +374,9 @@ int main(int argc, char** argv) if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); win->preloadROMs(dsfile, gbafile, options->boot); + + if (options->fullscreen) + win->toggleFullscreen(); } int ret = melon.exec(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 77cdf4ee..e0d38963 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -31,6 +31,15 @@ #include "ScreenLayout.h" #include "MPInterface.h" +enum +{ + InstCmd_Pause, + InstCmd_Unpause, + + InstCmd_UpdateRecentFiles, + //InstCmd_UpdateVideoSettings, +}; + class MelonApplication : public QApplication { Q_OBJECT @@ -50,6 +59,8 @@ void deleteEmuInstance(int id); void deleteAllEmuInstances(int first = 0); int numEmuInstances(); +void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst); + void setMPInterface(melonDS::MPInterfaceType type); #endif // MAIN_H diff --git a/src/version.h.in b/src/version.h.in index 5aed0d49..9b4cd8ce 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -25,5 +25,11 @@ #define MELONDS_VERSION_SUFFIX "${MELONDS_VERSION_SUFFIX}" #define MELONDS_VERSION MELONDS_VERSION_BASE MELONDS_VERSION_SUFFIX +#ifdef MELONDS_EMBED_BUILD_INFO +#define MELONDS_GIT_BRANCH "${MELONDS_GIT_BRANCH}" +#define MELONDS_GIT_HASH "${MELONDS_GIT_HASH}" +#define MELONDS_BUILD_PROVIDER "${MELONDS_BUILD_PROVIDER}" +#endif + #endif // VERSION_H