mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 13:27:41 -07:00
Merge remote-tracking branch 'upstream/master' into RDLines
This commit is contained in:
commit
ba4b4e2263
55
.github/workflows/build-appimage.yml
vendored
55
.github/workflows/build-appimage.yml
vendored
@ -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
|
|
76
.github/workflows/build-macos-universal.yml
vendored
76
.github/workflows/build-macos-universal.yml
vendored
@ -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
84
.github/workflows/build-macos.yml
vendored
Normal 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
|
50
.github/workflows/build-ubuntu-aarch64.yml
vendored
50
.github/workflows/build-ubuntu-aarch64.yml
vendored
@ -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
|
|
82
.github/workflows/build-ubuntu.yml
vendored
82
.github/workflows/build-ubuntu.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: CMake Build (Ubuntu x86-64)
|
name: Ubuntu
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -9,29 +9,77 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-x86_64:
|
||||||
|
name: x86_64
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
|
name: Check out sources
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
|
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
|
||||||
sudo apt update
|
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
|
sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev \
|
||||||
- name: Create build environment
|
qt6-{base,base-private,multimedia}-dev libslirp0 libslirp-dev libarchive-dev libzstd-dev libfuse2
|
||||||
run: mkdir ${{runner.workspace}}/build
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
working-directory: ${{runner.workspace}}/build
|
run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
run: cmake $GITHUB_WORKSPACE
|
- name: Build
|
||||||
- name: Make
|
|
||||||
working-directory: ${{runner.workspace}}/build
|
|
||||||
run: |
|
run: |
|
||||||
make -j$(nproc --all)
|
cmake --build build
|
||||||
mkdir dist
|
DESTDIR=AppDir cmake --install build
|
||||||
cp melonDS dist
|
- uses: actions/upload-artifact@v4
|
||||||
- uses: actions/upload-artifact@v1
|
|
||||||
with:
|
with:
|
||||||
name: melonDS-ubuntu-x86_64
|
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
|
||||||
|
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: CMake Build (Windows x86-64)
|
name: Windows
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
88
CMakePresets.json
Normal file
88
CMakePresets.json
Normal 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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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+%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+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?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>
|
</p>
|
||||||
DS emulator, sorta
|
DS emulator, sorta
|
||||||
|
|
||||||
|
@ -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")
|
set(VCPKG_ROOT "${_DEFAULT_VCPKG_ROOT}" CACHE STRING "The path to the vcpkg repository")
|
||||||
|
|
||||||
if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
|
if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
|
||||||
|
file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE)
|
||||||
FetchContent_Declare(vcpkg
|
FetchContent_Declare(vcpkg
|
||||||
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
|
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
|
||||||
GIT_TAG 2023.10.19
|
GIT_TAG 2024.01.12
|
||||||
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
|
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
|
||||||
FetchContent_MakeAvailable(vcpkg)
|
FetchContent_MakeAvailable(vcpkg)
|
||||||
endif()
|
endif()
|
||||||
|
@ -69,7 +69,7 @@ struct Op2
|
|||||||
bool IsSimpleReg()
|
bool IsSimpleReg()
|
||||||
{ return !IsImm && !Reg.ShiftAmount && Reg.ShiftType == Arm64Gen::ST_LSL; }
|
{ return !IsImm && !Reg.ShiftAmount && Reg.ShiftType == Arm64Gen::ST_LSL; }
|
||||||
bool ImmFits12Bit()
|
bool ImmFits12Bit()
|
||||||
{ return IsImm && (Imm & 0xFFF == Imm); }
|
{ return IsImm && ((Imm & 0xFFF) == Imm); }
|
||||||
bool IsZero()
|
bool IsZero()
|
||||||
{ return IsImm && !Imm; }
|
{ return IsImm && !Imm; }
|
||||||
|
|
||||||
|
@ -128,6 +128,15 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
|
|||||||
target_compile_options(teakra PRIVATE "$<$<CONFIG:DEBUG>:-Og>")
|
target_compile_options(teakra PRIVATE "$<$<CONFIG:DEBUG>:-Og>")
|
||||||
target_link_libraries(core PRIVATE teakra)
|
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)
|
find_library(m MATH_LIBRARY)
|
||||||
|
|
||||||
if (MATH_LIBRARY)
|
if (MATH_LIBRARY)
|
||||||
@ -145,11 +154,13 @@ endif()
|
|||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(core PRIVATE ole32 comctl32 wsock32 ws2_32)
|
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)
|
check_library_exists(rt shm_open "" NEED_LIBRT)
|
||||||
if (NEED_LIBRT)
|
if (NEED_LIBRT)
|
||||||
target_link_libraries(core PRIVATE rt)
|
target_link_libraries(core PRIVATE rt)
|
||||||
endif()
|
endif()
|
||||||
|
elseif(HAIKU)
|
||||||
|
target_link_libraries(core PRIVATE network)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_JIT_PROFILING)
|
if (ENABLE_JIT_PROFILING)
|
||||||
|
@ -122,7 +122,8 @@ NANDImage::NANDImage(NANDImage&& other) noexcept :
|
|||||||
ConsoleID(other.ConsoleID),
|
ConsoleID(other.ConsoleID),
|
||||||
FATIV(other.FATIV),
|
FATIV(other.FATIV),
|
||||||
FATKey(other.FATKey),
|
FATKey(other.FATKey),
|
||||||
ESKey(other.ESKey)
|
ESKey(other.ESKey),
|
||||||
|
Length(other.Length)
|
||||||
{
|
{
|
||||||
other.CurFile = nullptr;
|
other.CurFile = nullptr;
|
||||||
}
|
}
|
||||||
@ -140,6 +141,7 @@ NANDImage& NANDImage::operator=(NANDImage&& other) noexcept
|
|||||||
FATIV = other.FATIV;
|
FATIV = other.FATIV;
|
||||||
FATKey = other.FATKey;
|
FATKey = other.FATKey;
|
||||||
ESKey = other.ESKey;
|
ESKey = other.ESKey;
|
||||||
|
Length = other.Length;
|
||||||
|
|
||||||
other.CurFile = nullptr;
|
other.CurFile = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,8 @@ using namespace Platform;
|
|||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<string>& sourcedir) :
|
FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<string>& sourcedir) :
|
||||||
FilePath(filename),
|
FATStorage(FATStorageArgs { filename, size, readonly, sourcedir })
|
||||||
FileSize(size),
|
|
||||||
ReadOnly(readonly),
|
|
||||||
SourceDir(sourcedir)
|
|
||||||
{
|
{
|
||||||
Load(filename, size, sourcedir);
|
|
||||||
|
|
||||||
File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FATStorage::FATStorage(const FATStorageArgs& args) noexcept :
|
FATStorage::FATStorage(const FATStorageArgs& args) noexcept :
|
||||||
@ -54,8 +48,6 @@ FATStorage::FATStorage(FATStorageArgs&& args) noexcept :
|
|||||||
SourceDir(std::move(args.SourceDir))
|
SourceDir(std::move(args.SourceDir))
|
||||||
{
|
{
|
||||||
Load(FilePath, FileSize, SourceDir);
|
Load(FilePath, FileSize, SourceDir);
|
||||||
|
|
||||||
File = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FATStorage::FATStorage(FATStorage&& other) noexcept
|
FATStorage::FATStorage(FATStorage&& other) noexcept
|
||||||
@ -77,7 +69,10 @@ FATStorage& FATStorage::operator=(FATStorage&& other) noexcept
|
|||||||
if (this != &other)
|
if (this != &other)
|
||||||
{
|
{
|
||||||
if (File)
|
if (File)
|
||||||
|
{ // Sync this file's contents to the host (if applicable) before closing it
|
||||||
|
if (!ReadOnly) Save();
|
||||||
CloseFile(File);
|
CloseFile(File);
|
||||||
|
}
|
||||||
|
|
||||||
FilePath = std::move(other.FilePath);
|
FilePath = std::move(other.FilePath);
|
||||||
IndexPath = std::move(other.IndexPath);
|
IndexPath = std::move(other.IndexPath);
|
||||||
@ -89,6 +84,7 @@ FATStorage& FATStorage::operator=(FATStorage&& other) noexcept
|
|||||||
FileIndex = std::move(other.FileIndex);
|
FileIndex = std::move(other.FileIndex);
|
||||||
|
|
||||||
other.File = nullptr;
|
other.File = nullptr;
|
||||||
|
other.SourceDir = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@ -105,11 +101,8 @@ FATStorage::~FATStorage()
|
|||||||
bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
|
bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
|
||||||
{
|
{
|
||||||
if (!File) return false;
|
if (!File) return false;
|
||||||
if (FF_File) return false;
|
|
||||||
|
|
||||||
FF_File = File;
|
ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9));
|
||||||
FF_FileSize = FileSize;
|
|
||||||
ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9));
|
|
||||||
|
|
||||||
FRESULT res;
|
FRESULT res;
|
||||||
FATFS fs;
|
FATFS fs;
|
||||||
@ -118,7 +111,6 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
|
|||||||
if (res != FR_OK)
|
if (res != FR_OK)
|
||||||
{
|
{
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
FF_File = nullptr;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +122,6 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
|
|||||||
{
|
{
|
||||||
f_unmount("0:");
|
f_unmount("0:");
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
FF_File = nullptr;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,18 +131,14 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
|
|||||||
|
|
||||||
f_unmount("0:");
|
f_unmount("0:");
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
FF_File = nullptr;
|
|
||||||
return nwrite==len;
|
return nwrite==len;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data)
|
u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data)
|
||||||
{
|
{
|
||||||
if (!File) return false;
|
if (!File) return false;
|
||||||
if (FF_File) return false;
|
|
||||||
|
|
||||||
FF_File = File;
|
ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9));
|
||||||
FF_FileSize = FileSize;
|
|
||||||
ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9));
|
|
||||||
|
|
||||||
FRESULT res;
|
FRESULT res;
|
||||||
FATFS fs;
|
FATFS fs;
|
||||||
@ -160,7 +147,6 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data)
|
|||||||
if (res != FR_OK)
|
if (res != FR_OK)
|
||||||
{
|
{
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
FF_File = nullptr;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +158,6 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data)
|
|||||||
{
|
{
|
||||||
f_unmount("0:");
|
f_unmount("0:");
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
FF_File = nullptr;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +168,6 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data)
|
|||||||
|
|
||||||
f_unmount("0:");
|
f_unmount("0:");
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
FF_File = nullptr;
|
|
||||||
return nread;
|
return nread;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,18 +187,18 @@ u64 FATStorage::GetSectorCount() const
|
|||||||
return FileSize / 0x200;
|
return FileSize / 0x200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ff_disk_read_cb FATStorage::FF_ReadStorage() const noexcept
|
||||||
FileHandle* FATStorage::FF_File;
|
|
||||||
u64 FATStorage::FF_FileSize;
|
|
||||||
|
|
||||||
UINT FATStorage::FF_ReadStorage(BYTE* buf, LBA_t sector, UINT num)
|
|
||||||
{
|
{
|
||||||
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
|
// with a minimum 128MB extra, otherwise size is defaulted to 512MB
|
||||||
|
|
||||||
bool isnew = !Platform::LocalFileExists(filename);
|
bool isnew = !Platform::LocalFileExists(filename);
|
||||||
FF_File = Platform::OpenLocalFile(filename, static_cast<FileMode>(FileMode::ReadWrite | FileMode::Preserve));
|
File = Platform::OpenLocalFile(filename, static_cast<FileMode>(FileMode::ReadWrite | FileMode::Preserve));
|
||||||
if (!FF_File)
|
if (!File)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
IndexPath = FilePath + ".idx";
|
IndexPath = FilePath + ".idx";
|
||||||
@ -1053,7 +1037,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional
|
|||||||
|
|
||||||
if (FileSize == 0)
|
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
|
else
|
||||||
{
|
{
|
||||||
FF_FileSize = FileSize;
|
ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9));
|
||||||
ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FF_FileSize>>9));
|
|
||||||
|
|
||||||
res = f_mount(&fs, "0:", 1);
|
res = f_mount(&fs, "0:", 1);
|
||||||
if (res != FR_OK)
|
if (res != FR_OK)
|
||||||
@ -1104,9 +1087,8 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional
|
|||||||
FileSize = 0x20000000ULL; // 512MB
|
FileSize = 0x20000000ULL; // 512MB
|
||||||
}
|
}
|
||||||
|
|
||||||
FF_FileSize = FileSize;
|
|
||||||
ff_disk_close();
|
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();
|
DirIndex.clear();
|
||||||
FileIndex.clear();
|
FileIndex.clear();
|
||||||
@ -1143,8 +1125,6 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::optional
|
|||||||
f_unmount("0:");
|
f_unmount("0:");
|
||||||
|
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
CloseFile(FF_File);
|
|
||||||
FF_File = nullptr;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1156,14 +1136,7 @@ bool FATStorage::Save()
|
|||||||
return true; // Not an error.
|
return true; // Not an error.
|
||||||
}
|
}
|
||||||
|
|
||||||
FF_File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting);
|
ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9));
|
||||||
if (!FF_File)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FF_FileSize = FileSize;
|
|
||||||
ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9));
|
|
||||||
|
|
||||||
FRESULT res;
|
FRESULT res;
|
||||||
FATFS fs;
|
FATFS fs;
|
||||||
@ -1172,8 +1145,6 @@ bool FATStorage::Save()
|
|||||||
if (res != FR_OK)
|
if (res != FR_OK)
|
||||||
{
|
{
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
CloseFile(FF_File);
|
|
||||||
FF_File = nullptr;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1184,8 +1155,6 @@ bool FATStorage::Save()
|
|||||||
f_unmount("0:");
|
f_unmount("0:");
|
||||||
|
|
||||||
ff_disk_close();
|
ff_disk_close();
|
||||||
CloseFile(FF_File);
|
|
||||||
FF_File = nullptr;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "fatfs/ff.h"
|
#include "fatfs/ff.h"
|
||||||
|
#include "FATIO.h"
|
||||||
|
|
||||||
namespace melonDS
|
namespace melonDS
|
||||||
{
|
{
|
||||||
@ -39,6 +40,8 @@ namespace melonDS
|
|||||||
struct FATStorageArgs
|
struct FATStorageArgs
|
||||||
{
|
{
|
||||||
std::string Filename;
|
std::string Filename;
|
||||||
|
|
||||||
|
/// Size of the desired SD card in bytes, or 0 for auto-detect.
|
||||||
u64 Size;
|
u64 Size;
|
||||||
bool ReadOnly;
|
bool ReadOnly;
|
||||||
std::optional<std::string> SourceDir;
|
std::optional<std::string> SourceDir;
|
||||||
@ -48,8 +51,8 @@ class FATStorage
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<std::string>& sourcedir = std::nullopt);
|
FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<std::string>& sourcedir = std::nullopt);
|
||||||
FATStorage(const FATStorageArgs& args) noexcept;
|
explicit FATStorage(const FATStorageArgs& args) noexcept;
|
||||||
FATStorage(FATStorageArgs&& args) noexcept;
|
explicit FATStorage(FATStorageArgs&& args) noexcept;
|
||||||
FATStorage(FATStorage&& other) noexcept;
|
FATStorage(FATStorage&& other) noexcept;
|
||||||
FATStorage(const FATStorage& other) = delete;
|
FATStorage(const FATStorage& other) = delete;
|
||||||
FATStorage& operator=(const FATStorage& other) = delete;
|
FATStorage& operator=(const FATStorage& other) = delete;
|
||||||
@ -74,10 +77,8 @@ private:
|
|||||||
Platform::FileHandle* File;
|
Platform::FileHandle* File;
|
||||||
u64 FileSize;
|
u64 FileSize;
|
||||||
|
|
||||||
static Platform::FileHandle* FF_File;
|
[[nodiscard]] ff_disk_read_cb FF_ReadStorage() const noexcept;
|
||||||
static u64 FF_FileSize;
|
[[nodiscard]] ff_disk_write_cb FF_WriteStorage() const noexcept;
|
||||||
static UINT FF_ReadStorage(BYTE* buf, LBA_t sector, UINT num);
|
|
||||||
static UINT FF_WriteStorage(const BYTE* buf, LBA_t sector, UINT num);
|
|
||||||
|
|
||||||
static u32 ReadSectorsInternal(Platform::FileHandle* file, u64 filelen, u32 start, u32 num, u8* data);
|
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);
|
static u32 WriteSectorsInternal(Platform::FileHandle* file, u64 filelen, u32 start, u32 num, const u8* data);
|
||||||
|
@ -275,7 +275,8 @@ void GPU::DoSavestate(Savestate* file) noexcept
|
|||||||
GPU2D_B.DoSavestate(file);
|
GPU2D_B.DoSavestate(file);
|
||||||
GPU3D.DoSavestate(file);
|
GPU3D.DoSavestate(file);
|
||||||
|
|
||||||
ResetVRAMCache();
|
if (!file->Saving)
|
||||||
|
ResetVRAMCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::AssignFramebuffers() noexcept
|
void GPU::AssignFramebuffers() noexcept
|
||||||
|
34
src/GPU.h
34
src/GPU.h
@ -536,18 +536,18 @@ public:
|
|||||||
u8 VRAMCNT[9] {};
|
u8 VRAMCNT[9] {};
|
||||||
u8 VRAMSTAT = 0;
|
u8 VRAMSTAT = 0;
|
||||||
|
|
||||||
u8 Palette[2*1024] {};
|
alignas(u64) u8 Palette[2*1024] {};
|
||||||
u8 OAM[2*1024] {};
|
alignas(u64) u8 OAM[2*1024] {};
|
||||||
|
|
||||||
u8 VRAM_A[128*1024] {};
|
alignas(u64) u8 VRAM_A[128*1024] {};
|
||||||
u8 VRAM_B[128*1024] {};
|
alignas(u64) u8 VRAM_B[128*1024] {};
|
||||||
u8 VRAM_C[128*1024] {};
|
alignas(u64) u8 VRAM_C[128*1024] {};
|
||||||
u8 VRAM_D[128*1024] {};
|
alignas(u64) u8 VRAM_D[128*1024] {};
|
||||||
u8 VRAM_E[ 64*1024] {};
|
alignas(u64) u8 VRAM_E[ 64*1024] {};
|
||||||
u8 VRAM_F[ 16*1024] {};
|
alignas(u64) u8 VRAM_F[ 16*1024] {};
|
||||||
u8 VRAM_G[ 16*1024] {};
|
alignas(u64) u8 VRAM_G[ 16*1024] {};
|
||||||
u8 VRAM_H[ 32*1024] {};
|
alignas(u64) u8 VRAM_H[ 32*1024] {};
|
||||||
u8 VRAM_I[ 16*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};
|
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};
|
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_AOBJ[256*1024] {};
|
||||||
u8 VRAMFlat_BOBJ[128*1024] {};
|
u8 VRAMFlat_BOBJ[128*1024] {};
|
||||||
|
|
||||||
u8 VRAMFlat_ABGExtPal[32*1024] {};
|
alignas(u16) u8 VRAMFlat_ABGExtPal[32*1024] {};
|
||||||
u8 VRAMFlat_BBGExtPal[32*1024] {};
|
alignas(u16) u8 VRAMFlat_BBGExtPal[32*1024] {};
|
||||||
|
|
||||||
u8 VRAMFlat_AOBJExtPal[8*1024] {};
|
alignas(u16) u8 VRAMFlat_AOBJExtPal[8*1024] {};
|
||||||
u8 VRAMFlat_BOBJExtPal[8*1024] {};
|
alignas(u16) u8 VRAMFlat_BOBJExtPal[8*1024] {};
|
||||||
|
|
||||||
u8 VRAMFlat_Texture[512*1024] {};
|
alignas(u64) u8 VRAMFlat_Texture[512*1024] {};
|
||||||
u8 VRAMFlat_TexPal[128*1024] {};
|
alignas(u64) u8 VRAMFlat_TexPal[128*1024] {};
|
||||||
private:
|
private:
|
||||||
void ResetVRAMCache() noexcept;
|
void ResetVRAMCache() noexcept;
|
||||||
void AssignFramebuffers() noexcept;
|
void AssignFramebuffers() noexcept;
|
||||||
|
107
src/GPU3D.cpp
107
src/GPU3D.cpp
@ -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
|
void GPU3D::SetCurrentRenderer(std::unique_ptr<Renderer3D>&& renderer) noexcept
|
||||||
{
|
{
|
||||||
CurrentRenderer = std::move(renderer);
|
CurrentRenderer = std::move(renderer);
|
||||||
@ -299,6 +312,12 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
|
|||||||
{
|
{
|
||||||
file->Section("GP3D");
|
file->Section("GP3D");
|
||||||
|
|
||||||
|
SoftRenderer* softRenderer = dynamic_cast<SoftRenderer*>(CurrentRenderer.get());
|
||||||
|
if (softRenderer && softRenderer->IsThreaded())
|
||||||
|
{
|
||||||
|
softRenderer->SetupRenderThread(NDS.GPU);
|
||||||
|
}
|
||||||
|
|
||||||
CmdFIFO.DoSavestate(file);
|
CmdFIFO.DoSavestate(file);
|
||||||
CmdPIPE.DoSavestate(file);
|
CmdPIPE.DoSavestate(file);
|
||||||
|
|
||||||
@ -374,33 +393,21 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
|
|||||||
file->Var32(&VertexNumInPoly);
|
file->Var32(&VertexNumInPoly);
|
||||||
file->Var32(&NumConsecutivePolygons);
|
file->Var32(&NumConsecutivePolygons);
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (Vertex& vtx : TempVertexBuffer)
|
||||||
{
|
{
|
||||||
Vertex* vtx = &TempVertexBuffer[i];
|
vtx.DoSavestate(file);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file->Saving)
|
if (file->Saving)
|
||||||
{
|
{
|
||||||
u32 id;
|
u32 index = LastStripPolygon ? (u32)(LastStripPolygon - &PolygonRAM[0]) : UINT32_MAX;
|
||||||
if (LastStripPolygon) id = (u32)((LastStripPolygon - (&PolygonRAM[0])) / sizeof(Polygon));
|
file->Var32(&index);
|
||||||
else id = -1;
|
|
||||||
file->Var32(&id);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
u32 id;
|
u32 index = UINT32_MAX;
|
||||||
file->Var32(&id);
|
file->Var32(&index);
|
||||||
if (id == 0xFFFFFFFF) LastStripPolygon = NULL;
|
LastStripPolygon = (index == UINT32_MAX) ? nullptr : &PolygonRAM[index];
|
||||||
else LastStripPolygon = &PolygonRAM[id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file->Var32(&CurRAMBank);
|
file->Var32(&CurRAMBank);
|
||||||
@ -411,18 +418,9 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
|
|||||||
file->Var32(&FlushRequest);
|
file->Var32(&FlushRequest);
|
||||||
file->Var32(&FlushAttributes);
|
file->Var32(&FlushAttributes);
|
||||||
|
|
||||||
for (int i = 0; i < 6144*2; i++)
|
for (Vertex& vtx : VertexRAM)
|
||||||
{
|
{
|
||||||
Vertex* vtx = &VertexRAM[i];
|
vtx.DoSavestate(file);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i < 2048*2; i++)
|
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++)
|
for (int j = 0; j < 10; j++)
|
||||||
{
|
{
|
||||||
Vertex* ptr = poly->Vertices[j];
|
Vertex* ptr = poly->Vertices[j];
|
||||||
u32 id;
|
u32 index = ptr ? (u32)(ptr - &VertexRAM[0]) : UINT32_MAX;
|
||||||
if (ptr) id = (u32)((ptr - (&VertexRAM[0])) / sizeof(Vertex));
|
file->Var32(&index);
|
||||||
else id = -1;
|
|
||||||
file->Var32(&id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int j = 0; j < 10; j++)
|
for (int j = 0; j < 10; j++)
|
||||||
{
|
{
|
||||||
u32 id = -1;
|
u32 index = UINT32_MAX;
|
||||||
file->Var32(&id);
|
file->Var32(&index);
|
||||||
if (id == 0xFFFFFFFF) poly->Vertices[j] = NULL;
|
poly->Vertices[j] = index == UINT32_MAX ? nullptr : &VertexRAM[index];
|
||||||
else poly->Vertices[j] = &VertexRAM[id];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,7 +492,6 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// probably not worth storing the vblank-latched Renderxxxxxx variables
|
|
||||||
CmdStallQueue.DoSavestate(file);
|
CmdStallQueue.DoSavestate(file);
|
||||||
|
|
||||||
file->Var32((u32*)&VertexPipeline);
|
file->Var32((u32*)&VertexPipeline);
|
||||||
@ -513,10 +507,27 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
|
|||||||
|
|
||||||
CurVertexRAM = &VertexRAM[CurRAMBank ? 6144 : 0];
|
CurVertexRAM = &VertexRAM[CurRAMBank ? 6144 : 0];
|
||||||
CurPolygonRAM = &PolygonRAM[CurRAMBank ? 2048 : 0];
|
CurPolygonRAM = &PolygonRAM[CurRAMBank ? 2048 : 0];
|
||||||
|
}
|
||||||
|
|
||||||
// better safe than sorry, I guess
|
file->Var32(&RenderNumPolygons);
|
||||||
// might cause a blank frame but atleast it won't shit itself
|
if (file->Saving)
|
||||||
RenderNumPolygons = 0;
|
{
|
||||||
|
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);
|
file->VarArray(CurVertex, sizeof(s16)*3);
|
||||||
@ -536,6 +547,18 @@ void GPU3D::DoSavestate(Savestate* file) noexcept
|
|||||||
file->VarArray(ShininessTable, 128*sizeof(u8));
|
file->VarArray(ShininessTable, 128*sizeof(u8));
|
||||||
|
|
||||||
file->Bool32(&AbortFrame);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ struct Vertex
|
|||||||
// TODO maybe: hi-res color? (that survives clipping)
|
// TODO maybe: hi-res color? (that survives clipping)
|
||||||
s32 HiresPosition[2];
|
s32 HiresPosition[2];
|
||||||
|
|
||||||
|
void DoSavestate(Savestate* file) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Polygon
|
struct Polygon
|
||||||
@ -80,6 +81,7 @@ struct Polygon
|
|||||||
|
|
||||||
u32 SortKey;
|
u32 SortKey;
|
||||||
|
|
||||||
|
void DoSavestate(Savestate* file) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Renderer3D;
|
class Renderer3D;
|
||||||
@ -272,7 +274,7 @@ public:
|
|||||||
u32 RenderClearAttr1 = 0;
|
u32 RenderClearAttr1 = 0;
|
||||||
u32 RenderClearAttr2 = 0;
|
u32 RenderClearAttr2 = 0;
|
||||||
|
|
||||||
bool RenderFrameIdentical = false;
|
bool RenderFrameIdentical = false; // not part of the hardware state, don't serialize
|
||||||
|
|
||||||
bool AbortFrame = false;
|
bool AbortFrame = false;
|
||||||
|
|
||||||
@ -326,7 +328,7 @@ public:
|
|||||||
|
|
||||||
u32 FlushRequest = 0;
|
u32 FlushRequest = 0;
|
||||||
u32 FlushAttributes = 0;
|
u32 FlushAttributes = 0;
|
||||||
u32 ScrolledLine[256];
|
u32 ScrolledLine[256]; // not part of the hardware state, don't serialize
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rasterization Timing Constants
|
// Rasterization Timing Constants
|
||||||
|
@ -31,7 +31,7 @@ namespace melonDS
|
|||||||
bool GLRenderer::BuildRenderShader(u32 flags, const char* vs, const char* fs)
|
bool GLRenderer::BuildRenderShader(u32 flags, const char* vs, const char* fs)
|
||||||
{
|
{
|
||||||
char shadername[32];
|
char shadername[32];
|
||||||
sprintf(shadername, "RenderShader%02X", flags);
|
snprintf(shadername, sizeof(shadername), "RenderShader%02X", flags);
|
||||||
|
|
||||||
int headerlen = strlen(kShaderHeader);
|
int headerlen = strlen(kShaderHeader);
|
||||||
|
|
||||||
|
@ -34,8 +34,11 @@ void SoftRenderer::StopRenderThread()
|
|||||||
{
|
{
|
||||||
if (RenderThreadRunning.load(std::memory_order_relaxed))
|
if (RenderThreadRunning.load(std::memory_order_relaxed))
|
||||||
{
|
{
|
||||||
|
// Tell the render thread to stop drawing new frames, and finish up the current one.
|
||||||
RenderThreadRunning = false;
|
RenderThreadRunning = false;
|
||||||
|
|
||||||
Platform::Semaphore_Post(Sema_RenderStart);
|
Platform::Semaphore_Post(Sema_RenderStart);
|
||||||
|
|
||||||
Platform::Thread_Wait(RenderThread);
|
Platform::Thread_Wait(RenderThread);
|
||||||
Platform::Thread_Free(RenderThread);
|
Platform::Thread_Free(RenderThread);
|
||||||
RenderThread = nullptr;
|
RenderThread = nullptr;
|
||||||
@ -47,24 +50,36 @@ void SoftRenderer::SetupRenderThread(GPU& gpu)
|
|||||||
if (Threaded)
|
if (Threaded)
|
||||||
{
|
{
|
||||||
if (!RenderThreadRunning.load(std::memory_order_relaxed))
|
if (!RenderThreadRunning.load(std::memory_order_relaxed))
|
||||||
{
|
{ // If the render thread isn't already running...
|
||||||
RenderThreadRunning = true;
|
RenderThreadRunning = true; // "Time for work, render thread!"
|
||||||
RenderThread = Platform::Thread_Create([this, &gpu]() {
|
RenderThread = Platform::Thread_Create([this, &gpu]() {
|
||||||
RenderThreadFunc(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);
|
Platform::Semaphore_Reset(Sema_RenderStart);
|
||||||
|
|
||||||
|
// "Oh, sorry, were you already in the middle of a frame from the last iteration?"
|
||||||
if (RenderThreadRendering)
|
if (RenderThreadRendering)
|
||||||
|
// "Tell me when you're done, I'll wait here."
|
||||||
Platform::Semaphore_Wait(Sema_RenderDone);
|
Platform::Semaphore_Wait(Sema_RenderDone);
|
||||||
|
|
||||||
Platform::Semaphore_Reset(Sema_RenderDone);
|
// "All good? Okay, let me give you your training."
|
||||||
Platform::Semaphore_Reset(Sema_RenderStart);
|
// "(Maybe you're still the same thread, but I have to tell you this stuff anyway.)"
|
||||||
Platform::Semaphore_Reset(Sema_ScanlineCount);
|
|
||||||
|
|
||||||
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
|
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
|
SoftRenderer::SoftRenderer(bool threaded) noexcept
|
||||||
: Renderer3D(false), Threaded(threaded)
|
: Renderer3D(false), Threaded(threaded)
|
||||||
@ -103,6 +125,7 @@ void SoftRenderer::Reset(GPU& gpu)
|
|||||||
PrevIsShadowMask = false;
|
PrevIsShadowMask = false;
|
||||||
|
|
||||||
SetupRenderThread(gpu);
|
SetupRenderThread(gpu);
|
||||||
|
EnableRenderThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept
|
void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept
|
||||||
@ -111,6 +134,7 @@ void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept
|
|||||||
{
|
{
|
||||||
Threaded = threaded;
|
Threaded = threaded;
|
||||||
SetupRenderThread(gpu);
|
SetupRenderThread(gpu);
|
||||||
|
EnableRenderThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1991,6 +2015,7 @@ void SoftRenderer::RenderFrame(GPU& gpu)
|
|||||||
|
|
||||||
if (RenderThreadRunning.load(std::memory_order_relaxed))
|
if (RenderThreadRunning.load(std::memory_order_relaxed))
|
||||||
{
|
{
|
||||||
|
// "Render thread, you're up! Get moving."
|
||||||
Platform::Semaphore_Post(Sema_RenderStart);
|
Platform::Semaphore_Post(Sema_RenderStart);
|
||||||
}
|
}
|
||||||
else if (!FrameIdentical) RenderPolygons<false>(gpu, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons);
|
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)
|
void SoftRenderer::RestartFrame(GPU& gpu)
|
||||||
{
|
{
|
||||||
SetupRenderThread(gpu);
|
SetupRenderThread(gpu);
|
||||||
|
EnableRenderThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoftRenderer::RenderThreadFunc(GPU& gpu)
|
void SoftRenderer::RenderThreadFunc(GPU& gpu)
|
||||||
{
|
{
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
// Wait for a notice from the main thread to start rendering (or to stop entirely).
|
||||||
Platform::Semaphore_Wait(Sema_RenderStart);
|
Platform::Semaphore_Wait(Sema_RenderStart);
|
||||||
if (!RenderThreadRunning) return;
|
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;
|
RenderThreadRendering = true;
|
||||||
if (FrameIdentical)
|
if (FrameIdentical)
|
||||||
{
|
{ // If no rendering is needed, just say we're done.
|
||||||
Platform::Semaphore_Post(Sema_ScanlineCount, 192);
|
Platform::Semaphore_Post(Sema_ScanlineCount, 192);
|
||||||
}
|
}
|
||||||
else RenderPolygons<true>(gpu, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons);
|
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);
|
Platform::Semaphore_Post(Sema_RenderDone);
|
||||||
|
|
||||||
RenderThreadRendering = false;
|
RenderThreadRendering = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2025,6 +2061,9 @@ u32* SoftRenderer::GetLine(int line)
|
|||||||
if (RenderThreadRunning.load(std::memory_order_relaxed))
|
if (RenderThreadRunning.load(std::memory_order_relaxed))
|
||||||
{
|
{
|
||||||
if (line < 192)
|
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);
|
Platform::Semaphore_Wait(Sema_ScanlineCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +42,10 @@ public:
|
|||||||
u32* GetLine(int line) override;
|
u32* GetLine(int line) override;
|
||||||
|
|
||||||
void SetupRenderThread(GPU& gpu);
|
void SetupRenderThread(GPU& gpu);
|
||||||
|
void EnableRenderThread();
|
||||||
void StopRenderThread();
|
void StopRenderThread();
|
||||||
private:
|
private:
|
||||||
|
friend void GPU3D::DoSavestate(Savestate* file) noexcept;
|
||||||
// Notes on the interpolator:
|
// Notes on the interpolator:
|
||||||
//
|
//
|
||||||
// This is a theory on how the DS hardware interpolates values. It matches hardware output
|
// This is a theory on how the DS hardware interpolates values. It matches hardware output
|
||||||
@ -178,7 +180,7 @@ private:
|
|||||||
{
|
{
|
||||||
// Z-buffering: linear interpolation
|
// Z-buffering: linear interpolation
|
||||||
// still doesn't quite match hardware...
|
// still doesn't quite match hardware...
|
||||||
s32 base, disp, factor;
|
s32 base = 0, disp = 0, factor = 0;
|
||||||
|
|
||||||
if (z0 < z1)
|
if (z0 < z1)
|
||||||
{
|
{
|
||||||
@ -337,7 +339,7 @@ private:
|
|||||||
|
|
||||||
constexpr s32 XVal() const
|
constexpr s32 XVal() const
|
||||||
{
|
{
|
||||||
s32 ret;
|
s32 ret = 0;
|
||||||
if (Negative) ret = x0 - (dx >> 18);
|
if (Negative) ret = x0 - (dx >> 18);
|
||||||
else ret = x0 + (dx >> 18);
|
else ret = x0 + (dx >> 18);
|
||||||
|
|
||||||
@ -534,8 +536,15 @@ private:
|
|||||||
Platform::Thread* RenderThread;
|
Platform::Thread* RenderThread;
|
||||||
std::atomic_bool RenderThreadRunning;
|
std::atomic_bool RenderThreadRunning;
|
||||||
std::atomic_bool RenderThreadRendering;
|
std::atomic_bool RenderThreadRendering;
|
||||||
|
|
||||||
|
// Used by the main thread to tell the render thread to start rendering a frame
|
||||||
Platform::Semaphore* Sema_RenderStart;
|
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;
|
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;
|
Platform::Semaphore* Sema_ScanlineCount;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
180
src/NDS.cpp
180
src/NDS.cpp
@ -713,15 +713,11 @@ bool NDS::DoSavestate(Savestate* file)
|
|||||||
|
|
||||||
SPU.SetPowerCnt(PowerControl7 & 0x0001);
|
SPU.SetPowerCnt(PowerControl7 & 0x0001);
|
||||||
Wifi.SetPowerCnt(PowerControl7 & 0x0002);
|
Wifi.SetPowerCnt(PowerControl7 & 0x0002);
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
if (!file->Saving)
|
JIT.Reset();
|
||||||
{
|
|
||||||
JIT.ResetBlockCache();
|
|
||||||
JIT.Memory.Reset();
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
file->Finish();
|
file->Finish();
|
||||||
|
|
||||||
@ -1497,40 +1493,40 @@ void NDS::NocashPrint(u32 ncpu, u32 addr)
|
|||||||
|
|
||||||
if (cmd[0] == 'r')
|
if (cmd[0] == 'r')
|
||||||
{
|
{
|
||||||
if (!strcmp(cmd, "r0")) sprintf(subs, "%08X", cpu->R[0]);
|
if (!strcmp(cmd, "r0")) snprintf(subs, sizeof(subs), "%08X", cpu->R[0]);
|
||||||
else if (!strcmp(cmd, "r1")) sprintf(subs, "%08X", cpu->R[1]);
|
else if (!strcmp(cmd, "r1")) snprintf(subs, sizeof(subs), "%08X", cpu->R[1]);
|
||||||
else if (!strcmp(cmd, "r2")) sprintf(subs, "%08X", cpu->R[2]);
|
else if (!strcmp(cmd, "r2")) snprintf(subs, sizeof(subs), "%08X", cpu->R[2]);
|
||||||
else if (!strcmp(cmd, "r3")) sprintf(subs, "%08X", cpu->R[3]);
|
else if (!strcmp(cmd, "r3")) snprintf(subs, sizeof(subs), "%08X", cpu->R[3]);
|
||||||
else if (!strcmp(cmd, "r4")) sprintf(subs, "%08X", cpu->R[4]);
|
else if (!strcmp(cmd, "r4")) snprintf(subs, sizeof(subs), "%08X", cpu->R[4]);
|
||||||
else if (!strcmp(cmd, "r5")) sprintf(subs, "%08X", cpu->R[5]);
|
else if (!strcmp(cmd, "r5")) snprintf(subs, sizeof(subs), "%08X", cpu->R[5]);
|
||||||
else if (!strcmp(cmd, "r6")) sprintf(subs, "%08X", cpu->R[6]);
|
else if (!strcmp(cmd, "r6")) snprintf(subs, sizeof(subs), "%08X", cpu->R[6]);
|
||||||
else if (!strcmp(cmd, "r7")) sprintf(subs, "%08X", cpu->R[7]);
|
else if (!strcmp(cmd, "r7")) snprintf(subs, sizeof(subs), "%08X", cpu->R[7]);
|
||||||
else if (!strcmp(cmd, "r8")) sprintf(subs, "%08X", cpu->R[8]);
|
else if (!strcmp(cmd, "r8")) snprintf(subs, sizeof(subs), "%08X", cpu->R[8]);
|
||||||
else if (!strcmp(cmd, "r9")) sprintf(subs, "%08X", cpu->R[9]);
|
else if (!strcmp(cmd, "r9")) snprintf(subs, sizeof(subs), "%08X", cpu->R[9]);
|
||||||
else if (!strcmp(cmd, "r10")) sprintf(subs, "%08X", cpu->R[10]);
|
else if (!strcmp(cmd, "r10")) snprintf(subs, sizeof(subs), "%08X", cpu->R[10]);
|
||||||
else if (!strcmp(cmd, "r11")) sprintf(subs, "%08X", cpu->R[11]);
|
else if (!strcmp(cmd, "r11")) snprintf(subs, sizeof(subs), "%08X", cpu->R[11]);
|
||||||
else if (!strcmp(cmd, "r12")) sprintf(subs, "%08X", cpu->R[12]);
|
else if (!strcmp(cmd, "r12")) snprintf(subs, sizeof(subs), "%08X", cpu->R[12]);
|
||||||
else if (!strcmp(cmd, "r13")) sprintf(subs, "%08X", cpu->R[13]);
|
else if (!strcmp(cmd, "r13")) snprintf(subs, sizeof(subs), "%08X", cpu->R[13]);
|
||||||
else if (!strcmp(cmd, "r14")) sprintf(subs, "%08X", cpu->R[14]);
|
else if (!strcmp(cmd, "r14")) snprintf(subs, sizeof(subs), "%08X", cpu->R[14]);
|
||||||
else if (!strcmp(cmd, "r15")) sprintf(subs, "%08X", cpu->R[15]);
|
else if (!strcmp(cmd, "r15")) snprintf(subs, sizeof(subs), "%08X", cpu->R[15]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!strcmp(cmd, "sp")) sprintf(subs, "%08X", cpu->R[13]);
|
if (!strcmp(cmd, "sp")) snprintf(subs, sizeof(subs), "%08X", cpu->R[13]);
|
||||||
else if (!strcmp(cmd, "lr")) sprintf(subs, "%08X", cpu->R[14]);
|
else if (!strcmp(cmd, "lr")) snprintf(subs, sizeof(subs), "%08X", cpu->R[14]);
|
||||||
else if (!strcmp(cmd, "pc")) sprintf(subs, "%08X", cpu->R[15]);
|
else if (!strcmp(cmd, "pc")) snprintf(subs, sizeof(subs), "%08X", cpu->R[15]);
|
||||||
else if (!strcmp(cmd, "frame")) sprintf(subs, "%u", NumFrames);
|
else if (!strcmp(cmd, "frame")) snprintf(subs, sizeof(subs), "%u", NumFrames);
|
||||||
else if (!strcmp(cmd, "scanline")) sprintf(subs, "%u", GPU.VCount);
|
else if (!strcmp(cmd, "scanline")) snprintf(subs, sizeof(subs), "%u", GPU.VCount);
|
||||||
else if (!strcmp(cmd, "totalclks")) sprintf(subs, "%" PRIu64, GetSysClockCycles(0));
|
else if (!strcmp(cmd, "totalclks")) snprintf(subs, sizeof(subs), "%" PRIu64, GetSysClockCycles(0));
|
||||||
else if (!strcmp(cmd, "lastclks")) sprintf(subs, "%" PRIu64, GetSysClockCycles(1));
|
else if (!strcmp(cmd, "lastclks")) snprintf(subs, sizeof(subs), "%" PRIu64, GetSysClockCycles(1));
|
||||||
else if (!strcmp(cmd, "zeroclks"))
|
else if (!strcmp(cmd, "zeroclks"))
|
||||||
{
|
{
|
||||||
sprintf(subs, "%s", "");
|
snprintf(subs, sizeof(subs), "%s", "");
|
||||||
GetSysClockCycles(1);
|
GetSysClockCycles(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int slen = strlen(subs);
|
int slen = strnlen(subs, sizeof(subs));
|
||||||
if ((ptr+slen) > 1023) slen = 1023-ptr;
|
if ((ptr+slen) > 1023) slen = 1023-ptr;
|
||||||
strncpy(&output[ptr], subs, slen);
|
strncpy(&output[ptr], subs, slen);
|
||||||
ptr += slen;
|
ptr += slen;
|
||||||
@ -2732,11 +2728,37 @@ u8 NDS::ARM9IORead8(u32 addr)
|
|||||||
case 0x04000132: return KeyCnt[0] & 0xFF;
|
case 0x04000132: return KeyCnt[0] & 0xFF;
|
||||||
case 0x04000133: return KeyCnt[0] >> 8;
|
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:
|
case 0x040001A2:
|
||||||
if (!(ExMemCnt[0] & (1<<11)))
|
if (!(ExMemCnt[0] & (1<<11)))
|
||||||
return NDSCartSlot.ReadSPIData();
|
return NDSCartSlot.ReadSPIData();
|
||||||
return 0;
|
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:
|
case 0x040001A8:
|
||||||
if (!(ExMemCnt[0] & (1<<11)))
|
if (!(ExMemCnt[0] & (1<<11)))
|
||||||
return NDSCartSlot.GetROMCommand(0);
|
return NDSCartSlot.GetROMCommand(0);
|
||||||
@ -2888,6 +2910,15 @@ u16 NDS::ARM9IORead16(u32 addr)
|
|||||||
return NDSCartSlot.ReadSPIData();
|
return NDSCartSlot.ReadSPIData();
|
||||||
return 0;
|
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:
|
case 0x040001A8:
|
||||||
if (!(ExMemCnt[0] & (1<<11)))
|
if (!(ExMemCnt[0] & (1<<11)))
|
||||||
return NDSCartSlot.GetROMCommand(0) |
|
return NDSCartSlot.GetROMCommand(0) |
|
||||||
@ -3151,6 +3182,23 @@ void NDS::ARM9IOWrite8(u32 addr, u8 val)
|
|||||||
NDSCartSlot.WriteSPIData(val);
|
NDSCartSlot.WriteSPIData(val);
|
||||||
return;
|
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 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(0, val); return;
|
||||||
case 0x040001A9: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(1, 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;
|
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);
|
NDSCartSlot.WriteSPIData(val & 0xFF);
|
||||||
return;
|
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:
|
case 0x040001A8:
|
||||||
if (!(ExMemCnt[0] & (1<<11)))
|
if (!(ExMemCnt[0] & (1<<11)))
|
||||||
{
|
{
|
||||||
@ -3596,11 +3653,37 @@ u8 NDS::ARM7IORead8(u32 addr)
|
|||||||
|
|
||||||
case 0x04000138: return RTC.Read() & 0xFF;
|
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:
|
case 0x040001A2:
|
||||||
if (ExMemCnt[0] & (1<<11))
|
if (ExMemCnt[0] & (1<<11))
|
||||||
return NDSCartSlot.ReadSPIData();
|
return NDSCartSlot.ReadSPIData();
|
||||||
return 0;
|
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:
|
case 0x040001A8:
|
||||||
if (ExMemCnt[0] & (1<<11))
|
if (ExMemCnt[0] & (1<<11))
|
||||||
return NDSCartSlot.GetROMCommand(0);
|
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 0x040001A0: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetSPICnt(); return 0;
|
||||||
case 0x040001A2: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.ReadSPIData(); 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:
|
case 0x040001A8:
|
||||||
if (ExMemCnt[0] & (1<<11))
|
if (ExMemCnt[0] & (1<<11))
|
||||||
return NDSCartSlot.GetROMCommand(0) |
|
return NDSCartSlot.GetROMCommand(0) |
|
||||||
@ -3888,6 +3980,23 @@ void NDS::ARM7IOWrite8(u32 addr, u8 val)
|
|||||||
NDSCartSlot.WriteSPIData(val);
|
NDSCartSlot.WriteSPIData(val);
|
||||||
return;
|
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 0x040001A8: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(0, val); return;
|
||||||
case 0x040001A9: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(1, 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;
|
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);
|
NDSCartSlot.WriteSPIData(val & 0xFF);
|
||||||
return;
|
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:
|
case 0x040001A8:
|
||||||
if (ExMemCnt[0] & (1<<11))
|
if (ExMemCnt[0] & (1<<11))
|
||||||
{
|
{
|
||||||
|
14
src/NDS.h
14
src/NDS.h
@ -259,8 +259,8 @@ public: // TODO: Encapsulate the rest of these members
|
|||||||
u16 PowerControl9;
|
u16 PowerControl9;
|
||||||
|
|
||||||
u16 ExMemCnt[2];
|
u16 ExMemCnt[2];
|
||||||
u8 ROMSeed0[2*8];
|
alignas(u32) u8 ROMSeed0[2*8];
|
||||||
u8 ROMSeed1[2*8];
|
alignas(u32) u8 ROMSeed1[2*8];
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// These BIOS arrays should be declared *before* the component objects (JIT, SPI, etc.)
|
// 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> IPCFIFO9; // FIFO in which the ARM9 writes
|
||||||
FIFO<u32, 16> IPCFIFO7;
|
FIFO<u32, 16> IPCFIFO7;
|
||||||
u16 DivCnt;
|
u16 DivCnt;
|
||||||
u32 DivNumerator[2];
|
alignas(u64) u32 DivNumerator[2];
|
||||||
u32 DivDenominator[2];
|
alignas(u64) u32 DivDenominator[2];
|
||||||
u32 DivQuotient[2];
|
alignas(u64) u32 DivQuotient[2];
|
||||||
u32 DivRemainder[2];
|
alignas(u64) u32 DivRemainder[2];
|
||||||
u16 SqrtCnt;
|
u16 SqrtCnt;
|
||||||
u32 SqrtVal[2];
|
alignas(u64) u32 SqrtVal[2];
|
||||||
u32 SqrtRes;
|
u32 SqrtRes;
|
||||||
u16 KeyCnt[2];
|
u16 KeyCnt[2];
|
||||||
bool Running;
|
bool Running;
|
||||||
|
@ -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;
|
std::unique_ptr<u8[]> sram = args ? std::move(args->SRAM) : nullptr;
|
||||||
u32 sramlen = args ? args->SRAMLength : 0;
|
u32 sramlen = args ? args->SRAMLength : 0;
|
||||||
if (homebrew)
|
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)
|
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)
|
else if (cartid & 0x08000000)
|
||||||
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
|
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
|
||||||
else if (irversion != 0)
|
else if (irversion != 0)
|
||||||
|
@ -260,6 +260,22 @@ public:
|
|||||||
// it just leaves behind an optional with a moved-from value
|
// 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:
|
protected:
|
||||||
void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const;
|
void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const;
|
||||||
void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly);
|
void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly);
|
||||||
|
@ -39,6 +39,8 @@ enum RegionMask : u32
|
|||||||
RegionFree = 0xFFFFFFFF,
|
RegionFree = 0xFFFFFFFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr u32 DSiWareTitleIDHigh = 0x00030004;
|
||||||
|
|
||||||
// Consult GBATEK for info on what these are
|
// Consult GBATEK for info on what these are
|
||||||
struct NDSHeader
|
struct NDSHeader
|
||||||
{
|
{
|
||||||
@ -198,8 +200,9 @@ struct NDSHeader
|
|||||||
|
|
||||||
u8 HeaderSignature[128]; // RSA-SHA1 across 0x000..0xDFF
|
u8 HeaderSignature[128]; // RSA-SHA1 across 0x000..0xDFF
|
||||||
|
|
||||||
/// @return \c true if this header represents a DSi title
|
/// @return \c true if this header represents a title
|
||||||
/// (either a physical cartridge or a DSiWare title).
|
/// that is DSi-exclusive (including DSiWare)
|
||||||
|
/// or DSi-enhanced (including cartridges).
|
||||||
[[nodiscard]] bool IsDSi() const { return (UnitCode & 0x02) != 0; }
|
[[nodiscard]] bool IsDSi() const { return (UnitCode & 0x02) != 0; }
|
||||||
[[nodiscard]] u32 GameCodeAsU32() const {
|
[[nodiscard]] u32 GameCodeAsU32() const {
|
||||||
return (u32)GameCode[3] << 24 |
|
return (u32)GameCode[3] << 24 |
|
||||||
@ -213,7 +216,7 @@ struct NDSHeader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @return \c true if this header represents a DSiWare title.
|
/// @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!");
|
static_assert(sizeof(NDSHeader) == 4096, "NDSHeader is not 4096 bytes!");
|
||||||
|
@ -136,6 +136,11 @@ enum FileMode : unsigned {
|
|||||||
*/
|
*/
|
||||||
Text = 0b01'00'00,
|
Text = 0b01'00'00,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file in append mode.
|
||||||
|
*/
|
||||||
|
Append = 0b10'00'00,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a file for reading and writing.
|
* Opens a file for reading and writing.
|
||||||
* Equivalent to <tt>Read | Write</tt>.
|
* 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 FileExists(const std::string& name);
|
||||||
bool LocalFileExists(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.
|
/** Close a file opened with \c OpenFile.
|
||||||
* @returns \c true if the file was closed successfully, false otherwise.
|
* @returns \c true if the file was closed successfully, false otherwise.
|
||||||
* @post \c file is no longer valid and should not be used.
|
* @post \c file is no longer valid and should not be used.
|
||||||
|
@ -621,6 +621,8 @@ s32 SPUChannel::Run()
|
|||||||
(PrevSample[0] * InterpCubic[samplepos][2]) +
|
(PrevSample[0] * InterpCubic[samplepos][2]) +
|
||||||
(val * InterpCubic[samplepos][3])) >> 14;
|
(val * InterpCubic[samplepos][3])) >> 14;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#define SAVESTATE_MAJOR 11
|
#define SAVESTATE_MAJOR 12
|
||||||
#define SAVESTATE_MINOR 1
|
#define SAVESTATE_MINOR 1
|
||||||
|
|
||||||
namespace melonDS
|
namespace melonDS
|
||||||
|
@ -7,6 +7,7 @@ set(SOURCES_QT_SDL
|
|||||||
main_shaders.h
|
main_shaders.h
|
||||||
Screen.cpp
|
Screen.cpp
|
||||||
Window.cpp
|
Window.cpp
|
||||||
|
EmuThread.cpp
|
||||||
CheatsDialog.cpp
|
CheatsDialog.cpp
|
||||||
Config.cpp
|
Config.cpp
|
||||||
DateTimeDialog.cpp
|
DateTimeDialog.cpp
|
||||||
@ -31,7 +32,6 @@ set(SOURCES_QT_SDL
|
|||||||
LAN_PCap.cpp
|
LAN_PCap.cpp
|
||||||
LAN_Socket.cpp
|
LAN_Socket.cpp
|
||||||
LocalMP.cpp
|
LocalMP.cpp
|
||||||
OSD.cpp
|
|
||||||
OSD_shaders.h
|
OSD_shaders.h
|
||||||
font.h
|
font.h
|
||||||
Platform.cpp
|
Platform.cpp
|
||||||
@ -84,11 +84,11 @@ if (BUILD_STATIC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
|
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(LibArchive REQUIRED IMPORTED_TARGET libarchive)
|
||||||
pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd)
|
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)
|
add_compile_definitions(ARCHIVE_SUPPORT_ENABLED)
|
||||||
|
|
||||||
@ -160,14 +160,19 @@ else()
|
|||||||
target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(melonDS PRIVATE core)
|
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})
|
target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS})
|
||||||
|
|
||||||
if (UNIX)
|
target_include_directories(melonDS PRIVATE "${Slirp_INCLUDE_DIRS}")
|
||||||
option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF)
|
target_link_libraries(melonDS PRIVATE "${Slirp_LINK_LIBRARIES}")
|
||||||
elseif (WIN32)
|
|
||||||
|
if (WIN32)
|
||||||
option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON)
|
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")
|
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_sources(melonDS PUBLIC "${CMAKE_BINARY_DIR}/res/melon.rc")
|
||||||
target_include_directories(melonDS PRIVATE "${CMAKE_BINARY_DIR}/res")
|
target_include_directories(melonDS PRIVATE "${CMAKE_BINARY_DIR}/res")
|
||||||
@ -186,10 +191,6 @@ elseif (WIN32)
|
|||||||
set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole")
|
set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (PORTABLE)
|
|
||||||
target_compile_definitions(melonDS PRIVATE PORTABLE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
target_sources(melonDS PRIVATE sem_timedwait.cpp)
|
target_sources(melonDS PRIVATE sem_timedwait.cpp)
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ int GL_ScaleFactor;
|
|||||||
bool GL_BetterPolygons;
|
bool GL_BetterPolygons;
|
||||||
|
|
||||||
bool LimitFPS;
|
bool LimitFPS;
|
||||||
|
int MaxFPS;
|
||||||
bool AudioSync;
|
bool AudioSync;
|
||||||
bool ShowOSD;
|
bool ShowOSD;
|
||||||
|
|
||||||
@ -141,6 +142,7 @@ bool MouseHide;
|
|||||||
int MouseHideSeconds;
|
int MouseHideSeconds;
|
||||||
|
|
||||||
bool PauseLostFocus;
|
bool PauseLostFocus;
|
||||||
|
std::string UITheme;
|
||||||
|
|
||||||
int64_t RTCOffset;
|
int64_t RTCOffset;
|
||||||
|
|
||||||
@ -251,6 +253,7 @@ ConfigEntry ConfigFile[] =
|
|||||||
{"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false},
|
{"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false},
|
||||||
|
|
||||||
{"LimitFPS", 1, &LimitFPS, true, false},
|
{"LimitFPS", 1, &LimitFPS, true, false},
|
||||||
|
{"MaxFPS", 0, &MaxFPS, 1000, false},
|
||||||
{"AudioSync", 1, &AudioSync, false},
|
{"AudioSync", 1, &AudioSync, false},
|
||||||
{"ShowOSD", 1, &ShowOSD, true, false},
|
{"ShowOSD", 1, &ShowOSD, true, false},
|
||||||
|
|
||||||
@ -342,6 +345,7 @@ ConfigEntry ConfigFile[] =
|
|||||||
{"MouseHide", 1, &MouseHide, false, false},
|
{"MouseHide", 1, &MouseHide, false, false},
|
||||||
{"MouseHideSeconds", 0, &MouseHideSeconds, 5, false},
|
{"MouseHideSeconds", 0, &MouseHideSeconds, 5, false},
|
||||||
{"PauseLostFocus", 1, &PauseLostFocus, false, false},
|
{"PauseLostFocus", 1, &PauseLostFocus, false, false},
|
||||||
|
{"UITheme", 2, &UITheme, (std::string)"", false},
|
||||||
|
|
||||||
{"RTCOffset", 3, &RTCOffset, (int64_t)0, true},
|
{"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;
|
Platform::FileHandle* f;
|
||||||
if (inst > 0)
|
if (inst > 0)
|
||||||
@ -382,11 +386,17 @@ void LoadFile(int inst)
|
|||||||
char name[100] = {0};
|
char name[100] = {0};
|
||||||
snprintf(name, 99, kUniqueConfigFile, inst+1);
|
snprintf(name, 99, kUniqueConfigFile, inst+1);
|
||||||
f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText);
|
f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText);
|
||||||
|
|
||||||
|
if (!Platform::CheckLocalFileWritable(name)) return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText);
|
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 linebuf[1024];
|
||||||
char entryname[32];
|
char entryname[32];
|
||||||
@ -421,9 +431,10 @@ void LoadFile(int inst)
|
|||||||
}
|
}
|
||||||
|
|
||||||
CloseFile(f);
|
CloseFile(f);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Load()
|
bool Load()
|
||||||
{
|
{
|
||||||
|
|
||||||
for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
|
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;
|
case 3: *(int64_t*)entry->Value = std::get<int64_t>(entry->Default); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadFile(0);
|
|
||||||
|
|
||||||
int inst = Platform::InstanceID();
|
int inst = Platform::InstanceID();
|
||||||
|
|
||||||
|
bool ret = LoadFile(0, inst);
|
||||||
if (inst > 0)
|
if (inst > 0)
|
||||||
LoadFile(inst);
|
ret = LoadFile(inst, inst);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Save()
|
void Save()
|
||||||
|
@ -105,6 +105,7 @@ extern int GL_ScaleFactor;
|
|||||||
extern bool GL_BetterPolygons;
|
extern bool GL_BetterPolygons;
|
||||||
|
|
||||||
extern bool LimitFPS;
|
extern bool LimitFPS;
|
||||||
|
extern int MaxFPS;
|
||||||
extern bool AudioSync;
|
extern bool AudioSync;
|
||||||
extern bool ShowOSD;
|
extern bool ShowOSD;
|
||||||
|
|
||||||
@ -184,6 +185,7 @@ extern bool EnableCheats;
|
|||||||
extern bool MouseHide;
|
extern bool MouseHide;
|
||||||
extern int MouseHideSeconds;
|
extern int MouseHideSeconds;
|
||||||
extern bool PauseLostFocus;
|
extern bool PauseLostFocus;
|
||||||
|
extern std::string UITheme;
|
||||||
|
|
||||||
extern int64_t RTCOffset;
|
extern int64_t RTCOffset;
|
||||||
|
|
||||||
@ -202,7 +204,7 @@ extern bool GdbARM7BreakOnStartup;
|
|||||||
extern bool GdbARM9BreakOnStartup;
|
extern bool GdbARM9BreakOnStartup;
|
||||||
|
|
||||||
|
|
||||||
void Load();
|
bool Load();
|
||||||
void Save();
|
void Save();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -380,6 +380,12 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked()
|
|||||||
|
|
||||||
if (file.isEmpty()) return;
|
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);
|
updateLastBIOSFolder(file);
|
||||||
|
|
||||||
ui->txtFirmwarePath->setText(file);
|
ui->txtFirmwarePath->setText(file);
|
||||||
@ -436,6 +442,12 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked()
|
|||||||
|
|
||||||
if (file.isEmpty()) return;
|
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);
|
updateLastBIOSFolder(file);
|
||||||
|
|
||||||
ui->txtDLDISDPath->setText(file);
|
ui->txtDLDISDPath->setText(file);
|
||||||
@ -468,6 +480,13 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked()
|
|||||||
|
|
||||||
if (file.isEmpty()) return;
|
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);
|
updateLastBIOSFolder(file);
|
||||||
|
|
||||||
ui->txtDSiFirmwarePath->setText(file);
|
ui->txtDSiFirmwarePath->setText(file);
|
||||||
@ -482,6 +501,13 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked()
|
|||||||
|
|
||||||
if (file.isEmpty()) return;
|
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);
|
updateLastBIOSFolder(file);
|
||||||
|
|
||||||
ui->txtDSiNANDPath->setText(file);
|
ui->txtDSiNANDPath->setText(file);
|
||||||
@ -510,6 +536,12 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked()
|
|||||||
|
|
||||||
if (file.isEmpty()) return;
|
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);
|
updateLastBIOSFolder(file);
|
||||||
|
|
||||||
ui->txtDSiSDPath->setText(file);
|
ui->txtDSiSDPath->setText(file);
|
||||||
|
752
src/frontend/qt_sdl/EmuThread.cpp
Normal file
752
src/frontend/qt_sdl/EmuThread.cpp
Normal 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);
|
||||||
|
}
|
134
src/frontend/qt_sdl/EmuThread.h
Normal file
134
src/frontend/qt_sdl/EmuThread.h
Normal 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
|
@ -16,15 +16,16 @@
|
|||||||
with melonDS. If not, see http://www.gnu.org/licenses/.
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QStyleFactory>
|
||||||
#include "InterfaceSettingsDialog.h"
|
#include "InterfaceSettingsDialog.h"
|
||||||
#include "ui_InterfaceSettingsDialog.h"
|
#include "ui_InterfaceSettingsDialog.h"
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
InterfaceSettingsDialog* InterfaceSettingsDialog::currentDlg = nullptr;
|
InterfaceSettingsDialog* InterfaceSettingsDialog::currentDlg = nullptr;
|
||||||
|
|
||||||
InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::InterfaceSettingsDialog)
|
InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::InterfaceSettingsDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
@ -34,6 +35,19 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare
|
|||||||
ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0);
|
ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0);
|
||||||
ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds);
|
ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds);
|
||||||
ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0);
|
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()
|
InterfaceSettingsDialog::~InterfaceSettingsDialog()
|
||||||
@ -60,9 +74,18 @@ void InterfaceSettingsDialog::done(int r)
|
|||||||
Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0;
|
Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0;
|
||||||
Config::MouseHideSeconds = ui->spinMouseHideSeconds->value();
|
Config::MouseHideSeconds = ui->spinMouseHideSeconds->value();
|
||||||
Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0;
|
Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0;
|
||||||
|
Config::MaxFPS = ui->spinMaxFPS->value();
|
||||||
|
|
||||||
|
QString themeName = ui->cbxUITheme->currentData().toString();
|
||||||
|
Config::UITheme = themeName.toStdString();
|
||||||
|
|
||||||
Config::Save();
|
Config::Save();
|
||||||
|
|
||||||
|
if (!Config::UITheme.empty())
|
||||||
|
qApp->setStyle(themeName);
|
||||||
|
else
|
||||||
|
qApp->setStyle(*systemThemeName);
|
||||||
|
|
||||||
emit updateMouseTimer();
|
emit updateMouseTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>262</width>
|
<width>337</width>
|
||||||
<height>113</height>
|
<height>275</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -19,32 +19,113 @@
|
|||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Interface settings - melonDS</string>
|
<string>Interface settings - melonDS</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
|
||||||
<item row="1" column="0" alignment="Qt::AlignLeft">
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="text">
|
<property name="title">
|
||||||
<string>Hide after</string>
|
<string>User interface</string>
|
||||||
</property>
|
</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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="4">
|
<item>
|
||||||
<widget class="QCheckBox" name="cbPauseLostFocus">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="text">
|
<property name="title">
|
||||||
<string>Pause emulation when window is not in focus</string>
|
<string>Framerate </string>
|
||||||
</property>
|
</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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" colspan="5">
|
<item>
|
||||||
<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">
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -54,20 +135,8 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="2">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>seconds of inactivity</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
|
||||||
<tabstop>cbMouseHide</tabstop>
|
|
||||||
<tabstop>spinMouseHideSeconds</tabstop>
|
|
||||||
<tabstop>cbPauseLostFocus</tabstop>
|
|
||||||
</tabstops>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
<connection>
|
<connection>
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
@ -19,6 +19,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
@ -37,6 +38,7 @@ extern bool RunningSomething;
|
|||||||
|
|
||||||
bool PathSettingsDialog::needsReset = false;
|
bool PathSettingsDialog::needsReset = false;
|
||||||
|
|
||||||
|
constexpr char errordialog[] = "melonDS cannot write to that directory.";
|
||||||
|
|
||||||
PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog)
|
PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog)
|
||||||
{
|
{
|
||||||
@ -101,6 +103,12 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked()
|
|||||||
QString::fromStdString(EmuDirectory));
|
QString::fromStdString(EmuDirectory));
|
||||||
|
|
||||||
if (dir.isEmpty()) return;
|
if (dir.isEmpty()) return;
|
||||||
|
|
||||||
|
if (!QTemporaryFile(dir).open())
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, "melonDS", errordialog);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ui->txtSaveFilePath->setText(dir);
|
ui->txtSaveFilePath->setText(dir);
|
||||||
}
|
}
|
||||||
@ -112,6 +120,12 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked()
|
|||||||
QString::fromStdString(EmuDirectory));
|
QString::fromStdString(EmuDirectory));
|
||||||
|
|
||||||
if (dir.isEmpty()) return;
|
if (dir.isEmpty()) return;
|
||||||
|
|
||||||
|
if (!QTemporaryFile(dir).open())
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, "melonDS", errordialog);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ui->txtSavestatePath->setText(dir);
|
ui->txtSavestatePath->setText(dir);
|
||||||
}
|
}
|
||||||
@ -123,6 +137,12 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked()
|
|||||||
QString::fromStdString(EmuDirectory));
|
QString::fromStdString(EmuDirectory));
|
||||||
|
|
||||||
if (dir.isEmpty()) return;
|
if (dir.isEmpty()) return;
|
||||||
|
|
||||||
|
if (!QTemporaryFile(dir).open())
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, "melonDS", errordialog);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ui->txtCheatFilePath->setText(dir);
|
ui->txtCheatFilePath->setText(dir);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
@ -39,7 +40,6 @@
|
|||||||
#include "LAN_Socket.h"
|
#include "LAN_Socket.h"
|
||||||
#include "LAN_PCap.h"
|
#include "LAN_PCap.h"
|
||||||
#include "LocalMP.h"
|
#include "LocalMP.h"
|
||||||
#include "OSD.h"
|
|
||||||
#include "SPI_Firmware.h"
|
#include "SPI_Firmware.h"
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
@ -53,10 +53,50 @@ extern CameraManager* camManager[2];
|
|||||||
|
|
||||||
void emuStop();
|
void emuStop();
|
||||||
|
|
||||||
|
// TEMP
|
||||||
|
//#include "main.h"
|
||||||
|
//extern MainWindow* mainWindow;
|
||||||
|
|
||||||
|
|
||||||
namespace melonDS::Platform
|
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;
|
QSharedMemory* IPCBuffer = nullptr;
|
||||||
int IPCInstanceID;
|
int IPCInstanceID;
|
||||||
|
|
||||||
@ -130,38 +170,7 @@ void IPCDeInit()
|
|||||||
|
|
||||||
void Init(int argc, char** argv)
|
void Init(int argc, char** argv)
|
||||||
{
|
{
|
||||||
#if defined(__WIN32__) || defined(PORTABLE)
|
PathInit(argc, argv);
|
||||||
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
|
|
||||||
|
|
||||||
IPCInit();
|
IPCInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,14 +186,14 @@ void SignalStop(StopReason reason)
|
|||||||
{
|
{
|
||||||
case StopReason::GBAModeNotSupported:
|
case StopReason::GBAModeNotSupported:
|
||||||
Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n");
|
Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n");
|
||||||
OSD::AddMessage(0xFFA0A0, "GBA mode not supported.");
|
//mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported.");
|
||||||
break;
|
break;
|
||||||
case StopReason::BadExceptionRegion:
|
case StopReason::BadExceptionRegion:
|
||||||
OSD::AddMessage(0xFFA0A0, "Internal error.");
|
//mainWindow->osdAddMessage(0xFFA0A0, "Internal error.");
|
||||||
break;
|
break;
|
||||||
case StopReason::PowerOff:
|
case StopReason::PowerOff:
|
||||||
case StopReason::External:
|
case StopReason::External:
|
||||||
OSD::AddMessage(0xFFC040, "Shutdown");
|
//mainWindow->osdAddMessage(0xFFC040, "Shutdown");
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -208,6 +217,10 @@ std::string InstanceFileSuffix()
|
|||||||
|
|
||||||
constexpr char AccessMode(FileMode mode, bool file_exists)
|
constexpr char AccessMode(FileMode mode, bool file_exists)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (mode & FileMode::Append)
|
||||||
|
return 'a';
|
||||||
|
|
||||||
if (!(mode & FileMode::Write))
|
if (!(mode & FileMode::Write))
|
||||||
// If we're only opening the file for reading...
|
// If we're only opening the file for reading...
|
||||||
return 'r';
|
return 'r';
|
||||||
@ -246,7 +259,7 @@ static std::string GetModeString(FileMode mode, bool file_exists)
|
|||||||
|
|
||||||
FileHandle* OpenFile(const std::string& path, FileMode mode)
|
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
|
{ // 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);
|
Log(LogLevel::Error, "Attempted to open \"%s\" in neither read nor write mode (FileMode 0x%x)\n", path.c_str(), mode);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -281,15 +294,7 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef PORTABLE
|
|
||||||
fullpath = QString::fromStdString(EmuDirectory) + QDir::separator() + qpath;
|
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);
|
return OpenFile(fullpath.toStdString(), mode);
|
||||||
@ -326,6 +331,28 @@ bool LocalFileExists(const std::string& name)
|
|||||||
return true;
|
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)
|
bool FileSeek(FileHandle* file, s64 offset, FileSeekOrigin origin)
|
||||||
{
|
{
|
||||||
int stdorigin;
|
int stdorigin;
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
#include <zstd.h>
|
#include <zstd.h>
|
||||||
#ifdef ARCHIVE_SUPPORT_ENABLED
|
#ifdef ARCHIVE_SUPPORT_ENABLED
|
||||||
@ -210,6 +211,9 @@ QString VerifyDSFirmware()
|
|||||||
f = Platform::OpenLocalFile(Config::FirmwarePath, FileMode::Read);
|
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 (!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);
|
len = FileLength(f);
|
||||||
if (len == 0x20000)
|
if (len == 0x20000)
|
||||||
{
|
{
|
||||||
@ -237,6 +241,9 @@ QString VerifyDSiFirmware()
|
|||||||
f = Platform::OpenLocalFile(Config::DSiFirmwarePath, FileMode::Read);
|
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 (!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);
|
len = FileLength(f);
|
||||||
if (len != 0x20000)
|
if (len != 0x20000)
|
||||||
{
|
{
|
||||||
@ -259,6 +266,9 @@ QString VerifyDSiNAND()
|
|||||||
f = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting);
|
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 (!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
|
// TODO: some basic checks
|
||||||
// check that it has the nocash footer, and all
|
// 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;
|
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
|
std::optional<FATStorageArgs> GetDSiSDCardArgs() noexcept
|
||||||
{
|
{
|
||||||
if (!Config::DSiSDEnable)
|
if (!Config::DSiSDEnable)
|
||||||
@ -804,12 +819,7 @@ std::optional<FATStorage> LoadDLDISDCard() noexcept
|
|||||||
if (!Config::DLDIEnable)
|
if (!Config::DLDIEnable)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
return FATStorage(
|
return FATStorage(*GetDLDISDCardArgs());
|
||||||
Config::DLDISDPath,
|
|
||||||
imgsizes[Config::DLDISize],
|
|
||||||
Config::DLDIReadOnly,
|
|
||||||
Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableCheats(NDS& nds, bool enable)
|
void EnableCheats(NDS& nds, bool enable)
|
||||||
@ -1276,7 +1286,18 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr<u8[]>& filedata, u
|
|||||||
return false;
|
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;
|
unique_ptr<u8[]> filedata = nullptr;
|
||||||
u32 filelen;
|
u32 filelen;
|
||||||
@ -1284,7 +1305,10 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
|
|||||||
std::string romname;
|
std::string romname;
|
||||||
|
|
||||||
if (!LoadROMData(filepath, filedata, filelen, basepath, romname))
|
if (!LoadROMData(filepath, filedata, filelen, basepath, romname))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM.");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
NDSSave = nullptr;
|
NDSSave = nullptr;
|
||||||
|
|
||||||
@ -1300,7 +1324,22 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
|
|||||||
savname += Platform::InstanceFileSuffix();
|
savname += Platform::InstanceFileSuffix();
|
||||||
|
|
||||||
FileHandle* sav = Platform::OpenFile(savname, FileMode::Read);
|
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)
|
if (sav)
|
||||||
{
|
{
|
||||||
savelen = (u32)Platform::FileLength(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));
|
auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs));
|
||||||
if (!cart)
|
if (!cart)
|
||||||
|
{
|
||||||
// If we couldn't parse the ROM...
|
// If we couldn't parse the ROM...
|
||||||
|
QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM.");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (reset)
|
if (reset)
|
||||||
{
|
{
|
||||||
if (!emuthread->UpdateConsole(std::move(cart), Keep {}))
|
if (!emuthread->UpdateConsole(std::move(cart), Keep {}))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM.");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
InitFirmwareSaveManager(emuthread);
|
InitFirmwareSaveManager(emuthread);
|
||||||
emuthread->NDS->Reset();
|
emuthread->NDS->Reset();
|
||||||
@ -1351,7 +1396,7 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
|
|||||||
NDSSave = std::make_unique<SaveManager>(savname);
|
NDSSave = std::make_unique<SaveManager>(savname);
|
||||||
LoadCheats(*emuthread->NDS);
|
LoadCheats(*emuthread->NDS);
|
||||||
|
|
||||||
return true;
|
return true; // success
|
||||||
}
|
}
|
||||||
|
|
||||||
void EjectCart(NDS& nds)
|
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;
|
unique_ptr<u8[]> filedata = nullptr;
|
||||||
u32 filelen;
|
u32 filelen;
|
||||||
@ -1398,7 +1447,10 @@ bool LoadGBAROM(NDS& nds, QStringList filepath)
|
|||||||
std::string romname;
|
std::string romname;
|
||||||
|
|
||||||
if (!LoadROMData(filepath, filedata, filelen, basepath, romname))
|
if (!LoadROMData(filepath, filedata, filelen, basepath, romname))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM.");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GBASave = nullptr;
|
GBASave = nullptr;
|
||||||
|
|
||||||
@ -1414,7 +1466,22 @@ bool LoadGBAROM(NDS& nds, QStringList filepath)
|
|||||||
savname += Platform::InstanceFileSuffix();
|
savname += Platform::InstanceFileSuffix();
|
||||||
|
|
||||||
FileHandle* sav = Platform::OpenFile(savname, FileMode::Read);
|
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)
|
if (sav)
|
||||||
{
|
{
|
||||||
savelen = (u32)FileLength(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);
|
auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen);
|
||||||
if (!cart)
|
if (!cart)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM.");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
nds.SetGBACart(std::move(cart));
|
nds.SetGBACart(std::move(cart));
|
||||||
GBACartType = 0;
|
GBACartType = 0;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "SaveManager.h"
|
#include "SaveManager.h"
|
||||||
#include "AREngine.h"
|
#include "AREngine.h"
|
||||||
#include "DSi_NAND.h"
|
#include "DSi_NAND.h"
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
#include "MemConstants.h"
|
#include "MemConstants.h"
|
||||||
#include <optional>
|
#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;
|
std::optional<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& arm7ibios) noexcept;
|
||||||
|
|
||||||
/// Inserts a ROM into the emulated console.
|
/// 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);
|
void EjectCart(NDS& nds);
|
||||||
bool CartInserted();
|
bool CartInserted();
|
||||||
QString CartLabel();
|
QString CartLabel();
|
||||||
|
|
||||||
bool LoadGBAROM(NDS& nds, QStringList filepath);
|
bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath);
|
||||||
void LoadGBAAddon(NDS& nds, int type);
|
void LoadGBAAddon(NDS& nds, int type);
|
||||||
void EjectGBACart(NDS& nds);
|
void EjectGBACart(NDS& nds);
|
||||||
bool GBACartInserted();
|
bool GBACartInserted();
|
||||||
|
@ -35,30 +35,27 @@
|
|||||||
#include <qpa/qplatformnativeinterface.h>
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "OpenGLSupport.h"
|
||||||
|
#include "duckstation/gl/context.h"
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
#include "NDS.h"
|
#include "NDS.h"
|
||||||
|
#include "GPU.h"
|
||||||
|
#include "GPU3D_Soft.h"
|
||||||
|
#include "GPU3D_OpenGL.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
//#include "main_shaders.h"
|
#include "main_shaders.h"
|
||||||
|
#include "OSD_shaders.h"
|
||||||
#include "OSD.h"
|
#include "font.h"
|
||||||
|
|
||||||
using namespace melonDS;
|
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
|
// TEMP
|
||||||
extern MainWindow* mainWindow;
|
extern MainWindow* mainWindow;
|
||||||
extern EmuThread* emuThread;
|
extern EmuThread* emuThread;
|
||||||
@ -68,23 +65,31 @@ extern int autoScreenSizing;
|
|||||||
extern int videoRenderer;
|
extern int videoRenderer;
|
||||||
extern bool videoSettingsDirty;
|
extern bool videoSettingsDirty;
|
||||||
|
|
||||||
|
const u32 kOSDMargin = 6;
|
||||||
|
|
||||||
ScreenHandler::ScreenHandler(QWidget* widget)
|
|
||||||
|
ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
widget->setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
widget->setAttribute(Qt::WA_AcceptTouchEvents);
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||||
QTimer* mouseTimer = setupMouseTimer();
|
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();
|
mouseTimer->stop();
|
||||||
delete mouseTimer;
|
delete mouseTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenHandler::screenSetupLayout(int w, int h)
|
void ScreenPanel::setupScreenLayout()
|
||||||
{
|
{
|
||||||
|
int w = width();
|
||||||
|
int h = height();
|
||||||
|
|
||||||
int sizing = Config::ScreenSizing;
|
int sizing = Config::ScreenSizing;
|
||||||
if (sizing == 3) sizing = autoScreenSizing;
|
if (sizing == 3) sizing = autoScreenSizing;
|
||||||
|
|
||||||
@ -117,7 +122,7 @@ void ScreenHandler::screenSetupLayout(int w, int h)
|
|||||||
numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind);
|
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
|
bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg
|
||||||
|| Config::ScreenRotation == Frontend::screenRot_270Deg);
|
|| 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();
|
event->accept();
|
||||||
if (event->button() != Qt::LeftButton) return;
|
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();
|
event->accept();
|
||||||
if (event->button() != Qt::LeftButton) return;
|
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();
|
event->accept();
|
||||||
|
|
||||||
@ -210,7 +227,7 @@ void ScreenHandler::screenOnMouseMove(QMouseEvent* event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenHandler::screenHandleTablet(QTabletEvent* event)
|
void ScreenPanel::tabletEvent(QTabletEvent* event)
|
||||||
{
|
{
|
||||||
event->accept();
|
event->accept();
|
||||||
|
|
||||||
@ -243,7 +260,7 @@ void ScreenHandler::screenHandleTablet(QTabletEvent* event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenHandler::screenHandleTouch(QTouchEvent* event)
|
void ScreenPanel::touchEvent(QTouchEvent* event)
|
||||||
{
|
{
|
||||||
event->accept();
|
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();
|
mouseTimer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
QTimer* ScreenHandler::setupMouseTimer()
|
QTimer* ScreenPanel::setupMouseTimer()
|
||||||
{
|
{
|
||||||
mouseTimer = new QTimer();
|
mouseTimer = new QTimer();
|
||||||
mouseTimer->setSingleShot(true);
|
mouseTimer->setSingleShot(true);
|
||||||
@ -294,35 +324,290 @@ QTimer* ScreenHandler::setupMouseTimer()
|
|||||||
return mouseTimer;
|
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[0] = QImage(256, 192, QImage::Format_RGB32);
|
||||||
screen[1] = QImage(256, 192, QImage::Format_RGB32);
|
screen[1] = QImage(256, 192, QImage::Format_RGB32);
|
||||||
|
|
||||||
screenTrans[0].reset();
|
screenTrans[0].reset();
|
||||||
screenTrans[1].reset();
|
screenTrans[1].reset();
|
||||||
|
|
||||||
OSD::Init(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenPanelNative::~ScreenPanelNative()
|
ScreenPanelNative::~ScreenPanelNative()
|
||||||
{
|
{
|
||||||
OSD::DeInit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenPanelNative::setupScreenLayout()
|
void ScreenPanelNative::setupScreenLayout()
|
||||||
{
|
{
|
||||||
int w = width();
|
ScreenPanel::setupScreenLayout();
|
||||||
int h = height();
|
|
||||||
|
|
||||||
screenSetupLayout(w, h);
|
|
||||||
|
|
||||||
for (int i = 0; i < numScreens; i++)
|
for (int i = 0; i < numScreens; i++)
|
||||||
{
|
{
|
||||||
float* mtx = screenMatrix[i];
|
float* mtx = screenMatrix[i];
|
||||||
screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f,
|
screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f,
|
||||||
mtx[2], mtx[3], 0.f,
|
mtx[2], mtx[3], 0.f,
|
||||||
mtx[4], mtx[5], 1.f);
|
mtx[4], mtx[5], 1.f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,55 +642,32 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OSD::Update();
|
osdUpdate();
|
||||||
OSD::DrawNative(painter);
|
if (osdEnabled)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
screenHandleTouch((QTouchEvent*)event);
|
osdMutex.lock();
|
||||||
return true;
|
|
||||||
|
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);
|
setAutoFillBackground(false);
|
||||||
setAttribute(Qt::WA_NativeWindow, true);
|
setAttribute(Qt::WA_NativeWindow, true);
|
||||||
@ -434,6 +696,284 @@ bool ScreenPanelGL::createContext()
|
|||||||
return glContext != nullptr;
|
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
|
qreal ScreenPanelGL::devicePixelRatioFromScreen() const
|
||||||
{
|
{
|
||||||
const QScreen* screen_for_ratio = window()->windowHandle()->screen();
|
const QScreen* screen_for_ratio = window()->windowHandle()->screen();
|
||||||
@ -505,62 +1045,28 @@ QPaintEngine* ScreenPanelGL::paintEngine() const
|
|||||||
|
|
||||||
void ScreenPanelGL::setupScreenLayout()
|
void ScreenPanelGL::setupScreenLayout()
|
||||||
{
|
{
|
||||||
int w = width();
|
ScreenPanel::setupScreenLayout();
|
||||||
int h = height();
|
transferLayout();
|
||||||
|
|
||||||
screenSetupLayout(w, h);
|
|
||||||
if (emuThread)
|
|
||||||
transferLayout(emuThread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenPanelGL::resizeEvent(QResizeEvent* event)
|
void ScreenPanelGL::transferLayout()
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
std::optional<WindowInfo> windowInfo = getWindowInfo();
|
std::optional<WindowInfo> windowInfo = getWindowInfo();
|
||||||
if (windowInfo.has_value())
|
if (windowInfo.has_value())
|
||||||
thread->updateScreenSettings(Config::ScreenFilter, *windowInfo, numScreens, screenKind, &screenMatrix[0][0]);
|
{
|
||||||
}
|
screenSettingsLock.lock();
|
||||||
|
|
||||||
void ScreenPanelGL::onScreenLayoutChanged()
|
if (lastScreenWidth != windowInfo->surface_width || lastScreenHeight != windowInfo->surface_height)
|
||||||
{
|
{
|
||||||
setMinimumSize(screenGetMinSize());
|
if (glContext)
|
||||||
setupScreenLayout();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,19 +19,20 @@
|
|||||||
#ifndef SCREEN_H
|
#ifndef SCREEN_H
|
||||||
#define SCREEN_H
|
#define SCREEN_H
|
||||||
|
|
||||||
#include "glad/glad.h"
|
#include <optional>
|
||||||
#include "FrontendUtil.h"
|
#include <deque>
|
||||||
#include "duckstation/gl/context.h"
|
#include <map>
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QWindow>
|
|
||||||
#include <QMainWindow>
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QActionGroup>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "glad/glad.h"
|
||||||
|
#include "FrontendUtil.h"
|
||||||
|
#include "duckstation/gl/context.h"
|
||||||
|
|
||||||
|
|
||||||
class EmuThread;
|
class EmuThread;
|
||||||
@ -48,27 +49,54 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] =
|
|||||||
constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]);
|
constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]);
|
||||||
|
|
||||||
|
|
||||||
class ScreenHandler
|
class ScreenPanel : public QWidget
|
||||||
{
|
{
|
||||||
Q_GADGET
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScreenHandler(QWidget* widget);
|
explicit ScreenPanel(QWidget* parent);
|
||||||
virtual ~ScreenHandler();
|
virtual ~ScreenPanel();
|
||||||
|
|
||||||
QTimer* setupMouseTimer();
|
QTimer* setupMouseTimer();
|
||||||
void updateMouseTimer();
|
void updateMouseTimer();
|
||||||
QTimer* mouseTimer;
|
QTimer* mouseTimer;
|
||||||
QSize screenGetMinSize(int factor);
|
QSize screenGetMinSize(int factor);
|
||||||
|
|
||||||
|
void osdSetEnabled(bool enabled);
|
||||||
|
void osdAddMessage(unsigned int color, const char* msg);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onScreenLayoutChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void screenSetupLayout(int w, int h);
|
struct OSDItem
|
||||||
|
{
|
||||||
|
unsigned int id;
|
||||||
|
qint64 timestamp;
|
||||||
|
|
||||||
void screenOnMousePress(QMouseEvent* event);
|
char text[256];
|
||||||
void screenOnMouseRelease(QMouseEvent* event);
|
unsigned int color;
|
||||||
void screenOnMouseMove(QMouseEvent* event);
|
|
||||||
|
|
||||||
void screenHandleTablet(QTabletEvent* event);
|
bool rendered;
|
||||||
void screenHandleTouch(QTouchEvent* event);
|
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];
|
float screenMatrix[Frontend::MaxScreenTransforms][6];
|
||||||
int screenKind[Frontend::MaxScreenTransforms];
|
int screenKind[Frontend::MaxScreenTransforms];
|
||||||
@ -77,10 +105,19 @@ protected:
|
|||||||
bool touching = false;
|
bool touching = false;
|
||||||
|
|
||||||
void showCursor();
|
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
|
Q_OBJECT
|
||||||
|
|
||||||
@ -91,26 +128,15 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent* event) override;
|
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:
|
private:
|
||||||
void setupScreenLayout();
|
void setupScreenLayout() override;
|
||||||
|
|
||||||
QImage screen[2];
|
QImage screen[2];
|
||||||
QTransform screenTrans[Frontend::MaxScreenTransforms];
|
QTransform screenTrans[Frontend::MaxScreenTransforms];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ScreenPanelGL : public QWidget, public ScreenHandler
|
class ScreenPanelGL : public ScreenPanel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -122,9 +148,15 @@ public:
|
|||||||
|
|
||||||
bool createContext();
|
bool createContext();
|
||||||
|
|
||||||
|
void setSwapInterval(int intv);
|
||||||
|
|
||||||
|
void initOpenGL();
|
||||||
|
void deinitOpenGL();
|
||||||
|
void drawScreenGL();
|
||||||
|
|
||||||
GL::Context* getContext() { return glContext.get(); }
|
GL::Context* getContext() { return glContext.get(); }
|
||||||
|
|
||||||
void transferLayout(EmuThread* thread);
|
void transferLayout();
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
qreal devicePixelRatioFromScreen() const;
|
qreal devicePixelRatioFromScreen() const;
|
||||||
@ -133,22 +165,31 @@ protected:
|
|||||||
|
|
||||||
QPaintEngine* paintEngine() const override;
|
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:
|
private:
|
||||||
void setupScreenLayout();
|
void setupScreenLayout() override;
|
||||||
|
|
||||||
std::unique_ptr<GL::Context> glContext;
|
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
|
#endif // SCREEN_H
|
||||||
|
@ -114,7 +114,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
|
|||||||
|
|
||||||
u32 icondata[32*32];
|
u32 icondata[32*32];
|
||||||
ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata);
|
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()));
|
QIcon icon(QPixmap::fromImage(iconimg.copy()));
|
||||||
|
|
||||||
// TODO: make it possible to select other languages?
|
// TODO: make it possible to select other languages?
|
||||||
|
@ -81,15 +81,12 @@
|
|||||||
#include "ArchiveUtil.h"
|
#include "ArchiveUtil.h"
|
||||||
#include "CameraManager.h"
|
#include "CameraManager.h"
|
||||||
|
|
||||||
#include "OSD.h"
|
|
||||||
|
|
||||||
using namespace melonDS;
|
using namespace melonDS;
|
||||||
|
|
||||||
// TEMP
|
// TEMP
|
||||||
extern MainWindow* mainWindow;
|
extern MainWindow* mainWindow;
|
||||||
extern EmuThread* emuThread;
|
extern EmuThread* emuThread;
|
||||||
extern bool RunningSomething;
|
extern bool RunningSomething;
|
||||||
extern int autoScreenSizing;
|
|
||||||
extern QString NdsRomMimeType;
|
extern QString NdsRomMimeType;
|
||||||
extern QStringList NdsRomExtensions;
|
extern QStringList NdsRomExtensions;
|
||||||
extern QString GbaRomMimeType;
|
extern QString GbaRomMimeType;
|
||||||
@ -690,13 +687,13 @@ void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...)
|
|||||||
if (fmt == nullptr)
|
if (fmt == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char msg[1024];
|
char msg[256];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(msg, 1024, fmt, args);
|
vsnprintf(msg, 256, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
OSD::AddMessage(color, msg);
|
panel->osdAddMessage(color, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent* event)
|
void MainWindow::closeEvent(QCloseEvent* event)
|
||||||
@ -721,7 +718,6 @@ void MainWindow::createScreenPanel()
|
|||||||
panelGL->show();
|
panelGL->show();
|
||||||
|
|
||||||
panel = panelGL;
|
panel = panelGL;
|
||||||
panelWidget = panelGL;
|
|
||||||
|
|
||||||
panelGL->createContext();
|
panelGL->createContext();
|
||||||
}
|
}
|
||||||
@ -730,14 +726,14 @@ void MainWindow::createScreenPanel()
|
|||||||
{
|
{
|
||||||
ScreenPanelNative* panelNative = new ScreenPanelNative(this);
|
ScreenPanelNative* panelNative = new ScreenPanelNative(this);
|
||||||
panel = panelNative;
|
panel = panelNative;
|
||||||
panelWidget = panelNative;
|
panel->show();
|
||||||
panelWidget->show();
|
|
||||||
}
|
}
|
||||||
setCentralWidget(panelWidget);
|
setCentralWidget(panel);
|
||||||
|
|
||||||
actScreenFiltering->setEnabled(hasOGL);
|
actScreenFiltering->setEnabled(hasOGL);
|
||||||
|
panel->osdSetEnabled(Config::ShowOSD);
|
||||||
|
|
||||||
connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged()));
|
connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged()));
|
||||||
emit screenLayoutChange();
|
emit screenLayoutChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,6 +745,30 @@ GL::Context* MainWindow::getOGLContext()
|
|||||||
return glpanel->getContext();
|
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)
|
void MainWindow::resizeEvent(QResizeEvent* event)
|
||||||
{
|
{
|
||||||
int w = event->size().width();
|
int w = event->size().width();
|
||||||
@ -845,10 +865,8 @@ void MainWindow::dropEvent(QDropEvent* event)
|
|||||||
|
|
||||||
if (isNdsRom)
|
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();
|
emuThread->emuUnpause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -866,10 +884,8 @@ void MainWindow::dropEvent(QDropEvent* event)
|
|||||||
}
|
}
|
||||||
else if (isGbaRom)
|
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();
|
emuThread->emuUnpause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -932,12 +948,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot)
|
|||||||
bool gbaloaded = false;
|
bool gbaloaded = false;
|
||||||
if (!gbafile.isEmpty())
|
if (!gbafile.isEmpty())
|
||||||
{
|
{
|
||||||
if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile))
|
if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, gbafile)) return false;
|
||||||
{
|
|
||||||
// TODO: better error reporting?
|
|
||||||
QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
gbaloaded = true;
|
gbaloaded = true;
|
||||||
}
|
}
|
||||||
@ -945,12 +956,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot)
|
|||||||
bool ndsloaded = false;
|
bool ndsloaded = false;
|
||||||
if (!file.isEmpty())
|
if (!file.isEmpty())
|
||||||
{
|
{
|
||||||
if (!ROMManager::LoadROM(emuThread, file, true))
|
if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) return false;
|
||||||
{
|
|
||||||
// TODO: better error reporting?
|
|
||||||
QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
recentFileList.removeAll(file.join("|"));
|
recentFileList.removeAll(file.join("|"));
|
||||||
recentFileList.prepend(file.join("|"));
|
recentFileList.prepend(file.join("|"));
|
||||||
updateRecentFilesMenu();
|
updateRecentFilesMenu();
|
||||||
@ -1153,11 +1160,9 @@ void MainWindow::onOpenFile()
|
|||||||
emuThread->emuUnpause();
|
emuThread->emuUnpause();
|
||||||
return;
|
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();
|
emuThread->emuUnpause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1252,11 +1257,9 @@ void MainWindow::onClickRecentFile()
|
|||||||
emuThread->emuUnpause();
|
emuThread->emuUnpause();
|
||||||
return;
|
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();
|
emuThread->emuUnpause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1306,10 +1309,8 @@ void MainWindow::onInsertCart()
|
|||||||
return;
|
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();
|
emuThread->emuUnpause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1341,10 +1342,8 @@ void MainWindow::onInsertGBACart()
|
|||||||
return;
|
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();
|
emuThread->emuUnpause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1843,7 +1842,7 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked)
|
|||||||
void MainWindow::onChangeScreenSize()
|
void MainWindow::onChangeScreenSize()
|
||||||
{
|
{
|
||||||
int factor = ((QAction*)sender())->data().toInt();
|
int factor = ((QAction*)sender())->data().toInt();
|
||||||
QSize diff = size() - panelWidget->size();
|
QSize diff = size() - panel->size();
|
||||||
resize(panel->screenGetMinSize(factor) + diff);
|
resize(panel->screenGetMinSize(factor) + diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1936,7 +1935,9 @@ void MainWindow::onChangeScreenFiltering(bool checked)
|
|||||||
void MainWindow::onChangeShowOSD(bool checked)
|
void MainWindow::onChangeShowOSD(bool checked)
|
||||||
{
|
{
|
||||||
Config::ShowOSD = checked?1:0;
|
Config::ShowOSD = checked?1:0;
|
||||||
|
panel->osdSetEnabled(Config::ShowOSD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onChangeLimitFramerate(bool checked)
|
void MainWindow::onChangeLimitFramerate(bool checked)
|
||||||
{
|
{
|
||||||
Config::LimitFPS = checked?1:0;
|
Config::LimitFPS = checked?1:0;
|
||||||
@ -2042,7 +2043,7 @@ void MainWindow::onUpdateVideoSettings(bool glchange)
|
|||||||
|
|
||||||
delete panel;
|
delete panel;
|
||||||
createScreenPanel();
|
createScreenPanel();
|
||||||
connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint()));
|
connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint()));
|
||||||
}
|
}
|
||||||
|
|
||||||
videoSettingsDirty = true;
|
videoSettingsDirty = true;
|
||||||
|
@ -92,8 +92,7 @@ private:
|
|||||||
bool oldMax;
|
bool oldMax;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScreenHandler* panel;
|
ScreenPanel* panel;
|
||||||
QWidget* panelWidget;
|
|
||||||
};*/
|
};*/
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
@ -106,6 +105,9 @@ public:
|
|||||||
|
|
||||||
bool hasOGL;
|
bool hasOGL;
|
||||||
GL::Context* getOGLContext();
|
GL::Context* getOGLContext();
|
||||||
|
/*void initOpenGL();
|
||||||
|
void deinitOpenGL();
|
||||||
|
void drawScreenGL();*/
|
||||||
|
|
||||||
bool preloadROMs(QStringList file, QStringList gbafile, bool boot);
|
bool preloadROMs(QStringList file, QStringList gbafile, bool boot);
|
||||||
QStringList splitArchivePath(const QString& filename, bool useMemberSyntax);
|
QStringList splitArchivePath(const QString& filename, bool useMemberSyntax);
|
||||||
@ -227,8 +229,7 @@ private:
|
|||||||
bool oldMax;
|
bool oldMax;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScreenHandler* panel;
|
ScreenPanel* panel;
|
||||||
QWidget* panelWidget;
|
|
||||||
|
|
||||||
QAction* actOpenROM;
|
QAction* actOpenROM;
|
||||||
QAction* actBootFirmware;
|
QAction* actBootFirmware;
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include "FrontendUtil.h"
|
#include "FrontendUtil.h"
|
||||||
#include "OSD.h"
|
|
||||||
|
|
||||||
#include "Args.h"
|
#include "Args.h"
|
||||||
#include "NDS.h"
|
#include "NDS.h"
|
||||||
@ -99,7 +98,7 @@
|
|||||||
|
|
||||||
#include "Savestate.h"
|
#include "Savestate.h"
|
||||||
|
|
||||||
#include "main_shaders.h"
|
//#include "main_shaders.h"
|
||||||
|
|
||||||
#include "ROMManager.h"
|
#include "ROMManager.h"
|
||||||
#include "ArchiveUtil.h"
|
#include "ArchiveUtil.h"
|
||||||
@ -115,6 +114,7 @@ QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" };
|
|||||||
QString GbaRomMimeType = "application/x-gba-rom";
|
QString GbaRomMimeType = "application/x-gba-rom";
|
||||||
QStringList GbaRomExtensions { ".gba", ".agb" };
|
QStringList GbaRomExtensions { ".gba", ".agb" };
|
||||||
|
|
||||||
|
QString* systemThemeName;
|
||||||
|
|
||||||
// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09).
|
// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09).
|
||||||
QStringList ArchiveMimeTypes
|
QStringList ArchiveMimeTypes
|
||||||
@ -175,861 +175,6 @@ bool camStarted[2];
|
|||||||
//extern int AspectRatiosNum;
|
//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");
|
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 " MELONDS_VERSION "\n");
|
||||||
printf(MELONDS_URL "\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")))
|
if (argc != 0 && (!strcasecmp(argv[0], "derpDS") || !strcasecmp(argv[0], "./derpDS")))
|
||||||
printf("did you just call me a derp???\n");
|
printf("did you just call me a derp???\n");
|
||||||
|
|
||||||
Platform::Init(argc, argv);
|
|
||||||
|
|
||||||
MelonApplication melon(argc, argv);
|
MelonApplication melon(argc, argv);
|
||||||
|
|
||||||
|
Platform::Init(argc, argv);
|
||||||
|
|
||||||
CLI::CommandLineOptions* options = CLI::ManageArgs(melon);
|
CLI::CommandLineOptions* options = CLI::ManageArgs(melon);
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl
|
// 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_InitSubSystem(SDL_INIT_VIDEO);
|
||||||
SDL_EnableScreenSaver(); SDL_DisableScreenSaver();
|
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); }
|
#define SANITIZE(var, min, max) { var = std::clamp(var, min, max); }
|
||||||
SANITIZE(Config::ConsoleType, 0, 1);
|
SANITIZE(Config::ConsoleType, 0, 1);
|
||||||
@ -1216,6 +366,12 @@ int main(int argc, char** argv)
|
|||||||
camManager[0]->setXFlip(Config::Camera[0].XFlip);
|
camManager[0]->setXFlip(Config::Camera[0].XFlip);
|
||||||
camManager[1]->setXFlip(Config::Camera[1].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::JoystickID = Config::JoystickID;
|
||||||
Input::OpenJoystick();
|
Input::OpenJoystick();
|
||||||
|
@ -22,140 +22,18 @@
|
|||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QThread>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QMutex>
|
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <variant>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
#include "EmuThread.h"
|
||||||
#include "FrontendUtil.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
|
class MelonApplication : public QApplication
|
||||||
{
|
{
|
||||||
@ -166,4 +44,6 @@ public:
|
|||||||
bool event(QEvent* event) override;
|
bool event(QEvent* event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern QString* systemThemeName;
|
||||||
|
|
||||||
#endif // MAIN_H
|
#endif // MAIN_H
|
||||||
|
@ -27,6 +27,9 @@ A million repetitions of "a"
|
|||||||
#if defined(__sun)
|
#if defined(__sun)
|
||||||
#include "solarisfixes.h"
|
#include "solarisfixes.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(__HAIKU__)
|
||||||
|
#include <ByteOrder.h>
|
||||||
|
#endif
|
||||||
#include "sha1.h"
|
#include "sha1.h"
|
||||||
|
|
||||||
#ifndef BYTE_ORDER
|
#ifndef BYTE_ORDER
|
||||||
|
@ -9,6 +9,11 @@
|
|||||||
"default-features": false,
|
"default-features": false,
|
||||||
"features": ["gui", "png", "thread", "widgets", "opengl", "zstd"]
|
"features": ["gui", "png", "thread", "widgets", "opengl", "zstd"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "qtbase",
|
||||||
|
"host": true,
|
||||||
|
"default-features": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "qtmultimedia",
|
"name": "qtmultimedia",
|
||||||
"default-features": false
|
"default-features": false
|
||||||
|
Loading…
Reference in New Issue
Block a user