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