diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml deleted file mode 100644 index 7e7df583..00000000 --- a/.github/workflows/build-appimage.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: CMake Build (AppImage x86-64) - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v1 - - name: Install dependencies - run: | - sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list - sudo apt update - sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev libqt5multimedia5-plugins qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades - - name: Create build environment - run: mkdir ${{runner.workspace}}/build - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE - - name: Make - working-directory: ${{runner.workspace}}/build - run: | - make -j$(nproc --all) - - name: Prepare AppDir for AppImage - working-directory: ${{runner.workspace}}/build - run: | - make install DESTDIR=AppDir - mv ./AppDir/usr/local/bin ./AppDir/usr/bin - mv ./AppDir/usr/local/share ./AppDir/usr/share - rm -rf ./AppDir/usr/local - - name: Prepare necessary Tools for building the AppImage - working-directory: ${{runner.workspace}}/build - run: | - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage - wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage - chmod a+x linuxdeploy-x86_64.AppImage - chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage - - name: Build the AppImage - working-directory: ${{runner.workspace}}/build - run: | - ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage - mkdir dist - cp ./melonDS*.AppImage ./dist - - uses: actions/upload-artifact@v1 - with: - name: melonDS-appimage-x86_64 - path: ${{runner.workspace}}/build/dist diff --git a/.github/workflows/build-macos-universal.yml b/.github/workflows/build-macos-universal.yml deleted file mode 100644 index 4416ce7a..00000000 --- a/.github/workflows/build-macos-universal.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CMake Build (macOS Universal) - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - prepare: - runs-on: [self-hosted, macOS, ARM64] - - steps: - - name: Clean workspace - run: rm -rf ${{runner.workspace}}/build - - - uses: actions/checkout@v3 - - - build-arm64: - needs: prepare - runs-on: [self-hosted, macOS, ARM64] - env: - homebrew_prefix: /opt/homebrew - - steps: - - name: Create build directory - run: mkdir -p ${{runner.workspace}}/build/arm64 - - - name: Configure - working-directory: ${{runner.workspace}}/build/arm64 - run: arch -arm64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON - - - name: Make - working-directory: ${{runner.workspace}}/build/arm64 - run: arch -arm64 make -j$(sysctl -n hw.logicalcpu) - - build-x86_64: - needs: prepare - runs-on: [self-hosted, macOS, ARM64] - env: - homebrew_prefix: /usr/local - - steps: - - name: Create build directory - run: mkdir -p ${{runner.workspace}}/build/x86_64 - - - name: Configure - working-directory: ${{runner.workspace}}/build/x86_64 - run: arch -x86_64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON - - - name: Make - working-directory: ${{runner.workspace}}/build/x86_64 - run: arch -x86_64 make -j$(sysctl -n hw.logicalcpu) - - universal-binary: - needs: [build-arm64, build-x86_64] - runs-on: [self-hosted, macOS, ARM64] - - steps: - - name: Merge binaries - run: $GITHUB_WORKSPACE/tools/mac-universal.py ${{runner.workspace}}/build/arm64/melonDS.app ${{runner.workspace}}/build/x86_64/melonDS.app ${{runner.workspace}}/build/universal/melonDS.app - - - name: Codesign app - run: codesign -s - --deep -f ${{runner.workspace}}/build/universal/melonDS.app - - - name: Create DMG - run: hdiutil create -fs HFS+ -volname melonDS -srcfolder ${{runner.workspace}}/build/universal/melonDS.app -ov -format UDBZ ${{runner.workspace}}/build/universal/melonDS.dmg - - - uses: actions/upload-artifact@v3 - with: - name: macOS-universal - path: ${{runner.workspace}}/build/universal/melonDS.dmg - diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 00000000..349f99d2 --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,84 @@ +name: macOS + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build-macos: + strategy: + matrix: + arch: [x86_64, arm64] + + name: ${{ matrix.arch }} + runs-on: macos-14 + steps: + - name: Check out sources + uses: actions/checkout@v3 + - name: Install dependencies for package building + run: | + brew install autoconf automake autoconf-archive libtool && pip3 install setuptools + - name: Set up CMake + uses: lukka/get-cmake@latest + - name: Set up vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 53bef8994c541b6561884a8395ea35715ece75db + - name: Build + uses: lukka/run-cmake@v10 + with: + configurePreset: release-mac-${{ matrix.arch }} + buildPreset: release-mac-${{ matrix.arch }} + - name: Compress app bundle + shell: bash + run: | + cd build/release-mac-${{ matrix.arch }} + zip -r -y ../../macOS-${{ matrix.arch }}.zip melonDS.app + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: macOS-${{ matrix.arch }} + path: macOS-${{ matrix.arch }}.zip + + universal-binary: + name: Universal binary + needs: [build-macos] + runs-on: macos-13 + continue-on-error: true + steps: + - name: Download x86_64 + uses: actions/download-artifact@v4 + with: + name: macOS-x86_64 + path: x86_64 + - name: Download arm64 + uses: actions/download-artifact@v4 + with: + name: macOS-arm64 + path: arm64 + - name: Combine app bundles + shell: bash + run: | + unzip x86_64/*.zip -d x86_64 + unzip arm64/*.zip -d arm64 + lipo {x86_64,arm64}/melonDS.app/Contents/MacOS/melonDS -create -output melonDS + cp -a arm64/melonDS.app melonDS.app + cp melonDS melonDS.app/Contents/MacOS/melonDS + codesign -s - --deep melonDS.app + zip -r -y macOS-universal.zip melonDS.app + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: macOS-universal + path: macOS-universal.zip + - name: Clean up architecture-specific artifacts + uses: geekyeggo/delete-artifact@v4 + with: + failOnError: false + name: | + macOS-x86_64 + macOS-arm64 diff --git a/.github/workflows/build-ubuntu-aarch64.yml b/.github/workflows/build-ubuntu-aarch64.yml deleted file mode 100644 index 096bc0b9..00000000 --- a/.github/workflows/build-ubuntu-aarch64.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: CMake Build (Ubuntu aarch64) - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: ubuntu-20.04 - container: ubuntu:20.04 - - steps: - - name: Prepare system - shell: bash - run: | - apt update - apt -y full-upgrade - apt -y install git - - name: Check out source - uses: actions/checkout@v1 - - name: Install dependencies - shell: bash - run: | - dpkg --add-architecture arm64 - sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new" - rm /etc/apt/sources.list - mv /etc/apt/sources.list{.new,} - apt update - DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtbase5-private,qtmultimedia5,libslirp,libarchive,libzstd}-dev:arm64 zstd:arm64 cmake extra-cmake-modules dpkg-dev - - name: Configure - shell: bash - run: | - CC=aarch64-linux-gnu-gcc-10 CXX=aarch64-linux-gnu-g++-10 cmake -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -B build - - name: Make - shell: bash - run: | - cmake --build build -j$(nproc --all) - mkdir dist - cp build/melonDS dist - - uses: actions/upload-artifact@v1 - with: - name: melonDS-ubuntu-aarch64 - path: dist diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index b6c50e9a..e96f98aa 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -1,4 +1,4 @@ -name: CMake Build (Ubuntu x86-64) +name: Ubuntu on: push: @@ -9,29 +9,77 @@ on: - master jobs: - build: - - runs-on: ubuntu-20.04 + build-x86_64: + name: x86_64 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + name: Check out sources - name: Install dependencies run: | sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo apt update - sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades - - name: Create build environment - run: mkdir ${{runner.workspace}}/build + sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev \ + qt6-{base,base-private,multimedia}-dev libslirp0 libslirp-dev libarchive-dev libzstd-dev libfuse2 - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE - - name: Make - working-directory: ${{runner.workspace}}/build + run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr + - name: Build run: | - make -j$(nproc --all) - mkdir dist - cp melonDS dist - - uses: actions/upload-artifact@v1 + cmake --build build + DESTDIR=AppDir cmake --install build + - uses: actions/upload-artifact@v4 with: name: melonDS-ubuntu-x86_64 - path: ${{runner.workspace}}/build/dist + path: AppDir/usr/bin/melonDS + - name: Fetch AppImage tools + run: | + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage + chmod a+x linuxdeploy-*.AppImage + - name: Build the AppImage + env: + QMAKE: /usr/lib/qt6/bin/qmake + run: | + ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage + - uses: actions/upload-artifact@v4 + with: + name: melonDS-appimage-x86_64 + path: melonDS*.AppImage + + build-aarch64: + name: aarch64 + runs-on: ubuntu-latest + container: ubuntu:22.04 + + steps: + - name: Prepare system + shell: bash + run: | + dpkg --add-architecture arm64 + sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new" + rm /etc/apt/sources.list + mv /etc/apt/sources.list{.new,} + apt update + apt -y full-upgrade + apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \ + {libsdl2,qt6-{base,base-private,multimedia},libslirp,libarchive,libzstd}-dev:arm64 \ + pkg-config dpkg-dev + - name: Check out source + uses: actions/checkout@v4 + - name: Configure + shell: bash + run: | + cmake -B build -G Ninja \ + -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 + - name: Build + shell: bash + run: | + cmake --build build + - uses: actions/upload-artifact@v4 + with: + name: melonDS-ubuntu-aarch64 + path: build/melonDS diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 70b11c05..e6846da7 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,4 +1,4 @@ -name: CMake Build (Windows x86-64) +name: Windows on: push: diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..e14eda24 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,88 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "release", + "displayName": "Release", + "description": "Default release build configuration.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/release" + }, + { + "inherits": "release", + "name": "release-vcpkg", + "displayName": "Release (vcpkg)", + "description": "Release build with packages from vcpkg.", + "cacheVariables": { + "USE_VCPKG": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "release-mac-x86_64", + "inherits": "release-vcpkg", + "displayName": "macOS release (x86_64)", + "binaryDir": "${sourceDir}/build/release-mac-x86_64", + "cacheVariables": { "CMAKE_OSX_ARCHITECTURES": "x86_64" } + }, + { + "name": "release-mac-arm64", + "inherits": "release-vcpkg", + "displayName": "macOS release (arm64)", + "binaryDir": "${sourceDir}/build/release-mac-arm64", + "cacheVariables": { "CMAKE_OSX_ARCHITECTURES": "arm64" } + } + ], + "buildPresets": [ + { + "name": "release", + "configurePreset": "release" + }, + { + "name": "release-vcpkg", + "configurePreset": "release-vcpkg" + }, + { + "name": "release-mac-x86_64", + "configurePreset": "release-mac-x86_64" + }, + { + "name": "release-mac-arm64", + "configurePreset": "release-mac-arm64" + } + ], + "workflowPresets": [ + { + "name": "release", + "displayName": "Release", + "steps": [ + { "type": "configure", "name": "release" }, + { "type": "build", "name": "release" } + ] + }, + { + "name": "release-vcpkg", + "displayName": "Release (vcpkg)", + "steps": [ + { "type": "configure", "name": "release-vcpkg" }, + { "type": "build", "name": "release-vcpkg" } + ] + }, + { + "name": "release-mac-x86_64", + "steps": [ + { "type": "configure", "name": "release-mac-x86_64" }, + { "type": "build", "name": "release-mac-x86_64" } + ] + }, + { + "name": "release-mac-arm64", + "steps": [ + { "type": "configure", "name": "release-mac-arm64" }, + { "type": "build", "name": "release-mac-arm64" } + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index de494305..268c2b3a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - +

DS emulator, sorta diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index f7f7f62f..c9f3e92f 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -4,9 +4,10 @@ set(_DEFAULT_VCPKG_ROOT "${CMAKE_SOURCE_DIR}/vcpkg") set(VCPKG_ROOT "${_DEFAULT_VCPKG_ROOT}" CACHE STRING "The path to the vcpkg repository") if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") + file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE) FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2023.10.19 + GIT_TAG 2024.01.12 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.h b/src/ARMJIT_A64/ARMJIT_Compiler.h index 04f12e85..2b0048a9 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.h +++ b/src/ARMJIT_A64/ARMJIT_Compiler.h @@ -69,7 +69,7 @@ struct Op2 bool IsSimpleReg() { return !IsImm && !Reg.ShiftAmount && Reg.ShiftType == Arm64Gen::ST_LSL; } bool ImmFits12Bit() - { return IsImm && (Imm & 0xFFF == Imm); } + { return IsImm && ((Imm & 0xFFF) == Imm); } bool IsZero() { return IsImm && !Imm; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b1ae4c47..3dfd3b0d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,6 +128,15 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL) target_compile_options(teakra PRIVATE "$<$:-Og>") target_link_libraries(core PRIVATE teakra) +if (NOT MSVC) + # MSVC has its own compiler flag syntax; if we ever support it, + # be sure to silence any equivalent warnings there. + + target_compile_options(core PRIVATE "$<$:-Wno-invalid-offsetof>") + # These warnings are excessive, and are only triggered in the ARMJIT code + # (which is fundamentally non-portable, so this is fine) +endif() + find_library(m MATH_LIBRARY) if (MATH_LIBRARY) @@ -145,11 +154,13 @@ endif() if (WIN32) target_link_libraries(core PRIVATE ole32 comctl32 wsock32 ws2_32) -elseif(NOT APPLE) +elseif(NOT APPLE AND NOT HAIKU) check_library_exists(rt shm_open "" NEED_LIBRT) if (NEED_LIBRT) target_link_libraries(core PRIVATE rt) endif() +elseif(HAIKU) + target_link_libraries(core PRIVATE network) endif() if (ENABLE_JIT_PROFILING) diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index 5f767142..8da02540 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -122,7 +122,8 @@ NANDImage::NANDImage(NANDImage&& other) noexcept : ConsoleID(other.ConsoleID), FATIV(other.FATIV), FATKey(other.FATKey), - ESKey(other.ESKey) + ESKey(other.ESKey), + Length(other.Length) { other.CurFile = nullptr; } @@ -140,6 +141,7 @@ NANDImage& NANDImage::operator=(NANDImage&& other) noexcept FATIV = other.FATIV; FATKey = other.FATKey; ESKey = other.ESKey; + Length = other.Length; other.CurFile = nullptr; } diff --git a/src/FATStorage.cpp b/src/FATStorage.cpp index 52011a8e..0f1bf235 100644 --- a/src/FATStorage.cpp +++ b/src/FATStorage.cpp @@ -32,14 +32,8 @@ using namespace Platform; using std::string; FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional& sourcedir) : - FilePath(filename), - FileSize(size), - ReadOnly(readonly), - SourceDir(sourcedir) + FATStorage(FATStorageArgs { filename, size, readonly, sourcedir }) { - Load(filename, size, sourcedir); - - File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); } FATStorage::FATStorage(const FATStorageArgs& args) noexcept : @@ -54,8 +48,6 @@ FATStorage::FATStorage(FATStorageArgs&& args) noexcept : SourceDir(std::move(args.SourceDir)) { Load(FilePath, FileSize, SourceDir); - - File = nullptr; } FATStorage::FATStorage(FATStorage&& other) noexcept @@ -77,7 +69,10 @@ FATStorage& FATStorage::operator=(FATStorage&& other) noexcept if (this != &other) { if (File) + { // Sync this file's contents to the host (if applicable) before closing it + if (!ReadOnly) Save(); CloseFile(File); + } FilePath = std::move(other.FilePath); IndexPath = std::move(other.IndexPath); @@ -89,6 +84,7 @@ FATStorage& FATStorage::operator=(FATStorage&& other) noexcept FileIndex = std::move(other.FileIndex); other.File = nullptr; + other.SourceDir = std::nullopt; } return *this; @@ -105,11 +101,8 @@ FATStorage::~FATStorage() bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) { if (!File) return false; - if (FF_File) return false; - FF_File = File; - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); FRESULT res; FATFS fs; @@ -118,7 +111,6 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) if (res != FR_OK) { ff_disk_close(); - FF_File = nullptr; return false; } @@ -130,7 +122,6 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) { f_unmount("0:"); ff_disk_close(); - FF_File = nullptr; return false; } @@ -140,18 +131,14 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) f_unmount("0:"); ff_disk_close(); - FF_File = nullptr; return nwrite==len; } u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) { if (!File) return false; - if (FF_File) return false; - FF_File = File; - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); FRESULT res; FATFS fs; @@ -160,7 +147,6 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) if (res != FR_OK) { ff_disk_close(); - FF_File = nullptr; return false; } @@ -172,7 +158,6 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) { f_unmount("0:"); ff_disk_close(); - FF_File = nullptr; return false; } @@ -183,7 +168,6 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) f_unmount("0:"); ff_disk_close(); - FF_File = nullptr; return nread; } @@ -203,18 +187,18 @@ u64 FATStorage::GetSectorCount() const return FileSize / 0x200; } - -FileHandle* FATStorage::FF_File; -u64 FATStorage::FF_FileSize; - -UINT FATStorage::FF_ReadStorage(BYTE* buf, LBA_t sector, UINT num) +ff_disk_read_cb FATStorage::FF_ReadStorage() const noexcept { - return ReadSectorsInternal(FF_File, FF_FileSize, sector, num, buf); + return [this](BYTE* buf, LBA_t sector, UINT num) { + return ReadSectorsInternal(File, FileSize, sector, num, buf); + }; } -UINT FATStorage::FF_WriteStorage(const BYTE* buf, LBA_t sector, UINT num) +ff_disk_write_cb FATStorage::FF_WriteStorage() const noexcept { - return WriteSectorsInternal(FF_File, FF_FileSize, sector, num, buf); + return [this](const BYTE* buf, LBA_t sector, UINT num) { + return WriteSectorsInternal(File, FileSize, sector, num, buf); + }; } @@ -1036,8 +1020,8 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional // with a minimum 128MB extra, otherwise size is defaulted to 512MB bool isnew = !Platform::LocalFileExists(filename); - FF_File = Platform::OpenLocalFile(filename, static_cast(FileMode::ReadWrite | FileMode::Preserve)); - if (!FF_File) + File = Platform::OpenLocalFile(filename, static_cast(FileMode::ReadWrite | FileMode::Preserve)); + if (!File) return false; IndexPath = FilePath + ".idx"; @@ -1053,7 +1037,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional if (FileSize == 0) { - FileSize = FileLength(FF_File); + FileSize = FileLength(File); } } @@ -1067,8 +1051,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional } else { - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FF_FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); res = f_mount(&fs, "0:", 1); if (res != FR_OK) @@ -1104,9 +1087,8 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional FileSize = 0x20000000ULL; // 512MB } - FF_FileSize = FileSize; ff_disk_close(); - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FF_FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); DirIndex.clear(); FileIndex.clear(); @@ -1143,8 +1125,6 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional f_unmount("0:"); ff_disk_close(); - CloseFile(FF_File); - FF_File = nullptr; return true; } @@ -1156,14 +1136,7 @@ bool FATStorage::Save() return true; // Not an error. } - FF_File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); - if (!FF_File) - { - return false; - } - - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); FRESULT res; FATFS fs; @@ -1172,8 +1145,6 @@ bool FATStorage::Save() if (res != FR_OK) { ff_disk_close(); - CloseFile(FF_File); - FF_File = nullptr; return false; } @@ -1184,8 +1155,6 @@ bool FATStorage::Save() f_unmount("0:"); ff_disk_close(); - CloseFile(FF_File); - FF_File = nullptr; return true; } diff --git a/src/FATStorage.h b/src/FATStorage.h index 1e89b764..00628461 100644 --- a/src/FATStorage.h +++ b/src/FATStorage.h @@ -28,6 +28,7 @@ #include "Platform.h" #include "types.h" #include "fatfs/ff.h" +#include "FATIO.h" namespace melonDS { @@ -39,6 +40,8 @@ namespace melonDS struct FATStorageArgs { std::string Filename; + + /// Size of the desired SD card in bytes, or 0 for auto-detect. u64 Size; bool ReadOnly; std::optional SourceDir; @@ -48,8 +51,8 @@ class FATStorage { public: FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional& sourcedir = std::nullopt); - FATStorage(const FATStorageArgs& args) noexcept; - FATStorage(FATStorageArgs&& args) noexcept; + explicit FATStorage(const FATStorageArgs& args) noexcept; + explicit FATStorage(FATStorageArgs&& args) noexcept; FATStorage(FATStorage&& other) noexcept; FATStorage(const FATStorage& other) = delete; FATStorage& operator=(const FATStorage& other) = delete; @@ -74,10 +77,8 @@ private: Platform::FileHandle* File; u64 FileSize; - static Platform::FileHandle* FF_File; - static u64 FF_FileSize; - static UINT FF_ReadStorage(BYTE* buf, LBA_t sector, UINT num); - static UINT FF_WriteStorage(const BYTE* buf, LBA_t sector, UINT num); + [[nodiscard]] ff_disk_read_cb FF_ReadStorage() const noexcept; + [[nodiscard]] ff_disk_write_cb FF_WriteStorage() const noexcept; static u32 ReadSectorsInternal(Platform::FileHandle* file, u64 filelen, u32 start, u32 num, u8* data); static u32 WriteSectorsInternal(Platform::FileHandle* file, u64 filelen, u32 start, u32 num, const u8* data); diff --git a/src/GPU.cpp b/src/GPU.cpp index c226ccbe..f23e641e 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -275,7 +275,8 @@ void GPU::DoSavestate(Savestate* file) noexcept GPU2D_B.DoSavestate(file); GPU3D.DoSavestate(file); - ResetVRAMCache(); + if (!file->Saving) + ResetVRAMCache(); } void GPU::AssignFramebuffers() noexcept diff --git a/src/GPU.h b/src/GPU.h index e070db78..780d5e01 100644 --- a/src/GPU.h +++ b/src/GPU.h @@ -536,18 +536,18 @@ public: u8 VRAMCNT[9] {}; u8 VRAMSTAT = 0; - u8 Palette[2*1024] {}; - u8 OAM[2*1024] {}; + alignas(u64) u8 Palette[2*1024] {}; + alignas(u64) u8 OAM[2*1024] {}; - u8 VRAM_A[128*1024] {}; - u8 VRAM_B[128*1024] {}; - u8 VRAM_C[128*1024] {}; - u8 VRAM_D[128*1024] {}; - u8 VRAM_E[ 64*1024] {}; - u8 VRAM_F[ 16*1024] {}; - u8 VRAM_G[ 16*1024] {}; - u8 VRAM_H[ 32*1024] {}; - u8 VRAM_I[ 16*1024] {}; + alignas(u64) u8 VRAM_A[128*1024] {}; + alignas(u64) u8 VRAM_B[128*1024] {}; + alignas(u64) u8 VRAM_C[128*1024] {}; + alignas(u64) u8 VRAM_D[128*1024] {}; + alignas(u64) u8 VRAM_E[ 64*1024] {}; + alignas(u64) u8 VRAM_F[ 16*1024] {}; + alignas(u64) u8 VRAM_G[ 16*1024] {}; + alignas(u64) u8 VRAM_H[ 32*1024] {}; + alignas(u64) u8 VRAM_I[ 16*1024] {}; u8* const VRAM[9] = {VRAM_A, VRAM_B, VRAM_C, VRAM_D, VRAM_E, VRAM_F, VRAM_G, VRAM_H, VRAM_I}; u32 const VRAMMask[9] = {0x1FFFF, 0x1FFFF, 0x1FFFF, 0x1FFFF, 0xFFFF, 0x3FFF, 0x3FFF, 0x7FFF, 0x3FFF}; @@ -596,14 +596,14 @@ public: u8 VRAMFlat_AOBJ[256*1024] {}; u8 VRAMFlat_BOBJ[128*1024] {}; - u8 VRAMFlat_ABGExtPal[32*1024] {}; - u8 VRAMFlat_BBGExtPal[32*1024] {}; + alignas(u16) u8 VRAMFlat_ABGExtPal[32*1024] {}; + alignas(u16) u8 VRAMFlat_BBGExtPal[32*1024] {}; - u8 VRAMFlat_AOBJExtPal[8*1024] {}; - u8 VRAMFlat_BOBJExtPal[8*1024] {}; + alignas(u16) u8 VRAMFlat_AOBJExtPal[8*1024] {}; + alignas(u16) u8 VRAMFlat_BOBJExtPal[8*1024] {}; - u8 VRAMFlat_Texture[512*1024] {}; - u8 VRAMFlat_TexPal[128*1024] {}; + alignas(u64) u8 VRAMFlat_Texture[512*1024] {}; + alignas(u64) u8 VRAMFlat_TexPal[128*1024] {}; private: void ResetVRAMCache() noexcept; void AssignFramebuffers() noexcept; diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index cdc890ab..5cf6426f 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -146,6 +146,19 @@ GPU3D::GPU3D(melonDS::NDS& nds, std::unique_ptr&& renderer) noexcept { } +void Vertex::DoSavestate(Savestate* file) noexcept +{ + file->VarArray(Position, sizeof(Position)); + file->VarArray(Color, sizeof(Color)); + file->VarArray(TexCoords, sizeof(TexCoords)); + + file->Bool32(&Clipped); + + file->VarArray(FinalPosition, sizeof(FinalPosition)); + file->VarArray(FinalColor, sizeof(FinalColor)); + file->VarArray(HiresPosition, sizeof(HiresPosition)); +} + void GPU3D::SetCurrentRenderer(std::unique_ptr&& renderer) noexcept { CurrentRenderer = std::move(renderer); @@ -299,6 +312,12 @@ void GPU3D::DoSavestate(Savestate* file) noexcept { file->Section("GP3D"); + SoftRenderer* softRenderer = dynamic_cast(CurrentRenderer.get()); + if (softRenderer && softRenderer->IsThreaded()) + { + softRenderer->SetupRenderThread(NDS.GPU); + } + CmdFIFO.DoSavestate(file); CmdPIPE.DoSavestate(file); @@ -374,33 +393,21 @@ void GPU3D::DoSavestate(Savestate* file) noexcept file->Var32(&VertexNumInPoly); file->Var32(&NumConsecutivePolygons); - for (int i = 0; i < 4; i++) + for (Vertex& vtx : TempVertexBuffer) { - Vertex* vtx = &TempVertexBuffer[i]; - - file->VarArray(vtx->Position, sizeof(s32)*4); - file->VarArray(vtx->Color, sizeof(s32)*3); - file->VarArray(vtx->TexCoords, sizeof(s16)*2); - - file->Bool32(&vtx->Clipped); - - file->VarArray(vtx->FinalPosition, sizeof(s32)*2); - file->VarArray(vtx->FinalColor, sizeof(s32)*3); + vtx.DoSavestate(file); } if (file->Saving) { - u32 id; - if (LastStripPolygon) id = (u32)((LastStripPolygon - (&PolygonRAM[0])) / sizeof(Polygon)); - else id = -1; - file->Var32(&id); + u32 index = LastStripPolygon ? (u32)(LastStripPolygon - &PolygonRAM[0]) : UINT32_MAX; + file->Var32(&index); } else { - u32 id; - file->Var32(&id); - if (id == 0xFFFFFFFF) LastStripPolygon = NULL; - else LastStripPolygon = &PolygonRAM[id]; + u32 index = UINT32_MAX; + file->Var32(&index); + LastStripPolygon = (index == UINT32_MAX) ? nullptr : &PolygonRAM[index]; } file->Var32(&CurRAMBank); @@ -411,18 +418,9 @@ void GPU3D::DoSavestate(Savestate* file) noexcept file->Var32(&FlushRequest); file->Var32(&FlushAttributes); - for (int i = 0; i < 6144*2; i++) + for (Vertex& vtx : VertexRAM) { - Vertex* vtx = &VertexRAM[i]; - - file->VarArray(vtx->Position, sizeof(s32)*4); - file->VarArray(vtx->Color, sizeof(s32)*3); - file->VarArray(vtx->TexCoords, sizeof(s16)*2); - - file->Bool32(&vtx->Clipped); - - file->VarArray(vtx->FinalPosition, sizeof(s32)*2); - file->VarArray(vtx->FinalColor, sizeof(s32)*3); + vtx.DoSavestate(file); } for(int i = 0; i < 2048*2; i++) @@ -436,20 +434,17 @@ void GPU3D::DoSavestate(Savestate* file) noexcept for (int j = 0; j < 10; j++) { Vertex* ptr = poly->Vertices[j]; - u32 id; - if (ptr) id = (u32)((ptr - (&VertexRAM[0])) / sizeof(Vertex)); - else id = -1; - file->Var32(&id); + u32 index = ptr ? (u32)(ptr - &VertexRAM[0]) : UINT32_MAX; + file->Var32(&index); } } else { for (int j = 0; j < 10; j++) { - u32 id = -1; - file->Var32(&id); - if (id == 0xFFFFFFFF) poly->Vertices[j] = NULL; - else poly->Vertices[j] = &VertexRAM[id]; + u32 index = UINT32_MAX; + file->Var32(&index); + poly->Vertices[j] = index == UINT32_MAX ? nullptr : &VertexRAM[index]; } } @@ -497,7 +492,6 @@ void GPU3D::DoSavestate(Savestate* file) noexcept } } - // probably not worth storing the vblank-latched Renderxxxxxx variables CmdStallQueue.DoSavestate(file); file->Var32((u32*)&VertexPipeline); @@ -513,10 +507,27 @@ void GPU3D::DoSavestate(Savestate* file) noexcept CurVertexRAM = &VertexRAM[CurRAMBank ? 6144 : 0]; CurPolygonRAM = &PolygonRAM[CurRAMBank ? 2048 : 0]; + } - // better safe than sorry, I guess - // might cause a blank frame but atleast it won't shit itself - RenderNumPolygons = 0; + file->Var32(&RenderNumPolygons); + if (file->Saving) + { + for (const Polygon* p : RenderPolygonRAM) + { + u32 index = p ? (p - &PolygonRAM[0]) : UINT32_MAX; + + file->Var32(&index); + } + } + else + { + for (int i = 0; i < RenderPolygonRAM.size(); ++i) + { + u32 index = UINT32_MAX; + file->Var32(&index); + + RenderPolygonRAM[i] = index == UINT32_MAX ? nullptr : &PolygonRAM[index]; + } } file->VarArray(CurVertex, sizeof(s16)*3); @@ -536,6 +547,18 @@ void GPU3D::DoSavestate(Savestate* file) noexcept file->VarArray(ShininessTable, 128*sizeof(u8)); file->Bool32(&AbortFrame); + file->Bool32(&GeometryEnabled); + file->Bool32(&RenderingEnabled); + file->Var32(&PolygonMode); + file->Var32(&PolygonAttr); + file->Var32(&CurPolygonAttr); + file->Var32(&TexParam); + file->Var32(&TexPalette); + RenderFrameIdentical = false; + if (softRenderer && softRenderer->IsThreaded()) + { + softRenderer->EnableRenderThread(); + } } diff --git a/src/GPU3D.h b/src/GPU3D.h index 685ece5d..27162854 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -49,6 +49,7 @@ struct Vertex // TODO maybe: hi-res color? (that survives clipping) s32 HiresPosition[2]; + void DoSavestate(Savestate* file) noexcept; }; struct Polygon @@ -80,6 +81,7 @@ struct Polygon u32 SortKey; + void DoSavestate(Savestate* file) noexcept; }; class Renderer3D; @@ -272,7 +274,7 @@ public: u32 RenderClearAttr1 = 0; u32 RenderClearAttr2 = 0; - bool RenderFrameIdentical = false; + bool RenderFrameIdentical = false; // not part of the hardware state, don't serialize bool AbortFrame = false; @@ -326,7 +328,7 @@ public: u32 FlushRequest = 0; u32 FlushAttributes = 0; - u32 ScrolledLine[256]; + u32 ScrolledLine[256]; // not part of the hardware state, don't serialize }; // Rasterization Timing Constants diff --git a/src/GPU3D_OpenGL.cpp b/src/GPU3D_OpenGL.cpp index 27711a89..3e9ce5b0 100644 --- a/src/GPU3D_OpenGL.cpp +++ b/src/GPU3D_OpenGL.cpp @@ -31,7 +31,7 @@ namespace melonDS bool GLRenderer::BuildRenderShader(u32 flags, const char* vs, const char* fs) { char shadername[32]; - sprintf(shadername, "RenderShader%02X", flags); + snprintf(shadername, sizeof(shadername), "RenderShader%02X", flags); int headerlen = strlen(kShaderHeader); diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index bd82a607..c4b93383 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -34,8 +34,11 @@ void SoftRenderer::StopRenderThread() { if (RenderThreadRunning.load(std::memory_order_relaxed)) { + // Tell the render thread to stop drawing new frames, and finish up the current one. RenderThreadRunning = false; + Platform::Semaphore_Post(Sema_RenderStart); + Platform::Thread_Wait(RenderThread); Platform::Thread_Free(RenderThread); RenderThread = nullptr; @@ -47,24 +50,36 @@ void SoftRenderer::SetupRenderThread(GPU& gpu) if (Threaded) { if (!RenderThreadRunning.load(std::memory_order_relaxed)) - { - RenderThreadRunning = true; + { // If the render thread isn't already running... + RenderThreadRunning = true; // "Time for work, render thread!" RenderThread = Platform::Thread_Create([this, &gpu]() { RenderThreadFunc(gpu); }); } - // otherwise more than one frame can be queued up at once + // "Be on standby, but don't start rendering until I tell you to!" Platform::Semaphore_Reset(Sema_RenderStart); + // "Oh, sorry, were you already in the middle of a frame from the last iteration?" if (RenderThreadRendering) + // "Tell me when you're done, I'll wait here." Platform::Semaphore_Wait(Sema_RenderDone); - Platform::Semaphore_Reset(Sema_RenderDone); - Platform::Semaphore_Reset(Sema_RenderStart); - Platform::Semaphore_Reset(Sema_ScanlineCount); + // "All good? Okay, let me give you your training." + // "(Maybe you're still the same thread, but I have to tell you this stuff anyway.)" - Platform::Semaphore_Post(Sema_RenderStart); + // "This is the signal you'll send when you're done with a frame." + // "I'll listen for it when I need to show something to the frontend." + Platform::Semaphore_Reset(Sema_RenderDone); + + // "This is the signal I'll send when I want you to start rendering." + // "Don't do anything until you get the message." + Platform::Semaphore_Reset(Sema_RenderStart); + + // "This is the signal you'll send every time you finish drawing a line." + // "I might need some of your scanlines before you finish the whole buffer," + // "so let me know as soon as you're done with each one." + Platform::Semaphore_Reset(Sema_ScanlineCount); } else { @@ -72,6 +87,13 @@ void SoftRenderer::SetupRenderThread(GPU& gpu) } } +void SoftRenderer::EnableRenderThread() +{ + if (Threaded && Sema_RenderStart) + { + Platform::Semaphore_Post(Sema_RenderStart); + } +} SoftRenderer::SoftRenderer(bool threaded) noexcept : Renderer3D(false), Threaded(threaded) @@ -103,6 +125,7 @@ void SoftRenderer::Reset(GPU& gpu) PrevIsShadowMask = false; SetupRenderThread(gpu); + EnableRenderThread(); } void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept @@ -111,6 +134,7 @@ void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept { Threaded = threaded; SetupRenderThread(gpu); + EnableRenderThread(); } } @@ -1991,6 +2015,7 @@ void SoftRenderer::RenderFrame(GPU& gpu) if (RenderThreadRunning.load(std::memory_order_relaxed)) { + // "Render thread, you're up! Get moving." Platform::Semaphore_Post(Sema_RenderStart); } else if (!FrameIdentical) RenderPolygons(gpu, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons); @@ -1999,23 +2024,34 @@ void SoftRenderer::RenderFrame(GPU& gpu) void SoftRenderer::RestartFrame(GPU& gpu) { SetupRenderThread(gpu); + EnableRenderThread(); } void SoftRenderer::RenderThreadFunc(GPU& gpu) { for (;;) { + // Wait for a notice from the main thread to start rendering (or to stop entirely). Platform::Semaphore_Wait(Sema_RenderStart); if (!RenderThreadRunning) return; + // Protect the GPU state from the main thread. + // Some melonDS frontends (though not ours) + // will repeatedly save or load states; + // if they do so while the render thread is busy here, + // the ensuing race conditions may cause a crash + // (since some of the GPU state includes pointers). RenderThreadRendering = true; if (FrameIdentical) - { + { // If no rendering is needed, just say we're done. Platform::Semaphore_Post(Sema_ScanlineCount, 192); } else RenderPolygons(gpu, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons); + // Tell the main thread that we're done rendering + // and that it's safe to access the GPU state again. Platform::Semaphore_Post(Sema_RenderDone); + RenderThreadRendering = false; } } @@ -2025,6 +2061,9 @@ u32* SoftRenderer::GetLine(int line) if (RenderThreadRunning.load(std::memory_order_relaxed)) { if (line < 192) + // We need a scanline, so let's wait for the render thread to finish it. + // (both threads process scanlines from top-to-bottom, + // so we don't need to wait for a specific row) Platform::Semaphore_Wait(Sema_ScanlineCount); } diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index 33576e3b..3429f951 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -42,8 +42,10 @@ public: u32* GetLine(int line) override; void SetupRenderThread(GPU& gpu); + void EnableRenderThread(); void StopRenderThread(); private: + friend void GPU3D::DoSavestate(Savestate* file) noexcept; // Notes on the interpolator: // // This is a theory on how the DS hardware interpolates values. It matches hardware output @@ -178,7 +180,7 @@ private: { // Z-buffering: linear interpolation // still doesn't quite match hardware... - s32 base, disp, factor; + s32 base = 0, disp = 0, factor = 0; if (z0 < z1) { @@ -337,7 +339,7 @@ private: constexpr s32 XVal() const { - s32 ret; + s32 ret = 0; if (Negative) ret = x0 - (dx >> 18); else ret = x0 + (dx >> 18); @@ -534,8 +536,15 @@ private: Platform::Thread* RenderThread; std::atomic_bool RenderThreadRunning; std::atomic_bool RenderThreadRendering; + + // Used by the main thread to tell the render thread to start rendering a frame Platform::Semaphore* Sema_RenderStart; + + // Used by the render thread to tell the main thread that it's done rendering a frame Platform::Semaphore* Sema_RenderDone; + + // Used to allow the main thread to read some scanlines + // before (the 3D portion of) the entire frame is rasterized. Platform::Semaphore* Sema_ScanlineCount; }; } diff --git a/src/NDS.cpp b/src/NDS.cpp index 7c176fae..1f9597ce 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -713,15 +713,11 @@ bool NDS::DoSavestate(Savestate* file) SPU.SetPowerCnt(PowerControl7 & 0x0001); Wifi.SetPowerCnt(PowerControl7 & 0x0002); - } #ifdef JIT_ENABLED - if (!file->Saving) - { - JIT.ResetBlockCache(); - JIT.Memory.Reset(); - } + JIT.Reset(); #endif + } file->Finish(); @@ -1497,40 +1493,40 @@ void NDS::NocashPrint(u32 ncpu, u32 addr) if (cmd[0] == 'r') { - if (!strcmp(cmd, "r0")) sprintf(subs, "%08X", cpu->R[0]); - else if (!strcmp(cmd, "r1")) sprintf(subs, "%08X", cpu->R[1]); - else if (!strcmp(cmd, "r2")) sprintf(subs, "%08X", cpu->R[2]); - else if (!strcmp(cmd, "r3")) sprintf(subs, "%08X", cpu->R[3]); - else if (!strcmp(cmd, "r4")) sprintf(subs, "%08X", cpu->R[4]); - else if (!strcmp(cmd, "r5")) sprintf(subs, "%08X", cpu->R[5]); - else if (!strcmp(cmd, "r6")) sprintf(subs, "%08X", cpu->R[6]); - else if (!strcmp(cmd, "r7")) sprintf(subs, "%08X", cpu->R[7]); - else if (!strcmp(cmd, "r8")) sprintf(subs, "%08X", cpu->R[8]); - else if (!strcmp(cmd, "r9")) sprintf(subs, "%08X", cpu->R[9]); - else if (!strcmp(cmd, "r10")) sprintf(subs, "%08X", cpu->R[10]); - else if (!strcmp(cmd, "r11")) sprintf(subs, "%08X", cpu->R[11]); - else if (!strcmp(cmd, "r12")) sprintf(subs, "%08X", cpu->R[12]); - else if (!strcmp(cmd, "r13")) sprintf(subs, "%08X", cpu->R[13]); - else if (!strcmp(cmd, "r14")) sprintf(subs, "%08X", cpu->R[14]); - else if (!strcmp(cmd, "r15")) sprintf(subs, "%08X", cpu->R[15]); + if (!strcmp(cmd, "r0")) snprintf(subs, sizeof(subs), "%08X", cpu->R[0]); + else if (!strcmp(cmd, "r1")) snprintf(subs, sizeof(subs), "%08X", cpu->R[1]); + else if (!strcmp(cmd, "r2")) snprintf(subs, sizeof(subs), "%08X", cpu->R[2]); + else if (!strcmp(cmd, "r3")) snprintf(subs, sizeof(subs), "%08X", cpu->R[3]); + else if (!strcmp(cmd, "r4")) snprintf(subs, sizeof(subs), "%08X", cpu->R[4]); + else if (!strcmp(cmd, "r5")) snprintf(subs, sizeof(subs), "%08X", cpu->R[5]); + else if (!strcmp(cmd, "r6")) snprintf(subs, sizeof(subs), "%08X", cpu->R[6]); + else if (!strcmp(cmd, "r7")) snprintf(subs, sizeof(subs), "%08X", cpu->R[7]); + else if (!strcmp(cmd, "r8")) snprintf(subs, sizeof(subs), "%08X", cpu->R[8]); + else if (!strcmp(cmd, "r9")) snprintf(subs, sizeof(subs), "%08X", cpu->R[9]); + else if (!strcmp(cmd, "r10")) snprintf(subs, sizeof(subs), "%08X", cpu->R[10]); + else if (!strcmp(cmd, "r11")) snprintf(subs, sizeof(subs), "%08X", cpu->R[11]); + else if (!strcmp(cmd, "r12")) snprintf(subs, sizeof(subs), "%08X", cpu->R[12]); + else if (!strcmp(cmd, "r13")) snprintf(subs, sizeof(subs), "%08X", cpu->R[13]); + else if (!strcmp(cmd, "r14")) snprintf(subs, sizeof(subs), "%08X", cpu->R[14]); + else if (!strcmp(cmd, "r15")) snprintf(subs, sizeof(subs), "%08X", cpu->R[15]); } else { - if (!strcmp(cmd, "sp")) sprintf(subs, "%08X", cpu->R[13]); - else if (!strcmp(cmd, "lr")) sprintf(subs, "%08X", cpu->R[14]); - else if (!strcmp(cmd, "pc")) sprintf(subs, "%08X", cpu->R[15]); - else if (!strcmp(cmd, "frame")) sprintf(subs, "%u", NumFrames); - else if (!strcmp(cmd, "scanline")) sprintf(subs, "%u", GPU.VCount); - else if (!strcmp(cmd, "totalclks")) sprintf(subs, "%" PRIu64, GetSysClockCycles(0)); - else if (!strcmp(cmd, "lastclks")) sprintf(subs, "%" PRIu64, GetSysClockCycles(1)); + if (!strcmp(cmd, "sp")) snprintf(subs, sizeof(subs), "%08X", cpu->R[13]); + else if (!strcmp(cmd, "lr")) snprintf(subs, sizeof(subs), "%08X", cpu->R[14]); + else if (!strcmp(cmd, "pc")) snprintf(subs, sizeof(subs), "%08X", cpu->R[15]); + else if (!strcmp(cmd, "frame")) snprintf(subs, sizeof(subs), "%u", NumFrames); + else if (!strcmp(cmd, "scanline")) snprintf(subs, sizeof(subs), "%u", GPU.VCount); + else if (!strcmp(cmd, "totalclks")) snprintf(subs, sizeof(subs), "%" PRIu64, GetSysClockCycles(0)); + else if (!strcmp(cmd, "lastclks")) snprintf(subs, sizeof(subs), "%" PRIu64, GetSysClockCycles(1)); else if (!strcmp(cmd, "zeroclks")) { - sprintf(subs, "%s", ""); + snprintf(subs, sizeof(subs), "%s", ""); GetSysClockCycles(1); } } - int slen = strlen(subs); + int slen = strnlen(subs, sizeof(subs)); if ((ptr+slen) > 1023) slen = 1023-ptr; strncpy(&output[ptr], subs, slen); ptr += slen; @@ -2732,11 +2728,37 @@ u8 NDS::ARM9IORead8(u32 addr) case 0x04000132: return KeyCnt[0] & 0xFF; case 0x04000133: return KeyCnt[0] >> 8; + case 0x040001A0: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetSPICnt() & 0xFF; + return 0; + case 0x040001A1: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetSPICnt() >> 8; + return 0; + case 0x040001A2: if (!(ExMemCnt[0] & (1<<11))) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() & 0xFF; + return 0; + case 0x040001A5: + if (!(ExMemCnt[0] & (1<<11))) + return (NDSCartSlot.GetROMCnt() >> 8) & 0xFF; + return 0; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + return (NDSCartSlot.GetROMCnt() >> 16) & 0xFF; + return 0; + case 0x040001A7: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() >> 24; + return 0; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) return NDSCartSlot.GetROMCommand(0); @@ -2888,6 +2910,15 @@ u16 NDS::ARM9IORead16(u32 addr) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() & 0xFFFF; + return 0; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() >> 16; + return 0; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) return NDSCartSlot.GetROMCommand(0) | @@ -3151,6 +3182,23 @@ void NDS::ARM9IOWrite8(u32 addr, u8 val) NDSCartSlot.WriteSPIData(val); return; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFFFF00) | val); + return; + case 0x040001A5: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFF00FF) | (val << 8)); + return; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFF00FFFF) | (val << 16)); + return; + case 0x040001A7: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0x00FFFFFF) | (val << 24)); + return; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(0, val); return; case 0x040001A9: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(1, val); return; case 0x040001AA: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(2, val); return; @@ -3280,6 +3328,15 @@ void NDS::ARM9IOWrite16(u32 addr, u16 val) NDSCartSlot.WriteSPIData(val & 0xFF); return; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFF0000) | val); + return; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0x0000FFFF) | (val << 16)); + return; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) { @@ -3596,11 +3653,37 @@ u8 NDS::ARM7IORead8(u32 addr) case 0x04000138: return RTC.Read() & 0xFF; + case 0x040001A0: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetSPICnt() & 0xFF; + return 0; + case 0x040001A1: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetSPICnt() >> 8; + return 0; + case 0x040001A2: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() & 0xFF; + return 0; + case 0x040001A5: + if (ExMemCnt[0] & (1<<11)) + return (NDSCartSlot.GetROMCnt() >> 8) & 0xFF; + return 0; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + return (NDSCartSlot.GetROMCnt() >> 16) & 0xFF; + return 0; + case 0x040001A7: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() >> 24; + return 0; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetROMCommand(0); @@ -3701,6 +3784,15 @@ u16 NDS::ARM7IORead16(u32 addr) case 0x040001A0: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetSPICnt(); return 0; case 0x040001A2: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() & 0xFFFF; + return 0; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() >> 16; + return 0; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetROMCommand(0) | @@ -3888,6 +3980,23 @@ void NDS::ARM7IOWrite8(u32 addr, u8 val) NDSCartSlot.WriteSPIData(val); return; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFFFF00) | val); + return; + case 0x040001A5: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFF00FF) | (val << 8)); + return; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFF00FFFF) | (val << 16)); + return; + case 0x040001A7: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0x00FFFFFF) | (val << 24)); + return; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(0, val); return; case 0x040001A9: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(1, val); return; case 0x040001AA: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(2, val); return; @@ -3993,6 +4102,15 @@ void NDS::ARM7IOWrite16(u32 addr, u16 val) NDSCartSlot.WriteSPIData(val & 0xFF); return; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFFFF00) | val); + return; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFF00FFFF) | (val << 16)); + return; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) { diff --git a/src/NDS.h b/src/NDS.h index 23b1f888..f9df2d69 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -259,8 +259,8 @@ public: // TODO: Encapsulate the rest of these members u16 PowerControl9; u16 ExMemCnt[2]; - u8 ROMSeed0[2*8]; - u8 ROMSeed1[2*8]; + alignas(u32) u8 ROMSeed0[2*8]; + alignas(u32) u8 ROMSeed1[2*8]; protected: // These BIOS arrays should be declared *before* the component objects (JIT, SPI, etc.) @@ -489,12 +489,12 @@ private: FIFO IPCFIFO9; // FIFO in which the ARM9 writes FIFO IPCFIFO7; u16 DivCnt; - u32 DivNumerator[2]; - u32 DivDenominator[2]; - u32 DivQuotient[2]; - u32 DivRemainder[2]; + alignas(u64) u32 DivNumerator[2]; + alignas(u64) u32 DivDenominator[2]; + alignas(u64) u32 DivQuotient[2]; + alignas(u64) u32 DivRemainder[2]; u16 SqrtCnt; - u32 SqrtVal[2]; + alignas(u64) u32 SqrtVal[2]; u32 SqrtRes; u16 KeyCnt[2]; bool Running; diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index a5fe3180..a64d8a27 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1657,9 +1657,15 @@ std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen std::unique_ptr sram = args ? std::move(args->SRAM) : nullptr; u32 sramlen = args ? args->SRAMLength : 0; if (homebrew) - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, args ? std::move(args->SDCard) : std::nullopt); + { + std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sdcard)); + } else if (gametitle[0] == 0 && !strncmp("SD/TF-NDS", gametitle + 1, 9) && gamecode == 0x414D5341) - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, args ? std::move(args->SDCard) : std::nullopt); + { + std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard)); + } else if (cartid & 0x08000000) cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else if (irversion != 0) diff --git a/src/NDSCart.h b/src/NDSCart.h index 78439a2c..2f6a3be5 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -260,6 +260,22 @@ public: // it just leaves behind an optional with a moved-from value } + void SetSDCard(std::optional&& args) noexcept + { + // Close the open SD card (if any) so that its contents are flushed to disk. + // Also, if args refers to the same image file that SD is currently using, + // this will ensure that we don't have two open read-write handles + // to the same file. + SD = std::nullopt; + + if (args) + SD = FATStorage(std::move(*args)); + + args = std::nullopt; + // moving from an optional doesn't set it to nullopt, + // it just leaves behind an optional with a moved-from value + } + protected: void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const; void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly); diff --git a/src/NDS_Header.h b/src/NDS_Header.h index de75f7c7..77a5baca 100644 --- a/src/NDS_Header.h +++ b/src/NDS_Header.h @@ -39,6 +39,8 @@ enum RegionMask : u32 RegionFree = 0xFFFFFFFF, }; +constexpr u32 DSiWareTitleIDHigh = 0x00030004; + // Consult GBATEK for info on what these are struct NDSHeader { @@ -198,8 +200,9 @@ struct NDSHeader u8 HeaderSignature[128]; // RSA-SHA1 across 0x000..0xDFF - /// @return \c true if this header represents a DSi title - /// (either a physical cartridge or a DSiWare title). + /// @return \c true if this header represents a title + /// that is DSi-exclusive (including DSiWare) + /// or DSi-enhanced (including cartridges). [[nodiscard]] bool IsDSi() const { return (UnitCode & 0x02) != 0; } [[nodiscard]] u32 GameCodeAsU32() const { return (u32)GameCode[3] << 24 | @@ -213,7 +216,7 @@ struct NDSHeader } /// @return \c true if this header represents a DSiWare title. - [[nodiscard]] bool IsDSiWare() const { return IsDSi() && DSiRegionStart == 0; } + [[nodiscard]] bool IsDSiWare() const { return IsDSi() && DSiTitleIDHigh == DSiWareTitleIDHigh; } }; static_assert(sizeof(NDSHeader) == 4096, "NDSHeader is not 4096 bytes!"); diff --git a/src/Platform.h b/src/Platform.h index 21b3d465..425c712c 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -136,6 +136,11 @@ enum FileMode : unsigned { */ Text = 0b01'00'00, + /** + * Opens a file in append mode. + */ + Append = 0b10'00'00, + /** * Opens a file for reading and writing. * Equivalent to Read | Write. @@ -201,6 +206,13 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode); bool FileExists(const std::string& name); bool LocalFileExists(const std::string& name); +// Returns true if we have permission to write to the file. +// Warning: Also creates the file if not present! +bool CheckFileWritable(const std::string& filepath); + +// Same as above (CheckFileWritable()) but for local files. +bool CheckLocalFileWritable(const std::string& filepath); + /** Close a file opened with \c OpenFile. * @returns \c true if the file was closed successfully, false otherwise. * @post \c file is no longer valid and should not be used. diff --git a/src/SPU.cpp b/src/SPU.cpp index f0d59464..86307097 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -621,6 +621,8 @@ s32 SPUChannel::Run() (PrevSample[0] * InterpCubic[samplepos][2]) + (val * InterpCubic[samplepos][3])) >> 14; break; + default: + break; } } diff --git a/src/Savestate.h b/src/Savestate.h index 236aa643..2e1400a0 100644 --- a/src/Savestate.h +++ b/src/Savestate.h @@ -24,7 +24,7 @@ #include #include "types.h" -#define SAVESTATE_MAJOR 11 +#define SAVESTATE_MAJOR 12 #define SAVESTATE_MINOR 1 namespace melonDS diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 9da2c91d..1dec174b 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES_QT_SDL main_shaders.h Screen.cpp Window.cpp + EmuThread.cpp CheatsDialog.cpp Config.cpp DateTimeDialog.cpp @@ -31,7 +32,6 @@ set(SOURCES_QT_SDL LAN_PCap.cpp LAN_Socket.cpp LocalMP.cpp - OSD.cpp OSD_shaders.h font.h Platform.cpp @@ -84,11 +84,11 @@ if (BUILD_STATIC) endif() pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) -pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp) +pkg_check_modules(Slirp REQUIRED slirp) pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive) pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd) -fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) +fix_interface_includes(PkgConfig::SDL2 PkgConfig::LibArchive) add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) @@ -160,14 +160,19 @@ else() target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() target_link_libraries(melonDS PRIVATE core) -target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive PkgConfig::Zstd) +target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd) target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS}) -if (UNIX) - option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF) -elseif (WIN32) +target_include_directories(melonDS PRIVATE "${Slirp_INCLUDE_DIRS}") +target_link_libraries(melonDS PRIVATE "${Slirp_LINK_LIBRARIES}") + +if (WIN32) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON) + if (PORTABLE) + target_compile_definitions(melonDS PRIVATE WIN32_PORTABLE) + endif() + configure_file("${CMAKE_SOURCE_DIR}/res/melon.rc.in" "${CMAKE_BINARY_DIR}/res/melon.rc") target_sources(melonDS PUBLIC "${CMAKE_BINARY_DIR}/res/melon.rc") target_include_directories(melonDS PRIVATE "${CMAKE_BINARY_DIR}/res") @@ -186,10 +191,6 @@ elseif (WIN32) set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole") endif() -if (PORTABLE) - target_compile_definitions(melonDS PRIVATE PORTABLE) -endif() - if (APPLE) target_sources(melonDS PRIVATE sem_timedwait.cpp) diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index b6fca7d6..2fdfc3ba 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -61,6 +61,7 @@ int GL_ScaleFactor; bool GL_BetterPolygons; bool LimitFPS; +int MaxFPS; bool AudioSync; bool ShowOSD; @@ -141,6 +142,7 @@ bool MouseHide; int MouseHideSeconds; bool PauseLostFocus; +std::string UITheme; int64_t RTCOffset; @@ -251,6 +253,7 @@ ConfigEntry ConfigFile[] = {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, {"LimitFPS", 1, &LimitFPS, true, false}, + {"MaxFPS", 0, &MaxFPS, 1000, false}, {"AudioSync", 1, &AudioSync, false}, {"ShowOSD", 1, &ShowOSD, true, false}, @@ -342,6 +345,7 @@ ConfigEntry ConfigFile[] = {"MouseHide", 1, &MouseHide, false, false}, {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + {"UITheme", 2, &UITheme, (std::string)"", false}, {"RTCOffset", 3, &RTCOffset, (int64_t)0, true}, @@ -374,7 +378,7 @@ ConfigEntry ConfigFile[] = }; -void LoadFile(int inst) +bool LoadFile(int inst, int actualinst) { Platform::FileHandle* f; if (inst > 0) @@ -382,11 +386,17 @@ void LoadFile(int inst) char name[100] = {0}; snprintf(name, 99, kUniqueConfigFile, inst+1); f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText); + + if (!Platform::CheckLocalFileWritable(name)) return false; } else + { f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText); - if (!f) return; + if (actualinst == 0 && !Platform::CheckLocalFileWritable(kConfigFile)) return false; + } + + if (!f) return true; char linebuf[1024]; char entryname[32]; @@ -421,9 +431,10 @@ void LoadFile(int inst) } CloseFile(f); + return true; } -void Load() +bool Load() { for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) @@ -436,12 +447,14 @@ void Load() case 3: *(int64_t*)entry->Value = std::get(entry->Default); break; } } - - LoadFile(0); - + int inst = Platform::InstanceID(); + + bool ret = LoadFile(0, inst); if (inst > 0) - LoadFile(inst); + ret = LoadFile(inst, inst); + + return ret; } void Save() diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 5e3db823..722384a3 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -105,6 +105,7 @@ extern int GL_ScaleFactor; extern bool GL_BetterPolygons; extern bool LimitFPS; +extern int MaxFPS; extern bool AudioSync; extern bool ShowOSD; @@ -184,6 +185,7 @@ extern bool EnableCheats; extern bool MouseHide; extern int MouseHideSeconds; extern bool PauseLostFocus; +extern std::string UITheme; extern int64_t RTCOffset; @@ -202,7 +204,7 @@ extern bool GdbARM7BreakOnStartup; extern bool GdbARM9BreakOnStartup; -void Load(); +bool Load(); void Save(); } diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index 0a834a65..ca9c6716 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -380,6 +380,12 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to firmware file.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtFirmwarePath->setText(file); @@ -436,6 +442,12 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DLDI SD image.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtDLDISDPath->setText(file); @@ -468,6 +480,13 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi firmware file.\nPlease check file/folder write permissions."); + return; + } + + updateLastBIOSFolder(file); ui->txtDSiFirmwarePath->setText(file); @@ -482,6 +501,13 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi NAND image.\nPlease check file/folder write permissions."); + return; + } + + updateLastBIOSFolder(file); ui->txtDSiNANDPath->setText(file); @@ -510,6 +536,12 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi SD image.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtDSiSDPath->setText(file); diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp new file mode 100644 index 00000000..0728a085 --- /dev/null +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -0,0 +1,752 @@ +/* + 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 +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "main.h" +#include "Input.h" +#include "AudioInOut.h" + +#include "types.h" +#include "version.h" + +#include "FrontendUtil.h" + +#include "Args.h" +#include "NDS.h" +#include "NDSCart.h" +#include "GBACart.h" +#include "GPU.h" +#include "SPU.h" +#include "Wifi.h" +#include "Platform.h" +#include "LocalMP.h" +#include "Config.h" +#include "RTC.h" +#include "DSi.h" +#include "DSi_I2C.h" +#include "GPU3D_Soft.h" +#include "GPU3D_OpenGL.h" + +#include "Savestate.h" + +#include "ROMManager.h" +//#include "ArchiveUtil.h" +//#include "CameraManager.h" + +//#include "CLI.h" + +// TODO: uniform variable spelling +using namespace melonDS; + +// TEMP +extern bool RunningSomething; +extern MainWindow* mainWindow; +extern int autoScreenSizing; +extern int videoRenderer; +extern bool videoSettingsDirty; + + +EmuThread::EmuThread(QObject* parent) : QThread(parent) +{ + EmuStatus = emuStatus_Exit; + EmuRunning = emuStatus_Paused; + EmuPauseStack = EmuPauseStackRunning; + RunningSomething = false; + + connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint())); + connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); + connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); + connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); + connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger())); + connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); + connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); + connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); + connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); + connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); + connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); +} + +std::unique_ptr EmuThread::CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& gbacart +) noexcept +{ + auto arm7bios = ROMManager::LoadARM7BIOS(); + if (!arm7bios) + return nullptr; + + auto arm9bios = ROMManager::LoadARM9BIOS(); + if (!arm9bios) + return nullptr; + + auto firmware = ROMManager::LoadFirmware(Config::ConsoleType); + if (!firmware) + return nullptr; + +#ifdef JIT_ENABLED + JITArgs jitargs { + static_cast(Config::JIT_MaxBlockSize), + Config::JIT_LiteralOptimisations, + Config::JIT_BranchOptimisations, + Config::JIT_FastMemory, + }; +#endif + +#ifdef GDBSTUB_ENABLED + GDBArgs gdbargs { + static_cast(Config::GdbPortARM7), + static_cast(Config::GdbPortARM9), + Config::GdbARM7BreakOnStartup, + Config::GdbARM9BreakOnStartup, + }; +#endif + + NDSArgs ndsargs { + std::move(ndscart), + std::move(gbacart), + *arm9bios, + *arm7bios, + std::move(*firmware), +#ifdef JIT_ENABLED + Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt, +#else + std::nullopt, +#endif + static_cast(Config::AudioBitDepth), + static_cast(Config::AudioInterp), +#ifdef GDBSTUB_ENABLED + Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt, +#else + std::nullopt, +#endif + }; + + if (Config::ConsoleType == 1) + { + auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); + if (!arm7ibios) + return nullptr; + + auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); + if (!arm9ibios) + return nullptr; + + auto nand = ROMManager::LoadNAND(*arm7ibios); + if (!nand) + return nullptr; + + auto sdcard = ROMManager::LoadDSiSDCard(); + DSiArgs args { + std::move(ndsargs), + *arm9ibios, + *arm7ibios, + std::move(*nand), + std::move(sdcard), + Config::DSiFullBIOSBoot, + }; + + args.GBAROM = nullptr; + + return std::make_unique(std::move(args)); + } + + return std::make_unique(std::move(ndsargs)); +} + +bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept +{ + // 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; + if (std::holds_alternative(ndsargs)) + { // If we want to keep the existing cart (if any)... + nextndscart = NDS ? NDS->EjectCart() : nullptr; + ndsargs = {}; + } + else if (const auto ptr = std::get_if>(&ndsargs)) + { + nextndscart = std::move(*ptr); + ndsargs = {}; + } + + if (auto* cartsd = dynamic_cast(nextndscart.get())) + { + // LoadDLDISDCard will return nullopt if the SD card is disabled; + // SetSDCard will accept nullopt, which means no SD card + cartsd->SetSDCard(ROMManager::GetDLDISDCardArgs()); + } + + std::unique_ptr nextgbacart; + if (std::holds_alternative(gbaargs)) + { + nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; + } + else if (const auto ptr = std::get_if>(&gbaargs)) + { + nextgbacart = std::move(*ptr); + gbaargs = {}; + } + + if (!NDS || NDS->ConsoleType != Config::ConsoleType) + { // If we're switching between DS and DSi mode, or there's no console... + // To ensure the destructor is called before a new one is created, + // as the presence of global signal handlers still complicates things a bit + NDS = nullptr; + NDS::Current = nullptr; + + NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart)); + + if (NDS == nullptr) + return false; + + NDS->Reset(); + NDS::Current = NDS.get(); + + return true; + } + + auto arm9bios = ROMManager::LoadARM9BIOS(); + if (!arm9bios) + return false; + + auto arm7bios = ROMManager::LoadARM7BIOS(); + if (!arm7bios) + return false; + + auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType); + if (!firmware) + return false; + + if (NDS->ConsoleType == 1) + { // If the console we're updating is a DSi... + DSi& dsi = static_cast(*NDS); + + auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); + if (!arm9ibios) + return false; + + auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); + if (!arm7ibios) + return false; + + auto nandimage = ROMManager::LoadNAND(*arm7ibios); + if (!nandimage) + return false; + + auto dsisdcard = ROMManager::LoadDSiSDCard(); + + dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot); + dsi.ARM7iBIOS = *arm7ibios; + dsi.ARM9iBIOS = *arm9ibios; + dsi.SetNAND(std::move(*nandimage)); + dsi.SetSDCard(std::move(dsisdcard)); + // We're moving the optional, not the card + // (inserting std::nullopt here is okay, it means no card) + + dsi.EjectGBACart(); + } + + if (NDS->ConsoleType == 0) + { + NDS->SetGBACart(std::move(nextgbacart)); + } + +#ifdef JIT_ENABLED + JITArgs jitargs { + static_cast(Config::JIT_MaxBlockSize), + Config::JIT_LiteralOptimisations, + Config::JIT_BranchOptimisations, + Config::JIT_FastMemory, + }; + NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt); +#endif + NDS->SetARM7BIOS(*arm7bios); + NDS->SetARM9BIOS(*arm9bios); + NDS->SetFirmware(std::move(*firmware)); + NDS->SetNDSCart(std::move(nextndscart)); + NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + NDS->SPU.SetDegrade10Bit(static_cast(Config::AudioBitDepth)); + + NDS::Current = NDS.get(); + + return true; +} + +void EmuThread::run() +{ + u32 mainScreenPos[3]; + Platform::FileHandle* file; + + UpdateConsole(nullptr, nullptr); + // No carts are inserted when melonDS first boots + + mainScreenPos[0] = 0; + mainScreenPos[1] = 0; + mainScreenPos[2] = 0; + autoScreenSizing = 0; + + videoSettingsDirty = false; + + if (mainWindow->hasOGL) + { + screenGL = static_cast(mainWindow->panel); + screenGL->initOpenGL(); + videoRenderer = Config::_3DRenderer; + } + else + { + screenGL = nullptr; + videoRenderer = 0; + } + + if (videoRenderer == 0) + { // If we're using the software renderer... + NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); + } + else + { + auto glrenderer = melonDS::GLRenderer::New(); + glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); + NDS->GPU.SetRenderer3D(std::move(glrenderer)); + } + + Input::Init(); + + u32 nframes = 0; + double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); + double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; + double frameLimitError = 0.0; + double lastMeasureTime = lastTime; + + 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); + NDS->RTC.SetState(state); + } + + char melontitle[100]; + + while (EmuRunning != emuStatus_Exit) + { + Input::Process(); + + if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + + if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); + if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); + if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); + + if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); + + if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); + if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); + + if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) + { + EmuStatus = emuStatus_Running; + if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; + + if (Input::HotkeyPressed(HK_SolarSensorDecrease)) + { + int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); + if (level != -1) + { + mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + } + } + if (Input::HotkeyPressed(HK_SolarSensorIncrease)) + { + int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); + if (level != -1) + { + mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + } + } + + if (NDS->ConsoleType == 1) + { + DSi& dsi = static_cast(*NDS); + double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; + + // Handle power button + if (Input::HotkeyDown(HK_PowerButton)) + { + dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); + } + else if (Input::HotkeyReleased(HK_PowerButton)) + { + dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); + } + + // Handle volume buttons + if (Input::HotkeyDown(HK_VolumeUp)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); + } + else if (Input::HotkeyReleased(HK_VolumeUp)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); + } + + if (Input::HotkeyDown(HK_VolumeDown)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); + } + else if (Input::HotkeyReleased(HK_VolumeDown)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); + } + + dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); + } + + // update render settings if needed + // HACK: + // once the fast forward hotkey is released, we need to update vsync + // to the old setting again + if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) + { + if (screenGL) + { + screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); + videoRenderer = Config::_3DRenderer; + } +#ifdef OGLRENDERER_ENABLED + else +#endif + { + videoRenderer = 0; + } + + videoRenderer = screenGL ? Config::_3DRenderer : 0; + + videoSettingsDirty = false; + + if (videoRenderer == 0) + { // If we're using the software renderer... + NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); + } + else + { + auto glrenderer = melonDS::GLRenderer::New(); + glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); + NDS->GPU.SetRenderer3D(std::move(glrenderer)); + } + } + + // process input and hotkeys + NDS->SetKeyMask(Input::InputMask); + + if (Input::HotkeyPressed(HK_Lid)) + { + bool lid = !NDS->IsLidClosed(); + NDS->SetLidClosed(lid); + mainWindow->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); + } + + // microphone input + AudioInOut::MicProcess(*NDS); + + // auto screen layout + if (Config::ScreenSizing == Frontend::screenSizing_Auto) + { + mainScreenPos[2] = mainScreenPos[1]; + mainScreenPos[1] = mainScreenPos[0]; + mainScreenPos[0] = NDS->PowerControl9 >> 15; + + int guess; + if (mainScreenPos[0] == mainScreenPos[2] && + mainScreenPos[0] != mainScreenPos[1]) + { + // constant flickering, likely displaying 3D on both screens + // TODO: when both screens are used for 2D only...??? + guess = Frontend::screenSizing_Even; + } + else + { + if (mainScreenPos[0] == 1) + guess = Frontend::screenSizing_EmphTop; + else + guess = Frontend::screenSizing_EmphBot; + } + + if (guess != autoScreenSizing) + { + autoScreenSizing = guess; + emit screenLayoutChange(); + } + } + + + // emulate + u32 nlines = NDS->RunFrame(); + + if (ROMManager::NDSSave) + ROMManager::NDSSave->CheckFlush(); + + if (ROMManager::GBASave) + ROMManager::GBASave->CheckFlush(); + + if (ROMManager::FirmwareSave) + ROMManager::FirmwareSave->CheckFlush(); + + if (!screenGL) + { + FrontBufferLock.lock(); + FrontBuffer = NDS->GPU.FrontBuffer; + FrontBufferLock.unlock(); + } + else + { + FrontBuffer = NDS->GPU.FrontBuffer; + screenGL->drawScreenGL(); + } + +#ifdef MELONCAP + MelonCap::Update(); +#endif // MELONCAP + + if (EmuRunning == emuStatus_Exit) break; + + winUpdateCount++; + if (winUpdateCount >= winUpdateFreq && !screenGL) + { + emit windowUpdate(); + winUpdateCount = 0; + } + + bool fastforward = Input::HotkeyDown(HK_FastForward); + + if (fastforward && screenGL && Config::ScreenVSync) + { + screenGL->setSwapInterval(0); + } + + if (Config::DSiVolumeSync && NDS->ConsoleType == 1) + { + DSi& dsi = static_cast(*NDS); + u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); + if (volumeLevel != dsiVolumeLevel) + { + dsiVolumeLevel = volumeLevel; + emit syncVolumeLevel(); + } + + Config::AudioVolume = volumeLevel * (256.0 / 31.0); + } + + if (Config::AudioSync && !fastforward) + AudioInOut::AudioSync(*this->NDS); + + double frametimeStep = nlines / (60.0 * 263.0); + + { + bool limitfps = Config::LimitFPS && !fastforward; + + double practicalFramelimit = limitfps ? frametimeStep : 1.0 / Config::MaxFPS; + + double curtime = SDL_GetPerformanceCounter() * perfCountsSec; + + frameLimitError += practicalFramelimit - (curtime - lastTime); + if (frameLimitError < -practicalFramelimit) + frameLimitError = -practicalFramelimit; + if (frameLimitError > practicalFramelimit) + frameLimitError = practicalFramelimit; + + if (round(frameLimitError * 1000.0) > 0.0) + { + SDL_Delay(round(frameLimitError * 1000.0)); + double timeBeforeSleep = curtime; + curtime = SDL_GetPerformanceCounter() * perfCountsSec; + frameLimitError -= curtime - timeBeforeSleep; + } + + lastTime = curtime; + } + + nframes++; + if (nframes >= 30) + { + double time = SDL_GetPerformanceCounter() * perfCountsSec; + double dt = time - lastMeasureTime; + lastMeasureTime = time; + + u32 fps = round(nframes / dt); + nframes = 0; + + float fpstarget = 1.0/frametimeStep; + + winUpdateFreq = fps / (u32)round(fpstarget); + if (winUpdateFreq < 1) + winUpdateFreq = 1; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); + changeWindowTitle(melontitle); + } + } + else + { + // paused + nframes = 0; + lastTime = SDL_GetPerformanceCounter() * perfCountsSec; + lastMeasureTime = lastTime; + + emit windowUpdate(); + + EmuStatus = EmuRunning; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); + changeWindowTitle(melontitle); + + SDL_Delay(75); + + if (screenGL) + screenGL->drawScreenGL(); + + ContextRequestKind contextRequest = ContextRequest; + if (contextRequest == contextRequest_InitGL) + { + screenGL = static_cast(mainWindow->panel); + screenGL->initOpenGL(); + ContextRequest = contextRequest_None; + } + else if (contextRequest == contextRequest_DeInitGL) + { + screenGL->deinitOpenGL(); + screenGL = nullptr; + ContextRequest = contextRequest_None; + } + } + } + + 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); + } + + EmuStatus = emuStatus_Exit; + + NDS::Current = nullptr; + // nds is out of scope, so unique_ptr cleans it up for us +} + +void EmuThread::changeWindowTitle(char* title) +{ + emit windowTitleChange(QString(title)); +} + +void EmuThread::emuRun() +{ + EmuRunning = emuStatus_Running; + EmuPauseStack = EmuPauseStackRunning; + RunningSomething = true; + + // checkme + emit windowEmuStart(); + AudioInOut::Enable(); +} + +void EmuThread::initContext() +{ + ContextRequest = contextRequest_InitGL; + while (ContextRequest != contextRequest_None); +} + +void EmuThread::deinitContext() +{ + ContextRequest = contextRequest_DeInitGL; + while (ContextRequest != contextRequest_None); +} + +void EmuThread::emuPause() +{ + EmuPauseStack++; + if (EmuPauseStack > EmuPauseStackPauseThreshold) return; + + PrevEmuStatus = EmuRunning; + EmuRunning = emuStatus_Paused; + while (EmuStatus != emuStatus_Paused); + + AudioInOut::Disable(); +} + +void EmuThread::emuUnpause() +{ + if (EmuPauseStack < EmuPauseStackPauseThreshold) return; + + EmuPauseStack--; + if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; + + EmuRunning = PrevEmuStatus; + + AudioInOut::Enable(); +} + +void EmuThread::emuStop() +{ + EmuRunning = emuStatus_Exit; + EmuPauseStack = EmuPauseStackRunning; + + AudioInOut::Disable(); +} + +void EmuThread::emuFrameStep() +{ + if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); + EmuRunning = emuStatus_FrameStep; +} + +bool EmuThread::emuIsRunning() +{ + return EmuRunning == emuStatus_Running; +} + +bool EmuThread::emuIsActive() +{ + return (RunningSomething == 1); +} diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h new file mode 100644 index 00000000..4950ebbf --- /dev/null +++ b/src/frontend/qt_sdl/EmuThread.h @@ -0,0 +1,134 @@ +/* + 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 EMUTHREAD_H +#define EMUTHREAD_H + +#include +#include + +#include +#include +#include + +#include "NDSCart.h" +#include "GBACart.h" + +using Keep = std::monostate; +using UpdateConsoleNDSArgs = std::variant>; +using UpdateConsoleGBAArgs = std::variant>; +namespace melonDS +{ +class NDS; +} + +class ScreenPanelGL; + +class EmuThread : public QThread +{ + Q_OBJECT + void run() override; + +public: + explicit EmuThread(QObject* parent = nullptr); + + void changeWindowTitle(char* title); + + // to be called from the UI thread + void emuRun(); + void emuPause(); + void emuUnpause(); + void emuStop(); + void emuFrameStep(); + + bool emuIsRunning(); + bool emuIsActive(); + + void initContext(); + void deinitContext(); + + int FrontBuffer = 0; + QMutex FrontBufferLock; + + /// Applies the config in args. + /// Creates a new NDS console if needed, + /// modifies the existing one if possible. + /// @return \c true if the console was updated. + /// If this returns \c false, then the existing NDS console is not modified. + bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; + std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization +signals: + void windowUpdate(); + void windowTitleChange(QString title); + + void windowEmuStart(); + void windowEmuStop(); + void windowEmuPause(); + void windowEmuReset(); + void windowEmuFrameStep(); + + void windowLimitFPSChange(); + + void screenLayoutChange(); + + void windowFullscreenToggle(); + + void swapScreensToggle(); + void screenEmphasisToggle(); + + void syncVolumeLevel(); + +private: + std::unique_ptr CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& gbacart + ) noexcept; + + enum EmuStatusKind + { + emuStatus_Exit, + emuStatus_Running, + emuStatus_Paused, + emuStatus_FrameStep, + }; + std::atomic EmuStatus; + + EmuStatusKind PrevEmuStatus; + EmuStatusKind EmuRunning; + + constexpr static int EmuPauseStackRunning = 0; + constexpr static int EmuPauseStackPauseThreshold = 1; + int EmuPauseStack; + + enum ContextRequestKind + { + contextRequest_None = 0, + contextRequest_InitGL, + contextRequest_DeInitGL + }; + std::atomic ContextRequest = contextRequest_None; + + ScreenPanelGL* screenGL; + + int autoScreenSizing; + + int videoRenderer; + bool videoSettingsDirty; +}; + +#endif // EMUTHREAD_H diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 2f7417f6..851e7abf 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -16,15 +16,16 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ +#include #include "InterfaceSettingsDialog.h" #include "ui_InterfaceSettingsDialog.h" #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" InterfaceSettingsDialog* InterfaceSettingsDialog::currentDlg = nullptr; - InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::InterfaceSettingsDialog) { ui->setupUi(this); @@ -34,6 +35,19 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0); ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds); ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0); + ui->spinMaxFPS->setValue(Config::MaxFPS); + + const QList themeKeys = QStyleFactory::keys(); + const QString currentTheme = qApp->style()->objectName(); + + ui->cbxUITheme->addItem("System default", ""); + + for (int i = 0; i < themeKeys.length(); i++) + { + ui->cbxUITheme->addItem(themeKeys[i], themeKeys[i]); + if (!Config::UITheme.empty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0) + ui->cbxUITheme->setCurrentIndex(i + 1); + } } InterfaceSettingsDialog::~InterfaceSettingsDialog() @@ -60,9 +74,18 @@ void InterfaceSettingsDialog::done(int r) Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0; Config::MouseHideSeconds = ui->spinMouseHideSeconds->value(); Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0; + Config::MaxFPS = ui->spinMaxFPS->value(); + + QString themeName = ui->cbxUITheme->currentData().toString(); + Config::UITheme = themeName.toStdString(); Config::Save(); + if (!Config::UITheme.empty()) + qApp->setStyle(themeName); + else + qApp->setStyle(*systemThemeName); + emit updateMouseTimer(); } diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui index 8ee9feda..21d8434e 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui @@ -6,8 +6,8 @@ 0 0 - 262 - 113 + 337 + 275 @@ -19,32 +19,113 @@ Interface settings - melonDS - - - - - Hide after + + + + + User interface + + + + + + + Theme + + + cbxUITheme + + + + + + + + + + + + Hide mouse after inactivity + + + + + + + 18 + + + + + After + + + spinMouseHideSeconds + + + + + + + + + + seconds + + + spinMouseHideSeconds + + + + + + + + + Pause emulation when window is not in focus + + + + - - - - Pause emulation when window is not in focus + + + + Framerate + + + + + Fast-forward limit + + + spinMaxFPS + + + + + + + FPS + + + 60 + + + 1000 + + + 1000 + + + + - - - - Hide mouse after inactivity - - - - - - - + Qt::Horizontal @@ -54,20 +135,8 @@ - - - - seconds of inactivity - - - - - cbMouseHide - spinMouseHideSeconds - cbPauseLostFocus - diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp deleted file mode 100644 index 25072df7..00000000 --- a/src/frontend/qt_sdl/OSD.cpp +++ /dev/null @@ -1,475 +0,0 @@ -/* - 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 -#include -#include -#include -#include "../types.h" - -#include "main.h" -#include "OpenGLSupport.h" -#include - -#include "OSD.h" -#include "OSD_shaders.h" -#include "font.h" - -#include "Config.h" - -using namespace melonDS; - -extern MainWindow* mainWindow; - -namespace OSD -{ - -const u32 kOSDMargin = 6; - -struct Item -{ - Uint32 Timestamp; - char Text[256]; - u32 Color; - - u32 Width, Height; - u32* Bitmap; - - bool NativeBitmapLoaded; - QImage NativeBitmap; - - bool GLTextureLoaded; - GLuint GLTexture; -}; - -std::deque ItemQueue; - -GLuint Shader[3]; -GLint uScreenSize, uOSDPos, uOSDSize; -GLfloat uScaleFactor; -GLuint OSDVertexArray; -GLuint OSDVertexBuffer; - -QMutex Rendering; - - -bool Init(bool openGL) -{ - if (openGL) - { - OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, Shader, "OSDShader"); - - GLuint pid = Shader[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindFragDataLocation(pid, 0, "oColor"); - - OpenGL::LinkShaderProgram(Shader); - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0); - - uScreenSize = glGetUniformLocation(pid, "uScreenSize"); - uOSDPos = glGetUniformLocation(pid, "uOSDPos"); - uOSDSize = glGetUniformLocation(pid, "uOSDSize"); - uScaleFactor = glGetUniformLocation(pid, "uScaleFactor"); - - float vertices[6*2] = - { - 0, 0, - 1, 1, - 1, 0, - 0, 0, - 0, 1, - 1, 1 - }; - - glGenBuffers(1, &OSDVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &OSDVertexArray); - glBindVertexArray(OSDVertexArray); - glEnableVertexAttribArray(0); // position - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); - } - - return true; -} - -void DeInit() -{ - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - } -} - - -int FindBreakPoint(const char* text, int i) -{ - // i = character that went out of bounds - - for (int j = i; j >= 0; j--) - { - if (text[j] == ' ') - return j; - } - - return i; -} - -void LayoutText(const char* text, u32* width, u32* height, int* breaks) -{ - u32 w = 0; - u32 h = 14; - u32 totalw = 0; - u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); - int lastbreak = -1; - int numbrk = 0; - u16* ptr; - - memset(breaks, 0, sizeof(int)*64); - - for (int i = 0; text[i] != '\0'; ) - { - int glyphsize; - if (text[i] == ' ') - { - glyphsize = 6; - } - else - { - u32 ch = text[i]; - if (ch < 0x10 || ch > 0x7E) ch = 0x7F; - - ptr = &font[(ch-0x10) << 4]; - glyphsize = ptr[0]; - if (!glyphsize) glyphsize = 6; - else glyphsize += 2; // space around the character - } - - w += glyphsize; - if (w > maxw) - { - // wrap shit as needed - if (text[i] == ' ') - { - if (numbrk >= 64) break; - breaks[numbrk++] = i; - i++; - } - else - { - int brk = FindBreakPoint(text, i); - if (brk != lastbreak) i = brk; - - if (numbrk >= 64) break; - breaks[numbrk++] = i; - - lastbreak = brk; - } - - w = 0; - h += 14; - } - else - i++; - - if (w > totalw) totalw = w; - } - - *width = totalw; - *height = h; -} - -u32 RainbowColor(u32 inc) -{ - // inspired from Acmlmboard - - if (inc < 100) return 0xFFFF9B9B + (inc << 8); - else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16); - else if (inc < 300) return 0xFF9BFF9B + (inc-200); - else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8); - else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16); - else return 0xFFFF9BFF - (inc-500); -} - -void RenderText(u32 color, const char* text, Item* item) -{ - u32 w, h; - int breaks[64]; - - bool rainbow = (color == 0); - u32 rainbowinc = ((text[0] * 17) + (SDL_GetTicks() * 13)) % 600; - - color |= 0xFF000000; - const u32 shadow = 0xE0000000; - - LayoutText(text, &w, &h, breaks); - - item->Width = w; - item->Height = h; - item->Bitmap = new u32[w*h]; - memset(item->Bitmap, 0, w*h*sizeof(u32)); - - u32 x = 0, y = 1; - u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); - int curline = 0; - u16* ptr; - - for (int i = 0; text[i] != '\0'; ) - { - int glyphsize; - if (text[i] == ' ') - { - x += 6; - } - else - { - u32 ch = text[i]; - if (ch < 0x10 || ch > 0x7E) ch = 0x7F; - - ptr = &font[(ch-0x10) << 4]; - int glyphsize = ptr[0]; - if (!glyphsize) x += 6; - else - { - x++; - - if (rainbow) - { - color = RainbowColor(rainbowinc); - rainbowinc = (rainbowinc + 30) % 600; - } - - // draw character - for (int cy = 0; cy < 12; cy++) - { - u16 val = ptr[4+cy]; - - for (int cx = 0; cx < glyphsize; cx++) - { - if (val & (1<Bitmap[((y+cy) * w) + x+cx] = color; - } - } - - x += glyphsize; - x++; - } - } - - i++; - if (breaks[curline] && i >= breaks[curline]) - { - i = breaks[curline++]; - if (text[i] == ' ') i++; - - x = 0; - y += 14; - } - } - - // shadow - for (y = 0; y < h; y++) - { - for (x = 0; x < w; x++) - { - u32 val; - - val = item->Bitmap[(y * w) + x]; - if ((val >> 24) == 0xFF) continue; - - if (x > 0) val = item->Bitmap[(y * w) + x-1]; - if (x < w-1) val |= item->Bitmap[(y * w) + x+1]; - if (y > 0) - { - if (x > 0) val |= item->Bitmap[((y-1) * w) + x-1]; - val |= item->Bitmap[((y-1) * w) + x]; - if (x < w-1) val |= item->Bitmap[((y-1) * w) + x+1]; - } - if (y < h-1) - { - if (x > 0) val |= item->Bitmap[((y+1) * w) + x-1]; - val |= item->Bitmap[((y+1) * w) + x]; - if (x < w-1) val |= item->Bitmap[((y+1) * w) + x+1]; - } - - if ((val >> 24) == 0xFF) - item->Bitmap[(y * w) + x] = shadow; - } - } -} - - -void AddMessage(u32 color, const char* text) -{ - if (!Config::ShowOSD) return; - - Rendering.lock(); - - Item item; - - item.Timestamp = SDL_GetTicks(); - strncpy(item.Text, text, 255); item.Text[255] = '\0'; - item.Color = color; - item.Bitmap = nullptr; - - item.NativeBitmapLoaded = false; - item.GLTextureLoaded = false; - - ItemQueue.push_back(item); - - Rendering.unlock(); -} - -void Update() -{ - if (!Config::ShowOSD) - { - Rendering.lock(); - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - } - Rendering.unlock(); - return; - } - - Rendering.lock(); - - Uint32 tick_now = SDL_GetTicks(); - Uint32 tick_min = tick_now - 2500; - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.Timestamp < tick_min) - { - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - continue; - } - - if (!item.Bitmap) - { - RenderText(item.Color, item.Text, &item); - } - - it++; - } - - Rendering.unlock(); -} - -void DrawNative(QPainter& painter) -{ - if (!Config::ShowOSD) return; - - Rendering.lock(); - - u32 y = kOSDMargin; - - painter.resetTransform(); - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (!item.NativeBitmapLoaded) - { - item.NativeBitmap = QImage((const uchar*)item.Bitmap, item.Width, item.Height, QImage::Format_ARGB32_Premultiplied); - item.NativeBitmapLoaded = true; - } - - painter.drawImage(kOSDMargin, y, item.NativeBitmap); - - y += item.Height; - it++; - } - - Rendering.unlock(); -} - -void DrawGL(float w, float h) -{ - if (!Config::ShowOSD) return; - if (!mainWindow || !mainWindow->panel) return; - - Rendering.lock(); - - u32 y = kOSDMargin; - - glUseProgram(Shader[2]); - - glUniform2f(uScreenSize, w, h); - glUniform1f(uScaleFactor, mainWindow->devicePixelRatioF()); - - glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); - glBindVertexArray(OSDVertexArray); - - glActiveTexture(GL_TEXTURE0); - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (!item.GLTextureLoaded) - { - glGenTextures(1, &item.GLTexture); - glBindTexture(GL_TEXTURE_2D, item.GLTexture); - 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, item.Width, item.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.Bitmap); - - item.GLTextureLoaded = true; - } - - glBindTexture(GL_TEXTURE_2D, item.GLTexture); - glUniform2i(uOSDPos, kOSDMargin, y); - glUniform2i(uOSDSize, item.Width, item.Height); - glDrawArrays(GL_TRIANGLES, 0, 2*3); - - y += item.Height; - it++; - } - - glDisable(GL_BLEND); - glUseProgram(0); - - Rendering.unlock(); -} - -} diff --git a/src/frontend/qt_sdl/OSD.h b/src/frontend/qt_sdl/OSD.h deleted file mode 100644 index 64131d5b..00000000 --- a/src/frontend/qt_sdl/OSD.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - 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 OSD_H -#define OSD_H - -#include "types.h" - -namespace OSD -{ - -using namespace melonDS; -bool Init(bool openGL); -void DeInit(); - -void AddMessage(u32 color, const char* text); - -void Update(); -void DrawNative(QPainter& painter); -void DrawGL(float w, float h); - -} - -#endif // OSD_H diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 1d698537..71342087 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "types.h" #include "Config.h" @@ -37,6 +38,7 @@ extern bool RunningSomething; bool PathSettingsDialog::needsReset = false; +constexpr char errordialog[] = "melonDS cannot write to that directory."; PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog) { @@ -101,6 +103,12 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSaveFilePath->setText(dir); } @@ -112,6 +120,12 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSavestatePath->setText(dir); } @@ -123,6 +137,12 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtCheatFilePath->setText(dir); } diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 6fe87ac4..9bb19d1a 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -39,7 +40,6 @@ #include "LAN_Socket.h" #include "LAN_PCap.h" #include "LocalMP.h" -#include "OSD.h" #include "SPI_Firmware.h" #ifdef __WIN32__ @@ -53,10 +53,50 @@ extern CameraManager* camManager[2]; void emuStop(); +// TEMP +//#include "main.h" +//extern MainWindow* mainWindow; + namespace melonDS::Platform { +void PathInit(int argc, char** argv) +{ + // First, check for the portable directory next to the executable. + QString appdirpath = QCoreApplication::applicationDirPath(); + QString portablepath = appdirpath + QDir::separator() + "portable"; + +#if defined(__APPLE__) + // On Apple platforms we may need to navigate outside an app bundle. + // The executable directory would be "melonDS.app/Contents/MacOS", so we need to go a total of three steps up. + QDir bundledir(appdirpath); + if (bundledir.cd("..") && bundledir.cd("..") && bundledir.dirName().endsWith(".app") && bundledir.cd("..")) + { + portablepath = bundledir.absolutePath() + QDir::separator() + "portable"; + } +#endif + + QDir portabledir(portablepath); + if (portabledir.exists()) + { + EmuDirectory = portabledir.absolutePath().toStdString(); + } + else + { + // If no overrides are specified, use the default path. +#if defined(__WIN32__) && defined(WIN32_PORTABLE) + EmuDirectory = appdirpath.toStdString(); +#else + QString confdir; + QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); + config.mkdir("melonDS"); + confdir = config.absolutePath() + QDir::separator() + "melonDS"; + EmuDirectory = confdir.toStdString(); +#endif + } +} + QSharedMemory* IPCBuffer = nullptr; int IPCInstanceID; @@ -130,38 +170,7 @@ void IPCDeInit() void Init(int argc, char** argv) { -#if defined(__WIN32__) || defined(PORTABLE) - if (argc > 0 && strlen(argv[0]) > 0) - { - int len = strlen(argv[0]); - while (len > 0) - { - if (argv[0][len] == '/') break; - if (argv[0][len] == '\\') break; - len--; - } - if (len > 0) - { - std::string emudir = argv[0]; - EmuDirectory = emudir.substr(0, len); - } - else - { - EmuDirectory = "."; - } - } - else - { - EmuDirectory = "."; - } -#else - QString confdir; - QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); - config.mkdir("melonDS"); - confdir = config.absolutePath() + "/melonDS/"; - EmuDirectory = confdir.toStdString(); -#endif - + PathInit(argc, argv); IPCInit(); } @@ -177,14 +186,14 @@ void SignalStop(StopReason reason) { case StopReason::GBAModeNotSupported: Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n"); - OSD::AddMessage(0xFFA0A0, "GBA mode not supported."); + //mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported."); break; case StopReason::BadExceptionRegion: - OSD::AddMessage(0xFFA0A0, "Internal error."); + //mainWindow->osdAddMessage(0xFFA0A0, "Internal error."); break; case StopReason::PowerOff: case StopReason::External: - OSD::AddMessage(0xFFC040, "Shutdown"); + //mainWindow->osdAddMessage(0xFFC040, "Shutdown"); default: break; } @@ -208,6 +217,10 @@ std::string InstanceFileSuffix() constexpr char AccessMode(FileMode mode, bool file_exists) { + + if (mode & FileMode::Append) + return 'a'; + if (!(mode & FileMode::Write)) // If we're only opening the file for reading... return 'r'; @@ -246,7 +259,7 @@ static std::string GetModeString(FileMode mode, bool file_exists) FileHandle* OpenFile(const std::string& path, FileMode mode) { - if ((mode & FileMode::ReadWrite) == FileMode::None) + if ((mode & (FileMode::ReadWrite | FileMode::Append)) == FileMode::None) { // If we aren't reading or writing, then we can't open the file Log(LogLevel::Error, "Attempted to open \"%s\" in neither read nor write mode (FileMode 0x%x)\n", path.c_str(), mode); return nullptr; @@ -281,15 +294,7 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode) } else { -#ifdef PORTABLE fullpath = QString::fromStdString(EmuDirectory) + QDir::separator() + qpath; -#else - // Check user configuration directory - QDir config(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); - config.mkdir("melonDS"); - fullpath = config.absolutePath() + "/melonDS/"; - fullpath.append(qpath); -#endif } return OpenFile(fullpath.toStdString(), mode); @@ -326,6 +331,28 @@ bool LocalFileExists(const std::string& name) return true; } +bool CheckFileWritable(const std::string& filepath) +{ + FileHandle* file = Platform::OpenFile(filepath.c_str(), FileMode::Append); + if (file) + { + Platform::CloseFile(file); + return true; + } + else return false; +} + +bool CheckLocalFileWritable(const std::string& name) +{ + FileHandle* file = Platform::OpenLocalFile(name.c_str(), FileMode::Append); + if (file) + { + Platform::CloseFile(file); + return true; + } + else return false; +} + bool FileSeek(FileHandle* file, s64 offset, FileSeekOrigin origin) { int stdorigin; diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index a20af206..e3ceebbe 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #ifdef ARCHIVE_SUPPORT_ENABLED @@ -210,6 +211,9 @@ QString VerifyDSFirmware() f = Platform::OpenLocalFile(Config::FirmwarePath, FileMode::Read); if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DS firmware is unable to be written to.\nPlease check file/folder write permissions."; + len = FileLength(f); if (len == 0x20000) { @@ -237,6 +241,9 @@ QString VerifyDSiFirmware() f = Platform::OpenLocalFile(Config::DSiFirmwarePath, FileMode::Read); if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DSi firmware is unable to be written to.\nPlease check file/folder write permissions."; + len = FileLength(f); if (len != 0x20000) { @@ -259,6 +266,9 @@ QString VerifyDSiNAND() f = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DSi NAND is unable to be written to.\nPlease check file/folder write permissions."; + // TODO: some basic checks // check that it has the nocash footer, and all @@ -759,7 +769,12 @@ std::optional LoadNAND(const std::array& a return nandImage; } -constexpr u64 imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; +constexpr u64 MB(u64 i) +{ + return i * 1024 * 1024; +} + +constexpr u64 imgsizes[] = {0, MB(256), MB(512), MB(1024), MB(2048), MB(4096)}; std::optional GetDSiSDCardArgs() noexcept { if (!Config::DSiSDEnable) @@ -804,12 +819,7 @@ std::optional LoadDLDISDCard() noexcept if (!Config::DLDIEnable) return std::nullopt; - return FATStorage( - Config::DLDISDPath, - imgsizes[Config::DLDISize], - Config::DLDIReadOnly, - Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt - ); + return FATStorage(*GetDLDISDCardArgs()); } void EnableCheats(NDS& nds, bool enable) @@ -1276,7 +1286,18 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u return false; } -bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) +QString GetSavErrorString(std::string& filepath, bool gba) +{ + std::string console = gba ? "GBA" : "DS"; + std::string err1 = "Unable to write to "; + std::string err2 = " save.\nPlease check file/folder write permissions.\n\nAttempted to Access:\n"; + + err1 += console + err2 + filepath; + + return QString::fromStdString(err1); +} + +bool LoadROM(QMainWindow* mainWindow, EmuThread* emuthread, QStringList filepath, bool reset) { unique_ptr filedata = nullptr; u32 filelen; @@ -1284,7 +1305,10 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) std::string romname; if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; + } NDSSave = nullptr; @@ -1300,7 +1324,22 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) savname += Platform::InstanceFileSuffix(); FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); - if (!sav) sav = Platform::OpenFile(origsav, FileMode::Read); + if (!sav) + { + if (!Platform::CheckFileWritable(origsav)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(origsav, false)); + return false; + } + + sav = Platform::OpenFile(origsav, FileMode::Read); + } + else if (!Platform::CheckFileWritable(savname)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(savname, false)); + return false; + } + if (sav) { savelen = (u32)Platform::FileLength(sav); @@ -1322,13 +1361,19 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs)); if (!cart) + { // If we couldn't parse the ROM... + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; + } if (reset) { if (!emuthread->UpdateConsole(std::move(cart), Keep {})) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; + } InitFirmwareSaveManager(emuthread); emuthread->NDS->Reset(); @@ -1351,7 +1396,7 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) NDSSave = std::make_unique(savname); LoadCheats(*emuthread->NDS); - return true; + return true; // success } void EjectCart(NDS& nds) @@ -1388,9 +1433,13 @@ QString CartLabel() } -bool LoadGBAROM(NDS& nds, QStringList filepath) +bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) { - if (nds.ConsoleType == 1) return false; // DSi doesn't have a GBA slot + if (nds.ConsoleType == 1) + { + QMessageBox::critical(mainWindow, "melonDS", "The DSi doesn't have a GBA slot."); + return false; + } unique_ptr filedata = nullptr; u32 filelen; @@ -1398,7 +1447,10 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) std::string romname; if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; + } GBASave = nullptr; @@ -1414,7 +1466,22 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) savname += Platform::InstanceFileSuffix(); FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); - if (!sav) sav = Platform::OpenFile(origsav, FileMode::Read); + if (!sav) + { + if (!Platform::CheckFileWritable(origsav)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(origsav, true)); + return false; + } + + sav = Platform::OpenFile(origsav, FileMode::Read); + } + else if (!Platform::CheckFileWritable(savname)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(savname, true)); + return false; + } + if (sav) { savelen = (u32)FileLength(sav); @@ -1430,7 +1497,10 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); if (!cart) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; + } nds.SetGBACart(std::move(cart)); GBACartType = 0; diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h index 0b640c84..38ed65cd 100644 --- a/src/frontend/qt_sdl/ROMManager.h +++ b/src/frontend/qt_sdl/ROMManager.h @@ -23,6 +23,7 @@ #include "SaveManager.h" #include "AREngine.h" #include "DSi_NAND.h" +#include #include "MemConstants.h" #include @@ -72,12 +73,12 @@ std::optional LoadFirmware(int type) noexcept; std::optional LoadNAND(const std::array& arm7ibios) noexcept; /// Inserts a ROM into the emulated console. -bool LoadROM(EmuThread*, QStringList filepath, bool reset); +bool LoadROM(QMainWindow* mainWindow, EmuThread*, QStringList filepath, bool reset); void EjectCart(NDS& nds); bool CartInserted(); QString CartLabel(); -bool LoadGBAROM(NDS& nds, QStringList filepath); +bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath); void LoadGBAAddon(NDS& nds, int type); void EjectGBACart(NDS& nds); bool GBACartInserted(); diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index 61b6891d..cfcbeed9 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -35,30 +35,27 @@ #include #endif #endif +#include + +#include "OpenGLSupport.h" +#include "duckstation/gl/context.h" #include "main.h" #include "NDS.h" +#include "GPU.h" +#include "GPU3D_Soft.h" +#include "GPU3D_OpenGL.h" #include "Platform.h" #include "Config.h" -//#include "main_shaders.h" - -#include "OSD.h" +#include "main_shaders.h" +#include "OSD_shaders.h" +#include "font.h" using namespace melonDS; -/*const struct { int id; float ratio; const char* label; } aspectRatios[] = -{ - { 0, 1, "4:3 (native)" }, - { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, - { 1, (16.f / 9) / (4.f / 3), "16:9" }, - { 2, (21.f / 9) / (4.f / 3), "21:9" }, - { 3, 0, "window" } -}; -int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]);*/ - // TEMP extern MainWindow* mainWindow; extern EmuThread* emuThread; @@ -68,23 +65,31 @@ extern int autoScreenSizing; extern int videoRenderer; extern bool videoSettingsDirty; +const u32 kOSDMargin = 6; -ScreenHandler::ScreenHandler(QWidget* widget) + +ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) { - widget->setMouseTracking(true); - widget->setAttribute(Qt::WA_AcceptTouchEvents); + setMouseTracking(true); + setAttribute(Qt::WA_AcceptTouchEvents); QTimer* mouseTimer = setupMouseTimer(); - widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); + connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);}); + + osdEnabled = false; + osdID = 1; } -ScreenHandler::~ScreenHandler() +ScreenPanel::~ScreenPanel() { mouseTimer->stop(); delete mouseTimer; } -void ScreenHandler::screenSetupLayout(int w, int h) +void ScreenPanel::setupScreenLayout() { + int w = width(); + int h = height(); + int sizing = Config::ScreenSizing; if (sizing == 3) sizing = autoScreenSizing; @@ -117,7 +122,7 @@ void ScreenHandler::screenSetupLayout(int w, int h) numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); } -QSize ScreenHandler::screenGetMinSize(int factor = 1) +QSize ScreenPanel::screenGetMinSize(int factor = 1) { bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg || Config::ScreenRotation == Frontend::screenRot_270Deg); @@ -162,7 +167,19 @@ QSize ScreenHandler::screenGetMinSize(int factor = 1) } } -void ScreenHandler::screenOnMousePress(QMouseEvent* event) +void ScreenPanel::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} + +void ScreenPanel::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); + QWidget::resizeEvent(event); +} + +void ScreenPanel::mousePressEvent(QMouseEvent* event) { event->accept(); if (event->button() != Qt::LeftButton) return; @@ -178,7 +195,7 @@ void ScreenHandler::screenOnMousePress(QMouseEvent* event) } } -void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) +void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) { event->accept(); if (event->button() != Qt::LeftButton) return; @@ -191,7 +208,7 @@ void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) } } -void ScreenHandler::screenOnMouseMove(QMouseEvent* event) +void ScreenPanel::mouseMoveEvent(QMouseEvent* event) { event->accept(); @@ -210,7 +227,7 @@ void ScreenHandler::screenOnMouseMove(QMouseEvent* event) } } -void ScreenHandler::screenHandleTablet(QTabletEvent* event) +void ScreenPanel::tabletEvent(QTabletEvent* event) { event->accept(); @@ -243,7 +260,7 @@ void ScreenHandler::screenHandleTablet(QTabletEvent* event) } } -void ScreenHandler::screenHandleTouch(QTouchEvent* event) +void ScreenPanel::touchEvent(QTouchEvent* event) { event->accept(); @@ -278,13 +295,26 @@ void ScreenHandler::screenHandleTouch(QTouchEvent* event) } } -void ScreenHandler::showCursor() +bool ScreenPanel::event(QEvent* event) { - mainWindow->panelWidget->setCursor(Qt::ArrowCursor); + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + touchEvent((QTouchEvent*)event); + return true; + } + + return QWidget::event(event); +} + +void ScreenPanel::showCursor() +{ + mainWindow->panel->setCursor(Qt::ArrowCursor); mouseTimer->start(); } -QTimer* ScreenHandler::setupMouseTimer() +QTimer* ScreenPanel::setupMouseTimer() { mouseTimer = new QTimer(); mouseTimer->setSingleShot(true); @@ -294,35 +324,290 @@ QTimer* ScreenHandler::setupMouseTimer() return mouseTimer; } -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) +int ScreenPanel::osdFindBreakPoint(const char* text, int i) +{ + // i = character that went out of bounds + + for (int j = i; j >= 0; j--) + { + if (text[j] == ' ') + return j; + } + + return i; +} + +void ScreenPanel::osdLayoutText(const char* text, int* width, int* height, int* breaks) +{ + int w = 0; + int h = 14; + int totalw = 0; + int maxw = ((QWidget*)this)->width() - (kOSDMargin*2); + int lastbreak = -1; + int numbrk = 0; + u16* ptr; + + memset(breaks, 0, sizeof(int)*64); + + for (int i = 0; text[i] != '\0'; ) + { + int glyphsize; + if (text[i] == ' ') + { + glyphsize = 6; + } + else + { + u32 ch = text[i]; + if (ch < 0x10 || ch > 0x7E) ch = 0x7F; + + ptr = &::font[(ch-0x10) << 4]; + glyphsize = ptr[0]; + if (!glyphsize) glyphsize = 6; + else glyphsize += 2; // space around the character + } + + w += glyphsize; + if (w > maxw) + { + // wrap shit as needed + if (text[i] == ' ') + { + if (numbrk >= 64) break; + breaks[numbrk++] = i; + i++; + } + else + { + int brk = osdFindBreakPoint(text, i); + if (brk != lastbreak) i = brk; + + if (numbrk >= 64) break; + breaks[numbrk++] = i; + + lastbreak = brk; + } + + w = 0; + h += 14; + } + else + i++; + + if (w > totalw) totalw = w; + } + + *width = totalw; + *height = h; +} + +unsigned int ScreenPanel::osdRainbowColor(int inc) +{ + // inspired from Acmlmboard + + if (inc < 100) return 0xFFFF9B9B + (inc << 8); + else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16); + else if (inc < 300) return 0xFF9BFF9B + (inc-200); + else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8); + else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16); + else return 0xFFFF9BFF - (inc-500); +} + +void ScreenPanel::osdRenderItem(OSDItem* item) +{ + int w, h; + int breaks[64]; + + char* text = item->text; + u32 color = item->color; + + bool rainbow = (color == 0); + u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch(); + u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + + color |= 0xFF000000; + const u32 shadow = 0xE0000000; + + osdLayoutText(text, &w, &h, breaks); + + item->bitmap = QImage(w, h, QImage::Format_ARGB32_Premultiplied); + u32* bitmap = (u32*)item->bitmap.bits(); + memset(bitmap, 0, w*h*sizeof(u32)); + + int x = 0, y = 1; + u32 maxw = ((QWidget*)this)->width() - (kOSDMargin*2); + int curline = 0; + u16* ptr; + + for (int i = 0; text[i] != '\0'; ) + { + int glyphsize; + if (text[i] == ' ') + { + x += 6; + } + else + { + u32 ch = text[i]; + if (ch < 0x10 || ch > 0x7E) ch = 0x7F; + + ptr = &::font[(ch-0x10) << 4]; + int glyphsize = ptr[0]; + if (!glyphsize) x += 6; + else + { + x++; + + if (rainbow) + { + color = osdRainbowColor(rainbowinc); + rainbowinc = (rainbowinc + 30) % 600; + } + + // draw character + for (int cy = 0; cy < 12; cy++) + { + u16 val = ptr[4+cy]; + + for (int cx = 0; cx < glyphsize; cx++) + { + if (val & (1<= breaks[curline]) + { + i = breaks[curline++]; + if (text[i] == ' ') i++; + + x = 0; + y += 14; + } + } + + // shadow + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + u32 val; + + val = bitmap[(y * w) + x]; + if ((val >> 24) == 0xFF) continue; + + if (x > 0) val = bitmap[(y * w) + x-1]; + if (x < w-1) val |= bitmap[(y * w) + x+1]; + if (y > 0) + { + if (x > 0) val |= bitmap[((y-1) * w) + x-1]; + val |= bitmap[((y-1) * w) + x]; + if (x < w-1) val |= bitmap[((y-1) * w) + x+1]; + } + if (y < h-1) + { + if (x > 0) val |= bitmap[((y+1) * w) + x-1]; + val |= bitmap[((y+1) * w) + x]; + if (x < w-1) val |= bitmap[((y+1) * w) + x+1]; + } + + if ((val >> 24) == 0xFF) + bitmap[(y * w) + x] = shadow; + } + } +} + +void ScreenPanel::osdDeleteItem(OSDItem* item) +{ +} + +void ScreenPanel::osdSetEnabled(bool enabled) +{ + osdMutex.lock(); + osdEnabled = enabled; + osdMutex.unlock(); +} + +void ScreenPanel::osdAddMessage(unsigned int color, const char* text) +{ + if (!osdEnabled) return; + + osdMutex.lock(); + + OSDItem item; + + item.id = osdID++; + item.timestamp = QDateTime::currentMSecsSinceEpoch(); + strncpy(item.text, text, 255); item.text[255] = '\0'; + item.color = color; + item.rendered = false; + + osdItems.push_back(item); + + osdMutex.unlock(); +} + +void ScreenPanel::osdUpdate() +{ + osdMutex.lock(); + + qint64 tick_now = QDateTime::currentMSecsSinceEpoch(); + qint64 tick_min = tick_now - 2500; + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + if ((!osdEnabled) || (item.timestamp < tick_min)) + { + osdDeleteItem(&item); + it = osdItems.erase(it); + continue; + } + + if (!item.rendered) + { + osdRenderItem(&item); + item.rendered = true; + } + + it++; + } + + osdMutex.unlock(); +} + + + +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : ScreenPanel(parent) { screen[0] = QImage(256, 192, QImage::Format_RGB32); screen[1] = QImage(256, 192, QImage::Format_RGB32); screenTrans[0].reset(); screenTrans[1].reset(); - - OSD::Init(false); } ScreenPanelNative::~ScreenPanelNative() { - OSD::DeInit(); } void ScreenPanelNative::setupScreenLayout() { - int w = width(); - int h = height(); - - screenSetupLayout(w, h); + ScreenPanel::setupScreenLayout(); for (int i = 0; i < numScreens; i++) { float* mtx = screenMatrix[i]; screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, - mtx[2], mtx[3], 0.f, - mtx[4], mtx[5], 1.f); + mtx[2], mtx[3], 0.f, + mtx[4], mtx[5], 1.f); } } @@ -357,55 +642,32 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) } } - OSD::Update(); - OSD::DrawNative(painter); -} - -void ScreenPanelNative::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); -} - -void ScreenPanelNative::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelNative::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelNative::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) + osdUpdate(); + if (osdEnabled) { - screenHandleTouch((QTouchEvent*)event); - return true; + osdMutex.lock(); + + u32 y = kOSDMargin; + + painter.resetTransform(); + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + painter.drawImage(kOSDMargin, y, item.bitmap); + + y += item.bitmap.height(); + it++; + } + + osdMutex.unlock(); } - return QWidget::event(event); -} - -void ScreenPanelNative::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); } -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) + +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) { setAutoFillBackground(false); setAttribute(Qt::WA_NativeWindow, true); @@ -434,6 +696,284 @@ bool ScreenPanelGL::createContext() return glContext != nullptr; } +void ScreenPanelGL::setSwapInterval(int intv) +{ + if (!glContext) return; + + glContext->SetSwapInterval(intv); +} + +void ScreenPanelGL::initOpenGL() +{ + if (!glContext) return; + + glContext->MakeCurrent(); + + OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); + GLuint pid = screenShaderProgram[2]; + glBindAttribLocation(pid, 0, "vPosition"); + glBindAttribLocation(pid, 1, "vTexcoord"); + glBindFragDataLocation(pid, 0, "oColor"); + + OpenGL::LinkShaderProgram(screenShaderProgram); + + glUseProgram(pid); + glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); + + screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); + screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); + + // to prevent bleeding between both parts of the screen + // with bilinear filtering enabled + const int paddedHeight = 192*2+2; + const float padPixels = 1.f / paddedHeight; + + const float vertices[] = + { + 0.f, 0.f, 0.f, 0.f, + 0.f, 192.f, 0.f, 0.5f - padPixels, + 256.f, 192.f, 1.f, 0.5f - padPixels, + 0.f, 0.f, 0.f, 0.f, + 256.f, 192.f, 1.f, 0.5f - padPixels, + 256.f, 0.f, 1.f, 0.f, + + 0.f, 0.f, 0.f, 0.5f + padPixels, + 0.f, 192.f, 0.f, 1.f, + 256.f, 192.f, 1.f, 1.f, + 0.f, 0.f, 0.f, 0.5f + padPixels, + 256.f, 192.f, 1.f, 1.f, + 256.f, 0.f, 1.f, 0.5f + padPixels + }; + + glGenBuffers(1, &screenVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenVertexArrays(1, &screenVertexArray); + glBindVertexArray(screenVertexArray); + glEnableVertexAttribArray(0); // position + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); + glEnableVertexAttribArray(1); // texcoord + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); + + glGenTextures(1, &screenTexture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, screenTexture); + 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, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + // fill the padding + u8 zeroData[256*4*4]; + memset(zeroData, 0, sizeof(zeroData)); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); + + + OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, osdShader, "OSDShader"); + + pid = osdShader[2]; + glBindAttribLocation(pid, 0, "vPosition"); + glBindFragDataLocation(pid, 0, "oColor"); + + OpenGL::LinkShaderProgram(osdShader); + glUseProgram(pid); + glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0); + + osdScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); + osdPosULoc = glGetUniformLocation(pid, "uOSDPos"); + osdSizeULoc = glGetUniformLocation(pid, "uOSDSize"); + osdScaleFactorULoc = glGetUniformLocation(pid, "uScaleFactor"); + + const float osdvertices[6*2] = + { + 0, 0, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 1 + }; + + glGenBuffers(1, &osdVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(osdvertices), osdvertices, GL_STATIC_DRAW); + + glGenVertexArrays(1, &osdVertexArray); + glBindVertexArray(osdVertexArray); + glEnableVertexAttribArray(0); // position + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); + + + glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); + transferLayout(); +} + +void ScreenPanelGL::deinitOpenGL() +{ + if (!glContext) return; + + glDeleteTextures(1, &screenTexture); + + glDeleteVertexArrays(1, &screenVertexArray); + glDeleteBuffers(1, &screenVertexBuffer); + + OpenGL::DeleteShaderProgram(screenShaderProgram); + + + for (const auto& [key, tex] : osdTextures) + { + glDeleteTextures(1, &tex); + } + osdTextures.clear(); + + glDeleteVertexArrays(1, &osdVertexArray); + glDeleteBuffers(1, &osdVertexBuffer); + + OpenGL::DeleteShaderProgram(osdShader); + + + glContext->DoneCurrent(); + + lastScreenWidth = lastScreenHeight = -1; +} + +void ScreenPanelGL::osdRenderItem(OSDItem* item) +{ + ScreenPanel::osdRenderItem(item); + + 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, item->bitmap.width(), item->bitmap.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, item->bitmap.bits()); + + osdTextures[item->id] = tex; +} + +void ScreenPanelGL::osdDeleteItem(OSDItem* item) +{ + if (osdTextures.count(item->id)) + { + GLuint tex = osdTextures[item->id]; + glDeleteTextures(1, &tex); + osdTextures.erase(item->id); + } + + ScreenPanel::osdDeleteItem(item); +} + +void ScreenPanelGL::drawScreenGL() +{ + if (!glContext) return; + if (!emuThread->NDS) return; + + int w = windowInfo.surface_width; + int h = windowInfo.surface_height; + float factor = windowInfo.surface_scale; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDisable(GL_DEPTH_TEST); + glDepthMask(false); + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, w, h); + + glUseProgram(screenShaderProgram[2]); + glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + + int frontbuf = emuThread->FrontBuffer; + glActiveTexture(GL_TEXTURE0); + +#ifdef OGLRENDERER_ENABLED + if (emuThread->NDS->GPU.GetRenderer3D().Accelerated) + { + // hardware-accelerated render + static_cast(emuThread->NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf); + } + else +#endif + { + // regular render + glBindTexture(GL_TEXTURE_2D, screenTexture); + + if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][0].get()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, emuThread->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(); + + osdUpdate(); + if (osdEnabled) + { + osdMutex.lock(); + + u32 y = kOSDMargin; + + glUseProgram(osdShader[2]); + + glUniform2f(osdScreenSizeULoc, w, h); + glUniform1f(osdScaleFactorULoc, factor); + + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBindVertexArray(osdVertexArray); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + if (!osdTextures.count(item.id)) + continue; + + glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]); + glUniform2i(osdPosULoc, kOSDMargin, y); + glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height()); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + + y += item.bitmap.height(); + it++; + } + + glDisable(GL_BLEND); + glUseProgram(0); + + osdMutex.unlock(); + } + + glContext->SwapBuffers(); +} + qreal ScreenPanelGL::devicePixelRatioFromScreen() const { const QScreen* screen_for_ratio = window()->windowHandle()->screen(); @@ -505,62 +1045,28 @@ QPaintEngine* ScreenPanelGL::paintEngine() const void ScreenPanelGL::setupScreenLayout() { - int w = width(); - int h = height(); - - screenSetupLayout(w, h); - if (emuThread) - transferLayout(emuThread); + ScreenPanel::setupScreenLayout(); + transferLayout(); } -void ScreenPanelGL::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); - - QWidget::resizeEvent(event); -} - -void ScreenPanelGL::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelGL::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelGL::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelGL::transferLayout(EmuThread* thread) +void ScreenPanelGL::transferLayout() { std::optional windowInfo = getWindowInfo(); if (windowInfo.has_value()) - thread->updateScreenSettings(Config::ScreenFilter, *windowInfo, numScreens, screenKind, &screenMatrix[0][0]); -} + { + screenSettingsLock.lock(); -void ScreenPanelGL::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); + if (lastScreenWidth != windowInfo->surface_width || lastScreenHeight != windowInfo->surface_height) + { + if (glContext) + glContext->ResizeSurface(windowInfo->surface_width, windowInfo->surface_height); + lastScreenWidth = windowInfo->surface_width; + lastScreenHeight = windowInfo->surface_height; + } + + this->filter = Config::ScreenFilter; + this->windowInfo = *windowInfo; + + screenSettingsLock.unlock(); + } } diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h index 955eb2e6..c2f7fda1 100644 --- a/src/frontend/qt_sdl/Screen.h +++ b/src/frontend/qt_sdl/Screen.h @@ -19,19 +19,20 @@ #ifndef SCREEN_H #define SCREEN_H -#include "glad/glad.h" -#include "FrontendUtil.h" -#include "duckstation/gl/context.h" +#include +#include +#include #include -#include -#include #include -#include -#include #include #include #include +#include + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" class EmuThread; @@ -48,27 +49,54 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] = constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); -class ScreenHandler +class ScreenPanel : public QWidget { - Q_GADGET + Q_OBJECT public: - ScreenHandler(QWidget* widget); - virtual ~ScreenHandler(); + explicit ScreenPanel(QWidget* parent); + virtual ~ScreenPanel(); + QTimer* setupMouseTimer(); void updateMouseTimer(); QTimer* mouseTimer; QSize screenGetMinSize(int factor); + void osdSetEnabled(bool enabled); + void osdAddMessage(unsigned int color, const char* msg); + +private slots: + void onScreenLayoutChanged(); + protected: - void screenSetupLayout(int w, int h); + struct OSDItem + { + unsigned int id; + qint64 timestamp; - void screenOnMousePress(QMouseEvent* event); - void screenOnMouseRelease(QMouseEvent* event); - void screenOnMouseMove(QMouseEvent* event); + char text[256]; + unsigned int color; - void screenHandleTablet(QTabletEvent* event); - void screenHandleTouch(QTouchEvent* event); + bool rendered; + QImage bitmap; + }; + + QMutex osdMutex; + bool osdEnabled; + unsigned int osdID; + std::deque osdItems; + + virtual void setupScreenLayout(); + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + void touchEvent(QTouchEvent* event); + bool event(QEvent* event) override; float screenMatrix[Frontend::MaxScreenTransforms][6]; int screenKind[Frontend::MaxScreenTransforms]; @@ -77,10 +105,19 @@ protected: bool touching = false; void showCursor(); + + int osdFindBreakPoint(const char* text, int i); + void osdLayoutText(const char* text, int* width, int* height, int* breaks); + unsigned int osdRainbowColor(int inc); + + virtual void osdRenderItem(OSDItem* item); + virtual void osdDeleteItem(OSDItem* item); + + void osdUpdate(); }; -class ScreenPanelNative : public QWidget, public ScreenHandler +class ScreenPanelNative : public ScreenPanel { Q_OBJECT @@ -91,26 +128,15 @@ public: protected: void paintEvent(QPaintEvent* event) override; - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; -private slots: - void onScreenLayoutChanged(); - private: - void setupScreenLayout(); + void setupScreenLayout() override; QImage screen[2]; QTransform screenTrans[Frontend::MaxScreenTransforms]; }; -class ScreenPanelGL : public QWidget, public ScreenHandler +class ScreenPanelGL : public ScreenPanel { Q_OBJECT @@ -122,9 +148,15 @@ public: bool createContext(); + void setSwapInterval(int intv); + + void initOpenGL(); + void deinitOpenGL(); + void drawScreenGL(); + GL::Context* getContext() { return glContext.get(); } - void transferLayout(EmuThread* thread); + void transferLayout(); protected: qreal devicePixelRatioFromScreen() const; @@ -133,22 +165,31 @@ protected: QPaintEngine* paintEngine() const override; - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; - -private slots: - void onScreenLayoutChanged(); - private: - void setupScreenLayout(); + void setupScreenLayout() override; std::unique_ptr glContext; + + GLuint screenVertexBuffer, screenVertexArray; + GLuint screenTexture; + GLuint screenShaderProgram[3]; + GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; + + QMutex screenSettingsLock; + WindowInfo windowInfo; + bool filter; + + int lastScreenWidth = -1, lastScreenHeight = -1; + + GLuint osdShader[3]; + GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc; + GLfloat osdScaleFactorULoc; + GLuint osdVertexArray; + GLuint osdVertexBuffer; + std::map osdTextures; + + void osdRenderItem(OSDItem* item) override; + void osdDeleteItem(OSDItem* item) override; }; #endif // SCREEN_H diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index eda1bbce..21ca844e 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -114,7 +114,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) u32 icondata[32*32]; ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata); - QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32); + QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_RGBA8888); QIcon icon(QPixmap::fromImage(iconimg.copy())); // TODO: make it possible to select other languages? diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index f373450b..0f0b71fd 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -81,15 +81,12 @@ #include "ArchiveUtil.h" #include "CameraManager.h" -#include "OSD.h" - using namespace melonDS; // TEMP extern MainWindow* mainWindow; extern EmuThread* emuThread; extern bool RunningSomething; -extern int autoScreenSizing; extern QString NdsRomMimeType; extern QStringList NdsRomExtensions; extern QString GbaRomMimeType; @@ -690,13 +687,13 @@ void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...) if (fmt == nullptr) return; - char msg[1024]; + char msg[256]; va_list args; va_start(args, fmt); - vsnprintf(msg, 1024, fmt, args); + vsnprintf(msg, 256, fmt, args); va_end(args); - OSD::AddMessage(color, msg); + panel->osdAddMessage(color, msg); } void MainWindow::closeEvent(QCloseEvent* event) @@ -721,7 +718,6 @@ void MainWindow::createScreenPanel() panelGL->show(); panel = panelGL; - panelWidget = panelGL; panelGL->createContext(); } @@ -730,14 +726,14 @@ void MainWindow::createScreenPanel() { ScreenPanelNative* panelNative = new ScreenPanelNative(this); panel = panelNative; - panelWidget = panelNative; - panelWidget->show(); + panel->show(); } - setCentralWidget(panelWidget); + setCentralWidget(panel); actScreenFiltering->setEnabled(hasOGL); + panel->osdSetEnabled(Config::ShowOSD); - connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); } @@ -749,6 +745,30 @@ GL::Context* MainWindow::getOGLContext() return glpanel->getContext(); } +/*void MainWindow::initOpenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->initOpenGL(); +} + +void MainWindow::deinitOpenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->deinitOpenGL(); +} + +void MainWindow::drawScreenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->drawScreenGL(); +}*/ + void MainWindow::resizeEvent(QResizeEvent* event) { int w = event->size().width(); @@ -845,10 +865,8 @@ void MainWindow::dropEvent(QDropEvent* event) if (isNdsRom) { - if (!ROMManager::LoadROM(emuThread, file, true)) + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); emuThread->emuUnpause(); return; } @@ -866,10 +884,8 @@ void MainWindow::dropEvent(QDropEvent* event) } else if (isGbaRom) { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); emuThread->emuUnpause(); return; } @@ -932,12 +948,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool gbaloaded = false; if (!gbafile.isEmpty()) { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - return false; - } + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, gbafile)) return false; gbaloaded = true; } @@ -945,12 +956,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool ndsloaded = false; if (!file.isEmpty()) { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - return false; - } + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) return false; + recentFileList.removeAll(file.join("|")); recentFileList.prepend(file.join("|")); updateRecentFilesMenu(); @@ -1153,11 +1160,9 @@ void MainWindow::onOpenFile() emuThread->emuUnpause(); return; } - - if (!ROMManager::LoadROM(emuThread, file, true)) + + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1252,11 +1257,9 @@ void MainWindow::onClickRecentFile() emuThread->emuUnpause(); return; } - - if (!ROMManager::LoadROM(emuThread, file, true)) + + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1306,10 +1309,8 @@ void MainWindow::onInsertCart() return; } - if (!ROMManager::LoadROM(emuThread, file, false)) + if (!ROMManager::LoadROM(mainWindow, emuThread, file, false)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1341,10 +1342,8 @@ void MainWindow::onInsertGBACart() return; } - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1843,7 +1842,7 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked) void MainWindow::onChangeScreenSize() { int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panelWidget->size(); + QSize diff = size() - panel->size(); resize(panel->screenGetMinSize(factor) + diff); } @@ -1936,7 +1935,9 @@ void MainWindow::onChangeScreenFiltering(bool checked) void MainWindow::onChangeShowOSD(bool checked) { Config::ShowOSD = checked?1:0; + panel->osdSetEnabled(Config::ShowOSD); } + void MainWindow::onChangeLimitFramerate(bool checked) { Config::LimitFPS = checked?1:0; @@ -2042,7 +2043,7 @@ void MainWindow::onUpdateVideoSettings(bool glchange) delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); + connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); } videoSettingsDirty = true; diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index 445d5e28..bc207480 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -92,8 +92,7 @@ private: bool oldMax; public: - ScreenHandler* panel; - QWidget* panelWidget; + ScreenPanel* panel; };*/ class MainWindow : public QMainWindow @@ -106,6 +105,9 @@ public: bool hasOGL; GL::Context* getOGLContext(); + /*void initOpenGL(); + void deinitOpenGL(); + void drawScreenGL();*/ bool preloadROMs(QStringList file, QStringList gbafile, bool boot); QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); @@ -227,8 +229,7 @@ private: bool oldMax; public: - ScreenHandler* panel; - QWidget* panelWidget; + ScreenPanel* panel; QAction* actOpenROM; QAction* actBootFirmware; diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 3f53f3cc..6e87d5bb 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -79,7 +79,6 @@ #include "version.h" #include "FrontendUtil.h" -#include "OSD.h" #include "Args.h" #include "NDS.h" @@ -99,7 +98,7 @@ #include "Savestate.h" -#include "main_shaders.h" +//#include "main_shaders.h" #include "ROMManager.h" #include "ArchiveUtil.h" @@ -115,6 +114,7 @@ QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; QString GbaRomMimeType = "application/x-gba-rom"; QStringList GbaRomExtensions { ".gba", ".agb" }; +QString* systemThemeName; // This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). QStringList ArchiveMimeTypes @@ -175,861 +175,6 @@ bool camStarted[2]; //extern int AspectRatiosNum; -EmuThread::EmuThread(QObject* parent) : QThread(parent) -{ - EmuStatus = emuStatus_Exit; - EmuRunning = emuStatus_Paused; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = false; - - connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); - connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); - connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); - connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); - connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger())); - connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); - connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); - connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); - connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); - connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); - connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); - - auto glPanel = dynamic_cast(mainWindow->panel); - if (glPanel) glPanel->transferLayout(this); -} - -std::unique_ptr EmuThread::CreateConsole( - std::unique_ptr&& ndscart, - std::unique_ptr&& gbacart -) noexcept -{ - auto arm7bios = ROMManager::LoadARM7BIOS(); - if (!arm7bios) - return nullptr; - - auto arm9bios = ROMManager::LoadARM9BIOS(); - if (!arm9bios) - return nullptr; - - auto firmware = ROMManager::LoadFirmware(Config::ConsoleType); - if (!firmware) - return nullptr; - -#ifdef JIT_ENABLED - JITArgs jitargs { - static_cast(Config::JIT_MaxBlockSize), - Config::JIT_LiteralOptimisations, - Config::JIT_BranchOptimisations, - Config::JIT_FastMemory, - }; -#endif - -#ifdef GDBSTUB_ENABLED - GDBArgs gdbargs { - static_cast(Config::GdbPortARM7), - static_cast(Config::GdbPortARM9), - Config::GdbARM7BreakOnStartup, - Config::GdbARM9BreakOnStartup, - }; -#endif - - NDSArgs ndsargs { - std::move(ndscart), - std::move(gbacart), - *arm9bios, - *arm7bios, - std::move(*firmware), -#ifdef JIT_ENABLED - Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt, -#else - std::nullopt, -#endif - static_cast(Config::AudioBitDepth), - static_cast(Config::AudioInterp), -#ifdef GDBSTUB_ENABLED - Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt, -#else - std::nullopt, -#endif - }; - - if (Config::ConsoleType == 1) - { - auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); - if (!arm7ibios) - return nullptr; - - auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); - if (!arm9ibios) - return nullptr; - - auto nand = ROMManager::LoadNAND(*arm7ibios); - if (!nand) - return nullptr; - - auto sdcard = ROMManager::LoadDSiSDCard(); - DSiArgs args { - std::move(ndsargs), - *arm9ibios, - *arm7ibios, - std::move(*nand), - std::move(sdcard), - Config::DSiFullBIOSBoot, - }; - - args.GBAROM = nullptr; - - return std::make_unique(std::move(args)); - } - - return std::make_unique(std::move(ndsargs)); -} - -bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept -{ - // 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; - if (std::holds_alternative(ndsargs)) - { // If we want to keep the existing cart (if any)... - nextndscart = NDS ? NDS->EjectCart() : nullptr; - ndsargs = {}; - } - else if (const auto ptr = std::get_if>(&ndsargs)) - { - nextndscart = std::move(*ptr); - ndsargs = {}; - } - - if (nextndscart && nextndscart->Type() == NDSCart::Homebrew) - { - // Load DLDISDCard will return nullopt if the SD card is disabled; - // SetSDCard will accept nullopt, which means no SD card - auto& homebrew = static_cast(*nextndscart); - homebrew.SetSDCard(ROMManager::LoadDLDISDCard()); - } - - std::unique_ptr nextgbacart; - if (std::holds_alternative(gbaargs)) - { - nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; - } - else if (const auto ptr = std::get_if>(&gbaargs)) - { - nextgbacart = std::move(*ptr); - gbaargs = {}; - } - - if (!NDS || NDS->ConsoleType != Config::ConsoleType) - { // If we're switching between DS and DSi mode, or there's no console... - // To ensure the destructor is called before a new one is created, - // as the presence of global signal handlers still complicates things a bit - NDS = nullptr; - NDS::Current = nullptr; - - NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart)); - NDS->Reset(); - NDS::Current = NDS.get(); - - return NDS != nullptr; - } - - auto arm9bios = ROMManager::LoadARM9BIOS(); - if (!arm9bios) - return false; - - auto arm7bios = ROMManager::LoadARM7BIOS(); - if (!arm7bios) - return false; - - auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType); - if (!firmware) - return false; - - if (NDS->ConsoleType == 1) - { // If the console we're updating is a DSi... - DSi& dsi = static_cast(*NDS); - - auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); - if (!arm9ibios) - return false; - - auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); - if (!arm7ibios) - return false; - - auto nandimage = ROMManager::LoadNAND(*arm7ibios); - if (!nandimage) - return false; - - auto dsisdcard = ROMManager::LoadDSiSDCard(); - - dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot); - dsi.ARM7iBIOS = *arm7ibios; - dsi.ARM9iBIOS = *arm9ibios; - dsi.SetNAND(std::move(*nandimage)); - dsi.SetSDCard(std::move(dsisdcard)); - // We're moving the optional, not the card - // (inserting std::nullopt here is okay, it means no card) - - dsi.EjectGBACart(); - } - - if (NDS->ConsoleType == 0) - { - NDS->SetGBACart(std::move(nextgbacart)); - } - -#ifdef JIT_ENABLED - JITArgs jitargs { - static_cast(Config::JIT_MaxBlockSize), - Config::JIT_LiteralOptimisations, - Config::JIT_BranchOptimisations, - Config::JIT_FastMemory, - }; - NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt); -#endif - NDS->SetARM7BIOS(*arm7bios); - NDS->SetARM9BIOS(*arm9bios); - NDS->SetFirmware(std::move(*firmware)); - NDS->SetNDSCart(std::move(nextndscart)); - NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); - NDS->SPU.SetDegrade10Bit(static_cast(Config::AudioBitDepth)); - - NDS::Current = NDS.get(); - - return true; -} - -void EmuThread::updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix) -{ - screenSettingsLock.lock(); - - if (lastScreenWidth != windowInfo.surface_width || lastScreenHeight != windowInfo.surface_height) - { - if (oglContext) - oglContext->ResizeSurface(windowInfo.surface_width, windowInfo.surface_height); - lastScreenWidth = windowInfo.surface_width; - lastScreenHeight = windowInfo.surface_height; - } - - this->filter = filter; - this->windowInfo = windowInfo; - this->numScreens = numScreens; - memcpy(this->screenKind, screenKind, sizeof(int)*numScreens); - memcpy(this->screenMatrix, screenMatrix, sizeof(float)*numScreens*6); - - screenSettingsLock.unlock(); -} - -void EmuThread::initOpenGL() -{ - GL::Context* windowctx = mainWindow->getOGLContext(); - - oglContext = windowctx; - oglContext->MakeCurrent(); - - OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); - GLuint pid = screenShaderProgram[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindAttribLocation(pid, 1, "vTexcoord"); - glBindFragDataLocation(pid, 0, "oColor"); - - OpenGL::LinkShaderProgram(screenShaderProgram); - - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); - - screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); - screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); - - // to prevent bleeding between both parts of the screen - // with bilinear filtering enabled - const int paddedHeight = 192*2+2; - const float padPixels = 1.f / paddedHeight; - - const float vertices[] = - { - 0.f, 0.f, 0.f, 0.f, - 0.f, 192.f, 0.f, 0.5f - padPixels, - 256.f, 192.f, 1.f, 0.5f - padPixels, - 0.f, 0.f, 0.f, 0.f, - 256.f, 192.f, 1.f, 0.5f - padPixels, - 256.f, 0.f, 1.f, 0.f, - - 0.f, 0.f, 0.f, 0.5f + padPixels, - 0.f, 192.f, 0.f, 1.f, - 256.f, 192.f, 1.f, 1.f, - 0.f, 0.f, 0.f, 0.5f + padPixels, - 256.f, 192.f, 1.f, 1.f, - 256.f, 0.f, 1.f, 0.5f + padPixels - }; - - glGenBuffers(1, &screenVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &screenVertexArray); - glBindVertexArray(screenVertexArray); - glEnableVertexAttribArray(0); // position - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); - glEnableVertexAttribArray(1); // texcoord - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); - - glGenTextures(1, &screenTexture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, screenTexture); - 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, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - // fill the padding - u8 zeroData[256*4*4]; - memset(zeroData, 0, sizeof(zeroData)); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); - - OSD::Init(true); - - oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); -} - -void EmuThread::deinitOpenGL() -{ - glDeleteTextures(1, &screenTexture); - - glDeleteVertexArrays(1, &screenVertexArray); - glDeleteBuffers(1, &screenVertexBuffer); - - OpenGL::DeleteShaderProgram(screenShaderProgram); - - OSD::DeInit(); - - oglContext->DoneCurrent(); - oglContext = nullptr; - - lastScreenWidth = lastScreenHeight = -1; -} - -void EmuThread::run() -{ - u32 mainScreenPos[3]; - Platform::FileHandle* file; - - UpdateConsole(nullptr, nullptr); - // No carts are inserted when melonDS first boots - - mainScreenPos[0] = 0; - mainScreenPos[1] = 0; - mainScreenPos[2] = 0; - autoScreenSizing = 0; - - videoSettingsDirty = false; - - if (mainWindow->hasOGL) - { - initOpenGL(); - videoRenderer = Config::_3DRenderer; - } - else - { - videoRenderer = 0; - } - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - - Input::Init(); - - u32 nframes = 0; - double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); - double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; - double frameLimitError = 0.0; - double lastMeasureTime = lastTime; - - 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); - NDS->RTC.SetState(state); - } - - char melontitle[100]; - - while (EmuRunning != emuStatus_Exit) - { - Input::Process(); - - if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); - - if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); - if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); - if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); - - if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); - - if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); - if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); - - if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) - { - EmuStatus = emuStatus_Running; - if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; - - if (Input::HotkeyPressed(HK_SolarSensorDecrease)) - { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); - if (level != -1) - { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); - } - } - if (Input::HotkeyPressed(HK_SolarSensorIncrease)) - { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); - if (level != -1) - { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); - } - } - - if (NDS->ConsoleType == 1) - { - DSi& dsi = static_cast(*NDS); - double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; - - // Handle power button - if (Input::HotkeyDown(HK_PowerButton)) - { - dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); - } - else if (Input::HotkeyReleased(HK_PowerButton)) - { - dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); - } - - // Handle volume buttons - if (Input::HotkeyDown(HK_VolumeUp)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); - } - else if (Input::HotkeyReleased(HK_VolumeUp)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); - } - - if (Input::HotkeyDown(HK_VolumeDown)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); - } - else if (Input::HotkeyReleased(HK_VolumeDown)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); - } - - dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); - } - - // update render settings if needed - // HACK: - // once the fast forward hotkey is released, we need to update vsync - // to the old setting again - if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) - { - if (oglContext) - { - oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); - videoRenderer = Config::_3DRenderer; - } -#ifdef OGLRENDERER_ENABLED - else -#endif - { - videoRenderer = 0; - } - - videoRenderer = oglContext ? Config::_3DRenderer : 0; - - videoSettingsDirty = false; - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - } - - // process input and hotkeys - NDS->SetKeyMask(Input::InputMask); - - if (Input::HotkeyPressed(HK_Lid)) - { - bool lid = !NDS->IsLidClosed(); - NDS->SetLidClosed(lid); - OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened"); - } - - // microphone input - AudioInOut::MicProcess(*NDS); - - // auto screen layout - if (Config::ScreenSizing == Frontend::screenSizing_Auto) - { - mainScreenPos[2] = mainScreenPos[1]; - mainScreenPos[1] = mainScreenPos[0]; - mainScreenPos[0] = NDS->PowerControl9 >> 15; - - int guess; - if (mainScreenPos[0] == mainScreenPos[2] && - mainScreenPos[0] != mainScreenPos[1]) - { - // constant flickering, likely displaying 3D on both screens - // TODO: when both screens are used for 2D only...??? - guess = Frontend::screenSizing_Even; - } - else - { - if (mainScreenPos[0] == 1) - guess = Frontend::screenSizing_EmphTop; - else - guess = Frontend::screenSizing_EmphBot; - } - - if (guess != autoScreenSizing) - { - autoScreenSizing = guess; - emit screenLayoutChange(); - } - } - - - // emulate - u32 nlines = NDS->RunFrame(); - - if (ROMManager::NDSSave) - ROMManager::NDSSave->CheckFlush(); - - if (ROMManager::GBASave) - ROMManager::GBASave->CheckFlush(); - - if (ROMManager::FirmwareSave) - ROMManager::FirmwareSave->CheckFlush(); - - if (!oglContext) - { - FrontBufferLock.lock(); - FrontBuffer = NDS->GPU.FrontBuffer; - FrontBufferLock.unlock(); - } - else - { - FrontBuffer = NDS->GPU.FrontBuffer; - drawScreenGL(); - } - -#ifdef MELONCAP - MelonCap::Update(); -#endif // MELONCAP - - if (EmuRunning == emuStatus_Exit) break; - - winUpdateCount++; - if (winUpdateCount >= winUpdateFreq && !oglContext) - { - emit windowUpdate(); - winUpdateCount = 0; - } - - bool fastforward = Input::HotkeyDown(HK_FastForward); - - if (fastforward && oglContext && Config::ScreenVSync) - { - oglContext->SetSwapInterval(0); - } - - if (Config::DSiVolumeSync && NDS->ConsoleType == 1) - { - DSi& dsi = static_cast(*NDS); - u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); - if (volumeLevel != dsiVolumeLevel) - { - dsiVolumeLevel = volumeLevel; - emit syncVolumeLevel(); - } - - Config::AudioVolume = volumeLevel * (256.0 / 31.0); - } - - if (Config::AudioSync && !fastforward) - AudioInOut::AudioSync(*emuThread->NDS); - - double frametimeStep = nlines / (60.0 * 263.0); - - { - bool limitfps = Config::LimitFPS && !fastforward; - - double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0; - - double curtime = SDL_GetPerformanceCounter() * perfCountsSec; - - frameLimitError += practicalFramelimit - (curtime - lastTime); - if (frameLimitError < -practicalFramelimit) - frameLimitError = -practicalFramelimit; - if (frameLimitError > practicalFramelimit) - frameLimitError = practicalFramelimit; - - if (round(frameLimitError * 1000.0) > 0.0) - { - SDL_Delay(round(frameLimitError * 1000.0)); - double timeBeforeSleep = curtime; - curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError -= curtime - timeBeforeSleep; - } - - lastTime = curtime; - } - - nframes++; - if (nframes >= 30) - { - double time = SDL_GetPerformanceCounter() * perfCountsSec; - double dt = time - lastMeasureTime; - lastMeasureTime = time; - - u32 fps = round(nframes / dt); - nframes = 0; - - float fpstarget = 1.0/frametimeStep; - - winUpdateFreq = fps / (u32)round(fpstarget); - if (winUpdateFreq < 1) - winUpdateFreq = 1; - - int inst = Platform::InstanceID(); - if (inst == 0) - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); - else - sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); - changeWindowTitle(melontitle); - } - } - else - { - // paused - nframes = 0; - lastTime = SDL_GetPerformanceCounter() * perfCountsSec; - lastMeasureTime = lastTime; - - emit windowUpdate(); - - EmuStatus = EmuRunning; - - int inst = Platform::InstanceID(); - if (inst == 0) - sprintf(melontitle, "melonDS " MELONDS_VERSION); - else - sprintf(melontitle, "melonDS (%d)", inst+1); - changeWindowTitle(melontitle); - - SDL_Delay(75); - - if (oglContext) - drawScreenGL(); - - ContextRequestKind contextRequest = ContextRequest; - if (contextRequest == contextRequest_InitGL) - { - initOpenGL(); - ContextRequest = contextRequest_None; - } - else if (contextRequest == contextRequest_DeInitGL) - { - deinitOpenGL(); - ContextRequest = contextRequest_None; - } - } - } - - 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); - } - - EmuStatus = emuStatus_Exit; - - NDS::Current = nullptr; - // nds is out of scope, so unique_ptr cleans it up for us -} - -void EmuThread::changeWindowTitle(char* title) -{ - emit windowTitleChange(QString(title)); -} - -void EmuThread::emuRun() -{ - EmuRunning = emuStatus_Running; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = true; - - // checkme - emit windowEmuStart(); - AudioInOut::Enable(); -} - -void EmuThread::initContext() -{ - ContextRequest = contextRequest_InitGL; - while (ContextRequest != contextRequest_None); -} - -void EmuThread::deinitContext() -{ - ContextRequest = contextRequest_DeInitGL; - while (ContextRequest != contextRequest_None); -} - -void EmuThread::emuPause() -{ - EmuPauseStack++; - if (EmuPauseStack > EmuPauseStackPauseThreshold) return; - - PrevEmuStatus = EmuRunning; - EmuRunning = emuStatus_Paused; - while (EmuStatus != emuStatus_Paused); - - AudioInOut::Disable(); -} - -void EmuThread::emuUnpause() -{ - if (EmuPauseStack < EmuPauseStackPauseThreshold) return; - - EmuPauseStack--; - if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; - - EmuRunning = PrevEmuStatus; - - AudioInOut::Enable(); -} - -void EmuThread::emuStop() -{ - EmuRunning = emuStatus_Exit; - EmuPauseStack = EmuPauseStackRunning; - - AudioInOut::Disable(); -} - -void EmuThread::emuFrameStep() -{ - if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); - EmuRunning = emuStatus_FrameStep; -} - -bool EmuThread::emuIsRunning() -{ - return EmuRunning == emuStatus_Running; -} - -bool EmuThread::emuIsActive() -{ - return (RunningSomething == 1); -} - -void EmuThread::drawScreenGL() -{ - if (!NDS) return; - int w = windowInfo.surface_width; - int h = windowInfo.surface_height; - float factor = windowInfo.surface_scale; - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDisable(GL_DEPTH_TEST); - glDepthMask(false); - glDisable(GL_BLEND); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, w, h); - - glUseProgram(screenShaderProgram[2]); - glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); - - int frontbuf = FrontBuffer; - glActiveTexture(GL_TEXTURE0); - -#ifdef OGLRENDERER_ENABLED - if (NDS->GPU.GetRenderer3D().Accelerated) - { - // hardware-accelerated render - static_cast(NDS->GPU.GetRenderer3D()).GetCompositor().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(); - - OSD::Update(); - OSD::DrawGL(w, h); - - oglContext->SwapBuffers(); -} @@ -1148,6 +293,11 @@ int main(int argc, char** argv) qputenv("QT_SCALE_FACTOR", "1"); +#if QT_VERSION_MAJOR == 6 && defined(__WIN32__) + // Allow using the system dark theme palette on Windows + qputenv("QT_QPA_PLATFORM", "windows:darkmode=2"); +#endif + printf("melonDS " MELONDS_VERSION "\n"); printf(MELONDS_URL "\n"); @@ -1155,10 +305,10 @@ int main(int argc, char** argv) if (argc != 0 && (!strcasecmp(argv[0], "derpDS") || !strcasecmp(argv[0], "./derpDS"))) printf("did you just call me a derp???\n"); - Platform::Init(argc, argv); - MelonApplication melon(argc, argv); + Platform::Init(argc, argv); + CLI::CommandLineOptions* options = CLI::ManageArgs(melon); // http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl @@ -1187,7 +337,7 @@ int main(int argc, char** argv) SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_EnableScreenSaver(); SDL_DisableScreenSaver(); - Config::Load(); + if (!Config::Load()) QMessageBox::critical(NULL, "melonDS", "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in."); #define SANITIZE(var, min, max) { var = std::clamp(var, min, max); } SANITIZE(Config::ConsoleType, 0, 1); @@ -1216,6 +366,12 @@ int main(int argc, char** argv) camManager[0]->setXFlip(Config::Camera[0].XFlip); camManager[1]->setXFlip(Config::Camera[1].XFlip); + systemThemeName = new QString(QApplication::style()->objectName()); + + if (!Config::UITheme.empty()) + { + QApplication::setStyle(QString::fromStdString(Config::UITheme)); + } Input::JoystickID = Config::JoystickID; Input::OpenJoystick(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 9703c178..5751f229 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -22,140 +22,18 @@ #include "glad/glad.h" #include -#include #include #include #include #include #include #include -#include #include #include -#include -#include -#include - #include "Window.h" +#include "EmuThread.h" #include "FrontendUtil.h" -#include "duckstation/gl/context.h" - -#include "NDSCart.h" -#include "GBACart.h" - -using Keep = std::monostate; -using UpdateConsoleNDSArgs = std::variant>; -using UpdateConsoleGBAArgs = std::variant>; -namespace melonDS -{ -class NDS; -} - -class EmuThread : public QThread -{ - Q_OBJECT - void run() override; - -public: - explicit EmuThread(QObject* parent = nullptr); - - void changeWindowTitle(char* title); - - // to be called from the UI thread - void emuRun(); - void emuPause(); - void emuUnpause(); - void emuStop(); - void emuFrameStep(); - - bool emuIsRunning(); - bool emuIsActive(); - - void initContext(); - void deinitContext(); - - int FrontBuffer = 0; - QMutex FrontBufferLock; - - void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix); - - /// Applies the config in args. - /// Creates a new NDS console if needed, - /// modifies the existing one if possible. - /// @return \c true if the console was updated. - /// If this returns \c false, then the existing NDS console is not modified. - bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; - std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization -signals: - void windowUpdate(); - void windowTitleChange(QString title); - - void windowEmuStart(); - void windowEmuStop(); - void windowEmuPause(); - void windowEmuReset(); - void windowEmuFrameStep(); - - void windowLimitFPSChange(); - - void screenLayoutChange(); - - void windowFullscreenToggle(); - - void swapScreensToggle(); - void screenEmphasisToggle(); - - void syncVolumeLevel(); - -private: - std::unique_ptr CreateConsole( - std::unique_ptr&& ndscart, - std::unique_ptr&& gbacart - ) noexcept; - void drawScreenGL(); - void initOpenGL(); - void deinitOpenGL(); - - enum EmuStatusKind - { - emuStatus_Exit, - emuStatus_Running, - emuStatus_Paused, - emuStatus_FrameStep, - }; - std::atomic EmuStatus; - - EmuStatusKind PrevEmuStatus; - EmuStatusKind EmuRunning; - - constexpr static int EmuPauseStackRunning = 0; - constexpr static int EmuPauseStackPauseThreshold = 1; - int EmuPauseStack; - - enum ContextRequestKind - { - contextRequest_None = 0, - contextRequest_InitGL, - contextRequest_DeInitGL - }; - std::atomic ContextRequest = contextRequest_None; - - GL::Context* oglContext = nullptr; - GLuint screenVertexBuffer, screenVertexArray; - GLuint screenTexture; - GLuint screenShaderProgram[3]; - GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; - - QMutex screenSettingsLock; - WindowInfo windowInfo; - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - bool filter; - - int lastScreenWidth = -1, lastScreenHeight = -1; -}; class MelonApplication : public QApplication { @@ -166,4 +44,6 @@ public: bool event(QEvent* event) override; }; +extern QString* systemThemeName; + #endif // MAIN_H diff --git a/src/sha1/sha1.c b/src/sha1/sha1.c index c0052b70..c34ace30 100644 --- a/src/sha1/sha1.c +++ b/src/sha1/sha1.c @@ -27,6 +27,9 @@ A million repetitions of "a" #if defined(__sun) #include "solarisfixes.h" #endif +#if defined(__HAIKU__) +#include +#endif #include "sha1.h" #ifndef BYTE_ORDER diff --git a/vcpkg.json b/vcpkg.json index 2ff0c549..a1bd0be5 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -9,6 +9,11 @@ "default-features": false, "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] }, + { + "name": "qtbase", + "host": true, + "default-features": false + }, { "name": "qtmultimedia", "default-features": false