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