Merge remote-tracking branch 'upstream/master' into RDLines

This commit is contained in:
Jaklyy 2024-02-13 09:56:44 -05:00
commit ba4b4e2263
52 changed files with 2696 additions and 2219 deletions

View File

@ -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

View File

@ -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

84
.github/workflows/build-macos.yml vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
name: CMake Build (Windows x86-64)
name: Windows
on:
push:

88
CMakePresets.json Normal file
View File

@ -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" }
]
}
]
}

View File

@ -9,7 +9,7 @@
<a href="https://github.com/melonDS-emu/melonDS/actions?query=workflow%3A%22CMake+Build+%28Windows+x86-64%29%22+event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-windows.yml?label=Windows%20x86-64&logo=GitHub&branch=master"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions?query=workflow%3A%22CMake+Build+%28Ubuntu+x86-64%29%22+event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-ubuntu.yml?label=Linux%20x86-64&logo=GitHub"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions?query=workflow%3A%22CMake+Build+%28Ubuntu+aarch64%29%22+event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-ubuntu-aarch64.yml?label=Linux%20ARM64&logo=GitHub"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions/workflows/build-macos-universal.yml?query=event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-macos-universal.yml?label=macOS%20Universal&logo=GitHub"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions/workflows/build-macos-universal.yml?query=event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-macos.yml?label=macOS%20Universal&logo=GitHub"></img></a>
</p>
DS emulator, sorta

View File

@ -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()

View File

@ -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; }

View File

@ -128,6 +128,15 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
target_compile_options(teakra PRIVATE "$<$<CONFIG:DEBUG>:-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 "$<$<COMPILE_LANGUAGE:CXX>:-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)

View File

@ -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;
}

View File

@ -32,14 +32,8 @@ using namespace Platform;
using std::string;
FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<string>& 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>(FileMode::ReadWrite | FileMode::Preserve));
if (!FF_File)
File = Platform::OpenLocalFile(filename, static_cast<FileMode>(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;
}

View File

@ -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<std::string> SourceDir;
@ -48,8 +51,8 @@ class FATStorage
{
public:
FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<std::string>& 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);

View File

@ -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

View File

@ -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;

View File

@ -146,6 +146,19 @@ GPU3D::GPU3D(melonDS::NDS& nds, std::unique_ptr<Renderer3D>&& 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<Renderer3D>&& renderer) noexcept
{
CurrentRenderer = std::move(renderer);
@ -299,6 +312,12 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
{
file->Section("GP3D");
SoftRenderer* softRenderer = dynamic_cast<SoftRenderer*>(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();
}
}

View File

@ -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

View File

@ -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);

View File

@ -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<false>(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<true>(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);
}

View File

@ -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;
};
}

View File

@ -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))
{

View File

@ -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<u32, 16> IPCFIFO9; // FIFO in which the ARM9 writes
FIFO<u32, 16> 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;

View File

@ -1657,9 +1657,15 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen
std::unique_ptr<u8[]> sram = args ? std::move(args->SRAM) : nullptr;
u32 sramlen = args ? args->SRAMLength : 0;
if (homebrew)
cart = std::make_unique<CartHomebrew>(std::move(cartrom), cartromsize, cartid, romparams, args ? std::move(args->SDCard) : std::nullopt);
{
std::optional<FATStorage> sdcard = args && args->SDCard ? std::make_optional<FATStorage>(std::move(*args->SDCard)) : std::nullopt;
cart = std::make_unique<CartHomebrew>(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<CartR4>(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, args ? std::move(args->SDCard) : std::nullopt);
{
std::optional<FATStorage> sdcard = args && args->SDCard ? std::make_optional<FATStorage>(std::move(*args->SDCard)) : std::nullopt;
cart = std::make_unique<CartR4>(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard));
}
else if (cartid & 0x08000000)
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
else if (irversion != 0)

View File

@ -260,6 +260,22 @@ public:
// it just leaves behind an optional with a moved-from value
}
void SetSDCard(std::optional<FATStorageArgs>&& 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);

View File

@ -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!");

View File

@ -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 <tt>Read | Write</tt>.
@ -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.

View File

@ -621,6 +621,8 @@ s32 SPUChannel::Run()
(PrevSample[0] * InterpCubic[samplepos][2]) +
(val * InterpCubic[samplepos][3])) >> 14;
break;
default:
break;
}
}

View File

@ -24,7 +24,7 @@
#include <stdio.h>
#include "types.h"
#define SAVESTATE_MAJOR 11
#define SAVESTATE_MAJOR 12
#define SAVESTATE_MINOR 1
namespace melonDS

View File

@ -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)

View File

@ -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<int64_t>(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()

View File

@ -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();
}

View File

@ -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);

View File

@ -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 <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <optional>
#include <vector>
#include <string>
#include <algorithm>
#include <SDL2/SDL.h>
#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<NDS> EmuThread::CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& 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<unsigned>(Config::JIT_MaxBlockSize),
Config::JIT_LiteralOptimisations,
Config::JIT_BranchOptimisations,
Config::JIT_FastMemory,
};
#endif
#ifdef GDBSTUB_ENABLED
GDBArgs gdbargs {
static_cast<u16>(Config::GdbPortARM7),
static_cast<u16>(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<AudioBitDepth>(Config::AudioBitDepth),
static_cast<AudioInterpolation>(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<melonDS::DSi>(std::move(args));
}
return std::make_unique<melonDS::NDS>(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<NDSCart::CartCommon> nextndscart;
if (std::holds_alternative<Keep>(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<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
{
nextndscart = std::move(*ptr);
ndsargs = {};
}
if (auto* cartsd = dynamic_cast<NDSCart::CartSD*>(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<GBACart::CartCommon> nextgbacart;
if (std::holds_alternative<Keep>(gbaargs))
{
nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
}
else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&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<DSi&>(*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<unsigned>(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<AudioInterpolation>(Config::AudioInterp));
NDS->SPU.SetDegrade10Bit(static_cast<AudioBitDepth>(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<ScreenPanelGL*>(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<SoftRenderer>(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<DSi&>(*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<SoftRenderer>(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<DSi&>(*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<ScreenPanelGL*>(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);
}

View File

@ -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 <QThread>
#include <QMutex>
#include <atomic>
#include <variant>
#include <optional>
#include "NDSCart.h"
#include "GBACart.h"
using Keep = std::monostate;
using UpdateConsoleNDSArgs = std::variant<Keep, std::unique_ptr<melonDS::NDSCart::CartCommon>>;
using UpdateConsoleGBAArgs = std::variant<Keep, std::unique_ptr<melonDS::GBACart::CartCommon>>;
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<melonDS::NDS> 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<melonDS::NDS> CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
) noexcept;
enum EmuStatusKind
{
emuStatus_Exit,
emuStatus_Running,
emuStatus_Paused,
emuStatus_FrameStep,
};
std::atomic<EmuStatusKind> 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<ContextRequestKind> ContextRequest = contextRequest_None;
ScreenPanelGL* screenGL;
int autoScreenSizing;
int videoRenderer;
bool videoSettingsDirty;
};
#endif // EMUTHREAD_H

View File

@ -16,15 +16,16 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <QStyleFactory>
#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<QString> 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();
}

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>262</width>
<height>113</height>
<width>337</width>
<height>275</height>
</rect>
</property>
<property name="sizePolicy">
@ -19,32 +19,113 @@
<property name="windowTitle">
<string>Interface settings - melonDS</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" alignment="Qt::AlignLeft">
<widget class="QLabel" name="label">
<property name="text">
<string>Hide after</string>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>User interface</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Theme</string>
</property>
<property name="buddy">
<cstring>cbxUITheme</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbxUITheme"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="cbMouseHide">
<property name="text">
<string>Hide mouse after inactivity</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,1">
<property name="leftMargin">
<number>18</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>After</string>
</property>
<property name="buddy">
<cstring>spinMouseHideSeconds</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinMouseHideSeconds"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>seconds</string>
</property>
<property name="buddy">
<cstring>spinMouseHideSeconds</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="cbPauseLostFocus">
<property name="text">
<string>Pause emulation when window is not in focus</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="4">
<widget class="QCheckBox" name="cbPauseLostFocus">
<property name="text">
<string>Pause emulation when window is not in focus</string>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Framerate </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Fast-forward limit</string>
</property>
<property name="buddy">
<cstring>spinMaxFPS</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinMaxFPS">
<property name="suffix">
<string> FPS</string>
</property>
<property name="minimum">
<number>60</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="5">
<widget class="QCheckBox" name="cbMouseHide">
<property name="text">
<string>Hide mouse after inactivity</string>
</property>
</widget>
</item>
<item row="1" column="1" alignment="Qt::AlignLeft">
<widget class="QSpinBox" name="spinMouseHideSeconds"/>
</item>
<item row="3" column="0" colspan="5">
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -54,20 +135,8 @@
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>seconds of inactivity</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>cbMouseHide</tabstop>
<tabstop>spinMouseHideSeconds</tabstop>
<tabstop>cbPauseLostFocus</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View File

@ -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 <stdio.h>
#include <string.h>
#include <deque>
#include <SDL2/SDL.h>
#include "../types.h"
#include "main.h"
#include "OpenGLSupport.h"
#include <QPainter>
#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<Item> 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<<cx))
item->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();
}
}

View File

@ -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

View File

@ -19,6 +19,7 @@
#include <stdio.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QTemporaryFile>
#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);
}

View File

@ -21,6 +21,7 @@
#include <string.h>
#include <string>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QString>
#include <QDateTime>
@ -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;

View File

@ -29,6 +29,7 @@
#include <fstream>
#include <QDateTime>
#include <QMessageBox>
#include <zstd.h>
#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<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& 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<FATStorageArgs> GetDSiSDCardArgs() noexcept
{
if (!Config::DSiSDEnable)
@ -804,12 +819,7 @@ std::optional<FATStorage> 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<u8[]>& 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<u8[]> 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<SaveManager>(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<u8[]> 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;

View File

@ -23,6 +23,7 @@
#include "SaveManager.h"
#include "AREngine.h"
#include "DSi_NAND.h"
#include <QMainWindow>
#include "MemConstants.h"
#include <optional>
@ -72,12 +73,12 @@ std::optional<Firmware> LoadFirmware(int type) noexcept;
std::optional<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& 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();

View File

@ -35,30 +35,27 @@
#include <qpa/qplatformnativeinterface.h>
#endif
#endif
#include <QDateTime>
#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<<cx))
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 = 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<GLRenderer&>(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> 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();
}
}

View File

@ -19,19 +19,20 @@
#ifndef SCREEN_H
#define SCREEN_H
#include "glad/glad.h"
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
#include <optional>
#include <deque>
#include <map>
#include <QWidget>
#include <QWindow>
#include <QMainWindow>
#include <QImage>
#include <QActionGroup>
#include <QTimer>
#include <QMutex>
#include <QScreen>
#include <QCloseEvent>
#include <QTimer>
#include "glad/glad.h"
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
class EmuThread;
@ -48,27 +49,54 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] =
constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]);
class ScreenHandler
class ScreenPanel : public QWidget
{
Q_GADGET
Q_OBJECT
public:
ScreenHandler(QWidget* widget);
virtual ~ScreenHandler();
explicit ScreenPanel(QWidget* parent);
virtual ~ScreenPanel();
QTimer* setupMouseTimer();
void updateMouseTimer();
QTimer* mouseTimer;
QSize screenGetMinSize(int factor);
void osdSetEnabled(bool enabled);
void osdAddMessage(unsigned int color, const char* msg);
private slots:
void onScreenLayoutChanged();
protected:
void screenSetupLayout(int w, int h);
struct OSDItem
{
unsigned int id;
qint64 timestamp;
void screenOnMousePress(QMouseEvent* event);
void screenOnMouseRelease(QMouseEvent* event);
void screenOnMouseMove(QMouseEvent* event);
char text[256];
unsigned int color;
void screenHandleTablet(QTabletEvent* event);
void screenHandleTouch(QTouchEvent* event);
bool rendered;
QImage bitmap;
};
QMutex osdMutex;
bool osdEnabled;
unsigned int osdID;
std::deque<OSDItem> osdItems;
virtual void setupScreenLayout();
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void tabletEvent(QTabletEvent* event) override;
void touchEvent(QTouchEvent* event);
bool event(QEvent* event) override;
float screenMatrix[Frontend::MaxScreenTransforms][6];
int screenKind[Frontend::MaxScreenTransforms];
@ -77,10 +105,19 @@ protected:
bool touching = false;
void showCursor();
int osdFindBreakPoint(const char* text, int i);
void osdLayoutText(const char* text, int* width, int* height, int* breaks);
unsigned int osdRainbowColor(int inc);
virtual void osdRenderItem(OSDItem* item);
virtual void osdDeleteItem(OSDItem* item);
void osdUpdate();
};
class ScreenPanelNative : public QWidget, public ScreenHandler
class ScreenPanelNative : public ScreenPanel
{
Q_OBJECT
@ -91,26 +128,15 @@ public:
protected:
void paintEvent(QPaintEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void tabletEvent(QTabletEvent* event) override;
bool event(QEvent* event) override;
private slots:
void onScreenLayoutChanged();
private:
void setupScreenLayout();
void setupScreenLayout() override;
QImage screen[2];
QTransform screenTrans[Frontend::MaxScreenTransforms];
};
class ScreenPanelGL : public QWidget, public ScreenHandler
class ScreenPanelGL : public ScreenPanel
{
Q_OBJECT
@ -122,9 +148,15 @@ public:
bool createContext();
void setSwapInterval(int intv);
void initOpenGL();
void deinitOpenGL();
void drawScreenGL();
GL::Context* getContext() { return glContext.get(); }
void transferLayout(EmuThread* thread);
void transferLayout();
protected:
qreal devicePixelRatioFromScreen() const;
@ -133,22 +165,31 @@ protected:
QPaintEngine* paintEngine() const override;
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void tabletEvent(QTabletEvent* event) override;
bool event(QEvent* event) override;
private slots:
void onScreenLayoutChanged();
private:
void setupScreenLayout();
void setupScreenLayout() override;
std::unique_ptr<GL::Context> glContext;
GLuint screenVertexBuffer, screenVertexArray;
GLuint screenTexture;
GLuint screenShaderProgram[3];
GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc;
QMutex screenSettingsLock;
WindowInfo windowInfo;
bool filter;
int lastScreenWidth = -1, lastScreenHeight = -1;
GLuint osdShader[3];
GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc;
GLfloat osdScaleFactorULoc;
GLuint osdVertexArray;
GLuint osdVertexBuffer;
std::map<unsigned int, GLuint> osdTextures;
void osdRenderItem(OSDItem* item) override;
void osdDeleteItem(OSDItem* item) override;
};
#endif // SCREEN_H

View File

@ -114,7 +114,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
u32 icondata[32*32];
ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata);
QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32);
QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_RGBA8888);
QIcon icon(QPixmap::fromImage(iconimg.copy()));
// TODO: make it possible to select other languages?

View File

@ -81,15 +81,12 @@
#include "ArchiveUtil.h"
#include "CameraManager.h"
#include "OSD.h"
using namespace melonDS;
// TEMP
extern MainWindow* mainWindow;
extern EmuThread* emuThread;
extern bool RunningSomething;
extern int autoScreenSizing;
extern QString NdsRomMimeType;
extern QStringList NdsRomExtensions;
extern QString GbaRomMimeType;
@ -690,13 +687,13 @@ void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...)
if (fmt == nullptr)
return;
char msg[1024];
char msg[256];
va_list args;
va_start(args, fmt);
vsnprintf(msg, 1024, fmt, args);
vsnprintf(msg, 256, fmt, args);
va_end(args);
OSD::AddMessage(color, msg);
panel->osdAddMessage(color, msg);
}
void MainWindow::closeEvent(QCloseEvent* event)
@ -721,7 +718,6 @@ void MainWindow::createScreenPanel()
panelGL->show();
panel = panelGL;
panelWidget = panelGL;
panelGL->createContext();
}
@ -730,14 +726,14 @@ void MainWindow::createScreenPanel()
{
ScreenPanelNative* panelNative = new ScreenPanelNative(this);
panel = panelNative;
panelWidget = panelNative;
panelWidget->show();
panel->show();
}
setCentralWidget(panelWidget);
setCentralWidget(panel);
actScreenFiltering->setEnabled(hasOGL);
panel->osdSetEnabled(Config::ShowOSD);
connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged()));
connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged()));
emit screenLayoutChange();
}
@ -749,6 +745,30 @@ GL::Context* MainWindow::getOGLContext()
return glpanel->getContext();
}
/*void MainWindow::initOpenGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->initOpenGL();
}
void MainWindow::deinitOpenGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->deinitOpenGL();
}
void MainWindow::drawScreenGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->drawScreenGL();
}*/
void MainWindow::resizeEvent(QResizeEvent* event)
{
int w = event->size().width();
@ -845,10 +865,8 @@ void MainWindow::dropEvent(QDropEvent* event)
if (isNdsRom)
{
if (!ROMManager::LoadROM(emuThread, file, true))
if (!ROMManager::LoadROM(mainWindow, emuThread, file, true))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM.");
emuThread->emuUnpause();
return;
}
@ -866,10 +884,8 @@ void MainWindow::dropEvent(QDropEvent* event)
}
else if (isGbaRom)
{
if (!ROMManager::LoadGBAROM(*emuThread->NDS, file))
if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM.");
emuThread->emuUnpause();
return;
}
@ -932,12 +948,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot)
bool gbaloaded = false;
if (!gbafile.isEmpty())
{
if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM.");
return false;
}
if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, gbafile)) return false;
gbaloaded = true;
}
@ -945,12 +956,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot)
bool ndsloaded = false;
if (!file.isEmpty())
{
if (!ROMManager::LoadROM(emuThread, file, true))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
return false;
}
if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) return false;
recentFileList.removeAll(file.join("|"));
recentFileList.prepend(file.join("|"));
updateRecentFilesMenu();
@ -1153,11 +1160,9 @@ void MainWindow::onOpenFile()
emuThread->emuUnpause();
return;
}
if (!ROMManager::LoadROM(emuThread, file, true))
if (!ROMManager::LoadROM(mainWindow, emuThread, file, true))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
emuThread->emuUnpause();
return;
}
@ -1252,11 +1257,9 @@ void MainWindow::onClickRecentFile()
emuThread->emuUnpause();
return;
}
if (!ROMManager::LoadROM(emuThread, file, true))
if (!ROMManager::LoadROM(mainWindow, emuThread, file, true))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
emuThread->emuUnpause();
return;
}
@ -1306,10 +1309,8 @@ void MainWindow::onInsertCart()
return;
}
if (!ROMManager::LoadROM(emuThread, file, false))
if (!ROMManager::LoadROM(mainWindow, emuThread, file, false))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
emuThread->emuUnpause();
return;
}
@ -1341,10 +1342,8 @@ void MainWindow::onInsertGBACart()
return;
}
if (!ROMManager::LoadGBAROM(*emuThread->NDS, file))
if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
emuThread->emuUnpause();
return;
}
@ -1843,7 +1842,7 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked)
void MainWindow::onChangeScreenSize()
{
int factor = ((QAction*)sender())->data().toInt();
QSize diff = size() - panelWidget->size();
QSize diff = size() - panel->size();
resize(panel->screenGetMinSize(factor) + diff);
}
@ -1936,7 +1935,9 @@ void MainWindow::onChangeScreenFiltering(bool checked)
void MainWindow::onChangeShowOSD(bool checked)
{
Config::ShowOSD = checked?1:0;
panel->osdSetEnabled(Config::ShowOSD);
}
void MainWindow::onChangeLimitFramerate(bool checked)
{
Config::LimitFPS = checked?1:0;
@ -2042,7 +2043,7 @@ void MainWindow::onUpdateVideoSettings(bool glchange)
delete panel;
createScreenPanel();
connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint()));
connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint()));
}
videoSettingsDirty = true;

View File

@ -92,8 +92,7 @@ private:
bool oldMax;
public:
ScreenHandler* panel;
QWidget* panelWidget;
ScreenPanel* panel;
};*/
class MainWindow : public QMainWindow
@ -106,6 +105,9 @@ public:
bool hasOGL;
GL::Context* getOGLContext();
/*void initOpenGL();
void deinitOpenGL();
void drawScreenGL();*/
bool preloadROMs(QStringList file, QStringList gbafile, bool boot);
QStringList splitArchivePath(const QString& filename, bool useMemberSyntax);
@ -227,8 +229,7 @@ private:
bool oldMax;
public:
ScreenHandler* panel;
QWidget* panelWidget;
ScreenPanel* panel;
QAction* actOpenROM;
QAction* actBootFirmware;

View File

@ -79,7 +79,6 @@
#include "version.h"
#include "FrontendUtil.h"
#include "OSD.h"
#include "Args.h"
#include "NDS.h"
@ -99,7 +98,7 @@
#include "Savestate.h"
#include "main_shaders.h"
//#include "main_shaders.h"
#include "ROMManager.h"
#include "ArchiveUtil.h"
@ -115,6 +114,7 @@ QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" };
QString GbaRomMimeType = "application/x-gba-rom";
QStringList GbaRomExtensions { ".gba", ".agb" };
QString* systemThemeName;
// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09).
QStringList ArchiveMimeTypes
@ -175,861 +175,6 @@ bool camStarted[2];
//extern int AspectRatiosNum;
EmuThread::EmuThread(QObject* parent) : QThread(parent)
{
EmuStatus = emuStatus_Exit;
EmuRunning = emuStatus_Paused;
EmuPauseStack = EmuPauseStackRunning;
RunningSomething = false;
connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint()));
connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger()));
connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger()));
connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger()));
connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger()));
connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged()));
connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled()));
connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger()));
connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled()));
auto glPanel = dynamic_cast<ScreenPanelGL*>(mainWindow->panel);
if (glPanel) glPanel->transferLayout(this);
}
std::unique_ptr<NDS> EmuThread::CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& 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<unsigned>(Config::JIT_MaxBlockSize),
Config::JIT_LiteralOptimisations,
Config::JIT_BranchOptimisations,
Config::JIT_FastMemory,
};
#endif
#ifdef GDBSTUB_ENABLED
GDBArgs gdbargs {
static_cast<u16>(Config::GdbPortARM7),
static_cast<u16>(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<AudioBitDepth>(Config::AudioBitDepth),
static_cast<AudioInterpolation>(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<melonDS::DSi>(std::move(args));
}
return std::make_unique<melonDS::NDS>(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<NDSCart::CartCommon> nextndscart;
if (std::holds_alternative<Keep>(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<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
{
nextndscart = std::move(*ptr);
ndsargs = {};
}
if (nextndscart && nextndscart->Type() == NDSCart::Homebrew)
{
// Load DLDISDCard will return nullopt if the SD card is disabled;
// SetSDCard will accept nullopt, which means no SD card
auto& homebrew = static_cast<NDSCart::CartHomebrew&>(*nextndscart);
homebrew.SetSDCard(ROMManager::LoadDLDISDCard());
}
std::unique_ptr<GBACart::CartCommon> nextgbacart;
if (std::holds_alternative<Keep>(gbaargs))
{
nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
}
else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&gbaargs))
{
nextgbacart = std::move(*ptr);
gbaargs = {};
}
if (!NDS || NDS->ConsoleType != Config::ConsoleType)
{ // If we're switching between DS and DSi mode, or there's no console...
// To ensure the destructor is called before a new one is created,
// as the presence of global signal handlers still complicates things a bit
NDS = nullptr;
NDS::Current = nullptr;
NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart));
NDS->Reset();
NDS::Current = NDS.get();
return NDS != nullptr;
}
auto arm9bios = ROMManager::LoadARM9BIOS();
if (!arm9bios)
return false;
auto arm7bios = ROMManager::LoadARM7BIOS();
if (!arm7bios)
return false;
auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType);
if (!firmware)
return false;
if (NDS->ConsoleType == 1)
{ // If the console we're updating is a DSi...
DSi& dsi = static_cast<DSi&>(*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<unsigned>(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<AudioInterpolation>(Config::AudioInterp));
NDS->SPU.SetDegrade10Bit(static_cast<AudioBitDepth>(Config::AudioBitDepth));
NDS::Current = NDS.get();
return true;
}
void EmuThread::updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix)
{
screenSettingsLock.lock();
if (lastScreenWidth != windowInfo.surface_width || lastScreenHeight != windowInfo.surface_height)
{
if (oglContext)
oglContext->ResizeSurface(windowInfo.surface_width, windowInfo.surface_height);
lastScreenWidth = windowInfo.surface_width;
lastScreenHeight = windowInfo.surface_height;
}
this->filter = filter;
this->windowInfo = windowInfo;
this->numScreens = numScreens;
memcpy(this->screenKind, screenKind, sizeof(int)*numScreens);
memcpy(this->screenMatrix, screenMatrix, sizeof(float)*numScreens*6);
screenSettingsLock.unlock();
}
void EmuThread::initOpenGL()
{
GL::Context* windowctx = mainWindow->getOGLContext();
oglContext = windowctx;
oglContext->MakeCurrent();
OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader");
GLuint pid = screenShaderProgram[2];
glBindAttribLocation(pid, 0, "vPosition");
glBindAttribLocation(pid, 1, "vTexcoord");
glBindFragDataLocation(pid, 0, "oColor");
OpenGL::LinkShaderProgram(screenShaderProgram);
glUseProgram(pid);
glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0);
screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize");
screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform");
// to prevent bleeding between both parts of the screen
// with bilinear filtering enabled
const int paddedHeight = 192*2+2;
const float padPixels = 1.f / paddedHeight;
const float vertices[] =
{
0.f, 0.f, 0.f, 0.f,
0.f, 192.f, 0.f, 0.5f - padPixels,
256.f, 192.f, 1.f, 0.5f - padPixels,
0.f, 0.f, 0.f, 0.f,
256.f, 192.f, 1.f, 0.5f - padPixels,
256.f, 0.f, 1.f, 0.f,
0.f, 0.f, 0.f, 0.5f + padPixels,
0.f, 192.f, 0.f, 1.f,
256.f, 192.f, 1.f, 1.f,
0.f, 0.f, 0.f, 0.5f + padPixels,
256.f, 192.f, 1.f, 1.f,
256.f, 0.f, 1.f, 0.5f + padPixels
};
glGenBuffers(1, &screenVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &screenVertexArray);
glBindVertexArray(screenVertexArray);
glEnableVertexAttribArray(0); // position
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0));
glEnableVertexAttribArray(1); // texcoord
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4));
glGenTextures(1, &screenTexture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, screenTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// fill the padding
u8 zeroData[256*4*4];
memset(zeroData, 0, sizeof(zeroData));
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData);
OSD::Init(true);
oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0);
}
void EmuThread::deinitOpenGL()
{
glDeleteTextures(1, &screenTexture);
glDeleteVertexArrays(1, &screenVertexArray);
glDeleteBuffers(1, &screenVertexBuffer);
OpenGL::DeleteShaderProgram(screenShaderProgram);
OSD::DeInit();
oglContext->DoneCurrent();
oglContext = nullptr;
lastScreenWidth = lastScreenHeight = -1;
}
void EmuThread::run()
{
u32 mainScreenPos[3];
Platform::FileHandle* file;
UpdateConsole(nullptr, nullptr);
// No carts are inserted when melonDS first boots
mainScreenPos[0] = 0;
mainScreenPos[1] = 0;
mainScreenPos[2] = 0;
autoScreenSizing = 0;
videoSettingsDirty = false;
if (mainWindow->hasOGL)
{
initOpenGL();
videoRenderer = Config::_3DRenderer;
}
else
{
videoRenderer = 0;
}
if (videoRenderer == 0)
{ // If we're using the software renderer...
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
}
else
{
auto glrenderer = melonDS::GLRenderer::New();
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
NDS->GPU.SetRenderer3D(std::move(glrenderer));
}
Input::Init();
u32 nframes = 0;
double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency();
double lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
double frameLimitError = 0.0;
double lastMeasureTime = lastTime;
u32 winUpdateCount = 0, winUpdateFreq = 1;
u8 dsiVolumeLevel = 0x1F;
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read);
if (file)
{
RTC::StateData state;
Platform::FileRead(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
NDS->RTC.SetState(state);
}
char melontitle[100];
while (EmuRunning != emuStatus_Exit)
{
Input::Process();
if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause();
if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset();
if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep();
if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle();
if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle();
if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle();
if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep)
{
EmuStatus = emuStatus_Running;
if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused;
if (Input::HotkeyPressed(HK_SolarSensorDecrease))
{
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true);
if (level != -1)
{
char msg[64];
sprintf(msg, "Solar sensor level: %d", level);
OSD::AddMessage(0, msg);
}
}
if (Input::HotkeyPressed(HK_SolarSensorIncrease))
{
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true);
if (level != -1)
{
char msg[64];
sprintf(msg, "Solar sensor level: %d", level);
OSD::AddMessage(0, msg);
}
}
if (NDS->ConsoleType == 1)
{
DSi& dsi = static_cast<DSi&>(*NDS);
double currentTime = SDL_GetPerformanceCounter() * perfCountsSec;
// Handle power button
if (Input::HotkeyDown(HK_PowerButton))
{
dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime);
}
else if (Input::HotkeyReleased(HK_PowerButton))
{
dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime);
}
// Handle volume buttons
if (Input::HotkeyDown(HK_VolumeUp))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up);
}
else if (Input::HotkeyReleased(HK_VolumeUp))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up);
}
if (Input::HotkeyDown(HK_VolumeDown))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down);
}
else if (Input::HotkeyReleased(HK_VolumeDown))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down);
}
dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime);
}
// update render settings if needed
// HACK:
// once the fast forward hotkey is released, we need to update vsync
// to the old setting again
if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward))
{
if (oglContext)
{
oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0);
videoRenderer = Config::_3DRenderer;
}
#ifdef OGLRENDERER_ENABLED
else
#endif
{
videoRenderer = 0;
}
videoRenderer = oglContext ? Config::_3DRenderer : 0;
videoSettingsDirty = false;
if (videoRenderer == 0)
{ // If we're using the software renderer...
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
}
else
{
auto glrenderer = melonDS::GLRenderer::New();
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
NDS->GPU.SetRenderer3D(std::move(glrenderer));
}
}
// process input and hotkeys
NDS->SetKeyMask(Input::InputMask);
if (Input::HotkeyPressed(HK_Lid))
{
bool lid = !NDS->IsLidClosed();
NDS->SetLidClosed(lid);
OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened");
}
// microphone input
AudioInOut::MicProcess(*NDS);
// auto screen layout
if (Config::ScreenSizing == Frontend::screenSizing_Auto)
{
mainScreenPos[2] = mainScreenPos[1];
mainScreenPos[1] = mainScreenPos[0];
mainScreenPos[0] = NDS->PowerControl9 >> 15;
int guess;
if (mainScreenPos[0] == mainScreenPos[2] &&
mainScreenPos[0] != mainScreenPos[1])
{
// constant flickering, likely displaying 3D on both screens
// TODO: when both screens are used for 2D only...???
guess = Frontend::screenSizing_Even;
}
else
{
if (mainScreenPos[0] == 1)
guess = Frontend::screenSizing_EmphTop;
else
guess = Frontend::screenSizing_EmphBot;
}
if (guess != autoScreenSizing)
{
autoScreenSizing = guess;
emit screenLayoutChange();
}
}
// emulate
u32 nlines = NDS->RunFrame();
if (ROMManager::NDSSave)
ROMManager::NDSSave->CheckFlush();
if (ROMManager::GBASave)
ROMManager::GBASave->CheckFlush();
if (ROMManager::FirmwareSave)
ROMManager::FirmwareSave->CheckFlush();
if (!oglContext)
{
FrontBufferLock.lock();
FrontBuffer = NDS->GPU.FrontBuffer;
FrontBufferLock.unlock();
}
else
{
FrontBuffer = NDS->GPU.FrontBuffer;
drawScreenGL();
}
#ifdef MELONCAP
MelonCap::Update();
#endif // MELONCAP
if (EmuRunning == emuStatus_Exit) break;
winUpdateCount++;
if (winUpdateCount >= winUpdateFreq && !oglContext)
{
emit windowUpdate();
winUpdateCount = 0;
}
bool fastforward = Input::HotkeyDown(HK_FastForward);
if (fastforward && oglContext && Config::ScreenVSync)
{
oglContext->SetSwapInterval(0);
}
if (Config::DSiVolumeSync && NDS->ConsoleType == 1)
{
DSi& dsi = static_cast<DSi&>(*NDS);
u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel();
if (volumeLevel != dsiVolumeLevel)
{
dsiVolumeLevel = volumeLevel;
emit syncVolumeLevel();
}
Config::AudioVolume = volumeLevel * (256.0 / 31.0);
}
if (Config::AudioSync && !fastforward)
AudioInOut::AudioSync(*emuThread->NDS);
double frametimeStep = nlines / (60.0 * 263.0);
{
bool limitfps = Config::LimitFPS && !fastforward;
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0;
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError += practicalFramelimit - (curtime - lastTime);
if (frameLimitError < -practicalFramelimit)
frameLimitError = -practicalFramelimit;
if (frameLimitError > practicalFramelimit)
frameLimitError = practicalFramelimit;
if (round(frameLimitError * 1000.0) > 0.0)
{
SDL_Delay(round(frameLimitError * 1000.0));
double timeBeforeSleep = curtime;
curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError -= curtime - timeBeforeSleep;
}
lastTime = curtime;
}
nframes++;
if (nframes >= 30)
{
double time = SDL_GetPerformanceCounter() * perfCountsSec;
double dt = time - lastMeasureTime;
lastMeasureTime = time;
u32 fps = round(nframes / dt);
nframes = 0;
float fpstarget = 1.0/frametimeStep;
winUpdateFreq = fps / (u32)round(fpstarget);
if (winUpdateFreq < 1)
winUpdateFreq = 1;
int inst = Platform::InstanceID();
if (inst == 0)
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
else
sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
changeWindowTitle(melontitle);
}
}
else
{
// paused
nframes = 0;
lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
lastMeasureTime = lastTime;
emit windowUpdate();
EmuStatus = EmuRunning;
int inst = Platform::InstanceID();
if (inst == 0)
sprintf(melontitle, "melonDS " MELONDS_VERSION);
else
sprintf(melontitle, "melonDS (%d)", inst+1);
changeWindowTitle(melontitle);
SDL_Delay(75);
if (oglContext)
drawScreenGL();
ContextRequestKind contextRequest = ContextRequest;
if (contextRequest == contextRequest_InitGL)
{
initOpenGL();
ContextRequest = contextRequest_None;
}
else if (contextRequest == contextRequest_DeInitGL)
{
deinitOpenGL();
ContextRequest = contextRequest_None;
}
}
}
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);
if (file)
{
RTC::StateData state;
NDS->RTC.GetState(state);
Platform::FileWrite(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
}
EmuStatus = emuStatus_Exit;
NDS::Current = nullptr;
// nds is out of scope, so unique_ptr cleans it up for us
}
void EmuThread::changeWindowTitle(char* title)
{
emit windowTitleChange(QString(title));
}
void EmuThread::emuRun()
{
EmuRunning = emuStatus_Running;
EmuPauseStack = EmuPauseStackRunning;
RunningSomething = true;
// checkme
emit windowEmuStart();
AudioInOut::Enable();
}
void EmuThread::initContext()
{
ContextRequest = contextRequest_InitGL;
while (ContextRequest != contextRequest_None);
}
void EmuThread::deinitContext()
{
ContextRequest = contextRequest_DeInitGL;
while (ContextRequest != contextRequest_None);
}
void EmuThread::emuPause()
{
EmuPauseStack++;
if (EmuPauseStack > EmuPauseStackPauseThreshold) return;
PrevEmuStatus = EmuRunning;
EmuRunning = emuStatus_Paused;
while (EmuStatus != emuStatus_Paused);
AudioInOut::Disable();
}
void EmuThread::emuUnpause()
{
if (EmuPauseStack < EmuPauseStackPauseThreshold) return;
EmuPauseStack--;
if (EmuPauseStack >= EmuPauseStackPauseThreshold) return;
EmuRunning = PrevEmuStatus;
AudioInOut::Enable();
}
void EmuThread::emuStop()
{
EmuRunning = emuStatus_Exit;
EmuPauseStack = EmuPauseStackRunning;
AudioInOut::Disable();
}
void EmuThread::emuFrameStep()
{
if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause();
EmuRunning = emuStatus_FrameStep;
}
bool EmuThread::emuIsRunning()
{
return EmuRunning == emuStatus_Running;
}
bool EmuThread::emuIsActive()
{
return (RunningSomething == 1);
}
void EmuThread::drawScreenGL()
{
if (!NDS) return;
int w = windowInfo.surface_width;
int h = windowInfo.surface_height;
float factor = windowInfo.surface_scale;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, w, h);
glUseProgram(screenShaderProgram[2]);
glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor);
int frontbuf = FrontBuffer;
glActiveTexture(GL_TEXTURE0);
#ifdef OGLRENDERER_ENABLED
if (NDS->GPU.GetRenderer3D().Accelerated)
{
// hardware-accelerated render
static_cast<GLRenderer&>(NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf);
}
else
#endif
{
// regular render
glBindTexture(GL_TEXTURE_2D, screenTexture);
if (NDS->GPU.Framebuffer[frontbuf][0] && NDS->GPU.Framebuffer[frontbuf][1])
{
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA,
GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][0].get());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA,
GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][1].get());
}
}
screenSettingsLock.lock();
GLint filter = this->filter ? GL_LINEAR : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
glBindVertexArray(screenVertexArray);
for (int i = 0; i < numScreens; i++)
{
glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]);
glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3);
}
screenSettingsLock.unlock();
OSD::Update();
OSD::DrawGL(w, h);
oglContext->SwapBuffers();
}
@ -1148,6 +293,11 @@ int main(int argc, char** argv)
qputenv("QT_SCALE_FACTOR", "1");
#if QT_VERSION_MAJOR == 6 && defined(__WIN32__)
// Allow using the system dark theme palette on Windows
qputenv("QT_QPA_PLATFORM", "windows:darkmode=2");
#endif
printf("melonDS " MELONDS_VERSION "\n");
printf(MELONDS_URL "\n");
@ -1155,10 +305,10 @@ int main(int argc, char** argv)
if (argc != 0 && (!strcasecmp(argv[0], "derpDS") || !strcasecmp(argv[0], "./derpDS")))
printf("did you just call me a derp???\n");
Platform::Init(argc, argv);
MelonApplication melon(argc, argv);
Platform::Init(argc, argv);
CLI::CommandLineOptions* options = CLI::ManageArgs(melon);
// http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl
@ -1187,7 +337,7 @@ int main(int argc, char** argv)
SDL_InitSubSystem(SDL_INIT_VIDEO);
SDL_EnableScreenSaver(); SDL_DisableScreenSaver();
Config::Load();
if (!Config::Load()) QMessageBox::critical(NULL, "melonDS", "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in.");
#define SANITIZE(var, min, max) { var = std::clamp(var, min, max); }
SANITIZE(Config::ConsoleType, 0, 1);
@ -1216,6 +366,12 @@ int main(int argc, char** argv)
camManager[0]->setXFlip(Config::Camera[0].XFlip);
camManager[1]->setXFlip(Config::Camera[1].XFlip);
systemThemeName = new QString(QApplication::style()->objectName());
if (!Config::UITheme.empty())
{
QApplication::setStyle(QString::fromStdString(Config::UITheme));
}
Input::JoystickID = Config::JoystickID;
Input::OpenJoystick();

View File

@ -22,140 +22,18 @@
#include "glad/glad.h"
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QWindow>
#include <QMainWindow>
#include <QImage>
#include <QActionGroup>
#include <QTimer>
#include <QMutex>
#include <QScreen>
#include <QCloseEvent>
#include <atomic>
#include <variant>
#include <optional>
#include "Window.h"
#include "EmuThread.h"
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
#include "NDSCart.h"
#include "GBACart.h"
using Keep = std::monostate;
using UpdateConsoleNDSArgs = std::variant<Keep, std::unique_ptr<melonDS::NDSCart::CartCommon>>;
using UpdateConsoleGBAArgs = std::variant<Keep, std::unique_ptr<melonDS::GBACart::CartCommon>>;
namespace melonDS
{
class NDS;
}
class EmuThread : public QThread
{
Q_OBJECT
void run() override;
public:
explicit EmuThread(QObject* parent = nullptr);
void changeWindowTitle(char* title);
// to be called from the UI thread
void emuRun();
void emuPause();
void emuUnpause();
void emuStop();
void emuFrameStep();
bool emuIsRunning();
bool emuIsActive();
void initContext();
void deinitContext();
int FrontBuffer = 0;
QMutex FrontBufferLock;
void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix);
/// Applies the config in args.
/// Creates a new NDS console if needed,
/// modifies the existing one if possible.
/// @return \c true if the console was updated.
/// If this returns \c false, then the existing NDS console is not modified.
bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept;
std::unique_ptr<melonDS::NDS> 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<melonDS::NDS> CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
) noexcept;
void drawScreenGL();
void initOpenGL();
void deinitOpenGL();
enum EmuStatusKind
{
emuStatus_Exit,
emuStatus_Running,
emuStatus_Paused,
emuStatus_FrameStep,
};
std::atomic<EmuStatusKind> 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<ContextRequestKind> ContextRequest = contextRequest_None;
GL::Context* oglContext = nullptr;
GLuint screenVertexBuffer, screenVertexArray;
GLuint screenTexture;
GLuint screenShaderProgram[3];
GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc;
QMutex screenSettingsLock;
WindowInfo windowInfo;
float screenMatrix[Frontend::MaxScreenTransforms][6];
int screenKind[Frontend::MaxScreenTransforms];
int numScreens;
bool filter;
int lastScreenWidth = -1, lastScreenHeight = -1;
};
class MelonApplication : public QApplication
{
@ -166,4 +44,6 @@ public:
bool event(QEvent* event) override;
};
extern QString* systemThemeName;
#endif // MAIN_H

View File

@ -27,6 +27,9 @@ A million repetitions of "a"
#if defined(__sun)
#include "solarisfixes.h"
#endif
#if defined(__HAIKU__)
#include <ByteOrder.h>
#endif
#include "sha1.h"
#ifndef BYTE_ORDER

View File

@ -9,6 +9,11 @@
"default-features": false,
"features": ["gui", "png", "thread", "widgets", "opengl", "zstd"]
},
{
"name": "qtbase",
"host": true,
"default-features": false
},
{
"name": "qtmultimedia",
"default-features": false