Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
943359e328 | ||
|
|
92c54cc1f8 | ||
|
|
6ec0b44487 | ||
|
|
85d2ca626f | ||
|
|
68865a2bca | ||
|
|
8362e6f435 | ||
|
|
3804a58729 | ||
|
|
fbb67a6ee9 | ||
|
|
19d8b59ca7 | ||
|
|
7ca726492f | ||
|
|
96a43ca254 | ||
|
|
bf34eb32ed | ||
|
|
433612cde0 | ||
|
|
807db91140 | ||
|
|
7791809224 | ||
|
|
9b3d31cd47 | ||
|
|
65a3a97ab2 | ||
|
|
e3593574b0 | ||
|
|
f81d391546 | ||
|
|
bd3b00793a | ||
|
|
757a72fb57 | ||
|
|
fbbec480af | ||
|
|
ba3b43c66c | ||
|
|
e040b83b2c | ||
|
|
76f4a5b2fe | ||
|
|
4f4b9956b3 | ||
|
|
ba37a0f3a9 | ||
|
|
d4acb95d7c | ||
|
|
2887baefb8 | ||
|
|
9678bf1e2f | ||
|
|
e2fc68ff2d | ||
|
|
4a7b641bc3 | ||
|
|
719b83bf42 | ||
|
|
f201a2e5dd | ||
|
|
fd6f47f5ca | ||
|
|
8524fbbaf9 | ||
|
|
a371c5b7d0 | ||
|
|
ef7ce4dfc8 | ||
|
|
189f6fa2a3 | ||
|
|
472f4922bd | ||
|
|
8ba2f342df | ||
|
|
5d6b9d25bd | ||
|
|
1231499aaa | ||
|
|
9c4788432d | ||
|
|
4ded2caa0b | ||
|
|
494afb9d43 | ||
|
|
c994e4828d | ||
|
|
e50f12a925 | ||
|
|
df75d70df4 | ||
|
|
c5aa04d4eb | ||
|
|
81ae1ce362 | ||
|
|
e1921e660d | ||
|
|
32aa24f6fd | ||
|
|
7fbc63b8e8 | ||
|
|
443adcdd5b | ||
|
|
05b064d513 | ||
|
|
b327033333 | ||
|
|
36c9b68b6c | ||
|
|
7d8d16091e | ||
|
|
c1c6bde429 | ||
|
|
8f0901e7a5 | ||
|
|
6edba3820b | ||
|
|
db1e1bcc1f | ||
|
|
51aeaeb508 | ||
|
|
dcd0a1f002 | ||
|
|
819b61937f | ||
|
|
9da7628083 | ||
|
|
c5f47a8db3 | ||
|
|
c2786b91ce | ||
|
|
4290c16997 | ||
|
|
475e90b654 | ||
|
|
ebb291e06d | ||
|
|
a2c9069dda | ||
|
|
48cfe9ca73 | ||
|
|
b53e9856b8 | ||
|
|
20420da326 |
@@ -0,0 +1,28 @@
|
||||
name: 🌎 Update Base Translation
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Every day at 12am UTC.
|
||||
workflow_dispatch: # As well as manually.
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: github.repository == 'PCSX2/pcsx2'
|
||||
name: "Update Base Translation"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Update Base Translation
|
||||
run: ./.github/workflows/scripts/common/update_base_translation.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
title: "Qt: Update Base Translation"
|
||||
commit-message: "Qt: Update Base Translation"
|
||||
committer: "PCSX2 Bot <PCSX2Bot@users.noreply.github.com>"
|
||||
author: "PCSX2 Bot <PCSX2Bot@users.noreply.github.com>"
|
||||
body: "Daily update of base translation sources."
|
||||
branch: update-base-translation
|
||||
delete-branch: true
|
||||
@@ -28,10 +28,6 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
detail:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
patches_url:
|
||||
required: false
|
||||
type: string
|
||||
@@ -61,7 +57,6 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
OS: linux
|
||||
GUI_FRAMEWORK: QT
|
||||
BUILD_SYSTEM: flatpak
|
||||
ARCH: ${{ inputs.platform }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
@@ -98,7 +93,7 @@ jobs:
|
||||
branch: ${{ inputs.branch }}
|
||||
cache: true
|
||||
restore-cache: true
|
||||
cache-key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} ${{ inputs.detail }} flatpak ${{ hashFiles('.github/workflows/scripts/linux/flatpak/**/*.json') }}
|
||||
cache-key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} flatpak ${{ hashFiles('.github/workflows/scripts/linux/flatpak/**/*.json') }}
|
||||
|
||||
- name: Commit screenshots to OSTree
|
||||
run: |
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/linux_build_qt.yml
|
||||
with:
|
||||
jobName: "Qt"
|
||||
jobName: "AppImage Build"
|
||||
compiler: clang
|
||||
cmakeflags: ""
|
||||
buildAppImage: true
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/linux_build_flatpak.yml
|
||||
with:
|
||||
jobName: "Qt"
|
||||
jobName: "Flatpak Build"
|
||||
compiler: clang
|
||||
cmakeflags: ""
|
||||
publish: false
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
os:
|
||||
required: false
|
||||
type: string
|
||||
default: ubuntu-20.04
|
||||
default: ubuntu-22.04
|
||||
platform:
|
||||
required: false
|
||||
type: string
|
||||
@@ -57,7 +57,6 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
OS: linux
|
||||
GUI_FRAMEWORK: QT
|
||||
ARCH: ${{ inputs.platform }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
@@ -87,7 +86,7 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/deps
|
||||
key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.gui }} deps ${{ hashFiles('.github/workflows/scripts/linux/build-dependencies-qt.sh') }}
|
||||
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/linux/build-dependencies-qt.sh') }}
|
||||
|
||||
- name: Build Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
@@ -120,9 +119,9 @@ jobs:
|
||||
env:
|
||||
NAME: ${{ steps.artifact-metadata.outputs.artifact-name }}
|
||||
run: |
|
||||
.github/workflows/scripts/linux/appimage-qt.sh "$(realpath .)" "$(realpath ./build)" "$HOME/deps" "$NAME"
|
||||
mkdir -p "$GITHUB_WORKSPACE"/ci-artifacts/
|
||||
mv "${NAME}.AppImage" "$GITHUB_WORKSPACE"/ci-artifacts/
|
||||
.github/workflows/scripts/linux/appimage-qt.sh "$(realpath .)" "$(realpath ./build)" "$HOME/deps" "$NAME"
|
||||
mkdir -p "$GITHUB_WORKSPACE"/ci-artifacts/
|
||||
mv "${NAME}.AppImage" "$GITHUB_WORKSPACE"/ci-artifacts/
|
||||
|
||||
- name: Upload artifact
|
||||
if: inputs.buildAppImage == true
|
||||
|
||||
@@ -10,13 +10,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: macos-13
|
||||
platform:
|
||||
required: false
|
||||
type: string
|
||||
default: x64
|
||||
gui:
|
||||
required: true
|
||||
type: string
|
||||
patches_url:
|
||||
required: false
|
||||
type: string
|
||||
@@ -50,8 +43,6 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
OS: macos
|
||||
GUI_FRAMEWORK: ${{ inputs.gui }}
|
||||
ARCH: ${{ inputs.platform }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
@@ -60,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Install Packages
|
||||
env:
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
PLATFORM: "x64"
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
HOMEBREW_NO_ANALYTICS: 1
|
||||
run: |
|
||||
@@ -77,12 +68,10 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/deps
|
||||
key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.gui }} deps ${{ hashFiles('.github/workflows/scripts/macos/build-dependencies.sh') }}
|
||||
key: ${{ inputs.os }} deps ${{ hashFiles('.github/workflows/scripts/macos/build-dependencies.sh') }}
|
||||
|
||||
- name: Build Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
env:
|
||||
GUI: ${{ inputs.gui }}
|
||||
run: .github/workflows/scripts/macos/build-dependencies.sh
|
||||
|
||||
- name: Download patches
|
||||
@@ -99,8 +88,8 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.gui }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
|
||||
restore-keys: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.gui }} ccache
|
||||
key: ${{ inputs.os }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
|
||||
restore-keys: ${{ inputs.os }} ccache
|
||||
|
||||
- name: Generate CMake Files
|
||||
run: |
|
||||
@@ -133,7 +122,6 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run Tests
|
||||
if: inputs.gui == 'Qt'
|
||||
working-directory: build
|
||||
run: make -j$(getconf _NPROCESSORS_ONLN) unittests
|
||||
|
||||
@@ -149,7 +137,7 @@ jobs:
|
||||
mv build/pcsx2*/PCSX2.app "$APPNAME.app"
|
||||
tar --options xz:compression-level=9 -cvJf "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" "$APPNAME.app"
|
||||
mkdir ci-artifacts
|
||||
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS-${{ inputs.gui }}.tar.xz
|
||||
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -14,6 +14,5 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/macos_build.yml
|
||||
with:
|
||||
jobName: "Qt"
|
||||
gui: "Qt"
|
||||
jobName: "MacOS Build"
|
||||
secrets: inherit
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: "Linux"
|
||||
uses: ./.github/workflows/linux_build_qt.yml
|
||||
with:
|
||||
jobName: "Qt"
|
||||
jobName: "AppImage Build"
|
||||
compiler: clang
|
||||
cmakeflags: ""
|
||||
buildAppImage: true
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
name: "Linux"
|
||||
uses: ./.github/workflows/linux_build_flatpak.yml
|
||||
with:
|
||||
jobName: "Flatpak"
|
||||
jobName: "Flatpak Build"
|
||||
compiler: clang
|
||||
cmakeflags: ""
|
||||
branch: "stable"
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
name: "Windows"
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt
|
||||
jobName: "Windows Build"
|
||||
configuration: CMake
|
||||
buildSystem: cmake
|
||||
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
|
||||
@@ -48,8 +48,7 @@ jobs:
|
||||
name: "MacOS"
|
||||
uses: ./.github/workflows/macos_build.yml
|
||||
with:
|
||||
jobName: "Qt"
|
||||
gui: "Qt"
|
||||
jobName: "MacOS Build"
|
||||
secrets: inherit
|
||||
|
||||
# Upload the Artifacts
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Artifact Naming Scheme:
|
||||
# PCSX2-<OS>-<GUI>-[ARCH]-[SIMD]-[pr\[PR_NUM\]]-[title|sha\[SHA|PR_TITLE\]
|
||||
# PCSX2-<OS>-Qt-[ARCH]-[SIMD]-[pr\[PR_NUM\]]-[title|sha\[SHA|PR_TITLE\]
|
||||
# -- limited to 200 chars
|
||||
# Outputs:
|
||||
# - artifact-name
|
||||
@@ -9,7 +9,6 @@
|
||||
# Inputs as env-vars
|
||||
# OS
|
||||
# BUILD_SYSTEM
|
||||
# GUI_FRAMEWORK
|
||||
# ARCH
|
||||
# SIMD
|
||||
# EVENT_NAME
|
||||
@@ -20,11 +19,12 @@
|
||||
NAME=""
|
||||
|
||||
if [ "${OS}" == "macos" ]; then
|
||||
NAME="PCSX2-${OS}-${GUI_FRAMEWORK}"
|
||||
# MacOS has combined binaries for x64 and ARM64.
|
||||
NAME="PCSX2-${OS}-Qt"
|
||||
elif [[ ("${OS}" == "windows" && "$BUILD_SYSTEM" != "cmake") ]]; then
|
||||
NAME="PCSX2-${OS}-${GUI_FRAMEWORK}-${ARCH}-${SIMD}"
|
||||
NAME="PCSX2-${OS}-Qt-${ARCH}-${SIMD}"
|
||||
else
|
||||
NAME="PCSX2-${OS}-${GUI_FRAMEWORK}-${ARCH}"
|
||||
NAME="PCSX2-${OS}-Qt-${ARCH}"
|
||||
fi
|
||||
|
||||
# Add cmake if used to differentate it from msbuild builds
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
source "$SCRIPTDIR/../linux/functions.sh"
|
||||
|
||||
set -e
|
||||
|
||||
# While we use custom Qt builds for our releases, the Qt6 package will be good enough
|
||||
# for just updating translations. Saves building it for this action alone.
|
||||
retry_command sudo apt-get -y install qt6-l10n-tools
|
||||
|
||||
PATH=/usr/lib/qt6/bin:$PATH "$SCRIPTDIR/../../../../pcsx2-qt/Translations/update_en_translation.sh"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
APPDIR=$(dirname "$0")
|
||||
exec "$APPDIR/usr/bin/pcsx2-qt" "$@"
|
||||
@@ -40,255 +40,94 @@ NAME=$4
|
||||
|
||||
BINARY=pcsx2-qt
|
||||
APPDIRNAME=PCSX2.AppDir
|
||||
STRIP=llvm-strip-12
|
||||
STRIP=strip
|
||||
|
||||
declare -a SYSLIBS=(
|
||||
"libaio.so.1"
|
||||
"libz.so.1"
|
||||
"libuuid.so.1"
|
||||
"libapparmor.so.1"
|
||||
"libblkid.so.1"
|
||||
"libbsd.so.0"
|
||||
"libdbus-1.so.3"
|
||||
"libgcrypt.so.20"
|
||||
"liblzma.so.5"
|
||||
"libmount.so.1"
|
||||
"libnsl.so.1"
|
||||
"libpcre.so.3"
|
||||
"libselinux.so.1"
|
||||
"libsystemd.so.0"
|
||||
"libudev.so.1"
|
||||
"libwrap.so.0"
|
||||
"libharfbuzz.so.0"
|
||||
"libFLAC.so.8"
|
||||
"libXau.so.6"
|
||||
"libXcomposite.so.1"
|
||||
"libXcursor.so.1"
|
||||
"libXdamage.so.1"
|
||||
"libXdmcp.so.6"
|
||||
"libXext.so.6"
|
||||
"libXfixes.so.3"
|
||||
"libXi.so.6"
|
||||
"libXinerama.so.1"
|
||||
"libXrandr.so.2"
|
||||
"libXrender.so.1"
|
||||
"libXxf86vm.so.1"
|
||||
"libasyncns.so.0"
|
||||
"libcrypto.so.1.1"
|
||||
"libjpeg.so.8"
|
||||
"liblz4.so.1"
|
||||
"libogg.so.0"
|
||||
"libpcap.so.0.8"
|
||||
"libpng16.so.16"
|
||||
"libpulse.so.0"
|
||||
"libsamplerate.so.0"
|
||||
"libsndfile.so.1"
|
||||
"libvorbis.so.0"
|
||||
"libvorbisenc.so.2"
|
||||
"libxcb.so.1"
|
||||
"libxcb-cursor.so.0"
|
||||
"libxcb-render.so.0"
|
||||
"libxcb-shm.so.0"
|
||||
"libxkbcommon.so.0"
|
||||
"libxkbcommon-x11.so.0"
|
||||
"pulseaudio/libpulsecommon-13.99.so"
|
||||
"libasound.so.2"
|
||||
"libfreetype.so.6"
|
||||
"libpcre2-16.so.0"
|
||||
"libexpat.so.1"
|
||||
"libffi.so.7"
|
||||
"libgraphite2.so.3"
|
||||
"libresolv.so.2"
|
||||
"libgpg-error.so.0"
|
||||
"libpcre2-16.so.0"
|
||||
"libpng16.so.16"
|
||||
"libxcb-icccm.so.4"
|
||||
"libxcb-image.so.0"
|
||||
"libxcb-keysyms.so.1"
|
||||
"libxcb-randr.so.0"
|
||||
"libxcb-render.so.0"
|
||||
"libxcb-render-util.so.0"
|
||||
"libxcb-shape.so.0"
|
||||
"libxcb-sync.so.1"
|
||||
"libxcb-util.so.1"
|
||||
"libxcb-xfixes.so.0"
|
||||
"libxcb-xkb.so.1"
|
||||
"libevdev.so.2"
|
||||
"libgudev-1.0.so.0"
|
||||
"libinput.so.10"
|
||||
"libjpeg.so.8"
|
||||
"libmtdev.so.1"
|
||||
"libpng16.so.16"
|
||||
"libudev.so.1"
|
||||
"libuuid.so.1"
|
||||
"libcurl.so.4"
|
||||
"libnghttp2.so.14"
|
||||
"libidn2.so.0"
|
||||
"librtmp.so.1"
|
||||
"libssh.so.4"
|
||||
"libpsl.so.5"
|
||||
"libssl.so.1.1"
|
||||
"libnettle.so.7"
|
||||
"libgnutls.so.30"
|
||||
"libgssapi_krb5.so.2"
|
||||
"libldap_r-2.4.so.2"
|
||||
"liblber-2.4.so.2"
|
||||
"libbrotlidec.so.1"
|
||||
"libunistring.so.2"
|
||||
"libhogweed.so.5"
|
||||
"libgmp.so.10"
|
||||
"libp11-kit.so.0"
|
||||
"libtasn1.so.6"
|
||||
"libkrb5.so.3"
|
||||
"libk5crypto.so.3"
|
||||
"libcom_err.so.2"
|
||||
"libkrb5support.so.0"
|
||||
"libsasl2.so.2"
|
||||
"libgssapi.so.3"
|
||||
"libbrotlicommon.so.1"
|
||||
"libkeyutils.so.1"
|
||||
"libheimntlm.so.0"
|
||||
"libkrb5.so.26"
|
||||
"libasn1.so.8"
|
||||
"libhcrypto.so.4"
|
||||
"libroken.so.18"
|
||||
"libwind.so.0"
|
||||
"libheimbase.so.1"
|
||||
"libhx509.so.5"
|
||||
"libsqlite3.so.0"
|
||||
"libcrypt.so.1"
|
||||
"libdbus-1.so.3"
|
||||
)
|
||||
|
||||
declare -a DEPLIBS=(
|
||||
"libSDL2-2.0.so.0"
|
||||
)
|
||||
|
||||
declare -a QTLIBS=(
|
||||
"libQt6Core.so.6"
|
||||
"libQt6Gui.so.6"
|
||||
"libQt6Network.so.6"
|
||||
"libQt6OpenGL.so.6"
|
||||
"libQt6Svg.so.6"
|
||||
"libQt6WaylandClient.so.6"
|
||||
"libQt6WaylandCompositor.so.6"
|
||||
declare -a MANUAL_QT_LIBS=(
|
||||
"libQt6WaylandEglClientHwIntegration.so.6"
|
||||
"libQt6WaylandEglCompositorHwIntegration.so.6"
|
||||
"libQt6Widgets.so.6"
|
||||
"libQt6XcbQpa.so.6"
|
||||
)
|
||||
|
||||
declare -a QTPLUGINS=(
|
||||
"plugins/iconengines"
|
||||
"plugins/imageformats"
|
||||
"plugins/platforms"
|
||||
#"plugins/platformthemes" # Enable this if we want to ship GTK+ themes at any point.
|
||||
"plugins/tls"
|
||||
"plugins/wayland-decoration-client"
|
||||
"plugins/wayland-graphics-integration-client"
|
||||
"plugins/wayland-graphics-integration-server"
|
||||
"plugins/wayland-shell-integration"
|
||||
"plugins/xcbglintegrations"
|
||||
declare -a MANUAL_QT_PLUGINS=(
|
||||
"wayland-decoration-client"
|
||||
"wayland-graphics-integration-client"
|
||||
"wayland-shell-integration"
|
||||
)
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f appimagetool-x86_64.AppImage ]; then
|
||||
retry_command wget -O appimagetool-x86_64.AppImage https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
LINUXDEPLOY=./linuxdeploy-x86_64.AppImage
|
||||
LINUXDEPLOY_PLUGIN_QT=./linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
APPIMAGETOOL=./appimagetool-x86_64.AppImage
|
||||
PATCHELF=patchelf
|
||||
|
||||
if [ ! -f "$LINUXDEPLOY" ]; then
|
||||
retry_command wget -O "$LINUXDEPLOY" https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod +x "$LINUXDEPLOY"
|
||||
fi
|
||||
|
||||
if [ ! -f "$LINUXDEPLOY_PLUGIN_QT" ]; then
|
||||
retry_command wget -O "$LINUXDEPLOY_PLUGIN_QT" https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
chmod +x "$LINUXDEPLOY_PLUGIN_QT"
|
||||
fi
|
||||
|
||||
if [ ! -f "$APPIMAGETOOL" ]; then
|
||||
retry_command wget -O "$APPIMAGETOOL" https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
chmod +x "$APPIMAGETOOL"
|
||||
fi
|
||||
|
||||
OUTDIR=$(realpath "./$APPDIRNAME")
|
||||
SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
rm -fr "$OUTDIR"
|
||||
mkdir "$OUTDIR"
|
||||
mkdir "$OUTDIR/usr"
|
||||
|
||||
echo "Copying binary and resources..."
|
||||
cp -a "$BUILDDIR/bin" "$OUTDIR/usr"
|
||||
echo "Copying desktop file..."
|
||||
cp "$PCSX2DIR/.github/workflows/scripts/linux/pcsx2-qt.desktop" .
|
||||
cp "$PCSX2DIR/bin/resources/icons/AppIconLarge.png" "PCSX2.png"
|
||||
|
||||
# Get rid of unit tests, we don't want them bloating the squashfs.
|
||||
rm -fv "$OUTDIR"/usr/bin/*_test
|
||||
echo "Running linuxdeploy to create AppDir..."
|
||||
EXTRA_QT_PLUGINS="core;gui;network;svg;waylandclient;widgets;xcbqpa" \
|
||||
EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so" \
|
||||
QMAKE="$DEPSDIR/bin/qmake" \
|
||||
$LINUXDEPLOY --plugin qt --appdir="$OUTDIR" --executable="$BUILDDIR/bin/pcsx2-qt" \
|
||||
--desktop-file="pcsx2-qt.desktop" --icon-file="PCSX2.png"
|
||||
|
||||
# Patch RPATH so the binary goes hunting for shared libraries in the AppDir instead of system.
|
||||
echo "Patching RPATH in ${BINARY}..."
|
||||
patchelf --set-rpath '$ORIGIN/../lib' "$OUTDIR/usr/bin/$BINARY"
|
||||
echo "Copying resources into AppDir..."
|
||||
cp -a "$BUILDDIR/bin/resources" "$OUTDIR/usr/bin"
|
||||
|
||||
# Currently we leave the main binary unstripped, uncomment if this is not desired.
|
||||
#$STRIP "$OUTDIR/usr/bin/$BINARY"
|
||||
|
||||
# Libraries we pull in from the system.
|
||||
echo "Copying system libraries..."
|
||||
mkdir -p "$OUTDIR/usr/lib" "$OUTDIR/usr/lib/pulseaudio"
|
||||
for lib in "${SYSLIBS[@]}"; do
|
||||
blib=$(basename "$lib")
|
||||
if [ -f "/lib/x86_64-linux-gnu/$lib" ]; then
|
||||
cp "/lib/x86_64-linux-gnu/$lib" "$OUTDIR/usr/lib/$blib"
|
||||
elif [ -f "$CHROOT/usr/lib/x86_64-linux-gnu/$lib" ]; then
|
||||
cp "$CHROOT/usr/lib/x86_64-linux-gnu/$lib" "$OUTDIR/usr/lib/$blib"
|
||||
elif [ -f "$CHROOT/lib/$lib" ]; then
|
||||
cp "$CHROOT/lib/$lib" "$OUTDIR/usr/lib/$blib"
|
||||
elif [ -f "$CHROOT/usr/lib/$lib" ]; then
|
||||
cp "$CHROOT/usr/lib/$lib" "$OUTDIR/usr/lib/$blib"
|
||||
else
|
||||
echo "*** Failed to find '$blib'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$STRIP "$OUTDIR/usr/lib/$blib"
|
||||
# LinuxDeploy's Qt plugin doesn't include Wayland support. So manually copy in the additional Wayland libraries.
|
||||
echo "Copying Qt Wayland libraries..."
|
||||
for lib in "${MANUAL_QT_LIBS[@]}"; do
|
||||
srcpath="$DEPSDIR/lib/$lib"
|
||||
dstpath="$OUTDIR/usr/lib/$lib"
|
||||
echo " $srcpath -> $dstpath"
|
||||
cp "$srcpath" "$dstpath"
|
||||
$PATCHELF --set-rpath '$ORIGIN' "$dstpath"
|
||||
$STRIP "$dstpath"
|
||||
done
|
||||
|
||||
# Dependencies we built, at this point it's just SDL.
|
||||
echo "Copying dependency libraries..."
|
||||
for lib in "${DEPLIBS[@]}"; do
|
||||
blib=$(basename "$lib")
|
||||
if [ -f "$DEPSDIR/lib/$lib" ]; then
|
||||
cp "$DEPSDIR/lib/$lib" "$OUTDIR/usr/lib/$blib"
|
||||
else
|
||||
echo "*** Failed to find '$blib'"
|
||||
exit 1
|
||||
fi
|
||||
# .. and plugins.
|
||||
echo "Copying Qt Wayland plugins..."
|
||||
for GROUP in "${MANUAL_QT_PLUGINS[@]}"; do
|
||||
srcpath="$DEPSDIR/plugins/$GROUP"
|
||||
dstpath="$OUTDIR/usr/plugins/$GROUP"
|
||||
echo " $srcpath -> $dstpath"
|
||||
mkdir -p "$dstpath"
|
||||
|
||||
$STRIP "$OUTDIR/usr/lib/$blib"
|
||||
for srcsopath in $(find "$DEPSDIR/plugins/$GROUP" -iname '*.so'); do
|
||||
# This is ../../ because it's usually plugins/group/name.so
|
||||
soname=$(basename "$srcsopath")
|
||||
dstsopath="$dstpath/$soname"
|
||||
echo " $srcsopath -> $dstsopath"
|
||||
cp "$srcsopath" "$dstsopath"
|
||||
$PATCHELF --set-rpath '$ORIGIN/../../lib:$ORIGIN' "$dstsopath"
|
||||
$STRIP "$dstsopath"
|
||||
done
|
||||
done
|
||||
|
||||
echo "Copying Qt libraries..."
|
||||
for lib in "${QTLIBS[@]}"; do
|
||||
cp -aL "$DEPSDIR/lib/$lib" "$OUTDIR/usr/lib"
|
||||
$STRIP "$OUTDIR/usr/lib/$lib"
|
||||
done
|
||||
# Fix up translations.
|
||||
rm -fr "$OUTDIR/usr/bin/translations"
|
||||
mv "$OUTDIR/usr/translations" "$OUTDIR/usr/bin"
|
||||
cp -a "$BUILDDIR/bin/translations" "$OUTDIR/usr/bin"
|
||||
|
||||
echo "Copying Qt plugins..."
|
||||
mkdir -p "$OUTDIR/usr/lib/plugins"
|
||||
for plugin in "${QTPLUGINS[@]}"; do
|
||||
mkdir -p "$OUTDIR/usr/lib/$plugin"
|
||||
cp -aL "$DEPSDIR/$plugin"/*.so "$OUTDIR/usr/lib/$plugin/"
|
||||
done
|
||||
echo "Generating AppImage..."
|
||||
rm -f "$NAME.AppImage"
|
||||
$APPIMAGETOOL -v "$OUTDIR" "$NAME.AppImage"
|
||||
|
||||
for so in $(find "$OUTDIR/usr/lib/plugins" -iname '*.so'); do
|
||||
# This is ../../ because it's usually plugins/group/name.so
|
||||
echo "Patching RPATH in ${so}..."
|
||||
patchelf --set-rpath '$ORIGIN/../..' "$so"
|
||||
$STRIP "$so"
|
||||
done
|
||||
|
||||
for so in $(find "$OUTDIR/usr/lib" -maxdepth 1); do
|
||||
if [ -f "$so" ]; then
|
||||
echo "Patching RPATH in ${so}"
|
||||
patchelf --set-rpath '$ORIGIN' "$so"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Creating qt.conf..."
|
||||
cat > "$OUTDIR/usr/bin/qt.conf" << EOF
|
||||
[Paths]
|
||||
Plugins = ../lib/plugins
|
||||
EOF
|
||||
|
||||
echo "Copy desktop/icon..."
|
||||
cp "$PCSX2DIR/bin/resources/icons/AppIconLarge.png" "$OUTDIR/PCSX2.png"
|
||||
cp "$SCRIPTDIR/pcsx2-qt.desktop" "$OUTDIR/PCSX2.desktop"
|
||||
cp "$SCRIPTDIR/AppRun-qt" "$OUTDIR/AppRun"
|
||||
|
||||
echo "Generate AppImage"
|
||||
./appimagetool-x86_64.AppImage -v "$OUTDIR" "$NAME.AppImage"
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--socket=pulseaudio",
|
||||
"--filesystem=host:ro"
|
||||
"--filesystem=host:ro",
|
||||
"--talk-name=org.freedesktop.ScreenSaver"
|
||||
],
|
||||
"modules": [
|
||||
"modules/10-libpcap.json",
|
||||
|
||||
@@ -8,14 +8,13 @@ set -e
|
||||
# Packages - Build and Qt
|
||||
declare -a BUILD_PACKAGES=(
|
||||
"build-essential"
|
||||
"g++"
|
||||
"git"
|
||||
"cmake"
|
||||
"ccache"
|
||||
"ninja-build"
|
||||
"libclang-dev" # Qt goes hunting for libclang-11 specifically.
|
||||
"libclang-11-dev"
|
||||
"libclang-12-dev"
|
||||
"patchelf"
|
||||
"libfuse2"
|
||||
"libglib2.0-dev"
|
||||
"libfontconfig1-dev"
|
||||
"libharfbuzz-dev"
|
||||
@@ -64,16 +63,12 @@ declare -a PCSX2_PACKAGES=(
|
||||
"zlib1g-dev"
|
||||
)
|
||||
|
||||
if [ "${COMPILER}" = "gcc" ]; then
|
||||
BUILD_PACKAGES+=("g++-10")
|
||||
else
|
||||
if [ "${COMPILER}" = "clang" ]; then
|
||||
BUILD_PACKAGES+=("llvm-16" "lld-16" "clang-16")
|
||||
|
||||
# Ubuntu 20.04 doesn't ship with LLVM 16, so we need to pull it from the llvm.org repos.
|
||||
# Ubuntu 22.04 doesn't ship with LLVM 16, so we need to pull it from the llvm.org repos.
|
||||
retry_command wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt-add-repository -n 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main'
|
||||
retry_command sudo apt-get update
|
||||
retry_command sudo apt-get install clang-16 lld-16
|
||||
sudo apt-add-repository -n 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main'
|
||||
fi
|
||||
|
||||
retry_command sudo apt-get -qq update && break
|
||||
|
||||
@@ -13,11 +13,11 @@ for dir_name in os.listdir(scan_dir):
|
||||
asset_name += "-macos"
|
||||
elif "linux" in dir_name.lower():
|
||||
if "flatpak" in dir_name.lower():
|
||||
asset_name += "-linux-Flatpak-64bit"
|
||||
asset_name += "-linux-flatpak-x64"
|
||||
else:
|
||||
asset_name += "-linux-AppImage-64bit"
|
||||
asset_name += "-linux-appimage-x64"
|
||||
elif "windows" in dir_name.lower():
|
||||
asset_name += "-windows-64bit"
|
||||
asset_name += "-windows-x64"
|
||||
else:
|
||||
continue;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt
|
||||
jobName: "MSVC SSE4"
|
||||
configuration: Release
|
||||
simd: "SSE4"
|
||||
secrets: inherit
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt
|
||||
jobName: "MSVC AVX2"
|
||||
configuration: Release AVX2
|
||||
secrets: inherit
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt
|
||||
jobName: "CMake MSVC"
|
||||
configuration: CMake
|
||||
buildSystem: cmake
|
||||
secrets: inherit
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt Clang
|
||||
jobName: "Clang SSE4"
|
||||
configuration: Release Clang
|
||||
simd: "SSE4"
|
||||
secrets: inherit
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt Clang
|
||||
jobName: "Clang AVX2"
|
||||
configuration: Release Clang AVX2
|
||||
secrets: inherit
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
if: github.repository != 'PCSX2/pcsx2' || github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/windows_build_qt.yml
|
||||
with:
|
||||
jobName: Qt Clang
|
||||
jobName: "CMake Clang"
|
||||
configuration: CMake
|
||||
buildSystem: cmake
|
||||
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DPCSX2_EXE_NAME=pcsx2-qt-clang
|
||||
|
||||
@@ -63,7 +63,6 @@ jobs:
|
||||
env:
|
||||
OS: windows
|
||||
BUILD_SYSTEM: ${{ inputs.buildSystem }}
|
||||
GUI_FRAMEWORK: Qt
|
||||
ARCH: ${{ inputs.platform }}
|
||||
SIMD: ${{ inputs.simd }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
|
||||
@@ -22,7 +22,7 @@ Installers and binaries for both stable and development builds are available fro
|
||||
|
||||
| Operating System | CPU | GPU | RAM |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
|
||||
| - Windows 10 21H2 (1809 or later) (64-bit) <br/> - Ubuntu 20.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 10.14 | - Supports SSE4.1 <br/> - [PassMark Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 1800<br/> - Two physical cores, with hyperthreading | - Direct3D10 support <br/> - OpenGL 3.x support <br/> - Vulkan 1.1 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 3000 (Geforce GTX 750, Radeon RX 560, Intel Arc A380) <br/> - 2 GB Video Memory | 4 GB |
|
||||
| - Windows 10 Version 1809 or later (64-bit) <br/> - Ubuntu 22.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 10.14 | - Supports SSE4.1 <br/> - [PassMark Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 1800<br/> - Two physical cores, with hyperthreading | - Direct3D10 support <br/> - OpenGL 3.x support <br/> - Vulkan 1.1 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 3000 (Geforce GTX 750, Radeon RX 560, Intel Arc A380) <br/> - 2 GB Video Memory | 4 GB |
|
||||
|
||||
_Note: Recommended Single Thread Performance is based on moderately complex games. Games that pushed the PS2 hardware to its limits will struggle on CPUs at this level. Some release titles and 2D games which underutilized the PS2 hardware may run on CPUs rated as low as 1200. A quick reference for CPU **intensive games**: [Wiki](https://wiki.pcsx2.net/Category:CPU_intensive_games), [Forum](https://forums.pcsx2.net/Thread-LIST-The-Most-CPU-Intensive-Games) and CPU **light** games: [Forum](https://forums.pcsx2.net/Thread-LIST-Games-that-don-t-need-a-strong-CPU-to-emulate)_
|
||||
|
||||
@@ -30,7 +30,7 @@ _Note: Recommended Single Thread Performance is based on moderately complex game
|
||||
|
||||
| Operating System | CPU | GPU | RAM |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
|
||||
| - Windows 10 21H2 (1809 or later) (64-bit) <br/> - Ubuntu 22.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 10.14 | - Supports AVX2 <br/> - [PassMark Single Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 2600<br/> - Four physical cores, with or without hyperthreading | - Direct3D12 support <br/> - OpenGL 4.6 support <br/> - Vulkan 1.3 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 6000 (GeForce GTX 1650, Radeon RX 570) <br/> - 4 GB Video Memory | 8 GB |
|
||||
| - Windows 10 22H2 (64-bit) <br/> - Ubuntu 23.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 10.14 | - Supports AVX2 <br/> - [PassMark Single Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 2600<br/> - Four physical cores, with or without hyperthreading | - Direct3D12 support <br/> - OpenGL 4.6 support <br/> - Vulkan 1.3 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 6000 (GeForce GTX 1650, Radeon RX 570) <br/> - 4 GB Video Memory | 8 GB |
|
||||
|
||||
_Note: Recommended GPU is based on 3x Internal, ~1080p resolution requirements. Higher resolutions will require stronger cards; 6x Internal, ~4K resolution will require a [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 12000 (GeForce RTX 2060 Radeon RX 6600 Intel Arc A750). Just like CPU requirements, this is also highly game dependent. A quick reference for GPU **intensive games**: [Wiki](https://wiki.pcsx2.net/Category:GPU_intensive_games)_
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ void main(uint3 LocalThreadId : SV_GroupThreadID, uint3 WorkGroupId : SV_GroupID
|
||||
#endif
|
||||
|
||||
// Filter.
|
||||
AF3 c;
|
||||
AF3 c = (float4)0.0f;
|
||||
|
||||
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
|
||||
OutputTexture[ASU2(gxy)] = AF4(c, 1);
|
||||
|
||||
@@ -395,4 +395,46 @@ PS_OUTPUT ps_filter_lottes(PS_INPUT input)
|
||||
return output;
|
||||
}
|
||||
|
||||
PS_OUTPUT ps_4x_rgss(PS_INPUT input)
|
||||
{
|
||||
PS_OUTPUT output;
|
||||
|
||||
float2 dxy = float2(ddx(input.t.x), ddy(input.t.y));
|
||||
float3 color = 0;
|
||||
|
||||
float s = 1.0/8.0;
|
||||
float l = 3.0/8.0;
|
||||
|
||||
color += sample_c(input.t + float2( s, l) * dxy).rgb;
|
||||
color += sample_c(input.t + float2( l,-s) * dxy).rgb;
|
||||
color += sample_c(input.t + float2(-s,-l) * dxy).rgb;
|
||||
color += sample_c(input.t + float2(-l, s) * dxy).rgb;
|
||||
|
||||
output.c = float4(color * 0.25,1);
|
||||
return output;
|
||||
}
|
||||
|
||||
PS_OUTPUT ps_automagical_supersampling(PS_INPUT input)
|
||||
{
|
||||
PS_OUTPUT output;
|
||||
|
||||
float2 ratio = (u_source_size / u_target_size) * 0.5;
|
||||
float2 steps = floor(ratio);
|
||||
float3 col = sample_c(input.t).rgb;
|
||||
float div = 1;
|
||||
|
||||
for (float y = 0; y < steps.y; y++)
|
||||
{
|
||||
for (float x = 0; x < steps.x; x++)
|
||||
{
|
||||
float2 offset = float2(x,y) - ratio * 0.5;
|
||||
col += sample_c(input.t + offset * u_rcp_source_resolution * 2.0).rgb;
|
||||
div++;
|
||||
}
|
||||
}
|
||||
|
||||
output.c = float4(col / div, 1);
|
||||
return output;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -46,7 +46,7 @@ void main()
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x)+AU2(gl_WorkGroupID.x<<4u,gl_WorkGroupID.y<<4u);
|
||||
|
||||
// Filter.
|
||||
AF4 c;
|
||||
AF4 c = vec4(0.0f);
|
||||
CasFilter(c.r, c.g, c.b, gxy, const0, const1, CAS_SHARPEN_ONLY);
|
||||
imageStore(imgDst, ASU2(gxy), c);
|
||||
gxy.x += 8u;
|
||||
|
||||
@@ -367,7 +367,7 @@ uniform ivec2 EMOD;
|
||||
void ps_yuv()
|
||||
{
|
||||
vec4 i = sample_c();
|
||||
vec4 o;
|
||||
vec4 o = vec4(0.0f);
|
||||
|
||||
mat3 rgb2yuv; // Value from GS manual
|
||||
rgb2yuv[0] = vec3(0.587, -0.311, -0.419);
|
||||
@@ -380,7 +380,8 @@ void ps_yuv()
|
||||
float Cr = float(0xE0)/255.0f * yuv.y + float(0x80)/255.0f;
|
||||
float Cb = float(0xE0)/255.0f * yuv.z + float(0x80)/255.0f;
|
||||
|
||||
switch(EMOD.x) {
|
||||
switch(EMOD.x)
|
||||
{
|
||||
case 0:
|
||||
o.a = i.a;
|
||||
break;
|
||||
@@ -395,7 +396,8 @@ void ps_yuv()
|
||||
break;
|
||||
}
|
||||
|
||||
switch(EMOD.y) {
|
||||
switch(EMOD.y)
|
||||
{
|
||||
case 0:
|
||||
o.rgb = i.rgb;
|
||||
break;
|
||||
|
||||
@@ -54,6 +54,11 @@ vec4 sample_c()
|
||||
return texture(TextureSampler, PSin_t);
|
||||
}
|
||||
|
||||
vec4 sample_c(vec2 uv)
|
||||
{
|
||||
return texture(TextureSampler, uv);
|
||||
}
|
||||
|
||||
vec4 ps_crt(uint i)
|
||||
{
|
||||
vec4 mask[4] = vec4[4](
|
||||
@@ -384,4 +389,44 @@ void ps_filter_lottes()
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ps_4x_rgss
|
||||
void ps_4x_rgss()
|
||||
{
|
||||
vec2 dxy = vec2(dFdx(PSin_t.x), dFdy(PSin_t.y));
|
||||
vec3 color = vec3(0);
|
||||
|
||||
float s = 1.0/8.0;
|
||||
float l = 3.0/8.0;
|
||||
|
||||
color += sample_c(PSin_t + vec2( s, l) * dxy).rgb;
|
||||
color += sample_c(PSin_t + vec2( l,-s) * dxy).rgb;
|
||||
color += sample_c(PSin_t + vec2(-s,-l) * dxy).rgb;
|
||||
color += sample_c(PSin_t + vec2(-l, s) * dxy).rgb;
|
||||
|
||||
SV_Target0 = vec4(color * 0.25,1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ps_automagical_supersampling
|
||||
void ps_automagical_supersampling()
|
||||
{
|
||||
vec2 ratio = (u_source_size / u_target_size) * 0.5;
|
||||
vec2 steps = floor(ratio);
|
||||
vec3 col = sample_c(PSin_t).rgb;
|
||||
float div = 1;
|
||||
|
||||
for (float y = 0; y < steps.y; y++)
|
||||
{
|
||||
for (float x = 0; x < steps.x; x++)
|
||||
{
|
||||
vec2 offset = vec2(x,y) - ratio * 0.5;
|
||||
col += sample_c(PSin_t + offset * u_rcp_source_resolution * 2.0).rgb;
|
||||
div++;
|
||||
}
|
||||
}
|
||||
|
||||
SV_Target0 = vec4(col / div, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -53,7 +53,7 @@ void main()
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x)+AU2(gl_WorkGroupID.x<<4u,gl_WorkGroupID.y<<4u);
|
||||
|
||||
// Filter.
|
||||
AF4 c;
|
||||
AF4 c = vec4(0.0f);
|
||||
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly != 0);
|
||||
imageStore(imgDst, ASU2(gxy), c);
|
||||
gxy.x += 8u;
|
||||
|
||||
@@ -348,7 +348,7 @@ layout(push_constant) uniform cb10
|
||||
void ps_yuv()
|
||||
{
|
||||
vec4 i = sample_c(v_tex);
|
||||
vec4 o;
|
||||
vec4 o = vec4(0.0f);
|
||||
|
||||
mat3 rgb2yuv;
|
||||
rgb2yuv[0] = vec3(0.587, -0.311, -0.419);
|
||||
@@ -361,7 +361,8 @@ void ps_yuv()
|
||||
float Cr = float(0xE0)/255.0f * yuv.y + float(0x80)/255.0f;
|
||||
float Cb = float(0xE0)/255.0f * yuv.z + float(0x80)/255.0f;
|
||||
|
||||
switch(EMODA) {
|
||||
switch(EMODA)
|
||||
{
|
||||
case 0:
|
||||
o.a = i.a;
|
||||
break;
|
||||
@@ -376,7 +377,8 @@ void ps_yuv()
|
||||
break;
|
||||
}
|
||||
|
||||
switch(EMODC) {
|
||||
switch(EMODC)
|
||||
{
|
||||
case 0:
|
||||
o.rgb = i.rgb;
|
||||
break;
|
||||
|
||||
@@ -359,4 +359,44 @@ void ps_filter_lottes()
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ps_4x_rgss
|
||||
void ps_4x_rgss()
|
||||
{
|
||||
vec2 dxy = vec2(dFdx(v_tex.x), dFdy(v_tex.y));
|
||||
vec3 color = vec3(0);
|
||||
|
||||
float s = 1.0/8.0;
|
||||
float l = 3.0/8.0;
|
||||
|
||||
color += sample_c(v_tex + vec2( s, l) * dxy).rgb;
|
||||
color += sample_c(v_tex + vec2( l,-s) * dxy).rgb;
|
||||
color += sample_c(v_tex + vec2(-s,-l) * dxy).rgb;
|
||||
color += sample_c(v_tex + vec2(-l, s) * dxy).rgb;
|
||||
|
||||
o_col0 = vec4(color * 0.25,1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ps_automagical_supersampling
|
||||
void ps_automagical_supersampling()
|
||||
{
|
||||
vec2 ratio = (u_source_size / u_target_size) * 0.5;
|
||||
vec2 steps = floor(ratio);
|
||||
vec3 col = sample_c(v_tex).rgb;
|
||||
float div = 1;
|
||||
|
||||
for (float y = 0; y < steps.y; y++)
|
||||
{
|
||||
for (float x = 0; x < steps.x; x++)
|
||||
{
|
||||
vec2 offset = vec2(x,y) - ratio * 0.5;
|
||||
col += sample_c(v_tex + offset * u_rcp_source_resolution * 2.0).rgb;
|
||||
div++;
|
||||
}
|
||||
}
|
||||
|
||||
o_col0 = vec4(col / div, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -336,12 +336,12 @@ layout(set = 1, binding = 1) uniform texture2D Palette;
|
||||
#endif
|
||||
|
||||
#if PS_FEEDBACK_LOOP_IS_NEEDED
|
||||
#ifndef DISABLE_TEXTURE_BARRIER
|
||||
layout(input_attachment_index = 0, set = 2, binding = 0) uniform subpassInput RtSampler;
|
||||
vec4 sample_from_rt() { return subpassLoad(RtSampler); }
|
||||
#else
|
||||
#if defined(DISABLE_TEXTURE_BARRIER) || defined(HAS_FEEDBACK_LOOP_LAYOUT)
|
||||
layout(set = 2, binding = 0) uniform texture2D RtSampler;
|
||||
vec4 sample_from_rt() { return texelFetch(RtSampler, ivec2(gl_FragCoord.xy), 0); }
|
||||
#else
|
||||
layout(input_attachment_index = 0, set = 2, binding = 0) uniform subpassInput RtSampler;
|
||||
vec4 sample_from_rt() { return subpassLoad(RtSampler); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1218,7 +1218,7 @@ void main()
|
||||
#endif
|
||||
|
||||
#if (SW_AD_TO_HW)
|
||||
vec4 RT = trunc(subpassLoad(RtSampler) * 255.0f + 0.1f);
|
||||
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
|
||||
vec4 alpha_blend = vec4(RT.a / 128.0f);
|
||||
#else
|
||||
vec4 alpha_blend = vec4(C.a / 128.0f);
|
||||
|
||||
@@ -30,6 +30,7 @@ target_sources(common PRIVATE
|
||||
Semaphore.cpp
|
||||
SettingsWrapper.cpp
|
||||
StringUtil.cpp
|
||||
TextureDecompress.cpp
|
||||
Timer.cpp
|
||||
ThreadPool.cpp
|
||||
WAVWriter.cpp
|
||||
@@ -94,6 +95,7 @@ target_sources(common PRIVATE
|
||||
SettingsWrapper.h
|
||||
StringUtil.h
|
||||
Timer.h
|
||||
TextureDecompress.h
|
||||
Threading.h
|
||||
ThreadPool.h
|
||||
TraceLog.h
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "common/General.h"
|
||||
#include "common/ScopedGuard.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/WindowInfo.h"
|
||||
@@ -70,66 +71,72 @@ std::string GetOSVersionString()
|
||||
|
||||
#ifdef DBUS_API
|
||||
|
||||
bool ChangeScreenSaverStateDBus(const bool inhibit_requested, const char* program_name, const char* reason)
|
||||
static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason)
|
||||
{
|
||||
static dbus_uint32_t s_cookie;
|
||||
// "error_dbus" doesn't need to be cleared in the end with "dbus_message_unref" at least if there is
|
||||
// no error set, since calling "dbus_error_free" reinitializes it like "dbus_error_init" after freeing.
|
||||
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
|
||||
DBusError error_dbus;
|
||||
dbus_error_init(&error_dbus);
|
||||
DBusConnection* connection = nullptr;
|
||||
static DBusConnection* s_comparison_connection;
|
||||
DBusMessage* message = nullptr;
|
||||
DBusMessage* response = nullptr;
|
||||
// Initialized here because initializations should be before "goto" statements.
|
||||
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
|
||||
// "dbus_bus_get" gets a pointer to the same connection in libdbus, if exists, without creating a new connection.
|
||||
// this doesn't need to be deleted, except if there's an error then calling "dbus_connection_unref", to free it,
|
||||
// might be better so a new connection is established on the next try.
|
||||
if (!(connection = dbus_bus_get(DBUS_BUS_SESSION, &error_dbus)) || (dbus_error_is_set(&error_dbus)))
|
||||
goto cleanup;
|
||||
if (!(message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", bus_method)))
|
||||
goto cleanup;
|
||||
// Initialize an append iterator for the message, gets freed with the message.
|
||||
DBusMessageIter message_itr;
|
||||
|
||||
ScopedGuard cleanup = [&]() {
|
||||
if (dbus_error_is_set(&error_dbus))
|
||||
dbus_error_free(&error_dbus);
|
||||
if (message)
|
||||
dbus_message_unref(message);
|
||||
if (response)
|
||||
dbus_message_unref(response);
|
||||
};
|
||||
|
||||
dbus_error_init(&error_dbus);
|
||||
// Calling dbus_bus_get() after the first time returns a pointer to the existing connection.
|
||||
connection = dbus_bus_get(DBUS_BUS_SESSION, &error_dbus);
|
||||
if (!connection || (dbus_error_is_set(&error_dbus)))
|
||||
return false;
|
||||
if (s_comparison_connection != connection)
|
||||
{
|
||||
dbus_connection_set_exit_on_disconnect(connection, false);
|
||||
s_cookie = 0;
|
||||
s_comparison_connection = connection;
|
||||
}
|
||||
message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", bus_method);
|
||||
if (!message)
|
||||
return false;
|
||||
// Initialize an append iterator for the message, gets freed with the message.
|
||||
dbus_message_iter_init_append(message, &message_itr);
|
||||
if (inhibit_requested)
|
||||
{
|
||||
// Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie.
|
||||
if (s_cookie)
|
||||
return false;
|
||||
// Append process/window name.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name))
|
||||
goto cleanup;
|
||||
return false;
|
||||
// Append reason for inhibiting the screensaver.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason))
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Append the cookie.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie))
|
||||
goto cleanup;
|
||||
return false;
|
||||
}
|
||||
// Send message and get response.
|
||||
if (!(response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error_dbus))
|
||||
|| dbus_error_is_set(&error_dbus))
|
||||
goto cleanup;
|
||||
response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error_dbus);
|
||||
if (!response || dbus_error_is_set(&error_dbus))
|
||||
return false;
|
||||
s_cookie = 0;
|
||||
if (inhibit_requested)
|
||||
{
|
||||
// Get the cookie from the response message.
|
||||
if (!dbus_message_get_args(response, &error_dbus, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID))
|
||||
goto cleanup;
|
||||
if (!dbus_message_get_args(response, &error_dbus, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) || dbus_error_is_set(&error_dbus))
|
||||
return false;
|
||||
}
|
||||
dbus_message_unref(message);
|
||||
dbus_message_unref(response);
|
||||
return true;
|
||||
cleanup:
|
||||
if (dbus_error_is_set(&error_dbus))
|
||||
dbus_error_free(&error_dbus);
|
||||
if (connection)
|
||||
dbus_connection_unref(connection);
|
||||
if (message)
|
||||
dbus_message_unref(message);
|
||||
if (response)
|
||||
dbus_message_unref(response);
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -176,11 +183,10 @@ bool WindowInfo::InhibitScreensaver(const WindowInfo& wi, bool inhibit)
|
||||
|
||||
#ifdef DBUS_API
|
||||
|
||||
return ChangeScreenSaverStateDBus(inhibit, "PCSX2", "PCSX2 VM is running.");
|
||||
return SetScreensaverInhibitDBus(inhibit, "PCSX2", "PCSX2 VM is running.");
|
||||
|
||||
#else
|
||||
|
||||
//ChangeScreenSaverStateDBus
|
||||
if (s_inhibit_window_info.has_value())
|
||||
{
|
||||
// Bit of extra logic here, because wx spams it and we don't want to
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Types.h"
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
@@ -340,4 +341,15 @@ namespace StringUtil
|
||||
/// Converts unsigned 128-bit data to string.
|
||||
std::string U128ToString(const u128& u);
|
||||
std::string& AppendU128ToString(const u128& u, std::string& s);
|
||||
|
||||
template <typename ContainerType>
|
||||
static inline bool ContainsSubString(const ContainerType& haystack, const std::string_view& needle)
|
||||
{
|
||||
using ValueType = typename ContainerType::value_type;
|
||||
if (needle.empty())
|
||||
return std::empty(haystack);
|
||||
|
||||
return std::search(std::begin(haystack), std::end(haystack), reinterpret_cast<const ValueType*>(needle.data()),
|
||||
reinterpret_cast<const ValueType*>(needle.data() + needle.length())) != std::end(haystack);
|
||||
}
|
||||
} // namespace StringUtil
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// See TextureDecompress.cpp for license info.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
enum BC4Mode
|
||||
{
|
||||
BC4_UNORM = 0,
|
||||
BC4_SNORM = 1
|
||||
};
|
||||
|
||||
enum BC5Mode
|
||||
{
|
||||
BC5_UNORM = 0,
|
||||
BC5_SNORM = 1
|
||||
};
|
||||
|
||||
void DecompressBlockBC1(uint32_t x, uint32_t y, uint32_t stride,
|
||||
const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC2(uint32_t x, uint32_t y, uint32_t stride,
|
||||
const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC3(uint32_t x, uint32_t y, uint32_t stride,
|
||||
const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC4(uint32_t x, uint32_t y, uint32_t stride,
|
||||
enum BC4Mode mode, const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC5(uint32_t x, uint32_t y, uint32_t stride,
|
||||
enum BC5Mode mode, const uint8_t* blockStorage, unsigned char* image);
|
||||
|
||||
namespace bc7decomp
|
||||
{
|
||||
|
||||
enum eNoClamp { cNoClamp };
|
||||
|
||||
template <typename S> inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); }
|
||||
|
||||
class color_rgba
|
||||
{
|
||||
public:
|
||||
union
|
||||
{
|
||||
uint8_t m_comps[4];
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
};
|
||||
};
|
||||
|
||||
inline color_rgba()
|
||||
{
|
||||
static_assert(sizeof(*this) == 4, "sizeof(*this) != 4");
|
||||
}
|
||||
|
||||
inline color_rgba(int y)
|
||||
{
|
||||
set(y);
|
||||
}
|
||||
|
||||
inline color_rgba(int y, int na)
|
||||
{
|
||||
set(y, na);
|
||||
}
|
||||
|
||||
inline color_rgba(int sr, int sg, int sb, int sa)
|
||||
{
|
||||
set(sr, sg, sb, sa);
|
||||
}
|
||||
|
||||
inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa)
|
||||
{
|
||||
set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa);
|
||||
}
|
||||
|
||||
inline color_rgba& set_noclamp_y(int y)
|
||||
{
|
||||
m_comps[0] = (uint8_t)y;
|
||||
m_comps[1] = (uint8_t)y;
|
||||
m_comps[2] = (uint8_t)y;
|
||||
m_comps[3] = (uint8_t)255;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa)
|
||||
{
|
||||
m_comps[0] = (uint8_t)sr;
|
||||
m_comps[1] = (uint8_t)sg;
|
||||
m_comps[2] = (uint8_t)sb;
|
||||
m_comps[3] = (uint8_t)sa;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set(int y)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
|
||||
m_comps[1] = m_comps[0];
|
||||
m_comps[2] = m_comps[0];
|
||||
m_comps[3] = 255;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set(int y, int na)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
|
||||
m_comps[1] = m_comps[0];
|
||||
m_comps[2] = m_comps[0];
|
||||
m_comps[3] = static_cast<uint8_t>(clamp<int>(na, 0, 255));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set(int sr, int sg, int sb, int sa)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
|
||||
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
|
||||
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
|
||||
m_comps[3] = static_cast<uint8_t>(clamp<int>(sa, 0, 255));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set_rgb(int sr, int sg, int sb)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
|
||||
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
|
||||
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set_rgb(const color_rgba &other)
|
||||
{
|
||||
r = other.r;
|
||||
g = other.g;
|
||||
b = other.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; }
|
||||
inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; }
|
||||
|
||||
inline void clear()
|
||||
{
|
||||
m_comps[0] = 0;
|
||||
m_comps[1] = 0;
|
||||
m_comps[2] = 0;
|
||||
m_comps[3] = 0;
|
||||
}
|
||||
|
||||
inline bool operator== (const color_rgba &rhs) const
|
||||
{
|
||||
if (m_comps[0] != rhs.m_comps[0]) return false;
|
||||
if (m_comps[1] != rhs.m_comps[1]) return false;
|
||||
if (m_comps[2] != rhs.m_comps[2]) return false;
|
||||
if (m_comps[3] != rhs.m_comps[3]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool operator!= (const color_rgba &rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
inline bool operator<(const color_rgba &rhs) const
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (m_comps[i] < rhs.m_comps[i])
|
||||
return true;
|
||||
else if (m_comps[i] != rhs.m_comps[i])
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; }
|
||||
inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; }
|
||||
inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); }
|
||||
|
||||
static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); }
|
||||
static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); }
|
||||
};
|
||||
|
||||
bool unpack_bc7(const void *pBlock, color_rgba *pPixels);
|
||||
|
||||
} // namespace bc7decomp
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
@@ -69,6 +69,7 @@
|
||||
<ClCompile Include="StackWalker.cpp" />
|
||||
<ClCompile Include="StringUtil.cpp" />
|
||||
<ClCompile Include="SettingsWrapper.cpp" />
|
||||
<ClCompile Include="TextureDecompress.cpp" />
|
||||
<ClCompile Include="Timer.cpp" />
|
||||
<ClCompile Include="WAVWriter.cpp" />
|
||||
<ClCompile Include="WindowInfo.cpp" />
|
||||
@@ -141,6 +142,7 @@
|
||||
<ClInclude Include="RedtapeWindows.h" />
|
||||
<ClInclude Include="SafeArray.h" />
|
||||
<ClInclude Include="ThreadPool.h" />
|
||||
<ClInclude Include="TextureDecompress.h" />
|
||||
<ClInclude Include="Timer.h" />
|
||||
<ClInclude Include="WAVWriter.h" />
|
||||
<ClInclude Include="WindowInfo.h" />
|
||||
|
||||
@@ -142,6 +142,9 @@
|
||||
<ClCompile Include="Assertions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TextureDecompress.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AlignedMalloc.h">
|
||||
@@ -354,6 +357,9 @@
|
||||
<ClInclude Include="Error.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TextureDecompress.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
|
||||
@@ -224,7 +224,6 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
|
||||
bool is_symbols = false;
|
||||
bool is_avx2 = false;
|
||||
bool is_sse4 = false;
|
||||
bool is_qt_asset = false;
|
||||
bool is_perfect_match = false;
|
||||
for (const QJsonValue& additional_tag : additional_tags_array)
|
||||
{
|
||||
@@ -235,12 +234,6 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
|
||||
is_symbols = true;
|
||||
break;
|
||||
}
|
||||
else if (additional_tag_str.startsWith(QStringLiteral("Qt")))
|
||||
{
|
||||
// found a qt build
|
||||
// Note: The website improperly parses macOS file names, and gives them the tag "Qt.tar" instead of "Qt"
|
||||
is_qt_asset = true;
|
||||
}
|
||||
else if (additional_tag_str == QStringLiteral("SSE4"))
|
||||
{
|
||||
is_sse4 = true;
|
||||
@@ -258,7 +251,7 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!is_qt_asset || is_symbols || (!x86caps.hasAVX2 && is_avx2))
|
||||
if (is_symbols || (!x86caps.hasAVX2 && is_avx2))
|
||||
{
|
||||
// skip this asset
|
||||
continue;
|
||||
|
||||
@@ -582,7 +582,7 @@ static std::vector<u32> searchWorker(DebugInterface* cpu, u32 start, u32 end, T
|
||||
return hitAddresses;
|
||||
}
|
||||
|
||||
static std::vector<u32> searchWorkerString(DebugInterface* cpu, u32 start, u32 end, std::string value)
|
||||
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, u32 start, u32 end, QByteArray value)
|
||||
{
|
||||
std::vector<u32> hitAddresses;
|
||||
for (u32 addr = start; addr < end; addr += 1)
|
||||
@@ -590,7 +590,7 @@ static std::vector<u32> searchWorkerString(DebugInterface* cpu, u32 start, u32 e
|
||||
bool hit = true;
|
||||
for (size_t i = 0; i < value.length(); i++)
|
||||
{
|
||||
if (static_cast<char>(cpu->read8(addr + i)) != value[i])
|
||||
if (cpu->read8(addr + i) != value[i])
|
||||
{
|
||||
hit = false;
|
||||
break;
|
||||
@@ -624,7 +624,9 @@ std::vector<u32> startWorker(DebugInterface* cpu, int type, u32 start, u32 end,
|
||||
case 5:
|
||||
return searchWorker<double>(cpu, start, end, value.toDouble());
|
||||
case 6:
|
||||
return searchWorkerString(cpu, start, end, value.toStdString());
|
||||
return searchWorkerByteArray(cpu, start, end, value.toUtf8());
|
||||
case 7:
|
||||
return searchWorkerByteArray(cpu, start, end, QByteArray::fromHex(value.toUtf8()));
|
||||
default:
|
||||
Console.Error("Debugger: Unknown type when doing memory search!");
|
||||
break;
|
||||
@@ -667,13 +669,24 @@ void CpuWidget::onSearchButtonClicked()
|
||||
|
||||
unsigned long long value;
|
||||
|
||||
if (searchType < 4)
|
||||
switch (searchType)
|
||||
{
|
||||
value = searchValue.toULongLong(&ok, searchHex ? 16 : 10);
|
||||
}
|
||||
else if (searchType != 6)
|
||||
{
|
||||
searchValue.toDouble(&ok);
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
value = searchValue.toULongLong(&ok, searchHex ? 16 : 10);
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
searchValue.toDouble(&ok);
|
||||
break;
|
||||
case 6:
|
||||
ok = !searchValue.isEmpty();
|
||||
break;
|
||||
case 7:
|
||||
ok = !searchValue.trimmed().isEmpty();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
@@ -684,6 +697,7 @@ void CpuWidget::onSearchButtonClicked()
|
||||
|
||||
switch (searchType)
|
||||
{
|
||||
case 7:
|
||||
case 6:
|
||||
case 5:
|
||||
case 4:
|
||||
|
||||
@@ -231,6 +231,11 @@
|
||||
<string>String</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Array of byte</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
|
||||
@@ -2134,7 +2134,11 @@ SettingsDialog* MainWindow::getSettingsDialog()
|
||||
connect(m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::languageChanged, this, [this]() {
|
||||
// reopen settings dialog after it applies
|
||||
updateLanguage();
|
||||
g_main_window->doSettings("Interface");
|
||||
// If you doSettings now, on macOS, the window will somehow end up underneath the main window that was created above
|
||||
// Delay it slightly...
|
||||
QtHost::RunOnUIThread([]{
|
||||
g_main_window->doSettings("Interface");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2144,7 +2148,11 @@ SettingsDialog* MainWindow::getSettingsDialog()
|
||||
void MainWindow::doSettings(const char* category /* = nullptr */)
|
||||
{
|
||||
SettingsDialog* dlg = getSettingsDialog();
|
||||
if (!dlg->isVisible())
|
||||
if (dlg->isVisible())
|
||||
{
|
||||
dlg->raise();
|
||||
}
|
||||
else
|
||||
{
|
||||
dlg->setModal(false);
|
||||
dlg->show();
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">Start &File...</string>
|
||||
<string comment="In Toolbar">Start File</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartDisc">
|
||||
@@ -292,7 +292,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">Start &Disc...</string>
|
||||
<string comment="In Toolbar">Start Disc</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartBios">
|
||||
@@ -310,7 +310,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">Start &BIOS</string>
|
||||
<string comment="In Toolbar">Start BIOS</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionScanForNewGames">
|
||||
@@ -346,7 +346,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">Shut &Down</string>
|
||||
<string comment="In Toolbar">Shut Down</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPowerOffWithoutSaving">
|
||||
@@ -373,7 +373,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Reset</string>
|
||||
<string comment="In Toolbar">Reset</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPause">
|
||||
@@ -397,7 +397,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Pause</string>
|
||||
<string comment="In Toolbar">Pause</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarLoadState">
|
||||
@@ -406,7 +406,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Load State</string>
|
||||
<string comment="In Toolbar">Load State</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarSaveState">
|
||||
@@ -415,7 +415,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Save State</string>
|
||||
<string comment="In Toolbar">Save State</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExit">
|
||||
@@ -460,7 +460,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Controllers</string>
|
||||
<string comment="In Toolbar">Controllers</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionHotkeySettings">
|
||||
@@ -635,7 +635,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Settings</string>
|
||||
<string comment="In Toolbar">Settings</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::PreferencesRole</enum>
|
||||
@@ -681,7 +681,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="In Toolbar">&Screenshot</string>
|
||||
<string comment="In Toolbar">Screenshot</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMemoryCardSettings">
|
||||
|
||||
@@ -101,7 +101,10 @@ void ControllerSettingsDialog::onCurrentProfileChanged(int index)
|
||||
|
||||
void ControllerSettingsDialog::onNewProfileClicked()
|
||||
{
|
||||
const QString profile_name(QInputDialog::getText(this, tr("Create Input Profile"), tr("Enter the name for the new input profile:")));
|
||||
const QString profile_name(QInputDialog::getText(this, tr("Create Input Profile"),
|
||||
tr("Custom input profiles are used to override the Shared input profile for specific games.\n"
|
||||
"To apply a custom input profile to a game, go to its Game Properties, then change the 'Input Profile' on the Summary tab.\n\n"
|
||||
"Enter the name for the new input profile:")));
|
||||
if (profile_name.isEmpty())
|
||||
return;
|
||||
|
||||
|
||||
@@ -1479,6 +1479,16 @@
|
||||
<string extracomment="Lottes = Timothy Lottes, the creator of the shader filter. Leave as-is. CRT= Cathode Ray Tube, an old type of television technology.">Lottes CRT</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4xRGSS downsampling (4x Rotated Grid SuperSampling)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>NxAGSS downsampling (Nx Automatic Grid SuperSampling)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
|
||||
@@ -74,9 +74,14 @@ void InputBindingWidget::initialize(
|
||||
|
||||
void InputBindingWidget::updateText()
|
||||
{
|
||||
const QString binding_tip(tr("\n\nLeft click to assign a new button\nShift + left click for additional bindings"));
|
||||
const QString binding_clear_tip(tr("\nRight click to clear binding"));
|
||||
|
||||
if (m_bindings.empty())
|
||||
{
|
||||
setText(QString());
|
||||
|
||||
setToolTip(tr("No bindings registered") + binding_tip);
|
||||
}
|
||||
else if (m_bindings.size() > 1)
|
||||
{
|
||||
@@ -93,12 +98,12 @@ void InputBindingWidget::updateText()
|
||||
ss << "\n";
|
||||
ss << binding;
|
||||
}
|
||||
setToolTip(QString::fromStdString(ss.str()));
|
||||
setToolTip(QString::fromStdString(ss.str()) + binding_tip + binding_clear_tip);
|
||||
}
|
||||
else
|
||||
{
|
||||
QString binding_text(QString::fromStdString(m_bindings[0]));
|
||||
setToolTip(binding_text);
|
||||
setToolTip(binding_text + binding_tip + binding_clear_tip);
|
||||
|
||||
// fix up accelerators, and if it's too long, ellipsise it
|
||||
if (binding_text.contains('&'))
|
||||
|
||||
@@ -111,11 +111,11 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
|
||||
if (isPerGameSettings() && game && game->crc != 0)
|
||||
{
|
||||
addWidget(m_game_patch_settings_widget = new GamePatchSettingsWidget(game, this, m_ui.settingsContainer),
|
||||
tr("Patches"), QStringLiteral("tools-line"),
|
||||
tr("Patches"), QStringLiteral("band-aid-line"),
|
||||
tr("<strong>Patches</strong><hr>This section allows you to select optional patches to apply to the game, "
|
||||
"which may provide performance, visual, or gameplay improvements."));
|
||||
addWidget(m_game_cheat_settings_widget = new GameCheatSettingsWidget(game, this, m_ui.settingsContainer),
|
||||
tr("Cheats"), QStringLiteral("flask-line"),
|
||||
tr("Cheats"), QStringLiteral("cheats-line"),
|
||||
tr("<strong>Cheats</strong><hr>This section allows you to select which cheats you wish to enable. You "
|
||||
"cannot enable/disable cheats without labels for old-format pnach files, those will automatically "
|
||||
"activate if the main cheat enable option is checked."));
|
||||
@@ -125,7 +125,7 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
|
||||
if (show_advanced_settings && isPerGameSettings())
|
||||
{
|
||||
addWidget(m_game_fix_settings_widget = new GameFixSettingsWidget(this, m_ui.settingsContainer), tr("Game Fixes"),
|
||||
QStringLiteral("hammer-line"),
|
||||
QStringLiteral("tools-line"),
|
||||
tr("<strong>Game Fixes Settings</strong><hr>Game Fixes can work around incorrect emulation in some titles.<br>However, they can "
|
||||
"also cause problems in games if used incorrectly.<br>It is best to leave them all disabled unless advised otherwise."));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
|
||||
OPTS="-tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATE,QT_TRANSLATE_NOOP+=TRANSLATE_SV,QT_TRANSLATE_NOOP+=TRANSLATE_STR,QT_TRANSLATE_N_NOOP3+=TRANSLATE_FMT,QT_TRANSLATE_NOOP+=TRANSLATE_NOOP"
|
||||
SRCDIRS=$(realpath "$SCRIPTDIR/..")/\ $(realpath "$SCRIPTDIR/../../pcsx2")/
|
||||
OUTDIR=$(realpath "$SCRIPTDIR")
|
||||
|
||||
lupdate $SRCDIRS $OPTS -no-obsolete -source-language en -ts "$OUTDIR/pcsx2-qt_en.ts"
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 26.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="bandaid"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 21.6 21.6"
|
||||
style="enable-background:new 0 0 21.6 21.6"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="band-aid-line-dark.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs8" /><sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="35.231481"
|
||||
inkscape:cx="10.785808"
|
||||
inkscape:cy="10.785808"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="956"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="42"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="bandaid" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path
|
||||
id="By_Maxihplay"
|
||||
class="st0"
|
||||
d="M16.4,10.8L19,8.3c1-1,1-2.7,0-3.8l-1.9-1.9c-0.5-0.5-1.2-0.8-1.9-0.8s-1.4,0.3-1.9,0.8 l-2.5,2.5L8.3,2.6c-1-1-2.7-1-3.8,0L2.6,4.5c-1,1-1,2.7,0,3.8l2.5,2.5l-2.5,2.5c-1,1-1,2.7,0,3.8L4.5,19c0.5,0.5,1.2,0.8,1.9,0.8 s1.4-0.3,1.9-0.8l2.5-2.5l2.5,2.5c1,1,2.7,1,3.8,0l1.9-1.9c1-1,1-2.7,0-3.8L16.4,10.8z M8.4,9.8c0.5,0,0.9,0.4,0.9,0.9 s-0.4,0.9-0.9,0.9s-0.9-0.4-0.9-0.9C7.6,10.2,8,9.8,8.4,9.8z M7.1,17.8c-0.2,0.2-0.5,0.3-0.7,0.3S5.8,18,5.6,17.8L3.8,16 c-0.4-0.4-0.4-1.1,0-1.5L6.3,12l3.3,3.3C9.7,15.3,7.1,17.8,7.1,17.8z M10.8,13.8c-0.5,0-0.9-0.4-0.9-0.9s0.4-0.9,0.9-0.9 s0.9,0.4,0.9,0.9C11.7,13.5,11.3,13.8,10.8,13.8z M10.8,9.1c-0.5,0-0.9-0.4-0.9-0.9s0.4-0.9,0.9-0.9s0.9,0.4,0.9,0.9 S11.3,9.1,10.8,9.1z M13.2,11.5c-0.5,0-0.9-0.4-0.9-0.9s0.4-0.9,0.9-0.9s0.9,0.4,0.9,0.9C14.1,11.1,13.7,11.5,13.2,11.5z M11.9,6.3 l2.5-2.5c0.2-0.2,0.5-0.3,0.7-0.3c0.3,0,0.5,0.1,0.7,0.3l1.9,1.9c0.4,0.4,0.4,1.1,0,1.5l-2.5,2.5L11.9,6.3z"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="cheats" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:none;}.cls-2{fill-rule:evenodd;}</style></defs><g id="tela"><g><path class="cls-1" d="M0,0H24V24H0V0Z"/><path d="M2,4c0-.5,.5-1,1-1H21c.5,0,1,.4,1,1v14c0,.5-.5,1-1,1H3c-.5,0-1-.5-1-1h0V4Zm2,1v12H20V5H4Zm1,15h14v2H5v-2Z"/></g></g><g id="by_maxihplay"><path class="cls-2" d="M13.4,6.7c-1.1-1.2-2.6-.9-4,0-1.4-.9-2.9-1.1-4,0-.3,.4-.4,.9-.5,1.4-.2,1.1,.3,2.3,1.1,3.1l3.4,3.5,3.4-3.5c.8-.8,1.3-1.9,1.1-3.1,0-.5-.1-1-.5-1.4Z"/><path d="M17.1,12.2c.6,0,1,.2,1.5,.6,.4,.4,.6,.9,.6,1.5s-.2,1.1-.6,1.5c-.4,.4-.9,.6-1.5,.6s-1.3-.4-1.8-1c-.6,.7-1.2,1-1.8,1s-1.1-.2-1.4-.6c-.4-.4-.6-.9-.6-1.5s.2-1.1,.6-1.5c.4-.4,.9-.6,1.4-.6,.6,0,1.3,.4,1.8,1.1,.6-.8,1.2-1.1,1.8-1.1Zm-3.7,3c.4,0,.8-.3,1.2-.9-.4-.6-.8-.9-1.2-.9-.2,0-.3,0-.5,.1s-.3,.2-.3,.3-.1,.3-.1,.4c0,.2,.1,.5,.3,.6,.1,.3,.3,.4,.6,.4h0Zm3.9,0h.3c.1,0,.2-.1,.3-.1s.1-.1,.1-.2,.1-.2,.1-.3v-.3c0-.2-.1-.5-.3-.6-.2-.2-.4-.3-.7-.3-.4,0-.8,.3-1.2,.9,.6,.6,1,.9,1.4,.9h0Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g>
|
||||
<path fill="none" d="M0 0H24V24H0z"/>
|
||||
<path d="M16 2v2h-1v3.243c0 1.158.251 2.301.736 3.352l4.282 9.276c.347.753.018 1.644-.734 1.99-.197.092-.411.139-.628.139H5.344c-.828 0-1.5-.672-1.5-1.5 0-.217.047-.432.138-.629l4.282-9.276C8.749 9.545 9 8.401 9 7.243V4H8V2h8zm-2.612 8.001h-2.776c-.104.363-.23.721-.374 1.071l-.158.361L6.125 20h11.749l-3.954-8.567c-.214-.464-.392-.943-.532-1.432zM11 7.243c0 .253-.01.506-.029.758h2.058c-.01-.121-.016-.242-.021-.364L13 7.243V4h-2v3.243z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 580 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 2C20.5523 2 21 2.44772 21 3V8C21 8.55228 20.5523 9 20 9H15V22C15 22.5523 14.5523 23 14 23H10C9.44772 23 9 22.5523 9 22V9H3.5C2.94772 9 2.5 8.55228 2.5 8V5.61803C2.5 5.23926 2.714 4.893 3.05279 4.72361L8.5 2H20ZM15 4H8.97214L4.5 6.23607V7H11V21H13V7H15V4ZM19 4H17V7H19V4Z" fill="#000000"></path></svg>
|
||||
|
Before Width: | Height: | Size: 373 B |
@@ -1,61 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="heart-monitor-line-black.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="178.33473"
|
||||
inkscape:cy="273.8301"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="956"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="42"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:41.8148;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect234"
|
||||
width="405.06821"
|
||||
height="238.85057"
|
||||
x="53.465897"
|
||||
y="95.476929" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:14.5739;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="rect404"
|
||||
width="305.17819"
|
||||
height="33.373306"
|
||||
x="103.41091"
|
||||
y="400.26862" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:18.3381;stroke-linecap:square;stroke-dasharray:none"
|
||||
d="M 54.043377,215.32624 H 200.04731 l 38.29399,-66.32716 35.31739,131.80626 44.38011,-76.86859 h 126.75493"
|
||||
id="path359" />
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="heart" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 467.6 467.6"><defs><style>.cls-1{stroke:#000;stroke-linecap:square;stroke-width:23px;}.cls-1,.cls-2{fill:none;}</style></defs><path id="By_Kam" class="cls-2" d="M0,0H467.6V467.6H0V0Z"/><path id="tela" d="M39,68.2c0-10.8,8.9-19.5,19.3-19.5H409.3c10.7,0,19.3,8.7,19.3,19.5V341c0,10.8-8.9,19.5-19.3,19.5H58.3c-10.7,0-19.3-8.7-19.3-19.4V68.2Zm38.9,19.5v233.8H389.6V87.7H77.9Zm19.5,292.2H370.2v39H97.4v-39Z"/><path id="raio" class="cls-1" d="M55.8,204.1h118l38.3-66.3,35.3,131.8,44.4-76.9h115.8"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 600 B |
@@ -1,64 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="interface-line-black.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="13.678097"
|
||||
inkscape:cy="16.30765"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="956"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="42"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<path
|
||||
style="fill:none;stroke:#010101;stroke-width:1.30518;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 6.727468,7.7120958 c 6.477744,0 6.477744,0.045113 6.477744,0.045113"
|
||||
id="path858" />
|
||||
<circle
|
||||
style="fill:none;stroke:#010101;stroke-width:1.26308;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1155"
|
||||
cx="15.312269"
|
||||
cy="7.6872182"
|
||||
r="1.9730911" />
|
||||
<circle
|
||||
style="fill:none;stroke:#010101;stroke-width:1.26308;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1155-3"
|
||||
cx="9.0217237"
|
||||
cy="12.922025"
|
||||
r="1.9730911" />
|
||||
<path
|
||||
style="fill:none;stroke:#010101;stroke-width:1.26308;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 17.36834,12.922025 c -6.299228,0 -6.299228,0 -6.299228,0"
|
||||
id="path1211" />
|
||||
<rect
|
||||
style="fill:none;stroke:#010101;stroke-width:1.60321;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect899"
|
||||
width="18.606068"
|
||||
height="13.961316"
|
||||
x="2.6969662"
|
||||
y="3.311192" />
|
||||
<path
|
||||
style="fill:none;stroke:#010101;stroke-width:2.13371;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none"
|
||||
d="M 4.1156628,20.476585 H 19.884335"
|
||||
id="path1269" />
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="interface" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{stroke-width:1.31px;}.cls-1,.cls-2,.cls-3{fill:none;}.cls-1,.cls-3{stroke:#010101;stroke-linecap:round;stroke-linejoin:round;}.cls-3{stroke-width:1.26px;}</style></defs><g id="tela"><g><path class="cls-2" d="M0,0H24V24H0V0Z"/><path d="M2,4c0-.6,.5-1,1-1H21c.5,0,1,.4,1,1v14c0,.6-.5,1-1,1H3c-.5,0-1-.4-1-1h0V4Zm2,1v12H20V5M5,20h14v2H5v-2Z"/></g></g><g id="guias"><path id="path858" class="cls-1" d="M6.7,8.5h6.5"/><circle id="path1155" class="cls-3" cx="15.3" cy="8.4" r="2"/><circle id="path1155-3" class="cls-3" cx="9" cy="13.7" r="2"/><path id="path1211" class="cls-3" d="M17.3,13.7h-6.3"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 735 B |
@@ -22,9 +22,9 @@
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.84555556"
|
||||
inkscape:cx="91.655716"
|
||||
inkscape:cy="449.40867"
|
||||
inkscape:zoom="0.59789807"
|
||||
inkscape:cx="71.918613"
|
||||
inkscape:cy="403.07874"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="956"
|
||||
inkscape:window-x="0"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="bandaid" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 21.6 21.6" style="enable-background:new 0 0 21.6 21.6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path id="By_Maxihplay" class="st0" d="M16.4,10.8L19,8.3c1-1,1-2.7,0-3.8l-1.9-1.9c-0.5-0.5-1.2-0.8-1.9-0.8s-1.4,0.3-1.9,0.8
|
||||
l-2.5,2.5L8.3,2.6c-1-1-2.7-1-3.8,0L2.6,4.5c-1,1-1,2.7,0,3.8l2.5,2.5l-2.5,2.5c-1,1-1,2.7,0,3.8L4.5,19c0.5,0.5,1.2,0.8,1.9,0.8
|
||||
s1.4-0.3,1.9-0.8l2.5-2.5l2.5,2.5c1,1,2.7,1,3.8,0l1.9-1.9c1-1,1-2.7,0-3.8L16.4,10.8z M8.4,9.8c0.5,0,0.9,0.4,0.9,0.9
|
||||
s-0.4,0.9-0.9,0.9s-0.9-0.4-0.9-0.9C7.6,10.2,8,9.8,8.4,9.8z M7.1,17.8c-0.2,0.2-0.5,0.3-0.7,0.3S5.8,18,5.6,17.8L3.8,16
|
||||
c-0.4-0.4-0.4-1.1,0-1.5L6.3,12l3.3,3.3C9.7,15.3,7.1,17.8,7.1,17.8z M10.8,13.8c-0.5,0-0.9-0.4-0.9-0.9s0.4-0.9,0.9-0.9
|
||||
s0.9,0.4,0.9,0.9C11.7,13.5,11.3,13.8,10.8,13.8z M10.8,9.1c-0.5,0-0.9-0.4-0.9-0.9s0.4-0.9,0.9-0.9s0.9,0.4,0.9,0.9
|
||||
S11.3,9.1,10.8,9.1z M13.2,11.5c-0.5,0-0.9-0.4-0.9-0.9s0.4-0.9,0.9-0.9s0.9,0.4,0.9,0.9C14.1,11.1,13.7,11.5,13.2,11.5z M11.9,6.3
|
||||
l2.5-2.5c0.2-0.2,0.5-0.3,0.7-0.3c0.3,0,0.5,0.1,0.7,0.3l1.9,1.9c0.4,0.4,0.4,1.1,0,1.5l-2.5,2.5L11.9,6.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="cheats" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1,.cls-2{fill:#fff;}.cls-2{fill-rule:evenodd;}.cls-3{fill:none;}</style></defs><g id="tela"><g><path class="cls-3" d="M0,0H24V24H0V0Z"/><path class="cls-1" d="M2,4c0-.5,.5-1,1-1H21c.5,0,1,.4,1,1v14c0,.5-.5,1-1,1H3c-.5,0-1-.5-1-1h0V4Zm2,1v12H20V5H4Zm1,15h14v2H5v-2Z"/></g></g><g id="by_maxihplay"><path class="cls-2" d="M13.4,6.7c-1.1-1.2-2.6-.71-4,.19-1.4-.9-2.9-1.29-4-.19-.3,.4-.4,.9-.5,1.4-.2,1.1,.3,2.3,1.1,3.1l3.4,3.5,3.4-3.5c.8-.8,1.3-1.9,1.1-3.1,0-.5-.1-1-.5-1.4Z"/><path class="cls-1" d="M17.1,12.2c.6,0,1,.2,1.5,.6,.4,.4,.6,.9,.6,1.5s-.2,1.1-.6,1.5c-.4,.4-.9,.6-1.5,.6s-1.3-.4-1.8-1c-.6,.7-1.2,1-1.8,1s-1.1-.2-1.4-.6c-.4-.4-.6-.9-.6-1.5s.2-1.1,.6-1.5c.4-.4,.9-.6,1.4-.6,.6,0,1.3,.4,1.8,1.1,.6-.8,1.2-1.1,1.8-1.1Zm-3.7,3c.4,0,.8-.3,1.2-.9-.4-.6-.8-.9-1.2-.9-.2,0-.3,0-.5,.1s-.3,.2-.3,.3-.1,.3-.1,.4c0,.2,.1,.5,.3,.6,.1,.3,.3,.4,.6,.4h0Zm3.9,0h.3c.1,0,.2-.1,.3-.1s.1-.1,.1-.2,.1-.2,.1-.3v-.3c0-.2-.1-.5-.3-.6-.2-.2-.4-.3-.7-.3-.4,0-.8,.3-1.2,.9,.6,.6,1,.9,1.4,.9h0Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g>
|
||||
<path fill="none" d="M0 0H24V24H0z"/>
|
||||
<path fill="#ffffff" d="M16 2v2h-1v3.243c0 1.158.251 2.301.736 3.352l4.282 9.276c.347.753.018 1.644-.734 1.99-.197.092-.411.139-.628.139H5.344c-.828 0-1.5-.672-1.5-1.5 0-.217.047-.432.138-.629l4.282-9.276C8.749 9.545 9 8.401 9 7.243V4H8V2h8zm-2.612 8.001h-2.776c-.104.363-.23.721-.374 1.071l-.158.361L6.125 20h11.749l-3.954-8.567c-.214-.464-.392-.943-.532-1.432zM11 7.243c0 .253-.01.506-.029.758h2.058c-.01-.121-.016-.242-.021-.364L13 7.243V4h-2v3.243z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 595 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 2C20.5523 2 21 2.44772 21 3V8C21 8.55228 20.5523 9 20 9H15V22C15 22.5523 14.5523 23 14 23H10C9.44772 23 9 22.5523 9 22V9H3.5C2.94772 9 2.5 8.55228 2.5 8V5.61803C2.5 5.23926 2.714 4.893 3.05279 4.72361L8.5 2H20ZM15 4H8.97214L4.5 6.23607V7H11V21H13V7H15V4ZM19 4H17V7H19V4Z" fill="#ffffff"></path></svg>
|
||||
|
Before Width: | Height: | Size: 373 B |
@@ -1,61 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="heart-monitor-line.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="177.5677"
|
||||
inkscape:cy="273.8301"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="956"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="42"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:none;stroke:#ffffff;stroke-width:41.8148;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect234"
|
||||
width="405.06821"
|
||||
height="238.85057"
|
||||
x="53.465897"
|
||||
y="95.476929" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:14.5739;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="rect404"
|
||||
width="305.17819"
|
||||
height="33.373306"
|
||||
x="103.41091"
|
||||
y="400.26862" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:18.3381;stroke-linecap:square;stroke-dasharray:none"
|
||||
d="M 54.043377,215.32624 H 200.04731 l 38.29399,-66.32716 35.31739,131.80626 44.38011,-76.86859 h 126.75493"
|
||||
id="path359" />
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="heart" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 467.6 467.6"><defs><style>.cls-1{fill:#fff;}.cls-2,.cls-3{fill:none;}.cls-3{stroke:#fff;stroke-linecap:square;stroke-width:23px;}</style></defs><path id="By_Kam" class="cls-2" d="M0,0H467.6V467.6H0V0Z"/><path id="tela" class="cls-1" d="M39,68.2c0-10.8,8.9-19.5,19.3-19.5H409.3c10.7,0,19.3,8.7,19.3,19.5V341c0,10.8-8.9,19.5-19.3,19.5H58.3c-10.7,0-19.3-8.7-19.3-19.4V68.2Zm38.9,19.5v233.8H389.6V87.7H77.9Zm19.5,292.2H370.2v39H97.4v-39Z"/><path id="raio" class="cls-3" d="M55.8,204.1h118l38.3-66.3,35.3,131.8,44.4-76.9h115.8"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 632 B |
@@ -1,64 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="interface-line.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="13.633903"
|
||||
inkscape:cy="16.30765"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="956"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="42"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<path
|
||||
style="fill:none;stroke:#fefefe;stroke-width:1.30518;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 6.727468,7.7120958 c 6.477744,0 6.477744,0.045113 6.477744,0.045113"
|
||||
id="path858" />
|
||||
<circle
|
||||
style="fill:none;stroke:#fefefe;stroke-width:1.26308;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1155"
|
||||
cx="15.312269"
|
||||
cy="7.6872182"
|
||||
r="1.9730911" />
|
||||
<circle
|
||||
style="fill:none;stroke:#fefefe;stroke-width:1.26308;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1155-3"
|
||||
cx="9.0217237"
|
||||
cy="12.922025"
|
||||
r="1.9730911" />
|
||||
<path
|
||||
style="fill:none;stroke:#fefefe;stroke-width:1.26308;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 17.36834,12.922025 c -6.299228,0 -6.299228,0 -6.299228,0"
|
||||
id="path1211" />
|
||||
<rect
|
||||
style="fill:none;stroke:#fefefe;stroke-width:1.60321;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect899"
|
||||
width="18.606068"
|
||||
height="13.961316"
|
||||
x="2.6969662"
|
||||
y="3.311192" />
|
||||
<path
|
||||
style="fill:none;stroke:#fefefe;stroke-width:2.13371;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none"
|
||||
d="M 4.1156628,20.476585 H 19.884335"
|
||||
id="path1269" />
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="interface" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#fff;}.cls-2{stroke-width:1.31px;}.cls-2,.cls-3,.cls-4{fill:none;}.cls-2,.cls-4{stroke:#fff;stroke-linecap:round;stroke-linejoin:round;}.cls-4{stroke-width:1.26px;}</style></defs><g id="tela"><g><path class="cls-3" d="M0,0H24V24H0V0Z"/><path class="cls-1" d="M2,4c0-.6,.5-1,1-1H21c.5,0,1,.4,1,1v14c0,.6-.5,1-1,1H3c-.5,0-1-.4-1-1h0V4Zm2,1v12H20V5M5,20h14v2H5v-2Z"/></g></g><g id="guias"><path id="path858" class="cls-2" d="M6.7,8.5h6.5"/><circle id="path1155" class="cls-4" cx="15.3" cy="8.4" r="2"/><circle id="path1155-3" class="cls-4" cx="9" cy="13.7" r="2"/><path id="path1211" class="cls-4" d="M17.3,13.7h-6.3"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 764 B |
@@ -6,7 +6,9 @@
|
||||
<file>icons/black/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/black/svg/artboard-2-line.svg</file>
|
||||
<file>icons/black/svg/at.svg</file>
|
||||
<file>icons/black/svg/band-aid-line.svg</file>
|
||||
<file>icons/black/svg/brush-line.svg</file>
|
||||
<file>icons/black/svg/cheats-line.svg</file>
|
||||
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
|
||||
<file>icons/black/svg/chip-line.svg</file>
|
||||
<file>icons/black/svg/close-line.svg</file>
|
||||
@@ -28,7 +30,6 @@
|
||||
<file>icons/black/svg/file-settings-line.svg</file>
|
||||
<file>icons/black/svg/filter-line.svg</file>
|
||||
<file>icons/black/svg/flashlight-line.svg</file>
|
||||
<file>icons/black/svg/flask-line.svg</file>
|
||||
<file>icons/black/svg/floppy-in-line.svg</file>
|
||||
<file>icons/black/svg/floppy-out-line.svg</file>
|
||||
<file>icons/black/svg/folder-add-line.svg</file>
|
||||
@@ -39,7 +40,6 @@
|
||||
<file>icons/black/svg/function-line.svg</file>
|
||||
<file>icons/black/svg/github.svg</file>
|
||||
<file>icons/black/svg/global-line.svg</file>
|
||||
<file>icons/black/svg/hammer-line.svg</file>
|
||||
<file>icons/black/svg/heart-circle-line.svg</file>
|
||||
<file>icons/black/svg/heart-monitor-line.svg</file>
|
||||
<file>icons/black/svg/image-fill.svg</file>
|
||||
@@ -78,7 +78,9 @@
|
||||
<file>icons/white/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/white/svg/artboard-2-line.svg</file>
|
||||
<file>icons/white/svg/at.svg</file>
|
||||
<file>icons/white/svg/band-aid-line.svg</file>
|
||||
<file>icons/white/svg/brush-line.svg</file>
|
||||
<file>icons/white/svg/cheats-line.svg</file>
|
||||
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
|
||||
<file>icons/white/svg/chip-line.svg</file>
|
||||
<file>icons/white/svg/close-line.svg</file>
|
||||
@@ -100,7 +102,6 @@
|
||||
<file>icons/white/svg/file-settings-line.svg</file>
|
||||
<file>icons/white/svg/filter-line.svg</file>
|
||||
<file>icons/white/svg/flashlight-line.svg</file>
|
||||
<file>icons/white/svg/flask-line.svg</file>
|
||||
<file>icons/white/svg/floppy-in-line.svg</file>
|
||||
<file>icons/white/svg/floppy-out-line.svg</file>
|
||||
<file>icons/white/svg/folder-add-line.svg</file>
|
||||
@@ -111,7 +112,6 @@
|
||||
<file>icons/white/svg/function-line.svg</file>
|
||||
<file>icons/white/svg/github.svg</file>
|
||||
<file>icons/white/svg/global-line.svg</file>
|
||||
<file>icons/white/svg/hammer-line.svg</file>
|
||||
<file>icons/white/svg/heart-circle-line.svg</file>
|
||||
<file>icons/white/svg/heart-monitor-line.svg</file>
|
||||
<file>icons/white/svg/image-fill.svg</file>
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
#ifdef ENABLE_ACHIEVEMENTS
|
||||
|
||||
#include "Achievements.h"
|
||||
#include "CDVD/IsoFS/IsoFS.h"
|
||||
#include "CDVD/IsoFS/IsoFSCDVD.h"
|
||||
#include "CDVD/CDVD.h"
|
||||
#include "Elfheader.h"
|
||||
#include "Host.h"
|
||||
#include "ImGui/FullscreenUI.h"
|
||||
@@ -36,6 +35,7 @@
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/General.h"
|
||||
#include "common/HTTPDownloader.h"
|
||||
#include "common/MD5Digest.h"
|
||||
@@ -121,7 +121,6 @@ namespace Achievements
|
||||
static void GetLbInfoCallback(s32 status_code, const std::string& content_type, Common::HTTPDownloader::Request::Data data);
|
||||
static void GetPatches(u32 game_id);
|
||||
static std::string_view GetELFNameForHash(const std::string& elf_path);
|
||||
static std::optional<std::vector<u8>> ReadELFFromCurrentDisc(const std::string& elf_path);
|
||||
static std::string GetGameHash();
|
||||
static void SetChallengeMode(bool enabled);
|
||||
static void SendGetGameId();
|
||||
@@ -1356,47 +1355,6 @@ std::string_view Achievements::GetELFNameForHash(const std::string& elf_path)
|
||||
return std::string_view(elf_path).substr(start, end - start);
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> Achievements::ReadELFFromCurrentDisc(const std::string& elf_path)
|
||||
{
|
||||
std::optional<std::vector<u8>> ret;
|
||||
|
||||
// ELF suffix hack. Taken from CDVD::loadElf
|
||||
// MLB2k6 has an elf whose suffix is actually ;2
|
||||
std::string filepath(elf_path);
|
||||
const std::string::size_type semi_pos = filepath.rfind(';');
|
||||
if (semi_pos != std::string::npos && std::string_view(filepath).substr(semi_pos) != ";1")
|
||||
{
|
||||
Console.Warning(fmt::format("(Achievements) Non-conforming version suffix ({}) detected and replaced.", elf_path));
|
||||
filepath.erase(semi_pos);
|
||||
filepath += ";1";
|
||||
}
|
||||
|
||||
IsoFSCDVD isofs;
|
||||
IsoFile file(isofs);
|
||||
Error error;
|
||||
if (!file.open(filepath, &error))
|
||||
{
|
||||
Console.Error(fmt::format("(Achievements) Failed to open ELF '{}' on disc: {}", elf_path, error.GetDescription()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
const u32 size = file.getLength();
|
||||
ret = std::vector<u8>();
|
||||
ret->resize(size);
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
const s32 bytes_read = file.read(ret->data(), static_cast<s32>(size));
|
||||
if (bytes_read != static_cast<s32>(size))
|
||||
{
|
||||
Console.Error(fmt::format("(Achievements) Only read {} of {} bytes of ELF '{}'", bytes_read, size, elf_path));
|
||||
ret.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Achievements::GetGameHash()
|
||||
{
|
||||
const std::string elf_path = VMManager::GetDiscELF();
|
||||
@@ -1404,34 +1362,37 @@ std::string Achievements::GetGameHash()
|
||||
return {};
|
||||
|
||||
// this.. really shouldn't be invalid
|
||||
const std::string_view name_for_hash(GetELFNameForHash(elf_path));
|
||||
const std::string_view name_for_hash = GetELFNameForHash(elf_path);
|
||||
if (name_for_hash.empty())
|
||||
return {};
|
||||
|
||||
std::optional<std::vector<u8>> elf_data(ReadELFFromCurrentDisc(elf_path));
|
||||
if (!elf_data.has_value())
|
||||
ElfObject elfo;
|
||||
Error error;
|
||||
if (!cdvdLoadElf(&elfo, elf_path, false, &error))
|
||||
{
|
||||
Console.Error(fmt::format("(Achievements) Failed to read ELF '{}' on disc: {}", elf_path, error.GetDescription()));
|
||||
return {};
|
||||
}
|
||||
|
||||
// See rcheevos hash.c - rc_hash_ps2().
|
||||
const u32 MAX_HASH_SIZE = 64 * 1024 * 1024;
|
||||
const u32 hash_size = std::min<u32>(static_cast<u32>(elf_data->size()), MAX_HASH_SIZE);
|
||||
pxAssert(hash_size <= elf_data->size());
|
||||
const u32 hash_size = std::min<u32>(elfo.GetSize(), MAX_HASH_SIZE);
|
||||
pxAssert(hash_size <= elfo.GetSize());
|
||||
|
||||
MD5Digest digest;
|
||||
if (!name_for_hash.empty())
|
||||
digest.Update(name_for_hash.data(), static_cast<u32>(name_for_hash.size()));
|
||||
if (hash_size > 0)
|
||||
digest.Update(elf_data->data(), hash_size);
|
||||
digest.Update(elfo.GetData().data(), hash_size);
|
||||
|
||||
u8 hash[16];
|
||||
digest.Final(hash);
|
||||
|
||||
std::string hash_str(
|
||||
const std::string hash_str =
|
||||
StringUtil::StdStringFromFormat("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", hash[0], hash[1], hash[2],
|
||||
hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]));
|
||||
hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]);
|
||||
|
||||
Console.WriteLn("Hash for '%.*s' (%zu bytes, %u bytes hashed): %s", static_cast<int>(name_for_hash.size()), name_for_hash.data(),
|
||||
elf_data->size(), hash_size, hash_str.c_str());
|
||||
Console.WriteLn(fmt::format("Hash for '{}' ({} bytes, {} bytes hashed): {}", name_for_hash, elfo.GetSize(), hash_size, hash_str));
|
||||
return hash_str;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2021 PCSX2 Dev Team
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
@@ -14,6 +14,17 @@
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "CDVD/CDVD.h"
|
||||
#include "CDVD/Ps1CD.h"
|
||||
#include "CDVD/CDVD_internal.h"
|
||||
#include "CDVD/IsoReader.h"
|
||||
#include "CDVD/IsoFileFormats.h"
|
||||
#include "GS.h"
|
||||
#include "Elfheader.h"
|
||||
#include "ps2/BiosTools.h"
|
||||
#include "Recording/InputRecording.h"
|
||||
#include "Host.h"
|
||||
#include "R3000A.h"
|
||||
#include "Common.h"
|
||||
#include "IopHw.h"
|
||||
@@ -21,25 +32,15 @@
|
||||
#include "VMManager.h"
|
||||
#include "Sio.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
#include "common/Error.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Threading.h"
|
||||
|
||||
#include "Ps1CD.h"
|
||||
#include "CDVD.h"
|
||||
#include "CDVD_internal.h"
|
||||
#include "IsoFileFormats.h"
|
||||
|
||||
#include "GS.h" // for gsVideoMode
|
||||
#include "Elfheader.h"
|
||||
#include "ps2/BiosTools.h"
|
||||
#include "Recording/InputRecording.h"
|
||||
#include "Host.h"
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
cdvdStruct cdvd;
|
||||
|
||||
@@ -383,49 +384,72 @@ s32 cdvdWriteConfig(const u8* config)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool cdvdLoadElf(ElfObject* elfo, std::string elfpath, bool isPSXElf, Error* error)
|
||||
static bool cdvdUncheckedLoadDiscElf(ElfObject* elfo, IsoReader& isor, const std::string_view& elfpath, bool isPSXElf, Error* error)
|
||||
{
|
||||
// Strip out cdrom: prefix, and any leading slashes.
|
||||
size_t start_pos = (elfpath[5] == '0') ? 7 : 6;
|
||||
while (start_pos < elfpath.size() && (elfpath[start_pos] == '\\' || elfpath[start_pos] == '/'))
|
||||
start_pos++;
|
||||
|
||||
// Strip out any version information. Some games use ;2 (MLB2k6), others put multiple versions in
|
||||
// (Syphon Filter Omega Strain). The PS2 BIOS appears to ignore the suffix entirely, so we'll do
|
||||
// the same, and hope that no games actually have multiple ELFs with different versions.
|
||||
// Previous notes:
|
||||
// Mimic PS2 behavior!
|
||||
// Much trial-and-error with changing the ISOFS and BOOT2 contents of an image have shown that
|
||||
// the PS2 BIOS performs the peculiar task of *ignoring* the version info from the parsed BOOT2
|
||||
// filename *and* the ISOFS, when loading the game's ELF image. What this means is:
|
||||
//
|
||||
// 1. a valid PS2 ELF can have any version (ISOFS), and the version need not match the one in SYSTEM.CNF.
|
||||
// 2. the version info on the file in the BOOT2 parameter of SYSTEM.CNF can be missing, 10 chars long,
|
||||
// or anything else. Its all ignored.
|
||||
// 3. Games loading their own files do *not* exhibit this behavior; likely due to using newer IOP modules
|
||||
// or lower level filesystem APIs (fortunately that doesn't affect us).
|
||||
//
|
||||
size_t length = elfpath.length() - start_pos;
|
||||
const size_t semi_pos = elfpath.find(';', start_pos);
|
||||
if (semi_pos != std::string::npos)
|
||||
length = semi_pos - start_pos;
|
||||
|
||||
std::string iso_filename(elfpath.substr(start_pos, length));
|
||||
DevCon.WriteLn(fmt::format("cvdLoadElf(): '{}' -> '{}' in ISO.", elfpath, iso_filename));
|
||||
if (iso_filename.empty())
|
||||
{
|
||||
Error::SetString(error, "ISO filename is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return elfo->OpenIsoFile(std::move(iso_filename), isor, isPSXElf, error);
|
||||
}
|
||||
|
||||
bool cdvdLoadElf(ElfObject* elfo, const std::string_view& elfpath, bool isPSXElf, Error* error)
|
||||
{
|
||||
if (StringUtil::StartsWith(elfpath, "host:"))
|
||||
{
|
||||
std::string host_filename(elfpath.substr(5));
|
||||
if (!elfo->OpenFile(host_filename, isPSXElf, error))
|
||||
return elfo->OpenFile(host_filename, isPSXElf, error);
|
||||
}
|
||||
else if (StringUtil::StartsWith(elfpath, "cdrom:") || StringUtil::StartsWith(elfpath, "cdrom0:"))
|
||||
{
|
||||
IsoReader isor;
|
||||
if (!isor.Open(error))
|
||||
return false;
|
||||
|
||||
return cdvdLoadDiscElf(elfo, isor, elfpath, isPSXElf, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mimic PS2 behavior!
|
||||
// Much trial-and-error with changing the ISOFS and BOOT2 contents of an image have shown that
|
||||
// the PS2 BIOS performs the peculiar task of *ignoring* the version info from the parsed BOOT2
|
||||
// filename *and* the ISOFS, when loading the game's ELF image. What this means is:
|
||||
//
|
||||
// 1. a valid PS2 ELF can have any version (ISOFS), and the version need not match the one in SYSTEM.CNF.
|
||||
// 2. the version info on the file in the BOOT2 parameter of SYSTEM.CNF can be missing, 10 chars long,
|
||||
// or anything else. Its all ignored.
|
||||
// 3. Games loading their own files do *not* exhibit this behavior; likely due to using newer IOP modules
|
||||
// or lower level filesystem APIs (fortunately that doesn't affect us).
|
||||
//
|
||||
// FIXME: Properly mimicing this behavior is troublesome since we need to add support for "ignoring"
|
||||
// version information when doing file searches. I'll add this later. For now, assuming a ;1 should
|
||||
// be sufficient (no known games have their ELF binary as anything but version ;1)
|
||||
const std::string::size_type semi_pos = elfpath.rfind(';');
|
||||
if (semi_pos != std::string::npos && std::string_view(elfpath).substr(semi_pos) != ";1")
|
||||
{
|
||||
Console.WriteLn(Color_Blue, "(LoadELF) Non-conforming version suffix (%s) detected and replaced.", elfpath.c_str());
|
||||
elfpath.erase(semi_pos);
|
||||
elfpath += ";1";
|
||||
}
|
||||
|
||||
// Fix cdrom:path, the iso reader doesn't like it.
|
||||
if (StringUtil::StartsWith(elfpath, "cdrom:") && elfpath[6] != '\\' && elfpath[6] != '/')
|
||||
elfpath.insert(6, 1, '\\');
|
||||
|
||||
IsoFSCDVD isofs;
|
||||
IsoFile file(isofs);
|
||||
if (!file.open(elfpath, error) || !elfo->OpenIsoFile(elfpath, file, isPSXElf, error))
|
||||
return false;
|
||||
Console.Error(fmt::format("cdvdLoadElf(): Unknown device in ELF path '{}'", elfpath));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
bool cdvdLoadDiscElf(ElfObject* elfo, IsoReader& isor, const std::string_view& elfpath, bool isPSXElf, Error* error)
|
||||
{
|
||||
if (!StringUtil::StartsWith(elfpath, "cdrom:") && !StringUtil::StartsWith(elfpath, "cdrom0:"))
|
||||
return false;
|
||||
|
||||
return cdvdUncheckedLoadDiscElf(elfo, isor, elfpath, isPSXElf, error);
|
||||
}
|
||||
|
||||
u32 cdvdGetElfCRC(const std::string& path)
|
||||
@@ -437,65 +461,57 @@ u32 cdvdGetElfCRC(const std::string& path)
|
||||
return elfo.GetCRC();
|
||||
}
|
||||
|
||||
static CDVDDiscType GetPS2ElfName(std::string* name, std::string* version)
|
||||
static CDVDDiscType GetPS2ElfName(IsoReader& isor, std::string* name, std::string* version, Error* error)
|
||||
{
|
||||
CDVDDiscType retype = CDVDDiscType::Other;
|
||||
name->clear();
|
||||
version->clear();
|
||||
|
||||
Error error;
|
||||
IsoFSCDVD isofs;
|
||||
IsoFile file(isofs);
|
||||
if (!file.open("SYSTEM.CNF;1", &error))
|
||||
{
|
||||
Console.Error(fmt::format("(GetElfName) Failed to open SYSTEM.CNF: {}", error.GetDescription()));
|
||||
return CDVDDiscType::Other;
|
||||
}
|
||||
|
||||
const int size = file.getLength();
|
||||
if (size == 0)
|
||||
std::vector<u8> data;
|
||||
if (!isor.ReadFile("SYSTEM.CNF", &data, error))
|
||||
return CDVDDiscType::Other;
|
||||
|
||||
while (!file.eof())
|
||||
const std::vector<std::string_view> lines =
|
||||
StringUtil::SplitString(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()), '\n');
|
||||
for (size_t lineno = 0; lineno < lines.size(); lineno++)
|
||||
{
|
||||
const std::string line(file.readLine());
|
||||
const std::string_view line = StringUtil::StripWhitespace(lines[lineno]);
|
||||
std::string_view key, value;
|
||||
if (!StringUtil::ParseAssignmentString(line, &key, &value))
|
||||
continue;
|
||||
|
||||
if (value.empty() && file.getLength() != file.getSeekPos())
|
||||
{ // Some games have a character on the last line of the file, don't print the error in those cases.
|
||||
// Some games have a character on the last line of the file, don't print the error in those cases.
|
||||
if (value.empty() && (lineno == (lines.size() - 1)))
|
||||
{
|
||||
Console.Warning("(SYSTEM.CNF) Unusual or malformed entry in SYSTEM.CNF ignored:");
|
||||
Console.Indent().WriteLn(line);
|
||||
Console.Indent().WriteLn(std::string(line));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "BOOT2")
|
||||
{
|
||||
Console.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PS2 Disc = {}", value));
|
||||
DevCon.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PS2 Disc = {}", value));
|
||||
*name = value;
|
||||
retype = CDVDDiscType::PS2Disc;
|
||||
}
|
||||
else if (key == "BOOT")
|
||||
{
|
||||
Console.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PSX/PSone Disc = {}", value));
|
||||
DevCon.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PSX/PSone Disc = {}", value));
|
||||
*name = value;
|
||||
retype = CDVDDiscType::PS1Disc;
|
||||
}
|
||||
else if (key == "VMODE")
|
||||
{
|
||||
Console.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Disc region type = {}", value));
|
||||
DevCon.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Disc region type = {}", value));
|
||||
}
|
||||
else if (key == "VER")
|
||||
{
|
||||
Console.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Software version = {}", value));
|
||||
DevCon.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Software version = {}", value));
|
||||
*version = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (retype == CDVDDiscType::Other)
|
||||
Console.Error("(GetElfName) Disc image is *not* a PlayStation or PS2 game!");
|
||||
|
||||
Error::SetString(error, "Disc image is *not* a PlayStation or PS2 game");
|
||||
return retype;
|
||||
}
|
||||
|
||||
@@ -554,8 +570,13 @@ static std::string ExecutablePathToSerial(const std::string& path)
|
||||
void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, u32* out_crc,
|
||||
CDVDDiscType* out_disc_type)
|
||||
{
|
||||
Error error;
|
||||
IsoReader isor;
|
||||
|
||||
std::string elfpath, version;
|
||||
const CDVDDiscType disc_type = GetPS2ElfName(&elfpath, &version);
|
||||
CDVDDiscType disc_type = CDVDDiscType::Other;
|
||||
if (!isor.Open(&error) || (disc_type = GetPS2ElfName(isor, &elfpath, &version, &error)) == CDVDDiscType::Other)
|
||||
Console.Error(fmt::format("Failed to get ELF name: {}", error.GetDescription()));
|
||||
|
||||
// Don't bother parsing it if we don't need the CRC.
|
||||
if (out_crc)
|
||||
@@ -564,10 +585,9 @@ void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::st
|
||||
|
||||
if (disc_type == CDVDDiscType::PS2Disc || disc_type == CDVDDiscType::PS1Disc)
|
||||
{
|
||||
Error error;
|
||||
ElfObject elfo;
|
||||
const bool isPSXElf = (disc_type == CDVDDiscType::PS1Disc);
|
||||
if (!cdvdLoadElf(&elfo, elfpath, isPSXElf, &error))
|
||||
if (!cdvdLoadDiscElf(&elfo, isor, elfpath, isPSXElf, &error))
|
||||
Console.Error(fmt::format("Failed to load ELF info for {}: {}", elfpath, error.GetDescription()));
|
||||
else
|
||||
crc = elfo.GetCRC();
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
class Error;
|
||||
class ElfObject;
|
||||
class IsoReader;
|
||||
|
||||
#define btoi(b) ((b) / 16 * 10 + (b) % 16) /* BCD to u_char */
|
||||
#define itob(i) ((i) / 10 * 16 + (i) % 10) /* u_char to BCD */
|
||||
@@ -190,7 +191,8 @@ extern void cdvdWrite(u8 key, u8 rt);
|
||||
extern void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, u32* out_crc,
|
||||
CDVDDiscType* out_disc_type);
|
||||
extern u32 cdvdGetElfCRC(const std::string& path);
|
||||
extern bool cdvdLoadElf(ElfObject* elfo, std::string filename, bool isPSXElf, Error* error);
|
||||
extern bool cdvdLoadElf(ElfObject* elfo, const std::string_view& elfpath, bool isPSXElf, Error* error);
|
||||
extern bool cdvdLoadDiscElf(ElfObject* elfo, IsoReader& isor, const std::string_view& elfpath, bool isPSXElf, Error* error);
|
||||
|
||||
extern s32 cdvdCtrlTrayOpen();
|
||||
extern s32 cdvdCtrlTrayClose();
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
/*
|
||||
Interrupts - values are flag bits.
|
||||
|
||||
|
||||
@@ -16,29 +16,29 @@
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#define ENABLE_TIMESTAMPS
|
||||
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include "IsoFS/IsoFS.h"
|
||||
#include "IsoFS/IsoFSCDVD.h"
|
||||
#include "IsoFileFormats.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "CDVD/CDVDcommon.h"
|
||||
#include "CDVD/IsoReader.h"
|
||||
#include "CDVD/IsoFileFormats.h"
|
||||
#include "DebugTools/SymbolMap.h"
|
||||
#include "Config.h"
|
||||
#include "Host.h"
|
||||
#include "IconsFontAwesome5.h"
|
||||
|
||||
CDVD_API* CDVD = NULL;
|
||||
#include "common/Assertions.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <time.h>
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#define ENABLE_TIMESTAMPS
|
||||
|
||||
CDVD_API* CDVD = nullptr;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// diskTypeCached
|
||||
@@ -71,37 +71,35 @@ static void CheckNullCDVD()
|
||||
//
|
||||
static int CheckDiskTypeFS(int baseType)
|
||||
{
|
||||
IsoFSCDVD isofs;
|
||||
IsoDirectory rootdir(isofs);
|
||||
if (rootdir.OpenRootDirectory())
|
||||
IsoReader isor;
|
||||
if (isor.Open())
|
||||
{
|
||||
if (IsoFile file(isofs); file.open(rootdir, "SYSTEM.CNF;1"))
|
||||
std::vector<u8> data;
|
||||
if (isor.ReadFile("SYSTEM.CNF", &data))
|
||||
{
|
||||
const int size = file.getLength();
|
||||
const std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size + 1);
|
||||
file.read(buffer.get(), size);
|
||||
buffer[size] = '\0';
|
||||
|
||||
char* pos = strstr(buffer.get(), "BOOT2");
|
||||
if (pos == NULL)
|
||||
if (StringUtil::ContainsSubString(data, "BOOT2"))
|
||||
{
|
||||
pos = strstr(buffer.get(), "BOOT");
|
||||
if (pos == NULL)
|
||||
return CDVD_TYPE_ILLEGAL;
|
||||
// PS2 DVD/CD.
|
||||
return (baseType == CDVD_TYPE_DETCTCD) ? CDVD_TYPE_PS2CD : CDVD_TYPE_PS2DVD;
|
||||
}
|
||||
|
||||
if (StringUtil::ContainsSubString(data, "BOOT"))
|
||||
{
|
||||
// PSX CD.
|
||||
return CDVD_TYPE_PSCD;
|
||||
}
|
||||
|
||||
return (baseType == CDVD_TYPE_DETCTCD) ? CDVD_TYPE_PS2CD : CDVD_TYPE_PS2DVD;
|
||||
return CDVD_TYPE_ILLEGAL;
|
||||
}
|
||||
|
||||
// PS2 Linux disc 2, doesn't have a System.CNF or a normal ELF
|
||||
if (rootdir.Exists("P2L_0100.02;1"))
|
||||
if (isor.FileExists("P2L_0100.02"))
|
||||
return CDVD_TYPE_PS2DVD;
|
||||
|
||||
if (rootdir.Exists("PSX.EXE;1"))
|
||||
if (isor.FileExists("PSX.EXE"))
|
||||
return CDVD_TYPE_PSCD;
|
||||
|
||||
if (rootdir.Exists("VIDEO_TS/VIDEO_TS.IFO;1"))
|
||||
if (isor.FileExists("VIDEO_TS/VIDEO_TS.IFO"))
|
||||
return CDVD_TYPE_DVDV;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
|
||||
enum IsoFS_Type
|
||||
{
|
||||
FStype_ISO9660 = 1,
|
||||
FStype_Joliet = 2,
|
||||
};
|
||||
|
||||
class IsoDirectory final
|
||||
{
|
||||
public:
|
||||
SectorSource& internalReader;
|
||||
std::vector<IsoFileDescriptor> files;
|
||||
IsoFS_Type m_fstype = FStype_ISO9660;
|
||||
|
||||
public:
|
||||
IsoDirectory(SectorSource& r);
|
||||
~IsoDirectory();
|
||||
|
||||
bool OpenRootDirectory(Error* error = nullptr);
|
||||
bool Open(const IsoFileDescriptor& directoryEntry, Error* error = nullptr);
|
||||
|
||||
std::string FStype_ToString() const;
|
||||
SectorSource& GetReader() const { return internalReader; }
|
||||
|
||||
bool Exists(const std::string_view& filePath) const;
|
||||
bool IsFile(const std::string_view& filePath) const;
|
||||
bool IsDir(const std::string_view& filePath) const;
|
||||
|
||||
u32 GetFileSize(const std::string_view& filePath) const;
|
||||
|
||||
std::optional<IsoFileDescriptor> FindFile(const std::string_view& filePath) const;
|
||||
|
||||
protected:
|
||||
const IsoFileDescriptor& GetEntry(size_t index) const;
|
||||
|
||||
int GetIndexOf(const std::string_view& fileName) const;
|
||||
};
|
||||
@@ -1,322 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "IsoFS.h"
|
||||
#include "IsoFile.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// IsoDirectory
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//u8 filesystemType; // 0x01 = ISO9660, 0x02 = Joliet, 0xFF = NULL
|
||||
//u8 volID[5]; // "CD001"
|
||||
|
||||
|
||||
std::string IsoDirectory::FStype_ToString() const
|
||||
{
|
||||
switch (m_fstype)
|
||||
{
|
||||
case FStype_ISO9660:
|
||||
return "ISO9660";
|
||||
case FStype_Joliet:
|
||||
return "Joliet";
|
||||
}
|
||||
|
||||
return StringUtil::StdStringFromFormat("Unrecognized Code (0x%x)", m_fstype);
|
||||
}
|
||||
|
||||
// Used to load the Root directory from an image
|
||||
IsoDirectory::IsoDirectory(SectorSource& r)
|
||||
: internalReader(r)
|
||||
{
|
||||
}
|
||||
|
||||
IsoDirectory::~IsoDirectory() = default;
|
||||
|
||||
bool IsoDirectory::OpenRootDirectory(Error* error /* = nullptr */)
|
||||
{
|
||||
IsoFileDescriptor rootDirEntry;
|
||||
bool isValid = false;
|
||||
bool done = false;
|
||||
uint i = 16;
|
||||
|
||||
m_fstype = FStype_ISO9660;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
u8 sector[2048];
|
||||
// If this fails, we're not reading an iso, or it's bad.
|
||||
if (!internalReader.readSector(sector, i))
|
||||
break;
|
||||
|
||||
if (memcmp(§or[1], "CD001", 5) == 0)
|
||||
{
|
||||
switch (sector[0])
|
||||
{
|
||||
case 0:
|
||||
DevCon.WriteLn(Color_Green, "(IsoFS) Block 0x%x: Boot partition info.", i);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
DevCon.WriteLn("(IsoFS) Block 0x%x: Primary partition info.", i);
|
||||
rootDirEntry.Load(sector + 156, 38);
|
||||
isValid = true;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Probably means Joliet (long filenames support), which PCSX2 doesn't care about.
|
||||
DevCon.WriteLn(Color_Green, "(IsoFS) Block 0x%x: Extended partition info.", i);
|
||||
m_fstype = FStype_Joliet;
|
||||
break;
|
||||
|
||||
case 0xff:
|
||||
// Null terminator. End of partition information.
|
||||
done = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.Error("(IsoFS) Unknown partition type ID=%d, encountered at block 0x%x", sector[0], i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sector[9] = 0;
|
||||
Console.Error("(IsoFS) Invalid partition descriptor encountered at block 0x%x: '%s'", i, §or[1]);
|
||||
break; // if no valid root partition was found, an exception will be thrown below.
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
Error::SetString(error, "IsoFS could not find the root directory on the ISO image.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DevCon.WriteLn("(IsoFS) Filesystem is %s", FStype_ToString().c_str());
|
||||
return Open(rootDirEntry);
|
||||
}
|
||||
|
||||
// Used to load a specific directory from a file descriptor
|
||||
bool IsoDirectory::Open(const IsoFileDescriptor& directoryEntry, Error* error /* = nullptr */)
|
||||
{
|
||||
// parse directory sector
|
||||
IsoFile dataStream(internalReader, directoryEntry);
|
||||
|
||||
files.clear();
|
||||
|
||||
uint remainingSize = directoryEntry.size;
|
||||
|
||||
u8 b[257];
|
||||
|
||||
while (remainingSize >= 4) // hm hack :P
|
||||
{
|
||||
b[0] = dataStream.read<u8>();
|
||||
|
||||
if (b[0] == 0)
|
||||
{
|
||||
break; // or continue?
|
||||
}
|
||||
|
||||
remainingSize -= b[0];
|
||||
|
||||
dataStream.read(b + 1, b[0] - 1);
|
||||
|
||||
auto isoFile = IsoFileDescriptor(b, b[0]);
|
||||
|
||||
files.push_back(isoFile);
|
||||
|
||||
const std::string::size_type semi_pos = isoFile.name.rfind(';');
|
||||
if (semi_pos != std::string::npos && std::string_view(isoFile.name).substr(semi_pos) != ";1")
|
||||
{
|
||||
const std::string origName = isoFile.name;
|
||||
isoFile.name.erase(semi_pos);
|
||||
isoFile.name += ";1";
|
||||
Console.WriteLn("(IsoFS) Non-conforming version suffix (%s) detected. Creating 'hard-linked' entry (%s)",origName.c_str(), isoFile.name.c_str());
|
||||
|
||||
files.push_back(isoFile);
|
||||
}
|
||||
}
|
||||
|
||||
b[0] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
const IsoFileDescriptor& IsoDirectory::GetEntry(size_t index) const
|
||||
{
|
||||
return files[index];
|
||||
}
|
||||
|
||||
int IsoDirectory::GetIndexOf(const std::string_view& fileName) const
|
||||
{
|
||||
for (unsigned int i = 0; i < files.size(); i++)
|
||||
{
|
||||
if (files[i].name == fileName)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::optional<IsoFileDescriptor> IsoDirectory::FindFile(const std::string_view& filePath) const
|
||||
{
|
||||
if (filePath.empty())
|
||||
return std::nullopt;
|
||||
|
||||
// DOS-style parser should work fine for ISO 9660 path names. Only practical difference
|
||||
// is case sensitivity, and that won't matter for path splitting.
|
||||
std::vector<std::string_view> parts(Path::SplitWindowsPath(filePath));
|
||||
const IsoDirectory* dir = this;
|
||||
IsoDirectory subdir(internalReader);
|
||||
|
||||
// walk through path ("." and ".." entries are in the directories themselves, so even if the
|
||||
// path included . and/or .., it still works)
|
||||
|
||||
// ignore the device (cdrom0:\)
|
||||
const bool has_device = (parts.front().back() == ':');
|
||||
|
||||
for (size_t index = has_device ? 1 : 0; index < (parts.size() - 1); index++)
|
||||
{
|
||||
const int subdir_index = GetIndexOf(parts[index]);
|
||||
if (subdir_index < 0)
|
||||
return std::nullopt;
|
||||
|
||||
const IsoFileDescriptor& subdir_entry = GetEntry(static_cast<size_t>(index));
|
||||
if (subdir_entry.IsFile() || !subdir.Open(subdir_entry, nullptr))
|
||||
return std::nullopt;
|
||||
|
||||
dir = &subdir;
|
||||
}
|
||||
|
||||
const int file_index = dir->GetIndexOf(parts.back());
|
||||
if (file_index < 0)
|
||||
return std::nullopt;
|
||||
|
||||
return GetEntry(static_cast<size_t>(file_index));
|
||||
}
|
||||
|
||||
bool IsoDirectory::Exists(const std::string_view& filePath) const
|
||||
{
|
||||
if (filePath.empty())
|
||||
return false;
|
||||
|
||||
const std::optional<IsoFileDescriptor> fd(FindFile(filePath));
|
||||
return fd.has_value();
|
||||
}
|
||||
|
||||
bool IsoDirectory::IsFile(const std::string_view& filePath) const
|
||||
{
|
||||
if (filePath.empty())
|
||||
return false;
|
||||
|
||||
const std::optional<IsoFileDescriptor> fd(FindFile(filePath));
|
||||
if (fd.has_value())
|
||||
return false;
|
||||
|
||||
return ((fd->flags & 2) != 2);
|
||||
}
|
||||
|
||||
bool IsoDirectory::IsDir(const std::string_view& filePath) const
|
||||
{
|
||||
if (filePath.empty())
|
||||
return false;
|
||||
|
||||
const std::optional<IsoFileDescriptor> fd(FindFile(filePath));
|
||||
if (fd.has_value())
|
||||
return false;
|
||||
|
||||
return ((fd->flags & 2) == 2);
|
||||
}
|
||||
|
||||
u32 IsoDirectory::GetFileSize(const std::string_view& filePath) const
|
||||
{
|
||||
if (filePath.empty())
|
||||
return 0;
|
||||
|
||||
const std::optional<IsoFileDescriptor> fd(FindFile(filePath));
|
||||
if (fd.has_value())
|
||||
return 0;
|
||||
|
||||
return fd->size;
|
||||
}
|
||||
|
||||
IsoFileDescriptor::IsoFileDescriptor()
|
||||
: lba(0)
|
||||
, size(0)
|
||||
, flags(0)
|
||||
{
|
||||
memset(&date, 0, sizeof(date));
|
||||
}
|
||||
|
||||
IsoFileDescriptor::IsoFileDescriptor(const u8* data, int length)
|
||||
{
|
||||
Load(data, length);
|
||||
}
|
||||
|
||||
void IsoFileDescriptor::Load(const u8* data, int length)
|
||||
{
|
||||
lba = (u32&)data[2];
|
||||
size = (u32&)data[10];
|
||||
|
||||
date.year = data[18] + 1900;
|
||||
date.month = data[19];
|
||||
date.day = data[20];
|
||||
date.hour = data[21];
|
||||
date.minute = data[22];
|
||||
date.second = data[23];
|
||||
date.gmtOffset = data[24];
|
||||
|
||||
flags = data[25];
|
||||
|
||||
int fileNameLength = data[32];
|
||||
|
||||
if (fileNameLength == 1)
|
||||
{
|
||||
u8 c = data[33];
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 0:
|
||||
name = ".";
|
||||
break;
|
||||
case 1:
|
||||
name = "..";
|
||||
break;
|
||||
default:
|
||||
name = static_cast<char>(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy string and up-convert from ascii to wxChar
|
||||
|
||||
const u8* fnsrc = data + 33;
|
||||
name.assign(reinterpret_cast<const char*>(fnsrc), fileNameLength);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class IsoFile;
|
||||
class IsoDirectory;
|
||||
struct IsoFileDescriptor;
|
||||
|
||||
#include "SectorSource.h"
|
||||
#include "IsoFileDescriptor.h"
|
||||
#include "IsoDirectory.h"
|
||||
#include "IsoFile.h"
|
||||
@@ -1,37 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "IsoFSCDVD.h"
|
||||
#include "CDVD/CDVDcommon.h"
|
||||
|
||||
IsoFSCDVD::IsoFSCDVD()
|
||||
{
|
||||
}
|
||||
|
||||
bool IsoFSCDVD::readSector(unsigned char* buffer, int lba)
|
||||
{
|
||||
return DoCDVDreadSector(buffer, lba, CDVD_MODE_2048) >= 0;
|
||||
}
|
||||
|
||||
int IsoFSCDVD::getNumSectors()
|
||||
{
|
||||
cdvdTD td;
|
||||
CDVD->getTD(0, &td);
|
||||
|
||||
return td.lsn;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "SectorSource.h"
|
||||
|
||||
class IsoFSCDVD final : public SectorSource
|
||||
{
|
||||
public:
|
||||
IsoFSCDVD();
|
||||
virtual ~IsoFSCDVD() = default;
|
||||
|
||||
virtual bool readSector(unsigned char* buffer, int lba);
|
||||
|
||||
virtual int getNumSectors();
|
||||
};
|
||||
@@ -1,262 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Error.h"
|
||||
|
||||
#include "IsoFS.h"
|
||||
#include "IsoFile.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
IsoFile::IsoFile(SectorSource& reader)
|
||||
: internalReader(reader)
|
||||
{
|
||||
}
|
||||
|
||||
IsoFile::~IsoFile() = default;
|
||||
|
||||
IsoFile::IsoFile(SectorSource& reader, const IsoFileDescriptor& fileEntry)
|
||||
: internalReader(reader)
|
||||
, fileEntry(fileEntry)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
void IsoFile::Init()
|
||||
{
|
||||
//pxAssertDev( fileEntry.IsFile(), "IsoFile Error: Filename points to a directory." );
|
||||
|
||||
currentSectorNumber = fileEntry.lba;
|
||||
currentOffset = 0;
|
||||
sectorOffset = 0;
|
||||
maxOffset = fileEntry.size;
|
||||
|
||||
if (maxOffset > 0)
|
||||
internalReader.readSector(currentSector, currentSectorNumber);
|
||||
}
|
||||
|
||||
bool IsoFile::open(const IsoDirectory& dir, const std::string_view& filename, Error* error /*= nullptr*/)
|
||||
{
|
||||
const std::optional<IsoFileDescriptor> fd(dir.FindFile(filename));
|
||||
if (!fd.has_value())
|
||||
{
|
||||
Error::SetString(error, fmt::format("Failed to find file '{}'", filename));
|
||||
return false;
|
||||
}
|
||||
|
||||
fileEntry = std::move(fd.value());
|
||||
Init();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsoFile::open(const std::string_view& filename, Error* error /*= nullptr*/)
|
||||
{
|
||||
IsoDirectory dir(internalReader);
|
||||
if (!dir.OpenRootDirectory(error))
|
||||
return false;
|
||||
|
||||
return open(dir, filename, error);
|
||||
}
|
||||
|
||||
u32 IsoFile::seek(u32 absoffset)
|
||||
{
|
||||
u32 endOffset = absoffset;
|
||||
|
||||
int oldSectorNumber = currentSectorNumber;
|
||||
int newSectorNumber = fileEntry.lba + (int)(endOffset / sectorLength);
|
||||
|
||||
if (oldSectorNumber != newSectorNumber)
|
||||
{
|
||||
internalReader.readSector(currentSector, newSectorNumber);
|
||||
}
|
||||
|
||||
currentOffset = endOffset;
|
||||
currentSectorNumber = newSectorNumber;
|
||||
sectorOffset = (int)(currentOffset % sectorLength);
|
||||
|
||||
return currentOffset;
|
||||
}
|
||||
|
||||
// Returns the new offset in the file. Out-of-bounds seeks are automatically truncated at 0
|
||||
// and fileLength.
|
||||
u32 IsoFile::seek(s64 offset, int mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case SEEK_SET:
|
||||
pxAssertDev(offset >= 0 && offset <= (s64)ULONG_MAX, "Invalid seek position from start.");
|
||||
return seek(offset);
|
||||
|
||||
case SEEK_CUR:
|
||||
// truncate negative values to zero, and positive values to 4gb
|
||||
return seek(std::min(std::max<s64>(0, (s64)currentOffset + offset), (s64)ULONG_MAX));
|
||||
|
||||
case SEEK_END:
|
||||
// truncate negative values to zero, and positive values to 4gb
|
||||
return seek(std::min(std::max<s64>(0, (s64)fileEntry.size + offset), (s64)ULONG_MAX));
|
||||
|
||||
jNO_DEFAULT;
|
||||
}
|
||||
|
||||
return 0; // unreachable
|
||||
}
|
||||
|
||||
void IsoFile::reset()
|
||||
{
|
||||
seek(0);
|
||||
}
|
||||
|
||||
// Returns the number of bytes actually skipped.
|
||||
s32 IsoFile::skip(s32 n)
|
||||
{
|
||||
s32 oldOffset = currentOffset;
|
||||
|
||||
if (n < 0)
|
||||
return 0;
|
||||
|
||||
seek(currentOffset + n);
|
||||
|
||||
return currentOffset - oldOffset;
|
||||
}
|
||||
|
||||
u32 IsoFile::getSeekPos() const
|
||||
{
|
||||
return currentOffset;
|
||||
}
|
||||
|
||||
bool IsoFile::eof() const
|
||||
{
|
||||
return (currentOffset == maxOffset);
|
||||
}
|
||||
|
||||
// loads the current sector index into the CurrentSector buffer.
|
||||
void IsoFile::makeDataAvailable()
|
||||
{
|
||||
if (sectorOffset >= sectorLength)
|
||||
{
|
||||
currentSectorNumber++;
|
||||
internalReader.readSector(currentSector, currentSectorNumber);
|
||||
sectorOffset -= sectorLength;
|
||||
}
|
||||
}
|
||||
|
||||
u8 IsoFile::readByte()
|
||||
{
|
||||
if (currentOffset >= maxOffset)
|
||||
return 0;
|
||||
|
||||
makeDataAvailable();
|
||||
|
||||
currentOffset++;
|
||||
|
||||
return currentSector[sectorOffset++];
|
||||
}
|
||||
|
||||
// Reads data from a single sector at a time. Reads cannot cross sector boundaries.
|
||||
int IsoFile::internalRead(void* dest, int off, int len)
|
||||
{
|
||||
if (len > 0)
|
||||
{
|
||||
size_t slen = len;
|
||||
if (slen > (maxOffset - currentOffset))
|
||||
{
|
||||
slen = (int)(maxOffset - currentOffset);
|
||||
}
|
||||
|
||||
memcpy((u8*)dest + off, currentSector + sectorOffset, slen);
|
||||
|
||||
sectorOffset += slen;
|
||||
currentOffset += slen;
|
||||
return slen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// returns the number of bytes actually read.
|
||||
s32 IsoFile::read(void* dest, s32 len)
|
||||
{
|
||||
pxAssert(dest != NULL);
|
||||
pxAssert(len >= 0); // should we silent-fail on negative length reads? prolly not...
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
int off = 0;
|
||||
|
||||
int totalLength = 0;
|
||||
|
||||
int firstSector = internalRead(dest, off, std::min(len, sectorLength - sectorOffset));
|
||||
off += firstSector;
|
||||
len -= firstSector;
|
||||
totalLength += firstSector;
|
||||
|
||||
// Read whole sectors
|
||||
while ((len >= sectorLength) && (currentOffset < maxOffset))
|
||||
{
|
||||
makeDataAvailable();
|
||||
int n = internalRead(dest, off, sectorLength);
|
||||
off += n;
|
||||
len -= n;
|
||||
totalLength += n;
|
||||
}
|
||||
|
||||
// Read remaining, if any
|
||||
if (len > 0)
|
||||
{
|
||||
makeDataAvailable();
|
||||
int lastSector = internalRead(dest, off, len);
|
||||
totalLength += lastSector;
|
||||
}
|
||||
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
// Reads data until it reaches a newline character (either \n, \r, or ASCII-Z). The caller is
|
||||
// responsible for handling files with DOS-style newlines (CR/LF pairs), if needed. The resulting
|
||||
// string has no newlines.
|
||||
//
|
||||
// Read data is unformatted 8 bit / Ascii. If the source file is known to be UTF8, use the fromUTF8()
|
||||
// conversion helper provided by PCSX2 utility classes.
|
||||
//
|
||||
std::string IsoFile::readLine()
|
||||
{
|
||||
std::string s;
|
||||
s.reserve(512);
|
||||
|
||||
while (!eof())
|
||||
{
|
||||
u8 c = read<u8>();
|
||||
|
||||
if ((c == '\n') || (c == '\r') || (c == 0))
|
||||
break;
|
||||
|
||||
s += c;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
u32 IsoFile::getLength()
|
||||
{
|
||||
return maxOffset;
|
||||
}
|
||||
|
||||
const IsoFileDescriptor& IsoFile::getEntry()
|
||||
{
|
||||
return fileEntry;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IsoFileDescriptor.h"
|
||||
#include "SectorSource.h"
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include <string_view>
|
||||
|
||||
class Error;
|
||||
|
||||
class IsoFile final
|
||||
{
|
||||
public:
|
||||
static const int sectorLength = 2048;
|
||||
|
||||
protected:
|
||||
SectorSource& internalReader;
|
||||
IsoFileDescriptor fileEntry = {};
|
||||
|
||||
u32 currentOffset = 0;
|
||||
u32 maxOffset = 0;
|
||||
|
||||
int currentSectorNumber = 0;
|
||||
int sectorOffset = 0;
|
||||
u8 currentSector[sectorLength];
|
||||
|
||||
public:
|
||||
IsoFile(SectorSource& reader);
|
||||
IsoFile(SectorSource& reader, const IsoFileDescriptor& fileEntry);
|
||||
~IsoFile();
|
||||
|
||||
bool open(const IsoDirectory& dir, const std::string_view& filename, Error* error = nullptr);
|
||||
bool open(const std::string_view& filename, Error* error = nullptr);
|
||||
|
||||
u32 seek(u32 absoffset);
|
||||
u32 seek(s64 offset, int mode);
|
||||
void reset();
|
||||
|
||||
s32 skip(s32 n);
|
||||
u32 getSeekPos() const;
|
||||
u32 getLength();
|
||||
bool eof() const;
|
||||
|
||||
const IsoFileDescriptor& getEntry();
|
||||
|
||||
u8 readByte();
|
||||
s32 read(void* dest, s32 len);
|
||||
std::string readLine();
|
||||
|
||||
// Tool to read a specific value type, including structs.
|
||||
template <class T>
|
||||
T read()
|
||||
{
|
||||
if (sizeof(T) == 1)
|
||||
return (T)readByte();
|
||||
else
|
||||
{
|
||||
T t;
|
||||
read((u8*)&t, sizeof(t));
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void makeDataAvailable();
|
||||
int internalRead(void* dest, int off, int len);
|
||||
void Init();
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include <string>
|
||||
|
||||
struct IsoFileDescriptor
|
||||
{
|
||||
struct FileDate // not 1:1 with iso9660 date struct
|
||||
{
|
||||
s32 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
u8 hour;
|
||||
u8 minute;
|
||||
u8 second;
|
||||
u8 gmtOffset; // Offset from Greenwich Mean Time in number of 15 min intervals from -48 (West) to + 52 (East)
|
||||
|
||||
} date;
|
||||
|
||||
u32 lba;
|
||||
u32 size;
|
||||
int flags;
|
||||
std::string name;
|
||||
|
||||
IsoFileDescriptor();
|
||||
IsoFileDescriptor(const u8* data, int length);
|
||||
|
||||
void Load(const u8* data, int length);
|
||||
|
||||
bool IsFile() const { return !(flags & 2); }
|
||||
bool IsDir() const { return !IsFile(); }
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class SectorSource
|
||||
{
|
||||
public:
|
||||
virtual int getNumSectors() = 0;
|
||||
virtual bool readSector(unsigned char* buffer, int lba) = 0;
|
||||
virtual ~SectorSource() = default;
|
||||
};
|
||||
@@ -0,0 +1,315 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "CDVD/CDVDcommon.h"
|
||||
#include "CDVD/IsoReader.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
IsoReader::IsoReader() = default;
|
||||
|
||||
IsoReader::~IsoReader() = default;
|
||||
|
||||
bool IsoReader::Open(Error* error)
|
||||
{
|
||||
if (!ReadPVD(error))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error)
|
||||
{
|
||||
if (DoCDVDreadSector(buf, lsn, CDVD_MODE_2048) != 0)
|
||||
{
|
||||
Error::SetString(error, fmt::format("Failed to read sector LSN #{}", lsn));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsoReader::ReadPVD(Error* error)
|
||||
{
|
||||
// volume descriptor start at sector 16
|
||||
static constexpr u32 START_SECTOR = 16;
|
||||
|
||||
// try only a maximum of 256 volume descriptors
|
||||
for (u32 i = 0; i < 256; i++)
|
||||
{
|
||||
u8 buffer[SECTOR_SIZE];
|
||||
if (!ReadSector(buffer, START_SECTOR + i, error))
|
||||
return false;
|
||||
|
||||
const ISOVolumeDescriptorHeader* header = reinterpret_cast<ISOVolumeDescriptorHeader*>(buffer);
|
||||
if (std::memcmp(header->standard_identifier, "CD001", 5) != 0)
|
||||
continue;
|
||||
else if (header->type_code != 1)
|
||||
continue;
|
||||
else if (header->type_code == 255)
|
||||
break;
|
||||
|
||||
std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor));
|
||||
DevCon.WriteLn(fmt::format("ISOReader: PVD found at index {}", i));
|
||||
return true;
|
||||
}
|
||||
|
||||
Error::SetString(error, "Failed to find the Primary Volume Descriptor.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(const std::string_view& path, Error* error)
|
||||
{
|
||||
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
|
||||
if (path.empty() || path == "/" || path == "\\")
|
||||
{
|
||||
// locating the root directory
|
||||
return *root_de;
|
||||
}
|
||||
|
||||
// start at the root directory
|
||||
u8 sector_buffer[SECTOR_SIZE];
|
||||
return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le, error);
|
||||
}
|
||||
|
||||
std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset)
|
||||
{
|
||||
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(sector + de_sector_offset);
|
||||
if ((sizeof(ISODirectoryEntry) + de->filename_length) > de->entry_length ||
|
||||
(sizeof(ISODirectoryEntry) + de->filename_length + de_sector_offset) > SECTOR_SIZE)
|
||||
{
|
||||
return std::string_view();
|
||||
}
|
||||
|
||||
const char* str = reinterpret_cast<const char*>(sector + de_sector_offset + sizeof(ISODirectoryEntry));
|
||||
if (de->filename_length == 1)
|
||||
{
|
||||
if (str[0] == '\0')
|
||||
return ".";
|
||||
else if (str[0] == '\1')
|
||||
return "..";
|
||||
}
|
||||
|
||||
// Strip any version information like the PS2 BIOS does.
|
||||
u32 length_without_version = 0;
|
||||
for (; length_without_version < de->filename_length; length_without_version++)
|
||||
{
|
||||
if (str[length_without_version] == ';' || str[length_without_version] == '\0')
|
||||
break;
|
||||
}
|
||||
|
||||
return std::string_view(str, length_without_version);
|
||||
}
|
||||
|
||||
std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(
|
||||
const std::string_view& path, u8* sector_buffer, u32 directory_record_lba, u32 directory_record_size, Error* error)
|
||||
{
|
||||
if (directory_record_size == 0)
|
||||
{
|
||||
Error::SetString(error, fmt::format("Directory entry record size 0 while looking for '{}'", path));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// strip any leading slashes
|
||||
size_t path_component_start = 0;
|
||||
while (path_component_start < path.length() &&
|
||||
(path[path_component_start] == '/' || path[path_component_start] == '\\'))
|
||||
{
|
||||
path_component_start++;
|
||||
}
|
||||
|
||||
size_t path_component_length = 0;
|
||||
while ((path_component_start + path_component_length) < path.length() &&
|
||||
path[path_component_start + path_component_length] != '/' &&
|
||||
path[path_component_start + path_component_length] != '\\')
|
||||
{
|
||||
path_component_length++;
|
||||
}
|
||||
|
||||
const std::string_view path_component = path.substr(path_component_start, path_component_length);
|
||||
if (path_component.empty())
|
||||
{
|
||||
Error::SetString(error, fmt::format("Empty path component in {}", path));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// start reading directory entries
|
||||
const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
||||
for (u32 i = 0; i < num_sectors; i++)
|
||||
{
|
||||
if (!ReadSector(sector_buffer, directory_record_lba + i, error))
|
||||
return std::nullopt;
|
||||
|
||||
u32 sector_offset = 0;
|
||||
while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
|
||||
{
|
||||
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
||||
if (de->entry_length < sizeof(ISODirectoryEntry))
|
||||
break;
|
||||
|
||||
const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
|
||||
sector_offset += de->entry_length;
|
||||
|
||||
// Empty file would be pretty strange..
|
||||
if (de_filename.empty() || de_filename == "." || de_filename == "..")
|
||||
continue;
|
||||
|
||||
if (!StringUtil::compareNoCase(de_filename, path_component))
|
||||
continue;
|
||||
|
||||
// found it. is this the file we're looking for?
|
||||
if ((path_component_start + path_component_length) == path.length())
|
||||
return *de;
|
||||
|
||||
// if it is a directory, recurse into it
|
||||
if (de->flags & ISODirectoryEntryFlag_Directory)
|
||||
{
|
||||
return LocateFile(path.substr(path_component_start + path_component_length), sector_buffer,
|
||||
de->location_le, de->length_le, error);
|
||||
}
|
||||
|
||||
// we're looking for a directory but got a file
|
||||
Error::SetString(error, fmt::format("Looking for directory '{}' but got file", path_component));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
Error::SetString(error, fmt::format("Path component '{}' not found", path_component));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::string> IsoReader::GetFilesInDirectory(const std::string_view& path, Error* error)
|
||||
{
|
||||
std::string base_path(path);
|
||||
u32 directory_record_lsn;
|
||||
u32 directory_record_length;
|
||||
if (base_path.empty())
|
||||
{
|
||||
// root directory
|
||||
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
|
||||
directory_record_lsn = root_de->location_le;
|
||||
directory_record_length = root_de->length_le;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto directory_de = LocateFile(base_path, error);
|
||||
if (!directory_de.has_value())
|
||||
return {};
|
||||
|
||||
if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0)
|
||||
{
|
||||
Error::SetString(error, fmt::format("Path '{}' is not a directory, can't list", path));
|
||||
return {};
|
||||
}
|
||||
|
||||
directory_record_lsn = directory_de->location_le;
|
||||
directory_record_length = directory_de->length_le;
|
||||
|
||||
if (base_path[base_path.size() - 1] != '/')
|
||||
base_path += '/';
|
||||
}
|
||||
|
||||
// start reading directory entries
|
||||
const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
||||
std::vector<std::string> files;
|
||||
u8 sector_buffer[SECTOR_SIZE];
|
||||
for (u32 i = 0; i < num_sectors; i++)
|
||||
{
|
||||
if (!ReadSector(sector_buffer, directory_record_lsn + i, error))
|
||||
break;
|
||||
|
||||
u32 sector_offset = 0;
|
||||
while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
|
||||
{
|
||||
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
||||
if (de->entry_length < sizeof(ISODirectoryEntry))
|
||||
break;
|
||||
|
||||
const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
|
||||
sector_offset += de->entry_length;
|
||||
|
||||
// Empty file would be pretty strange..
|
||||
if (de_filename.empty() || de_filename == "." || de_filename == "..")
|
||||
continue;
|
||||
|
||||
files.push_back(fmt::format("{}/{}", base_path, de_filename));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
bool IsoReader::FileExists(const std::string_view& path, Error* error)
|
||||
{
|
||||
auto de = LocateFile(path, error);
|
||||
if (!de)
|
||||
return false;
|
||||
|
||||
return (de->flags & ISODirectoryEntryFlag_Directory) == 0;
|
||||
}
|
||||
|
||||
bool IsoReader::DirectoryExists(const std::string_view& path, Error* error)
|
||||
{
|
||||
auto de = LocateFile(path, error);
|
||||
if (!de)
|
||||
return false;
|
||||
|
||||
return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory;
|
||||
}
|
||||
|
||||
bool IsoReader::ReadFile(const std::string_view& path, std::vector<u8>* data, Error* error)
|
||||
{
|
||||
auto de = LocateFile(path, error);
|
||||
if (!de)
|
||||
return false;
|
||||
|
||||
return ReadFile(de.value(), data, error);
|
||||
}
|
||||
|
||||
bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error /*= nullptr*/)
|
||||
{
|
||||
if (de.flags & ISODirectoryEntryFlag_Directory)
|
||||
{
|
||||
Error::SetString(error, "File is a directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (de.length_le == 0)
|
||||
{
|
||||
data->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
static_assert(sizeof(size_t) == sizeof(u64));
|
||||
const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
||||
data->resize(num_sectors * static_cast<u64>(SECTOR_SIZE));
|
||||
for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++)
|
||||
{
|
||||
if (!ReadSector(data->data() + (i * SECTOR_SIZE), lsn, error))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Might not be sector aligned, so reduce it back.
|
||||
data->resize(de.length_le);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
|
||||
class IsoReader
|
||||
{
|
||||
public:
|
||||
enum : u32
|
||||
{
|
||||
SECTOR_SIZE = 2048
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct ISOVolumeDescriptorHeader
|
||||
{
|
||||
u8 type_code;
|
||||
char standard_identifier[5];
|
||||
u8 version;
|
||||
};
|
||||
static_assert(sizeof(ISOVolumeDescriptorHeader) == 7);
|
||||
|
||||
struct ISOBootRecord
|
||||
{
|
||||
ISOVolumeDescriptorHeader header;
|
||||
char boot_system_identifier[32];
|
||||
char boot_identifier[32];
|
||||
u8 data[1977];
|
||||
};
|
||||
static_assert(sizeof(ISOBootRecord) == 2048);
|
||||
|
||||
struct ISOPVDDateTime
|
||||
{
|
||||
char year[4];
|
||||
char month[2];
|
||||
char day[2];
|
||||
char hour[2];
|
||||
char minute[2];
|
||||
char second[2];
|
||||
char milliseconds[2];
|
||||
s8 gmt_offset;
|
||||
};
|
||||
static_assert(sizeof(ISOPVDDateTime) == 17);
|
||||
|
||||
struct ISOPrimaryVolumeDescriptor
|
||||
{
|
||||
ISOVolumeDescriptorHeader header;
|
||||
u8 unused;
|
||||
char system_identifier[32];
|
||||
char volume_identifier[32];
|
||||
char unused2[8];
|
||||
u32 total_sectors_le;
|
||||
u32 total_sectors_be;
|
||||
char unused3[32];
|
||||
u16 volume_set_size_le;
|
||||
u16 volume_set_size_be;
|
||||
u16 volume_sequence_number_le;
|
||||
u16 volume_sequence_number_be;
|
||||
u16 block_size_le;
|
||||
u16 block_size_be;
|
||||
u32 path_table_size_le;
|
||||
u32 path_table_size_be;
|
||||
u32 path_table_location_le;
|
||||
u32 optional_path_table_location_le;
|
||||
u32 path_table_location_be;
|
||||
u32 optional_path_table_location_be;
|
||||
u8 root_directory_entry[34];
|
||||
char volume_set_identifier[128];
|
||||
char publisher_identifier[128];
|
||||
char data_preparer_identifier[128];
|
||||
char application_identifier[128];
|
||||
char copyright_file_identifier[38];
|
||||
char abstract_file_identifier[36];
|
||||
char bibliographic_file_identifier[37];
|
||||
ISOPVDDateTime volume_creation_time;
|
||||
ISOPVDDateTime volume_modification_time;
|
||||
ISOPVDDateTime volume_expiration_time;
|
||||
ISOPVDDateTime volume_effective_time;
|
||||
u8 structure_version;
|
||||
u8 unused4;
|
||||
u8 application_used[512];
|
||||
u8 reserved[653];
|
||||
};
|
||||
static_assert(sizeof(ISOPrimaryVolumeDescriptor) == 2048);
|
||||
|
||||
struct ISODirectoryEntryDateTime
|
||||
{
|
||||
u8 years_since_1900;
|
||||
u8 month;
|
||||
u8 day;
|
||||
u8 hour;
|
||||
u8 minute;
|
||||
u8 second;
|
||||
s8 gmt_offset;
|
||||
};
|
||||
|
||||
enum ISODirectoryEntryFlags : u8
|
||||
{
|
||||
ISODirectoryEntryFlag_Hidden = (1 << 0),
|
||||
ISODirectoryEntryFlag_Directory = (1 << 1),
|
||||
ISODirectoryEntryFlag_AssociatedFile = (1 << 2),
|
||||
ISODirectoryEntryFlag_ExtendedAttributePresent = (1 << 3),
|
||||
ISODirectoryEntryFlag_OwnerGroupPermissions = (1 << 4),
|
||||
ISODirectoryEntryFlag_MoreExtents = (1 << 7),
|
||||
};
|
||||
|
||||
struct ISODirectoryEntry
|
||||
{
|
||||
u8 entry_length;
|
||||
u8 extended_attribute_length;
|
||||
u32 location_le;
|
||||
u32 location_be;
|
||||
u32 length_le;
|
||||
u32 length_be;
|
||||
ISODirectoryEntryDateTime recoding_time;
|
||||
ISODirectoryEntryFlags flags;
|
||||
u8 interleaved_unit_size;
|
||||
u8 interleaved_gap_size;
|
||||
u16 sequence_le;
|
||||
u16 sequence_be;
|
||||
u8 filename_length;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
IsoReader();
|
||||
~IsoReader();
|
||||
|
||||
const ISOPrimaryVolumeDescriptor& GetPVD() const { return m_pvd; }
|
||||
|
||||
// TODO: Eventually we'll want to pass a handle to the currently-open file here.
|
||||
// ... once I have the energy to make CDVD not depend on a global object.
|
||||
bool Open(Error* error = nullptr);
|
||||
|
||||
std::vector<std::string> GetFilesInDirectory(const std::string_view& path, Error* error = nullptr);
|
||||
|
||||
std::optional<ISODirectoryEntry> LocateFile(const std::string_view& path, Error* error);
|
||||
|
||||
bool FileExists(const std::string_view& path, Error* error = nullptr);
|
||||
bool DirectoryExists(const std::string_view& path, Error* error = nullptr);
|
||||
bool ReadFile(const std::string_view& path, std::vector<u8>* data, Error* error = nullptr);
|
||||
bool ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error = nullptr);
|
||||
|
||||
private:
|
||||
static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset);
|
||||
|
||||
bool ReadSector(u8* buf, u32 lsn, Error* error);
|
||||
bool ReadPVD(Error* error);
|
||||
|
||||
std::optional<ISODirectoryEntry> LocateFile(const std::string_view& path, u8* sector_buffer,
|
||||
u32 directory_record_lba, u32 directory_record_size, Error* error);
|
||||
|
||||
ISOPrimaryVolumeDescriptor m_pvd = {};
|
||||
};
|
||||
@@ -247,6 +247,7 @@ set(pcsx2CDVDSources
|
||||
CDVD/CDVDisoReader.cpp
|
||||
CDVD/CDVDdiscThread.cpp
|
||||
CDVD/InputIsoFile.cpp
|
||||
CDVD/IsoReader.cpp
|
||||
CDVD/OutputIsoFile.cpp
|
||||
CDVD/ChunksCache.cpp
|
||||
CDVD/CompressedFileReader.cpp
|
||||
@@ -254,9 +255,6 @@ set(pcsx2CDVDSources
|
||||
CDVD/CsoFileReader.cpp
|
||||
CDVD/GzippedFileReader.cpp
|
||||
CDVD/ThreadedFileReader.cpp
|
||||
CDVD/IsoFS/IsoFile.cpp
|
||||
CDVD/IsoFS/IsoFSCDVD.cpp
|
||||
CDVD/IsoFS/IsoFS.cpp
|
||||
)
|
||||
|
||||
# CDVD headers
|
||||
@@ -273,12 +271,7 @@ set(pcsx2CDVDHeaders
|
||||
CDVD/GzippedFileReader.h
|
||||
CDVD/ThreadedFileReader.h
|
||||
CDVD/IsoFileFormats.h
|
||||
CDVD/IsoFS/IsoDirectory.h
|
||||
CDVD/IsoFS/IsoFileDescriptor.h
|
||||
CDVD/IsoFS/IsoFile.h
|
||||
CDVD/IsoFS/IsoFSCDVD.h
|
||||
CDVD/IsoFS/IsoFS.h
|
||||
CDVD/IsoFS/SectorSource.h
|
||||
CDVD/IsoReader.h
|
||||
CDVD/zlib_indexed.h
|
||||
)
|
||||
|
||||
@@ -607,7 +600,6 @@ set(pcsx2GSHeaders
|
||||
if(USE_OPENGL)
|
||||
list(APPEND pcsx2GSSources
|
||||
GS/Renderers/OpenGL/GLContext.cpp
|
||||
GS/Renderers/OpenGL/GLLoader.cpp
|
||||
GS/Renderers/OpenGL/GLProgram.cpp
|
||||
GS/Renderers/OpenGL/GLShaderCache.cpp
|
||||
GS/Renderers/OpenGL/GLState.cpp
|
||||
@@ -617,7 +609,6 @@ if(USE_OPENGL)
|
||||
)
|
||||
list(APPEND pcsx2GSHeaders
|
||||
GS/Renderers/OpenGL/GLContext.h
|
||||
GS/Renderers/OpenGL/GLLoader.h
|
||||
GS/Renderers/OpenGL/GLProgram.h
|
||||
GS/Renderers/OpenGL/GLShaderCache.h
|
||||
GS/Renderers/OpenGL/GLState.h
|
||||
@@ -663,25 +654,21 @@ if(USE_VULKAN)
|
||||
GS/Renderers/Vulkan/GSTextureVK.cpp
|
||||
GS/Renderers/Vulkan/vk_mem_alloc.cpp
|
||||
GS/Renderers/Vulkan/VKBuilders.cpp
|
||||
GS/Renderers/Vulkan/VKContext.cpp
|
||||
GS/Renderers/Vulkan/VKLoader.cpp
|
||||
GS/Renderers/Vulkan/VKShaderCache.cpp
|
||||
GS/Renderers/Vulkan/VKStreamBuffer.cpp
|
||||
GS/Renderers/Vulkan/VKSwapChain.cpp
|
||||
GS/Renderers/Vulkan/VKUtil.cpp
|
||||
)
|
||||
list(APPEND pcsx2GSHeaders
|
||||
GS/Renderers/Vulkan/GSDeviceVK.h
|
||||
GS/Renderers/Vulkan/GSTextureVK.h
|
||||
GS/Renderers/Vulkan/VKBuilders.h
|
||||
GS/Renderers/Vulkan/VKContext.h
|
||||
GS/Renderers/Vulkan/VKEntryPoints.h
|
||||
GS/Renderers/Vulkan/VKEntryPoints.inl
|
||||
GS/Renderers/Vulkan/VKLoader.h
|
||||
GS/Renderers/Vulkan/VKShaderCache.h
|
||||
GS/Renderers/Vulkan/VKStreamBuffer.h
|
||||
GS/Renderers/Vulkan/VKSwapChain.h
|
||||
GS/Renderers/Vulkan/VKUtil.h
|
||||
)
|
||||
target_link_libraries(PCSX2_FLAGS INTERFACE Vulkan-Headers glslang)
|
||||
endif()
|
||||
@@ -721,7 +708,6 @@ if(WIN32)
|
||||
GS/Renderers/DX11/GSTexture11.cpp
|
||||
GS/Renderers/DX11/GSTextureFX11.cpp
|
||||
GS/Renderers/DX12/D3D12Builders.cpp
|
||||
GS/Renderers/DX12/D3D12Context.cpp
|
||||
GS/Renderers/DX12/D3D12DescriptorHeapManager.cpp
|
||||
GS/Renderers/DX12/D3D12ShaderCache.cpp
|
||||
GS/Renderers/DX12/D3D12StreamBuffer.cpp
|
||||
@@ -734,7 +720,6 @@ if(WIN32)
|
||||
GS/Renderers/DX11/GSDevice11.h
|
||||
GS/Renderers/DX11/GSTexture11.h
|
||||
GS/Renderers/DX12/D3D12Builders.h
|
||||
GS/Renderers/DX12/D3D12Context.h
|
||||
GS/Renderers/DX12/D3D12DescriptorHeapManager.h
|
||||
GS/Renderers/DX12/D3D12ShaderCache.h
|
||||
GS/Renderers/DX12/D3D12StreamBuffer.h
|
||||
|
||||
@@ -170,15 +170,13 @@ enum GamefixId
|
||||
// TODO - config - not a fan of the excessive use of enums and macros to make them work
|
||||
// a proper object would likely make more sense (if possible).
|
||||
|
||||
enum SpeedhackId
|
||||
enum class SpeedHack
|
||||
{
|
||||
SpeedhackId_FIRST = 0,
|
||||
|
||||
Speedhack_mvuFlag = SpeedhackId_FIRST,
|
||||
Speedhack_InstantVU1,
|
||||
Speedhack_MTVU,
|
||||
|
||||
SpeedhackId_COUNT
|
||||
MVUFlag,
|
||||
InstantVU1,
|
||||
MTVU,
|
||||
EECycleRate,
|
||||
MaxCount,
|
||||
};
|
||||
|
||||
enum class VsyncMode
|
||||
@@ -374,7 +372,6 @@ typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
|
||||
}
|
||||
|
||||
ImplementEnumOperators(GamefixId);
|
||||
ImplementEnumOperators(SpeedhackId);
|
||||
|
||||
//------------ DEFAULT sseMXCSR VALUES ---------------
|
||||
#define DEFAULT_sseMXCSR 0xffc0 //FPU rounding > DaZ, FtZ, "chop"
|
||||
@@ -751,6 +748,7 @@ struct Pcsx2Config
|
||||
u8 TVShader = 0;
|
||||
s16 GetSkipCountFunctionId = -1;
|
||||
s16 BeforeDrawFunctionId = -1;
|
||||
s16 MoveHandlerFunctionId = -1;
|
||||
int SkipDrawStart = 0;
|
||||
int SkipDrawEnd = 0;
|
||||
|
||||
@@ -1068,6 +1066,10 @@ struct Pcsx2Config
|
||||
// ------------------------------------------------------------------------
|
||||
struct SpeedhackOptions
|
||||
{
|
||||
static constexpr s8 MIN_EE_CYCLE_RATE = -3;
|
||||
static constexpr s8 MAX_EE_CYCLE_RATE = 3;
|
||||
static constexpr u8 MAX_EE_CYCLE_SKIP = 3;
|
||||
|
||||
BITFIELD32()
|
||||
bool
|
||||
fastCDVD : 1, // enables fast CDVD access
|
||||
@@ -1085,17 +1087,13 @@ struct Pcsx2Config
|
||||
void LoadSave(SettingsWrapper& conf);
|
||||
SpeedhackOptions& DisableAll();
|
||||
|
||||
void Set(SpeedhackId id, bool enabled = true);
|
||||
void Set(SpeedHack id, int value);
|
||||
|
||||
bool operator==(const SpeedhackOptions& right) const
|
||||
{
|
||||
return OpEqu(bitset) && OpEqu(EECycleRate) && OpEqu(EECycleSkip);
|
||||
}
|
||||
bool operator==(const SpeedhackOptions& right) const;
|
||||
bool operator!=(const SpeedhackOptions& right) const;
|
||||
|
||||
bool operator!=(const SpeedhackOptions& right) const
|
||||
{
|
||||
return !this->operator==(right);
|
||||
}
|
||||
static const char* GetSpeedHackName(SpeedHack id);
|
||||
static std::optional<SpeedHack> ParseSpeedHackName(const std::string_view& name);
|
||||
};
|
||||
|
||||
struct DebugOptions
|
||||
|
||||
@@ -95,6 +95,7 @@ static __fi void _rcntSet( int cntidx )
|
||||
// Stopped or special hsync gate?
|
||||
if (!counter.mode.IsCounting || (counter.mode.ClockSource == 0x3) ) return;
|
||||
|
||||
if (!counter.mode.TargetInterrupt && !counter.mode.OverflowInterrupt) return;
|
||||
// check for special cases where the overflow or target has just passed
|
||||
// (we probably missed it because we're doing/checking other things)
|
||||
if( counter.count > 0x10000 || counter.count > counter.target )
|
||||
@@ -113,6 +114,7 @@ static __fi void _rcntSet( int cntidx )
|
||||
if (c < nextCounter)
|
||||
{
|
||||
nextCounter = c;
|
||||
|
||||
cpuSetNextEvent( nextsCounter, nextCounter ); // Need to update on counter resets/target changes
|
||||
}
|
||||
|
||||
@@ -154,7 +156,9 @@ static __fi void cpuRcntSet()
|
||||
_rcntSet( i );
|
||||
|
||||
// sanity check!
|
||||
if( nextCounter < 0 ) nextCounter = 0;
|
||||
if (nextCounter < 0)
|
||||
nextCounter = 0;
|
||||
|
||||
cpuSetNextEvent(nextsCounter, nextCounter); // Need to update on counter resets/target changes
|
||||
}
|
||||
|
||||
@@ -810,6 +814,8 @@ static __fi void _cpuTestOverflow( int i )
|
||||
__fi void rcntUpdate()
|
||||
{
|
||||
rcntUpdate_vSync();
|
||||
// HBlank after as VSync can do error compensation
|
||||
rcntUpdate_hScanline();
|
||||
|
||||
// Update counters so that we can perform overflow and target tests.
|
||||
|
||||
@@ -832,8 +838,9 @@ __fi void rcntUpdate()
|
||||
counters[i].sCycleT = cpuRegs.cycle - change;
|
||||
|
||||
// Check Counter Targets and Overflows:
|
||||
_cpuTestTarget( i );
|
||||
// Check Overflow first, in case the target is 0
|
||||
_cpuTestOverflow( i );
|
||||
_cpuTestTarget(i);
|
||||
}
|
||||
else counters[i].sCycleT = cpuRegs.cycle;
|
||||
}
|
||||
@@ -883,8 +890,8 @@ static __fi void rcntStartGate(bool isVblank, u32 sCycle)
|
||||
// currectly by rcntUpdate (since it's not being scheduled for these counters)
|
||||
|
||||
counters[i].count += HBLANK_COUNTER_SPEED;
|
||||
_cpuTestOverflow(i);
|
||||
_cpuTestTarget( i );
|
||||
_cpuTestOverflow( i );
|
||||
}
|
||||
|
||||
if (!(gates & (1<<i))) continue;
|
||||
|
||||
@@ -342,7 +342,7 @@ namespace MIPSAnalyst
|
||||
info.isLinkedBranch = (opcode.flags & IS_LINKED) != 0;
|
||||
info.isLikelyBranch = (opcode.flags & IS_LIKELY) != 0;
|
||||
|
||||
u64 rs,rt;
|
||||
s64 rs,rt;
|
||||
u32 value;
|
||||
switch (opcode.flags & BRANCHTYPE_MASK)
|
||||
{
|
||||
@@ -354,8 +354,15 @@ namespace MIPSAnalyst
|
||||
info.isConditional = true;
|
||||
info.branchTarget = info.opcodeAddress + 4 + ((s16)(op&0xFFFF)<<2);
|
||||
|
||||
rs = info.cpu->getRegister(0,MIPS_GET_RS(op))._u64[0];
|
||||
rt = info.cpu->getRegister(0,MIPS_GET_RT(op))._u64[0];
|
||||
// Sign extend from 32bit for IOP regs
|
||||
if (info.cpu->getRegisterSize(0) == 32) {
|
||||
rs = (s32)info.cpu->getRegister(0,MIPS_GET_RS(op))._u32[0];
|
||||
rt = (s32)info.cpu->getRegister(0,MIPS_GET_RT(op))._u32[0];
|
||||
} else {
|
||||
rs = (s64)info.cpu->getRegister(0,MIPS_GET_RS(op))._u64[0];
|
||||
rt = (s64)info.cpu->getRegister(0,MIPS_GET_RT(op))._u64[0];
|
||||
}
|
||||
|
||||
switch (opcode.flags & CONDTYPE_MASK)
|
||||
{
|
||||
case CONDTYPE_EQ:
|
||||
@@ -369,16 +376,16 @@ namespace MIPSAnalyst
|
||||
info.isConditional = false;
|
||||
break;
|
||||
case CONDTYPE_LEZ:
|
||||
info.conditionMet = (((s64)rs) <= 0);
|
||||
info.conditionMet = (rs <= 0);
|
||||
break;
|
||||
case CONDTYPE_GTZ:
|
||||
info.conditionMet = (((s64)rs) > 0);
|
||||
info.conditionMet = (rs > 0);
|
||||
break;
|
||||
case CONDTYPE_LTZ:
|
||||
info.conditionMet = (((s64)rs) < 0);
|
||||
info.conditionMet = (rs < 0);
|
||||
break;
|
||||
case CONDTYPE_GEZ:
|
||||
info.conditionMet = (((s64)rs) >= 0);
|
||||
info.conditionMet = (rs >= 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,9 @@
|
||||
},
|
||||
"beforeDraw": {
|
||||
"type": "string"
|
||||
},
|
||||
"moveHandler": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -303,20 +306,25 @@
|
||||
"speedHacks": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mvuFlagSpeedHack": {
|
||||
"mvuFlag": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"InstantVU1SpeedHack": {
|
||||
"instantVU1": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"MTVUSpeedHack": {
|
||||
"mtvu": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"eeCycleRate": {
|
||||
"type": "integer",
|
||||
"minimum": -3,
|
||||
"maximum": 3
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -14,14 +14,16 @@
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "Common.h"
|
||||
|
||||
#include "Elfheader.h"
|
||||
#include "CDVD/IsoReader.h"
|
||||
#include "DebugTools/Debug.h"
|
||||
#include "DebugTools/SymbolMap.h"
|
||||
|
||||
#include "common/Error.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "GS.h" // for sending game crc to mtgs
|
||||
#include "Elfheader.h"
|
||||
#include "DebugTools/SymbolMap.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PSXEXEHeader
|
||||
{
|
||||
@@ -47,20 +49,17 @@ ElfObject::ElfObject() = default;
|
||||
|
||||
ElfObject::~ElfObject() = default;
|
||||
|
||||
bool ElfObject::OpenIsoFile(std::string srcfile, IsoFile& isofile, bool isPSXElf_, Error* error)
|
||||
bool ElfObject::OpenIsoFile(std::string srcfile, IsoReader& isor, bool isPSXElf_, Error* error)
|
||||
{
|
||||
const u32 length = isofile.getLength();
|
||||
if (!CheckElfSize(length, error))
|
||||
const auto de = isor.LocateFile(srcfile, error);
|
||||
if (!de)
|
||||
return false;
|
||||
|
||||
data.resize(length);
|
||||
if (!CheckElfSize(de->length_le, error))
|
||||
return false;
|
||||
|
||||
const s32 rsize = isofile.read(data.data(), static_cast<s32>(length));
|
||||
if (rsize < static_cast<s32>(length))
|
||||
{
|
||||
Error::SetString(error, "Failed to read ELF from ISO");
|
||||
if (!isor.ReadFile(de.value(), &data, error))
|
||||
return false;
|
||||
}
|
||||
|
||||
filename = std::move(srcfile);
|
||||
isPSXElf = isPSXElf_;
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Error.h"
|
||||
#include "CDVD/IsoFS/IsoFSCDVD.h"
|
||||
#include "CDVD/IsoFS/IsoFS.h"
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
class IsoReader;
|
||||
|
||||
struct ELF_HEADER {
|
||||
u8 e_ident[16]; //0x7f,"ELF" (ELF file identifier)
|
||||
u16 e_type; //ELF type: 0=NONE, 1=REL, 2=EXEC, 3=SHARED, 4=CORE
|
||||
@@ -125,11 +125,12 @@ public:
|
||||
ElfObject(const ElfObject&) = delete;
|
||||
~ElfObject();
|
||||
|
||||
__fi const std::vector<u8>& GetData() const { return data; }
|
||||
__fi const ELF_HEADER& GetHeader() const { return *reinterpret_cast<const ELF_HEADER*>(data.data()); }
|
||||
__fi u32 GetSize() const { return static_cast<u32>(data.size()); }
|
||||
|
||||
bool OpenFile(std::string srcfile, bool isPSXElf_, Error* error);
|
||||
bool OpenIsoFile(std::string srcfile, IsoFile& isofile, bool isPSXElf_, Error* error);
|
||||
bool OpenIsoFile(std::string srcfile, IsoReader& isor, bool isPSXElf_, Error* error);
|
||||
|
||||
void LoadHeaders();
|
||||
|
||||
|
||||
@@ -19,15 +19,16 @@
|
||||
#include "Counters.h"
|
||||
#include "ImGui/FullscreenUI.h"
|
||||
#include "ImGui/ImGuiManager.h"
|
||||
#include "GS.h"
|
||||
#include "GSCapture.h"
|
||||
#include "GSExtra.h"
|
||||
#include "GSGL.h"
|
||||
#include "GSLzma.h"
|
||||
#include "GSUtil.h"
|
||||
#include "GS/GS.h"
|
||||
#include "GS/GSCapture.h"
|
||||
#include "GS/GSExtra.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/GSLzma.h"
|
||||
#include "GS/GSPerfMon.h"
|
||||
#include "GS/GSUtil.h"
|
||||
#include "GS/MultiISA.h"
|
||||
#include "Host.h"
|
||||
#include "Input/InputManager.h"
|
||||
#include "MultiISA.h"
|
||||
#include "MTGS.h"
|
||||
#include "pcsx2/GS.h"
|
||||
#include "GS/Renderers/Null/GSRendererNull.h"
|
||||
@@ -716,7 +717,8 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
if (GSConfig.UserHacks_DisableRenderFixes != old_config.UserHacks_DisableRenderFixes ||
|
||||
GSConfig.UpscaleMultiplier != old_config.UpscaleMultiplier ||
|
||||
GSConfig.GetSkipCountFunctionId != old_config.GetSkipCountFunctionId ||
|
||||
GSConfig.BeforeDrawFunctionId != old_config.BeforeDrawFunctionId)
|
||||
GSConfig.BeforeDrawFunctionId != old_config.BeforeDrawFunctionId ||
|
||||
GSConfig.MoveHandlerFunctionId != old_config.MoveHandlerFunctionId)
|
||||
{
|
||||
g_gs_renderer->UpdateCRCHacks();
|
||||
}
|
||||
@@ -916,6 +918,62 @@ void GSFreeWrappedMemory(void* ptr, size_t size, size_t repeat)
|
||||
|
||||
#endif
|
||||
|
||||
std::pair<u8, u8> GSGetRGBA8AlphaMinMax(const void* data, u32 width, u32 height, u32 stride)
|
||||
{
|
||||
GSVector4i minc = GSVector4i::xffffffff();
|
||||
GSVector4i maxc = GSVector4i::zero();
|
||||
|
||||
const u8* ptr = static_cast<const u8*>(data);
|
||||
if ((width % 4) == 0)
|
||||
{
|
||||
for (u32 r = 0; r < height; r++)
|
||||
{
|
||||
const u8* rptr = ptr;
|
||||
for (u32 c = 0; c < width; c += 4)
|
||||
{
|
||||
const GSVector4i v = GSVector4i::load<false>(rptr);
|
||||
rptr += sizeof(GSVector4i);
|
||||
minc = minc.min_u32(v);
|
||||
maxc = maxc.max_u32(v);
|
||||
}
|
||||
|
||||
ptr += stride;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 aligned_width = Common::AlignDownPow2(width, 4);
|
||||
static constexpr const GSVector4i masks[3][2] = {
|
||||
{GSVector4i::cxpr(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0), GSVector4i::cxpr(0, 0, 0, 0xFFFFFFFF)},
|
||||
{GSVector4i::cxpr(0xFFFFFFFF, 0xFFFFFFFF, 0, 0), GSVector4i::cxpr(0, 0, 0xFFFFFFFF, 0xFFFFFFFF)},
|
||||
{GSVector4i::cxpr(0xFFFFFFFF, 0, 0, 0), GSVector4i::cxpr(0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)},
|
||||
};
|
||||
const GSVector4i last_mask_and = masks[(width & 3) - 1][0];
|
||||
const GSVector4i last_mask_or = masks[(width & 3) - 1][1];
|
||||
|
||||
for (u32 r = 0; r < height; r++)
|
||||
{
|
||||
const u8* rptr = ptr;
|
||||
for (u32 c = 0; c < aligned_width; c += 4)
|
||||
{
|
||||
const GSVector4i v = GSVector4i::load<false>(rptr);
|
||||
rptr += sizeof(GSVector4i);
|
||||
minc = minc.min_u32(v);
|
||||
maxc = maxc.max_u32(v);
|
||||
}
|
||||
|
||||
const GSVector4i v = GSVector4i::load<false>(rptr);
|
||||
minc = minc.min_u32(v | last_mask_or);
|
||||
maxc = maxc.max_u32(v & last_mask_and);
|
||||
|
||||
ptr += stride;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair<u8, u8>(static_cast<u8>(minc.minv_u32() >> 24),
|
||||
static_cast<u8>(maxc.maxv_u32() >> 24));
|
||||
}
|
||||
|
||||
static void HotkeyAdjustUpscaleMultiplier(s32 delta)
|
||||
{
|
||||
const u32 new_multiplier = static_cast<u32>(std::clamp(static_cast<s32>(EmuConfig.GS.UpscaleMultiplier) + delta, 1, 8));
|
||||
|
||||
@@ -68,6 +68,7 @@ class HostDisplay;
|
||||
// Returns the ID for the specified function, otherwise -1.
|
||||
s16 GSLookupGetSkipCountFunctionId(const std::string_view& name);
|
||||
s16 GSLookupBeforeDrawFunctionId(const std::string_view& name);
|
||||
s16 GSLookupMoveHandlerFunctionId(const std::string_view& name);
|
||||
|
||||
void GSinit();
|
||||
void GSshutdown();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "GS/GSExtra.h"
|
||||
#include "GS/GSLocalMemory.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/GSUtil.h"
|
||||
#include "GS/Renderers/Common/GSDevice.h"
|
||||
#include "GS/Renderers/Common/GSRenderer.h"
|
||||
#include "common/AlignedMalloc.h"
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "pcsx2/Config.h"
|
||||
#include "common/Align.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/// Like `memcmp(&a, &b, sizeof(T)) == 0` but faster
|
||||
template <typename T>
|
||||
__forceinline bool BitEqual(const T& a, const T& b)
|
||||
@@ -129,6 +131,9 @@ __fi static T VectorAlign(T value)
|
||||
return Common::AlignUpPow2(value, VECTOR_ALIGNMENT);
|
||||
}
|
||||
|
||||
/// Returns the maximum alpha value across a range of data. Assumes stride is 16 byte aligned.
|
||||
std::pair<u8, u8> GSGetRGBA8AlphaMinMax(const void* data, u32 width, u32 height, u32 stride);
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
@@ -836,6 +836,15 @@ REG_END2
|
||||
// The recast of TBW seems useless but it avoid tons of warning from GCC...
|
||||
return ((u32)TBW << 6u) < (1u << TW);
|
||||
}
|
||||
|
||||
__forceinline static GIFRegTEX0 Create(u32 bp, u32 bw, u32 psm)
|
||||
{
|
||||
GIFRegTEX0 ret = {};
|
||||
ret.TBP0 = bp;
|
||||
ret.TBW = bw;
|
||||
ret.PSM = psm;
|
||||
return ret;
|
||||
}
|
||||
REG_END2
|
||||
|
||||
REG64_(GIFReg, TEX1)
|
||||
|
||||
@@ -14,16 +14,18 @@
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "GSState.h"
|
||||
#include "GSGL.h"
|
||||
#include "GSUtil.h"
|
||||
#include "GS/GSState.h"
|
||||
#include "GS/GSDump.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/GSPerfMon.h"
|
||||
#include "GS/GSUtil.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include <algorithm> // clamp
|
||||
#include <cfloat> // FLT_MAX
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <fstream>
|
||||
#include <iomanip> // Dump Verticles
|
||||
#include <iomanip>
|
||||
|
||||
int GSState::s_n = 0;
|
||||
int GSState::s_last_transfer_draw_n = 0;
|
||||
@@ -3512,7 +3514,7 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL
|
||||
// If it's the start of the texture and our little adjustment is all that pushed it over, clamp it to 0.
|
||||
// This stops the border check failing when using repeat but needed less than the full texture
|
||||
// since this was making it take the full texture even though it wasn't needed.
|
||||
if (((m_vt.m_min.t == GSVector4::zero()).mask() & 0x3) == 0x3)
|
||||
if (!clamp_to_tsize && ((m_vt.m_min.t == GSVector4::zero()).mask() & 0x3) == 0x3)
|
||||
st = st.max(GSVector4::zero());
|
||||
}
|
||||
else
|
||||
@@ -3667,12 +3669,13 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL
|
||||
return { vr, uses_border };
|
||||
}
|
||||
|
||||
void GSState::CalcAlphaMinMax()
|
||||
void GSState::CalcAlphaMinMax(const int tex_alpha_min, const int tex_alpha_max)
|
||||
{
|
||||
if (m_vt.m_alpha.valid)
|
||||
if (m_vt.m_alpha.valid && tex_alpha_min == 0 && tex_alpha_max == 255)
|
||||
return;
|
||||
|
||||
int min = 0, max = 0;
|
||||
// We wanted to force an update as we now know the alpha of the non-indexed texture.
|
||||
int min = tex_alpha_min, max = tex_alpha_max;
|
||||
|
||||
if (IsCoverageAlpha())
|
||||
{
|
||||
@@ -3690,8 +3693,8 @@ void GSState::CalcAlphaMinMax()
|
||||
switch (GSLocalMemory::m_psm[context->TEX0.PSM].fmt)
|
||||
{
|
||||
case 0:
|
||||
a.y = 0;
|
||||
a.w = 0xff;
|
||||
a.y = min;
|
||||
a.w = max;
|
||||
break;
|
||||
case 1:
|
||||
a.y = env.TEXA.AEM ? 0 : env.TEXA.TA0;
|
||||
@@ -4026,3 +4029,560 @@ bool GSState::GSTransferBuffer::Update(int tw, int th, int bpp, int& len)
|
||||
|
||||
return len > 0;
|
||||
}
|
||||
|
||||
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
|
||||
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
|
||||
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
|
||||
// The only conclusion I can come to is there is horizontal overscan expected so there would normally
|
||||
// be black borders either side anyway, or both sides slightly covered.
|
||||
static inline constexpr GSVector4i VideoModeOffsets[6] = {
|
||||
GSVector4i::cxpr(640, 224, 642, 25),
|
||||
GSVector4i::cxpr(640, 256, 676, 36),
|
||||
GSVector4i::cxpr(640, 480, 276, 34),
|
||||
GSVector4i::cxpr(720, 480, 232, 35),
|
||||
GSVector4i::cxpr(1280, 720, 302, 24),
|
||||
GSVector4i::cxpr(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
static inline constexpr GSVector4i VideoModeOffsetsOverscan[6] = {
|
||||
GSVector4i::cxpr(711, 240, 498, 17),
|
||||
GSVector4i::cxpr(711, 288, 532, 21),
|
||||
GSVector4i::cxpr(640, 480, 276, 34),
|
||||
GSVector4i::cxpr(720, 480, 232, 35),
|
||||
GSVector4i::cxpr(1280, 720, 302, 24),
|
||||
GSVector4i::cxpr(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
static inline constexpr GSVector4i VideoModeDividers[6] = {
|
||||
GSVector4i::cxpr(3, 0, 2559, 239),
|
||||
GSVector4i::cxpr(3, 0, 2559, 287),
|
||||
GSVector4i::cxpr(1, 0, 1279, 479),
|
||||
GSVector4i::cxpr(1, 0, 1439, 479),
|
||||
GSVector4i::cxpr(0, 0, 1279, 719),
|
||||
GSVector4i::cxpr(0, 0, 1919, 1079)
|
||||
};
|
||||
|
||||
bool GSState::GSPCRTCRegs::IsAnalogue()
|
||||
{
|
||||
const GSVideoMode video = static_cast<GSVideoMode>(videomode + 1);
|
||||
return video == GSVideoMode::NTSC || video == GSVideoMode::PAL || video == GSVideoMode::HDTV_1080I;
|
||||
}
|
||||
|
||||
// Calculates which display is closest to matching zero offsets in either direction.
|
||||
GSVector2i GSState::GSPCRTCRegs::NearestToZeroOffset()
|
||||
{
|
||||
GSVector2i returnValue = { 1, 1 };
|
||||
|
||||
if (!PCRTCDisplays[0].enabled && !PCRTCDisplays[1].enabled)
|
||||
return returnValue;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
{
|
||||
returnValue.x = 1 - i;
|
||||
returnValue.y = 1 - i;
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.x - VideoModeOffsets[videomode].z) <
|
||||
abs(PCRTCDisplays[1].displayOffset.x - VideoModeOffsets[videomode].z))
|
||||
returnValue.x = 0;
|
||||
|
||||
// When interlaced, the vertical base offset is doubled
|
||||
const int verticalOffset = VideoModeOffsets[videomode].w * (1 << interlaced);
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.y - verticalOffset) <
|
||||
abs(PCRTCDisplays[1].displayOffset.y - verticalOffset))
|
||||
returnValue.y = 0;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void GSState::GSPCRTCRegs::SetVideoMode(GSVideoMode videoModeIn)
|
||||
{
|
||||
videomode = static_cast<int>(videoModeIn) - 1;
|
||||
}
|
||||
|
||||
// Enable each of the displays.
|
||||
void GSState::GSPCRTCRegs::EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle)
|
||||
{
|
||||
PCRTCDisplays[0].enabled = pmode.EN1;
|
||||
PCRTCDisplays[1].enabled = pmode.EN2;
|
||||
|
||||
interlaced = smode2.INT && IsAnalogue();
|
||||
FFMD = smode2.FFMD;
|
||||
toggling_field = smodetoggle && IsAnalogue();
|
||||
}
|
||||
|
||||
void GSState::GSPCRTCRegs::CheckSameSource()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled != PCRTCDisplays[1].enabled || (PCRTCDisplays[0].enabled | PCRTCDisplays[1].enabled) == false)
|
||||
{
|
||||
PCRTCSameSrc = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PCRTCSameSrc = PCRTCDisplays[0].FBP == PCRTCDisplays[1].FBP &&
|
||||
PCRTCDisplays[0].FBW == PCRTCDisplays[1].FBW &&
|
||||
GSUtil::HasCompatibleBits(PCRTCDisplays[0].PSM, PCRTCDisplays[1].PSM);
|
||||
}
|
||||
|
||||
bool GSState::GSPCRTCRegs::FrameWrap()
|
||||
{
|
||||
const GSVector4i combined_rect = GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
return combined_rect.w >= 2048 || combined_rect.z >= 2048;
|
||||
}
|
||||
|
||||
// If the start point of both frames match, we can do a single read
|
||||
bool GSState::GSPCRTCRegs::FrameRectMatch()
|
||||
{
|
||||
return PCRTCSameSrc;
|
||||
}
|
||||
|
||||
GSVector2i GSState::GSPCRTCRegs::GetResolution()
|
||||
{
|
||||
GSVector2i resolution;
|
||||
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
const bool is_full_height = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off) || GSConfig.InterlaceMode == GSInterlaceMode::Off;
|
||||
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
|
||||
{
|
||||
const GSVector4i combined_size = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect);
|
||||
resolution = { combined_size.width(), combined_size.height() };
|
||||
}
|
||||
else if (PCRTCDisplays[0].enabled)
|
||||
{
|
||||
resolution = { PCRTCDisplays[0].displayRect.width(), PCRTCDisplays[0].displayRect.height() };
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = { PCRTCDisplays[1].displayRect.width(), PCRTCDisplays[1].displayRect.height() };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int shift = is_full_height ? 1 : 0;
|
||||
resolution = { offsets.x, offsets.y << shift };
|
||||
}
|
||||
|
||||
resolution.x = std::min(resolution.x, offsets.x);
|
||||
resolution.y = std::min(resolution.y, is_full_height ? offsets.y << 1 : offsets.y);
|
||||
|
||||
return resolution;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GSPCRTCRegs::GetFramebufferRect(int display)
|
||||
{
|
||||
if (display == -1)
|
||||
{
|
||||
return GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
}
|
||||
else
|
||||
{
|
||||
return PCRTCDisplays[display].framebufferRect;
|
||||
}
|
||||
}
|
||||
|
||||
int GSState::GSPCRTCRegs::GetFramebufferBitDepth()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[0].PSM].bpp;
|
||||
else if (PCRTCDisplays[1].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[1].PSM].bpp;
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
GSVector2i GSState::GSPCRTCRegs::GetFramebufferSize(int display)
|
||||
{
|
||||
int max_height = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode].y : VideoModeOffsetsOverscan[videomode].y;
|
||||
|
||||
if (!(FFMD && interlaced))
|
||||
{
|
||||
max_height *= 2;
|
||||
}
|
||||
|
||||
if (display == -1)
|
||||
{
|
||||
GSVector4i combined_rect = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
|
||||
if (combined_rect.z >= 2048)
|
||||
{
|
||||
const int high_x = (PCRTCDisplays[0].framebufferRect.x > PCRTCDisplays[1].framebufferRect.x) ? PCRTCDisplays[0].framebufferRect.x : PCRTCDisplays[1].framebufferRect.x;
|
||||
combined_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : high_x;
|
||||
combined_rect.x = 0;
|
||||
}
|
||||
|
||||
if (combined_rect.w >= 2048)
|
||||
{
|
||||
const int high_y = (PCRTCDisplays[0].framebufferRect.y > PCRTCDisplays[1].framebufferRect.y) ? PCRTCDisplays[0].framebufferRect.y : PCRTCDisplays[1].framebufferRect.y;
|
||||
combined_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : high_y;
|
||||
combined_rect.y = 0;
|
||||
}
|
||||
|
||||
// Cap the framebuffer read to the maximum display height, otherwise the hardware renderer gets messy.
|
||||
const int min_mag = std::max(1, std::min(PCRTCDisplays[0].magnification.y, PCRTCDisplays[1].magnification.y));
|
||||
int offset = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect).y;
|
||||
|
||||
if (FFMD && interlaced)
|
||||
{
|
||||
offset = (offset - 1) / 2;
|
||||
}
|
||||
|
||||
// Hardware mode needs a wider framebuffer as it can't offset the read.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
combined_rect.z += std::max(PCRTCDisplays[0].framebufferOffsets.x, PCRTCDisplays[1].framebufferOffsets.x);
|
||||
combined_rect.w += std::max(PCRTCDisplays[0].framebufferOffsets.y, PCRTCDisplays[1].framebufferOffsets.y);
|
||||
}
|
||||
offset = (max_height / min_mag) - offset;
|
||||
combined_rect.w = std::min(combined_rect.w, offset);
|
||||
return GSVector2i(combined_rect.z, combined_rect.w);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSVector4i out_rect = PCRTCDisplays[display].framebufferRect;
|
||||
|
||||
if (out_rect.z >= 2048)
|
||||
out_rect.z -= out_rect.x;
|
||||
|
||||
if (out_rect.w >= 2048)
|
||||
out_rect.w -= out_rect.y;
|
||||
|
||||
// Cap the framebuffer read to the maximum display height, otherwise the hardware renderer gets messy.
|
||||
const int min_mag = std::max(1, PCRTCDisplays[display].magnification.y);
|
||||
int offset = PCRTCDisplays[display].displayRect.y;
|
||||
|
||||
if (FFMD && interlaced)
|
||||
{
|
||||
offset = (offset - 1) / 2;
|
||||
}
|
||||
|
||||
offset = (max_height / min_mag) - offset;
|
||||
out_rect.w = std::min(out_rect.w, offset);
|
||||
|
||||
return GSVector2i(out_rect.z, out_rect.w);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
|
||||
void GSState::GSPCRTCRegs::SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg)
|
||||
{
|
||||
// Save framebuffer information first, while we're here.
|
||||
PCRTCDisplays[display].FBP = framebufferReg.FBP;
|
||||
PCRTCDisplays[display].FBW = framebufferReg.FBW;
|
||||
PCRTCDisplays[display].PSM = framebufferReg.PSM;
|
||||
PCRTCDisplays[display].prevFramebufferReg = framebufferReg;
|
||||
// Probably not really enabled but will cause a mess.
|
||||
// Q-Ball Billiards enables both circuits but doesn't set one of them up.
|
||||
if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0)
|
||||
{
|
||||
PCRTCDisplays[display].enabled = false;
|
||||
return;
|
||||
}
|
||||
PCRTCDisplays[display].magnification = GSVector2i(displayReg.MAGH + 1, displayReg.MAGV + 1);
|
||||
const u32 DW = displayReg.DW + 1;
|
||||
const u32 DH = displayReg.DH + 1;
|
||||
|
||||
const int renderWidth = DW / PCRTCDisplays[display].magnification.x;
|
||||
const int renderHeight = DH / PCRTCDisplays[display].magnification.y;
|
||||
|
||||
u32 finalDisplayWidth = renderWidth;
|
||||
u32 finalDisplayHeight = renderHeight;
|
||||
// When using screen offsets the screen gets squashed/resized in to the actual screen size.
|
||||
if (GSConfig.PCRTCOffsets)
|
||||
{
|
||||
finalDisplayWidth = DW / (VideoModeDividers[videomode].x + 1);
|
||||
finalDisplayHeight = DH / (VideoModeDividers[videomode].y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
finalDisplayWidth = std::min(finalDisplayWidth ,DW / (VideoModeDividers[videomode].x + 1));
|
||||
finalDisplayHeight = std::min(finalDisplayHeight, DH / (VideoModeDividers[videomode].y + 1));
|
||||
}
|
||||
|
||||
// Framebuffer size and offsets.
|
||||
PCRTCDisplays[display].prevFramebufferOffsets = PCRTCDisplays[display].framebufferOffsets;
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z = renderWidth;
|
||||
|
||||
if(FFMD && interlaced) // Round up the height as if it's an odd value, this will cause havok with the merge circuit.
|
||||
PCRTCDisplays[display].framebufferRect.w = (renderHeight + 1) >> (FFMD * interlaced); // Half height read if FFMD + INT enabled.
|
||||
else
|
||||
PCRTCDisplays[display].framebufferRect.w = renderHeight;
|
||||
PCRTCDisplays[display].framebufferOffsets.x = framebufferReg.DBX;
|
||||
PCRTCDisplays[display].framebufferOffsets.y = framebufferReg.DBY;
|
||||
|
||||
const bool is_interlaced_resolution = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off);
|
||||
|
||||
// If the interlace flag isn't set, but it's still interlacing, the height is likely reported wrong.
|
||||
// Q-Ball Billiards.
|
||||
if (is_interlaced_resolution && !interlaced)
|
||||
finalDisplayHeight *= 2;
|
||||
|
||||
// Display size and offsets.
|
||||
PCRTCDisplays[display].displayRect.x = 0;
|
||||
PCRTCDisplays[display].displayRect.y = 0;
|
||||
PCRTCDisplays[display].displayRect.z = finalDisplayWidth;
|
||||
PCRTCDisplays[display].displayRect.w = finalDisplayHeight;
|
||||
PCRTCDisplays[display].prevDisplayOffset = PCRTCDisplays[display].displayOffset;
|
||||
PCRTCDisplays[display].displayOffset.x = displayReg.DX;
|
||||
PCRTCDisplays[display].displayOffset.y = displayReg.DY;
|
||||
}
|
||||
|
||||
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
|
||||
// Only considered if "Anti-blur" is enabled.
|
||||
void GSState::GSPCRTCRegs::CalculateFramebufferOffset(bool scanmask)
|
||||
{
|
||||
if (GSConfig.PCRTCAntiBlur && PCRTCSameSrc && !scanmask)
|
||||
{
|
||||
GSVector2i fb0 = GSVector2i(PCRTCDisplays[0].framebufferOffsets.x, PCRTCDisplays[0].framebufferOffsets.y);
|
||||
GSVector2i fb1 = GSVector2i(PCRTCDisplays[1].framebufferOffsets.x, PCRTCDisplays[1].framebufferOffsets.y);
|
||||
|
||||
if (fb0.x + PCRTCDisplays[0].displayRect.z > 2048)
|
||||
{
|
||||
fb0.x -= 2048;
|
||||
fb0.x = abs(fb0.x);
|
||||
}
|
||||
if (fb0.y + PCRTCDisplays[0].displayRect.w > 2048)
|
||||
{
|
||||
fb0.y -= 2048;
|
||||
fb0.y = abs(fb0.y);
|
||||
}
|
||||
if (fb1.x + PCRTCDisplays[1].displayRect.z > 2048)
|
||||
{
|
||||
fb1.x -= 2048;
|
||||
fb1.x = abs(fb1.x);
|
||||
}
|
||||
if (fb1.y + PCRTCDisplays[1].displayRect.w > 2048)
|
||||
{
|
||||
fb1.y -= 2048;
|
||||
fb1.y = abs(fb1.y);
|
||||
}
|
||||
|
||||
if (abs(fb1.y - fb0.y) == 1
|
||||
&& PCRTCDisplays[0].displayRect.y == PCRTCDisplays[1].displayRect.y)
|
||||
{
|
||||
if (fb1.y < fb0.y)
|
||||
PCRTCDisplays[0].framebufferOffsets.y = fb1.y;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.y = fb0.y;
|
||||
}
|
||||
if (abs(fb1.x - fb0.x) == 1
|
||||
&& PCRTCDisplays[0].displayRect.x == PCRTCDisplays[1].displayRect.x)
|
||||
{
|
||||
if (fb1.x < fb0.x)
|
||||
PCRTCDisplays[0].framebufferOffsets.x = fb1.x;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.x = fb0.x;
|
||||
}
|
||||
}
|
||||
PCRTCDisplays[0].framebufferRect.x += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.z += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.y += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
PCRTCDisplays[0].framebufferRect.w += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
|
||||
PCRTCDisplays[1].framebufferRect.x += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.z += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.y += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
PCRTCDisplays[1].framebufferRect.w += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
}
|
||||
|
||||
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
|
||||
void GSState::GSPCRTCRegs::RemoveFramebufferOffset(int display)
|
||||
{
|
||||
if (display >= 0)
|
||||
{
|
||||
// Hardware needs nothing but handling for wrapped framebuffers.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
if (PCRTCDisplays[display].framebufferRect.z >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].displayRect.x += 2048 - PCRTCDisplays[display].framebufferRect.x;
|
||||
PCRTCDisplays[display].displayRect.z += 2048 - PCRTCDisplays[display].framebufferRect.x;
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z -= 2048;
|
||||
}
|
||||
if (PCRTCDisplays[display].framebufferRect.w >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].displayRect.y += 2048 - PCRTCDisplays[display].framebufferRect.y;
|
||||
PCRTCDisplays[display].displayRect.w += 2048 - PCRTCDisplays[display].framebufferRect.y;
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.w -= 2048;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[display].PSM];
|
||||
|
||||
// Software mode - See note below.
|
||||
GSVector4i r = PCRTCDisplays[display].framebufferRect;
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[display].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[display].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.y -= r.y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Software Mode Note:
|
||||
// This code is to read the framebuffer nicely block aligned in software, then leave the remaining offset in to the block.
|
||||
// In hardware mode this doesn't happen, it reads the whole framebuffer, so we need to keep the offset.
|
||||
if (!GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[1].PSM];
|
||||
|
||||
GSVector4i r = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[0].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[0].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.w -= r.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the two displays are offset from each other, move them to the correct offsets.
|
||||
// If using screen offsets, calculate the positions here.
|
||||
void GSState::GSPCRTCRegs::CalculateDisplayOffset(bool scanmask)
|
||||
{
|
||||
const bool both_enabled = PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled;
|
||||
// Offsets are generally ignored, the "hacky" way of doing the displays, but direct to framebuffers.
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
int int_off[2] = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
GSVector2i baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a single pixel offset, account for it else it can throw interlacing out.
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
continue;
|
||||
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
const int offset = (PCRTCDisplays[i].displayOffset.y - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset > 4)
|
||||
continue;
|
||||
|
||||
int_off[i] = offset & 1;
|
||||
if (offset < 0)
|
||||
int_off[i] = -int_off[i];
|
||||
|
||||
PCRTCDisplays[i].displayRect.y += int_off[i];
|
||||
PCRTCDisplays[i].displayRect.w += int_off[i];
|
||||
}
|
||||
|
||||
// Handle difference in offset between the two displays, used in games like DmC and Time Crisis 2 (for split screen).
|
||||
// Offset is not screen based, but relative to each other.
|
||||
if (both_enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayOffset.x - PCRTCDisplays[zeroDisplay.x].displayOffset.x) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayOffset.y - PCRTCDisplays[zeroDisplay.y].displayOffset.y) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset.x >= 4 || !GSConfig.PCRTCAntiBlur || scanmask)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x += offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z += offset.x;
|
||||
}
|
||||
if (offset.y >= 4 || !GSConfig.PCRTCAntiBlur || scanmask)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y += offset.y - int_off[1 - zeroDisplay.y];
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w += offset.y - int_off[1 - zeroDisplay.y];
|
||||
}
|
||||
|
||||
baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
}
|
||||
|
||||
// Handle any large vertical offset from the zero position on the screen.
|
||||
// Example: Hokuto no Ken, does a rougly -14 offset to bring the screen up.
|
||||
// Ignore the lowest bit, we've already accounted for this
|
||||
int vOffset = ((static_cast<int>(baseOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1));
|
||||
|
||||
if(vOffset <= 4 && vOffset != 0)
|
||||
{
|
||||
PCRTCDisplays[0].displayRect.y += vOffset - int_off[0];
|
||||
PCRTCDisplays[0].displayRect.w += vOffset - int_off[0];
|
||||
PCRTCDisplays[1].displayRect.y += vOffset - int_off[1];
|
||||
PCRTCDisplays[1].displayRect.w += vOffset - int_off[1];
|
||||
}
|
||||
}
|
||||
else // We're using screen offsets, so just calculate the entire offset.
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
GSVector2i offset = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
offset.x = (static_cast<int>(PCRTCDisplays[i].displayOffset.x) - offsets.z) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (static_cast<int>(PCRTCDisplays[i].displayOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
PCRTCDisplays[i].displayRect.x += offset.x;
|
||||
PCRTCDisplays[i].displayRect.z += offset.x;
|
||||
PCRTCDisplays[i].displayRect.y += offset.y;
|
||||
PCRTCDisplays[i].displayRect.w += offset.y;
|
||||
}
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayRect.x - PCRTCDisplays[zeroDisplay.x].displayRect.x);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayRect.y - PCRTCDisplays[zeroDisplay.y].displayRect.y);
|
||||
|
||||
if (offset.x > 0 && offset.x < 4 && GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x -= offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z -= offset.x;
|
||||
}
|
||||
if (offset.y > 0 && offset.y < 4 && GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y -= offset.y;
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w -= offset.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GS.h"
|
||||
#include "GSLocalMemory.h"
|
||||
#include "GSDrawingContext.h"
|
||||
#include "GSDrawingEnvironment.h"
|
||||
#include "Renderers/Common/GSVertex.h"
|
||||
#include "Renderers/Common/GSVertexTrace.h"
|
||||
#include "GSUtil.h"
|
||||
#include "GSPerfMon.h"
|
||||
#include "GSVector.h"
|
||||
#include "Renderers/Common/GSDevice.h"
|
||||
#include "GS/GS.h"
|
||||
#include "GS/GSLocalMemory.h"
|
||||
#include "GS/GSDrawingContext.h"
|
||||
#include "GS/GSDrawingEnvironment.h"
|
||||
#include "GS/Renderers/Common/GSVertex.h"
|
||||
#include "GS/Renderers/Common/GSVertexTrace.h"
|
||||
#include "GS/Renderers/Common/GSDevice.h"
|
||||
#include "GS/GSVector.h"
|
||||
#include "GSCrc.h"
|
||||
#include "GSAlignedClass.h"
|
||||
#include "GSDump.h"
|
||||
|
||||
class GSDumpBase;
|
||||
|
||||
class GSState : public GSAlignedClass<32>
|
||||
{
|
||||
@@ -128,16 +127,13 @@ private:
|
||||
bool write = false;
|
||||
|
||||
GSTransferBuffer();
|
||||
virtual ~GSTransferBuffer();
|
||||
~GSTransferBuffer();
|
||||
|
||||
void Init(int tx, int ty, const GIFRegBITBLTBUF& blit, bool write);
|
||||
bool Update(int tw, int th, int bpp, int& len);
|
||||
|
||||
} m_tr;
|
||||
|
||||
private:
|
||||
void CalcAlphaMinMax();
|
||||
|
||||
protected:
|
||||
GSVertex m_v = {};
|
||||
float m_q = 1.0f;
|
||||
@@ -180,7 +176,7 @@ protected:
|
||||
GSVertexTrace::VertexAlpha& GetAlphaMinMax()
|
||||
{
|
||||
if (!m_vt.m_alpha.valid)
|
||||
CalcAlphaMinMax();
|
||||
CalcAlphaMinMax(0, 255);
|
||||
return m_vt.m_alpha;
|
||||
}
|
||||
struct TextureMinMaxResult
|
||||
@@ -203,6 +199,7 @@ protected:
|
||||
bool IsMipMapDraw();
|
||||
bool IsMipMapActive();
|
||||
bool IsCoverageAlpha();
|
||||
void CalcAlphaMinMax(const int tex_min, const int tex_max);
|
||||
|
||||
public:
|
||||
struct GSUploadQueue
|
||||
@@ -300,38 +297,6 @@ public:
|
||||
|
||||
struct GSPCRTCRegs
|
||||
{
|
||||
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
|
||||
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
|
||||
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
|
||||
// The only conclusion I can come to is there is horizontal overscan expected so there would normally
|
||||
// be black borders either side anyway, or both sides slightly covered.
|
||||
static inline constexpr GSVector4i VideoModeOffsets[6] = {
|
||||
GSVector4i::cxpr(640, 224, 642, 25),
|
||||
GSVector4i::cxpr(640, 256, 676, 36),
|
||||
GSVector4i::cxpr(640, 480, 276, 34),
|
||||
GSVector4i::cxpr(720, 480, 232, 35),
|
||||
GSVector4i::cxpr(1280, 720, 302, 24),
|
||||
GSVector4i::cxpr(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
static inline constexpr GSVector4i VideoModeOffsetsOverscan[6] = {
|
||||
GSVector4i::cxpr(711, 240, 498, 17),
|
||||
GSVector4i::cxpr(711, 288, 532, 21),
|
||||
GSVector4i::cxpr(640, 480, 276, 34),
|
||||
GSVector4i::cxpr(720, 480, 232, 35),
|
||||
GSVector4i::cxpr(1280, 720, 302, 24),
|
||||
GSVector4i::cxpr(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
static inline constexpr GSVector4i VideoModeDividers[6] = {
|
||||
GSVector4i::cxpr(3, 0, 2559, 239),
|
||||
GSVector4i::cxpr(3, 0, 2559, 287),
|
||||
GSVector4i::cxpr(1, 0, 1279, 479),
|
||||
GSVector4i::cxpr(1, 0, 1439, 479),
|
||||
GSVector4i::cxpr(0, 0, 1279, 719),
|
||||
GSVector4i::cxpr(0, 0, 1919, 1079)
|
||||
};
|
||||
|
||||
struct PCRTCDisplay
|
||||
{
|
||||
bool enabled;
|
||||
@@ -347,10 +312,7 @@ public:
|
||||
GSVector2i framebufferOffsets;
|
||||
GSVector4i framebufferRect;
|
||||
|
||||
int Block()
|
||||
{
|
||||
return FBP << 5;
|
||||
}
|
||||
__fi int Block() const { return FBP << 5; }
|
||||
};
|
||||
|
||||
int videomode = 0;
|
||||
@@ -360,530 +322,44 @@ public:
|
||||
bool toggling_field = false;
|
||||
PCRTCDisplay PCRTCDisplays[2] = {};
|
||||
|
||||
bool IsAnalogue()
|
||||
{
|
||||
const GSVideoMode video = static_cast<GSVideoMode>(videomode + 1);
|
||||
return video == GSVideoMode::NTSC || video == GSVideoMode::PAL || video == GSVideoMode::HDTV_1080I;
|
||||
}
|
||||
bool IsAnalogue();
|
||||
|
||||
// Calculates which display is closest to matching zero offsets in either direction.
|
||||
GSVector2i NearestToZeroOffset()
|
||||
{
|
||||
GSVector2i returnValue = { 1, 1 };
|
||||
GSVector2i NearestToZeroOffset();
|
||||
|
||||
if (!PCRTCDisplays[0].enabled && !PCRTCDisplays[1].enabled)
|
||||
return returnValue;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
{
|
||||
returnValue.x = 1 - i;
|
||||
returnValue.y = 1 - i;
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.x - VideoModeOffsets[videomode].z) <
|
||||
abs(PCRTCDisplays[1].displayOffset.x - VideoModeOffsets[videomode].z))
|
||||
returnValue.x = 0;
|
||||
|
||||
// When interlaced, the vertical base offset is doubled
|
||||
const int verticalOffset = VideoModeOffsets[videomode].w * (1 << interlaced);
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.y - verticalOffset) <
|
||||
abs(PCRTCDisplays[1].displayOffset.y - verticalOffset))
|
||||
returnValue.y = 0;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void SetVideoMode(GSVideoMode videoModeIn)
|
||||
{
|
||||
videomode = static_cast<int>(videoModeIn) - 1;
|
||||
}
|
||||
void SetVideoMode(GSVideoMode videoModeIn);
|
||||
|
||||
// Enable each of the displays.
|
||||
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle)
|
||||
{
|
||||
PCRTCDisplays[0].enabled = pmode.EN1;
|
||||
PCRTCDisplays[1].enabled = pmode.EN2;
|
||||
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle);
|
||||
|
||||
interlaced = smode2.INT && IsAnalogue();
|
||||
FFMD = smode2.FFMD;
|
||||
toggling_field = smodetoggle && IsAnalogue();
|
||||
}
|
||||
|
||||
void CheckSameSource()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled != PCRTCDisplays[1].enabled || (PCRTCDisplays[0].enabled | PCRTCDisplays[1].enabled) == false)
|
||||
{
|
||||
PCRTCSameSrc = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PCRTCSameSrc = PCRTCDisplays[0].FBP == PCRTCDisplays[1].FBP &&
|
||||
PCRTCDisplays[0].FBW == PCRTCDisplays[1].FBW &&
|
||||
GSUtil::HasCompatibleBits(PCRTCDisplays[0].PSM, PCRTCDisplays[1].PSM);
|
||||
}
|
||||
void CheckSameSource();
|
||||
|
||||
bool FrameWrap()
|
||||
{
|
||||
const GSVector4i combined_rect = GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
return combined_rect.w >= 2048 || combined_rect.z >= 2048;
|
||||
}
|
||||
bool FrameWrap();
|
||||
|
||||
// If the start point of both frames match, we can do a single read
|
||||
bool FrameRectMatch()
|
||||
{
|
||||
return PCRTCSameSrc;
|
||||
}
|
||||
bool FrameRectMatch();
|
||||
|
||||
GSVector2i GetResolution()
|
||||
{
|
||||
GSVector2i resolution;
|
||||
GSVector2i GetResolution();
|
||||
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
const bool is_full_height = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off) || GSConfig.InterlaceMode == GSInterlaceMode::Off;
|
||||
GSVector4i GetFramebufferRect(int display);
|
||||
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
|
||||
{
|
||||
const GSVector4i combined_size = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect);
|
||||
resolution = { combined_size.width(), combined_size.height() };
|
||||
}
|
||||
else if (PCRTCDisplays[0].enabled)
|
||||
{
|
||||
resolution = { PCRTCDisplays[0].displayRect.width(), PCRTCDisplays[0].displayRect.height() };
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = { PCRTCDisplays[1].displayRect.width(), PCRTCDisplays[1].displayRect.height() };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int shift = is_full_height ? 1 : 0;
|
||||
resolution = { offsets.x, offsets.y << shift };
|
||||
}
|
||||
int GetFramebufferBitDepth();
|
||||
|
||||
resolution.x = std::min(resolution.x, offsets.x);
|
||||
resolution.y = std::min(resolution.y, is_full_height ? offsets.y << 1 : offsets.y);
|
||||
|
||||
return resolution;
|
||||
}
|
||||
|
||||
GSVector4i GetFramebufferRect(int display)
|
||||
{
|
||||
if (display == -1)
|
||||
{
|
||||
return GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
}
|
||||
else
|
||||
{
|
||||
return PCRTCDisplays[display].framebufferRect;
|
||||
}
|
||||
}
|
||||
|
||||
int GetFramebufferBitDepth()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[0].PSM].bpp;
|
||||
else if (PCRTCDisplays[1].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[1].PSM].bpp;
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
GSVector2i GetFramebufferSize(int display)
|
||||
{
|
||||
int max_height = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode].y : VideoModeOffsetsOverscan[videomode].y;
|
||||
|
||||
if (!(FFMD && interlaced))
|
||||
{
|
||||
max_height *= 2;
|
||||
}
|
||||
|
||||
if (display == -1)
|
||||
{
|
||||
GSVector4i combined_rect = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
|
||||
if (combined_rect.z >= 2048)
|
||||
{
|
||||
const int high_x = (PCRTCDisplays[0].framebufferRect.x > PCRTCDisplays[1].framebufferRect.x) ? PCRTCDisplays[0].framebufferRect.x : PCRTCDisplays[1].framebufferRect.x;
|
||||
combined_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : high_x;
|
||||
combined_rect.x = 0;
|
||||
}
|
||||
|
||||
if (combined_rect.w >= 2048)
|
||||
{
|
||||
const int high_y = (PCRTCDisplays[0].framebufferRect.y > PCRTCDisplays[1].framebufferRect.y) ? PCRTCDisplays[0].framebufferRect.y : PCRTCDisplays[1].framebufferRect.y;
|
||||
combined_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : high_y;
|
||||
combined_rect.y = 0;
|
||||
}
|
||||
|
||||
// Cap the framebuffer read to the maximum display height, otherwise the hardware renderer gets messy.
|
||||
const int min_mag = std::max(1, std::min(PCRTCDisplays[0].magnification.y, PCRTCDisplays[1].magnification.y));
|
||||
int offset = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect).y;
|
||||
|
||||
if (FFMD && interlaced)
|
||||
{
|
||||
offset = (offset - 1) / 2;
|
||||
}
|
||||
|
||||
// Hardware mode needs a wider framebuffer as it can't offset the read.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
combined_rect.z += std::max(PCRTCDisplays[0].framebufferOffsets.x, PCRTCDisplays[1].framebufferOffsets.x);
|
||||
combined_rect.w += std::max(PCRTCDisplays[0].framebufferOffsets.y, PCRTCDisplays[1].framebufferOffsets.y);
|
||||
}
|
||||
offset = (max_height / min_mag) - offset;
|
||||
combined_rect.w = std::min(combined_rect.w, offset);
|
||||
return GSVector2i(combined_rect.z, combined_rect.w);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSVector4i out_rect = PCRTCDisplays[display].framebufferRect;
|
||||
|
||||
if (out_rect.z >= 2048)
|
||||
out_rect.z -= out_rect.x;
|
||||
|
||||
if (out_rect.w >= 2048)
|
||||
out_rect.w -= out_rect.y;
|
||||
|
||||
// Cap the framebuffer read to the maximum display height, otherwise the hardware renderer gets messy.
|
||||
const int min_mag = std::max(1, PCRTCDisplays[display].magnification.y);
|
||||
int offset = PCRTCDisplays[display].displayRect.y;
|
||||
|
||||
if (FFMD && interlaced)
|
||||
{
|
||||
offset = (offset - 1) / 2;
|
||||
}
|
||||
|
||||
offset = (max_height / min_mag) - offset;
|
||||
out_rect.w = std::min(out_rect.w, offset);
|
||||
|
||||
return GSVector2i(out_rect.z, out_rect.w);
|
||||
}
|
||||
}
|
||||
GSVector2i GetFramebufferSize(int display);
|
||||
|
||||
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
|
||||
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg)
|
||||
{
|
||||
// Save framebuffer information first, while we're here.
|
||||
PCRTCDisplays[display].FBP = framebufferReg.FBP;
|
||||
PCRTCDisplays[display].FBW = framebufferReg.FBW;
|
||||
PCRTCDisplays[display].PSM = framebufferReg.PSM;
|
||||
PCRTCDisplays[display].prevFramebufferReg = framebufferReg;
|
||||
// Probably not really enabled but will cause a mess.
|
||||
// Q-Ball Billiards enables both circuits but doesn't set one of them up.
|
||||
if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0)
|
||||
{
|
||||
PCRTCDisplays[display].enabled = false;
|
||||
return;
|
||||
}
|
||||
PCRTCDisplays[display].magnification = GSVector2i(displayReg.MAGH + 1, displayReg.MAGV + 1);
|
||||
const u32 DW = displayReg.DW + 1;
|
||||
const u32 DH = displayReg.DH + 1;
|
||||
|
||||
const int renderWidth = DW / PCRTCDisplays[display].magnification.x;
|
||||
const int renderHeight = DH / PCRTCDisplays[display].magnification.y;
|
||||
|
||||
u32 finalDisplayWidth = renderWidth;
|
||||
u32 finalDisplayHeight = renderHeight;
|
||||
// When using screen offsets the screen gets squashed/resized in to the actual screen size.
|
||||
if (GSConfig.PCRTCOffsets)
|
||||
{
|
||||
finalDisplayWidth = DW / (VideoModeDividers[videomode].x + 1);
|
||||
finalDisplayHeight = DH / (VideoModeDividers[videomode].y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
finalDisplayWidth = std::min(finalDisplayWidth ,DW / (VideoModeDividers[videomode].x + 1));
|
||||
finalDisplayHeight = std::min(finalDisplayHeight, DH / (VideoModeDividers[videomode].y + 1));
|
||||
}
|
||||
|
||||
// Framebuffer size and offsets.
|
||||
PCRTCDisplays[display].prevFramebufferOffsets = PCRTCDisplays[display].framebufferOffsets;
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z = renderWidth;
|
||||
|
||||
if(FFMD && interlaced) // Round up the height as if it's an odd value, this will cause havok with the merge circuit.
|
||||
PCRTCDisplays[display].framebufferRect.w = (renderHeight + 1) >> (FFMD * interlaced); // Half height read if FFMD + INT enabled.
|
||||
else
|
||||
PCRTCDisplays[display].framebufferRect.w = renderHeight;
|
||||
PCRTCDisplays[display].framebufferOffsets.x = framebufferReg.DBX;
|
||||
PCRTCDisplays[display].framebufferOffsets.y = framebufferReg.DBY;
|
||||
|
||||
const bool is_interlaced_resolution = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off);
|
||||
|
||||
// If the interlace flag isn't set, but it's still interlacing, the height is likely reported wrong.
|
||||
// Q-Ball Billiards.
|
||||
if (is_interlaced_resolution && !interlaced)
|
||||
finalDisplayHeight *= 2;
|
||||
|
||||
// Display size and offsets.
|
||||
PCRTCDisplays[display].displayRect.x = 0;
|
||||
PCRTCDisplays[display].displayRect.y = 0;
|
||||
PCRTCDisplays[display].displayRect.z = finalDisplayWidth;
|
||||
PCRTCDisplays[display].displayRect.w = finalDisplayHeight;
|
||||
PCRTCDisplays[display].prevDisplayOffset = PCRTCDisplays[display].displayOffset;
|
||||
PCRTCDisplays[display].displayOffset.x = displayReg.DX;
|
||||
PCRTCDisplays[display].displayOffset.y = displayReg.DY;
|
||||
}
|
||||
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg);
|
||||
|
||||
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
|
||||
// Only considered if "Anti-blur" is enabled.
|
||||
void CalculateFramebufferOffset(bool scanmask)
|
||||
{
|
||||
if (GSConfig.PCRTCAntiBlur && PCRTCSameSrc && !scanmask)
|
||||
{
|
||||
GSVector2i fb0 = GSVector2i(PCRTCDisplays[0].framebufferOffsets.x, PCRTCDisplays[0].framebufferOffsets.y);
|
||||
GSVector2i fb1 = GSVector2i(PCRTCDisplays[1].framebufferOffsets.x, PCRTCDisplays[1].framebufferOffsets.y);
|
||||
|
||||
if (fb0.x + PCRTCDisplays[0].displayRect.z > 2048)
|
||||
{
|
||||
fb0.x -= 2048;
|
||||
fb0.x = abs(fb0.x);
|
||||
}
|
||||
if (fb0.y + PCRTCDisplays[0].displayRect.w > 2048)
|
||||
{
|
||||
fb0.y -= 2048;
|
||||
fb0.y = abs(fb0.y);
|
||||
}
|
||||
if (fb1.x + PCRTCDisplays[1].displayRect.z > 2048)
|
||||
{
|
||||
fb1.x -= 2048;
|
||||
fb1.x = abs(fb1.x);
|
||||
}
|
||||
if (fb1.y + PCRTCDisplays[1].displayRect.w > 2048)
|
||||
{
|
||||
fb1.y -= 2048;
|
||||
fb1.y = abs(fb1.y);
|
||||
}
|
||||
|
||||
if (abs(fb1.y - fb0.y) == 1
|
||||
&& PCRTCDisplays[0].displayRect.y == PCRTCDisplays[1].displayRect.y)
|
||||
{
|
||||
if (fb1.y < fb0.y)
|
||||
PCRTCDisplays[0].framebufferOffsets.y = fb1.y;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.y = fb0.y;
|
||||
}
|
||||
if (abs(fb1.x - fb0.x) == 1
|
||||
&& PCRTCDisplays[0].displayRect.x == PCRTCDisplays[1].displayRect.x)
|
||||
{
|
||||
if (fb1.x < fb0.x)
|
||||
PCRTCDisplays[0].framebufferOffsets.x = fb1.x;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.x = fb0.x;
|
||||
}
|
||||
}
|
||||
PCRTCDisplays[0].framebufferRect.x += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.z += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.y += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
PCRTCDisplays[0].framebufferRect.w += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
|
||||
PCRTCDisplays[1].framebufferRect.x += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.z += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.y += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
PCRTCDisplays[1].framebufferRect.w += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
}
|
||||
void CalculateFramebufferOffset(bool scanmask);
|
||||
|
||||
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
|
||||
void RemoveFramebufferOffset(int display)
|
||||
{
|
||||
if (display >= 0)
|
||||
{
|
||||
// Hardware needs nothing but handling for wrapped framebuffers.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
if (PCRTCDisplays[display].framebufferRect.z >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].displayRect.x += 2048 - PCRTCDisplays[display].framebufferRect.x;
|
||||
PCRTCDisplays[display].displayRect.z += 2048 - PCRTCDisplays[display].framebufferRect.x;
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z -= 2048;
|
||||
}
|
||||
if (PCRTCDisplays[display].framebufferRect.w >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].displayRect.y += 2048 - PCRTCDisplays[display].framebufferRect.y;
|
||||
PCRTCDisplays[display].displayRect.w += 2048 - PCRTCDisplays[display].framebufferRect.y;
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.w -= 2048;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[display].PSM];
|
||||
|
||||
// Software mode - See note below.
|
||||
GSVector4i r = PCRTCDisplays[display].framebufferRect;
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[display].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[display].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.y -= r.y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Software Mode Note:
|
||||
// This code is to read the framebuffer nicely block aligned in software, then leave the remaining offset in to the block.
|
||||
// In hardware mode this doesn't happen, it reads the whole framebuffer, so we need to keep the offset.
|
||||
if (!GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[1].PSM];
|
||||
|
||||
GSVector4i r = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[0].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[0].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.w -= r.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void RemoveFramebufferOffset(int display);
|
||||
|
||||
// If the two displays are offset from each other, move them to the correct offsets.
|
||||
// If using screen offsets, calculate the positions here.
|
||||
void CalculateDisplayOffset(bool scanmask)
|
||||
{
|
||||
const bool both_enabled = PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled;
|
||||
// Offsets are generally ignored, the "hacky" way of doing the displays, but direct to framebuffers.
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
int int_off[2] = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
GSVector2i baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a single pixel offset, account for it else it can throw interlacing out.
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
continue;
|
||||
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
const int offset = (PCRTCDisplays[i].displayOffset.y - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset > 4)
|
||||
continue;
|
||||
|
||||
int_off[i] = offset & 1;
|
||||
if (offset < 0)
|
||||
int_off[i] = -int_off[i];
|
||||
|
||||
PCRTCDisplays[i].displayRect.y += int_off[i];
|
||||
PCRTCDisplays[i].displayRect.w += int_off[i];
|
||||
}
|
||||
|
||||
// Handle difference in offset between the two displays, used in games like DmC and Time Crisis 2 (for split screen).
|
||||
// Offset is not screen based, but relative to each other.
|
||||
if (both_enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayOffset.x - PCRTCDisplays[zeroDisplay.x].displayOffset.x) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayOffset.y - PCRTCDisplays[zeroDisplay.y].displayOffset.y) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset.x >= 4 || !GSConfig.PCRTCAntiBlur || scanmask)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x += offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z += offset.x;
|
||||
}
|
||||
if (offset.y >= 4 || !GSConfig.PCRTCAntiBlur || scanmask)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y += offset.y - int_off[1 - zeroDisplay.y];
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w += offset.y - int_off[1 - zeroDisplay.y];
|
||||
}
|
||||
|
||||
baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
}
|
||||
|
||||
// Handle any large vertical offset from the zero position on the screen.
|
||||
// Example: Hokuto no Ken, does a rougly -14 offset to bring the screen up.
|
||||
// Ignore the lowest bit, we've already accounted for this
|
||||
int vOffset = ((static_cast<int>(baseOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1));
|
||||
|
||||
if(vOffset <= 4 && vOffset != 0)
|
||||
{
|
||||
PCRTCDisplays[0].displayRect.y += vOffset - int_off[0];
|
||||
PCRTCDisplays[0].displayRect.w += vOffset - int_off[0];
|
||||
PCRTCDisplays[1].displayRect.y += vOffset - int_off[1];
|
||||
PCRTCDisplays[1].displayRect.w += vOffset - int_off[1];
|
||||
}
|
||||
}
|
||||
else // We're using screen offsets, so just calculate the entire offset.
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
GSVector2i offset = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
offset.x = (static_cast<int>(PCRTCDisplays[i].displayOffset.x) - offsets.z) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (static_cast<int>(PCRTCDisplays[i].displayOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
PCRTCDisplays[i].displayRect.x += offset.x;
|
||||
PCRTCDisplays[i].displayRect.z += offset.x;
|
||||
PCRTCDisplays[i].displayRect.y += offset.y;
|
||||
PCRTCDisplays[i].displayRect.w += offset.y;
|
||||
}
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayRect.x - PCRTCDisplays[zeroDisplay.x].displayRect.x);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayRect.y - PCRTCDisplays[zeroDisplay.y].displayRect.y);
|
||||
|
||||
if (offset.x > 0 && offset.x < 4 && GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x -= offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z -= offset.x;
|
||||
}
|
||||
if (offset.y > 0 && offset.y < 4 && GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y -= offset.y;
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w -= offset.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void CalculateDisplayOffset(bool scanmask);
|
||||
} PCRTCDisplays;
|
||||
|
||||
public:
|
||||
|
||||
@@ -394,6 +394,30 @@ public:
|
||||
return GSVector4i(_mm_max_epu32(m, a));
|
||||
}
|
||||
|
||||
__forceinline u32 minv_s32() const
|
||||
{
|
||||
const __m128i vmin = _mm_min_epi32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(3, 2, 3, 2)));
|
||||
return std::min<s32>(_mm_extract_epi32(vmin, 0), _mm_extract_epi32(vmin, 1));
|
||||
}
|
||||
|
||||
__forceinline u32 minv_u32() const
|
||||
{
|
||||
const __m128i vmin = _mm_min_epu32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(3, 2, 3, 2)));
|
||||
return std::min<u32>(_mm_extract_epi32(vmin, 0), _mm_extract_epi32(vmin, 1));
|
||||
}
|
||||
|
||||
__forceinline u32 maxv_s32() const
|
||||
{
|
||||
const __m128i vmax = _mm_max_epi32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(3, 2, 3, 2)));
|
||||
return std::max<s32>(_mm_extract_epi32(vmax, 0), _mm_extract_epi32(vmax, 1));
|
||||
}
|
||||
|
||||
__forceinline u32 maxv_u32() const
|
||||
{
|
||||
const __m128i vmax = _mm_max_epu32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(3, 2, 3, 2)));
|
||||
return std::max<u32>(_mm_extract_epi32(vmax, 0), _mm_extract_epi32(vmax, 1));
|
||||
}
|
||||
|
||||
__forceinline static int min_i16(int a, int b)
|
||||
{
|
||||
return store(load(a).min_i16(load(b)));
|
||||
|
||||
@@ -68,12 +68,14 @@ const char* shaderName(PresentShader value)
|
||||
switch (value)
|
||||
{
|
||||
// clang-format off
|
||||
case PresentShader::COPY: return "ps_copy";
|
||||
case PresentShader::SCANLINE: return "ps_filter_scanlines";
|
||||
case PresentShader::DIAGONAL_FILTER: return "ps_filter_diagonal";
|
||||
case PresentShader::TRIANGULAR_FILTER: return "ps_filter_triangular";
|
||||
case PresentShader::COMPLEX_FILTER: return "ps_filter_complex";
|
||||
case PresentShader::LOTTES_FILTER: return "ps_filter_lottes";
|
||||
case PresentShader::COPY: return "ps_copy";
|
||||
case PresentShader::SCANLINE: return "ps_filter_scanlines";
|
||||
case PresentShader::DIAGONAL_FILTER: return "ps_filter_diagonal";
|
||||
case PresentShader::TRIANGULAR_FILTER: return "ps_filter_triangular";
|
||||
case PresentShader::COMPLEX_FILTER: return "ps_filter_complex";
|
||||
case PresentShader::LOTTES_FILTER: return "ps_filter_lottes";
|
||||
case PresentShader::SUPERSAMPLE_4xRGSS: return "ps_4x_rgss";
|
||||
case PresentShader::SUPERSAMPLE_AUTO: return "ps_automagical_supersampling";
|
||||
// clang-format on
|
||||
default:
|
||||
ASSERT(0);
|
||||
|
||||
@@ -131,6 +131,8 @@ enum class PresentShader
|
||||
TRIANGULAR_FILTER,
|
||||
COMPLEX_FILTER,
|
||||
LOTTES_FILTER,
|
||||
SUPERSAMPLE_4xRGSS,
|
||||
SUPERSAMPLE_AUTO,
|
||||
Count
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
#include "ImGui/ImGuiManager.h"
|
||||
#include "GS/Renderers/Common/GSRenderer.h"
|
||||
#include "GS/GSCapture.h"
|
||||
#include "GS/GSDump.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/GSPerfMon.h"
|
||||
#include "GS/GSUtil.h"
|
||||
#include "GSDumpReplayer.h"
|
||||
#include "Host.h"
|
||||
#include "PerformanceMetrics.h"
|
||||
@@ -41,10 +44,11 @@
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
static constexpr std::array<PresentShader, 6> s_tv_shader_indices = {
|
||||
static constexpr std::array<PresentShader, 8> s_tv_shader_indices = {
|
||||
PresentShader::COPY, PresentShader::SCANLINE,
|
||||
PresentShader::DIAGONAL_FILTER, PresentShader::TRIANGULAR_FILTER,
|
||||
PresentShader::COMPLEX_FILTER, PresentShader::LOTTES_FILTER};
|
||||
PresentShader::COMPLEX_FILTER, PresentShader::LOTTES_FILTER,
|
||||
PresentShader::SUPERSAMPLE_4xRGSS, PresentShader::SUPERSAMPLE_AUTO};
|
||||
|
||||
static std::deque<std::thread> s_screenshot_threads;
|
||||
static std::mutex s_screenshot_threads_mutex;
|
||||
|
||||
@@ -1282,17 +1282,13 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
|
||||
// ps
|
||||
|
||||
PSSetShaderResources(sTex, nullptr);
|
||||
PSSetShaderResource(0, sTex);
|
||||
PSSetSamplerState(linear ? m_convert.ln.get() : m_convert.pt.get());
|
||||
PSSetShader(ps, ps_cb);
|
||||
|
||||
//
|
||||
|
||||
DrawPrimitive();
|
||||
|
||||
//
|
||||
|
||||
PSSetShaderResources(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear)
|
||||
@@ -1350,17 +1346,13 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
|
||||
// ps
|
||||
|
||||
PSSetShaderResources(sTex, nullptr);
|
||||
PSSetShaderResource(0, sTex);
|
||||
PSSetSamplerState(linear ? m_convert.ln.get() : m_convert.pt.get());
|
||||
PSSetShader(m_present.ps[static_cast<u32>(shader)].get(), m_present.ps_cb.get());
|
||||
|
||||
//
|
||||
|
||||
DrawPrimitive();
|
||||
|
||||
//
|
||||
|
||||
PSSetShaderResources(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void GSDevice11::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
|
||||
@@ -1813,7 +1805,7 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert
|
||||
VSSetShader(m_convert.vs.get(), nullptr);
|
||||
|
||||
// ps
|
||||
PSSetShaderResources(rt, nullptr);
|
||||
PSSetShaderResource(0, rt);
|
||||
PSSetSamplerState(m_convert.pt.get());
|
||||
PSSetShader(m_convert.ps[static_cast<int>(datm ? ShaderConvert::DATM_1 : ShaderConvert::DATM_0)].get(), nullptr);
|
||||
|
||||
@@ -1993,16 +1985,9 @@ void GSDevice11::VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb)
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice11::PSSetShaderResources(GSTexture* sr0, GSTexture* sr1)
|
||||
{
|
||||
PSSetShaderResource(0, sr0);
|
||||
PSSetShaderResource(1, sr1);
|
||||
PSSetShaderResource(2, nullptr);
|
||||
}
|
||||
|
||||
void GSDevice11::PSSetShaderResource(int i, GSTexture* sr)
|
||||
{
|
||||
m_state.ps_sr_views[i] = sr ? static_cast<ID3D11ShaderResourceView*>(*static_cast<GSTexture11*>(sr)) : nullptr;
|
||||
m_state.ps_sr_views[i] = *static_cast<GSTexture11*>(sr);
|
||||
}
|
||||
|
||||
void GSDevice11::PSSetSamplerState(ID3D11SamplerState* ss0)
|
||||
@@ -2277,7 +2262,16 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
IASetPrimitiveTopology(topology);
|
||||
|
||||
PSSetShaderResources(config.tex, config.pal);
|
||||
if (config.tex)
|
||||
{
|
||||
CommitClear(config.tex);
|
||||
PSSetShaderResource(0, config.tex);
|
||||
}
|
||||
if (config.pal)
|
||||
{
|
||||
CommitClear(config.pal);
|
||||
PSSetShaderResource(1, config.pal);
|
||||
}
|
||||
|
||||
GSTexture* rt_copy = nullptr;
|
||||
if (config.require_one_barrier || (config.tex && config.tex == config.rt)) // Used as "bind rt" flag when texture barrier is unsupported
|
||||
|
||||
@@ -348,7 +348,6 @@ public:
|
||||
|
||||
void VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb);
|
||||
|
||||
void PSSetShaderResources(GSTexture* sr0, GSTexture* sr1);
|
||||
void PSSetShaderResource(int i, GSTexture* sr);
|
||||
void PSSetShader(ID3D11PixelShader* ps, ID3D11Buffer* ps_cb);
|
||||
void PSUpdateShaderState();
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "GS/Renderers/DX12/D3D12Builders.h"
|
||||
#include "GS/Renderers/DX12/D3D12Context.h"
|
||||
#include "GS/Renderers/DX12/D3D12ShaderCache.h"
|
||||
#include "GS/Renderers/DX12/GSDevice12.h"
|
||||
#include "common/Console.h"
|
||||
|
||||
#include <cstdarg>
|
||||
@@ -291,7 +291,7 @@ void D3D12::RootSignatureBuilder::Clear()
|
||||
|
||||
wil::com_ptr_nothrow<ID3D12RootSignature> D3D12::RootSignatureBuilder::Create(bool clear /*= true*/)
|
||||
{
|
||||
wil::com_ptr_nothrow<ID3D12RootSignature> rs = g_d3d12_context->CreateRootSignature(&m_desc);
|
||||
wil::com_ptr_nothrow<ID3D12RootSignature> rs = GSDevice12::GetInstance()->CreateRootSignature(&m_desc);
|
||||
if (!rs)
|
||||
return {};
|
||||
|
||||
@@ -362,3 +362,21 @@ u32 D3D12::RootSignatureBuilder::AddDescriptorTable(
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
void D3D12::SetObjectName(ID3D12Object* object, const char* name)
|
||||
{
|
||||
object->SetName(StringUtil::UTF8StringToWideString(name).c_str());
|
||||
}
|
||||
|
||||
void D3D12::SetObjectNameFormatted(ID3D12Object* object, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
SetObjectName(object, StringUtil::StdStringFromFormatV(format, ap).c_str());
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -141,4 +141,15 @@ namespace D3D12
|
||||
D3D12_COMPUTE_PIPELINE_STATE_DESC m_desc;
|
||||
};
|
||||
|
||||
} // namespace D3D12
|
||||
#ifdef _DEBUG
|
||||
void SetObjectName(ID3D12Object* object, const char* name);
|
||||
void SetObjectNameFormatted(ID3D12Object* object, const char* format, ...);
|
||||
#else
|
||||
static inline void SetObjectName(ID3D12Object* object, const char* name)
|
||||
{
|
||||
}
|
||||
static inline void SetObjectNameFormatted(ID3D12Object* object, const char* format, ...)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
} // namespace D3D12
|
||||
|
||||
@@ -1,686 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "GS/Renderers/DX12/D3D12Context.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/General.h"
|
||||
#include "common/ScopedGuard.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "D3D12MemAlloc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <dxgi1_4.h>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
std::unique_ptr<D3D12Context> g_d3d12_context;
|
||||
|
||||
D3D12Context::D3D12Context() = default;
|
||||
|
||||
D3D12Context::~D3D12Context()
|
||||
{
|
||||
DestroyResources();
|
||||
}
|
||||
|
||||
D3D12Context::ComPtr<ID3DBlob> D3D12Context::SerializeRootSignature(const D3D12_ROOT_SIGNATURE_DESC* desc)
|
||||
{
|
||||
ComPtr<ID3DBlob> blob;
|
||||
ComPtr<ID3DBlob> error_blob;
|
||||
const HRESULT hr = D3D12SerializeRootSignature(desc, D3D_ROOT_SIGNATURE_VERSION_1, blob.put(), error_blob.put());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("D3D12SerializeRootSignature() failed: %08X", hr);
|
||||
if (error_blob)
|
||||
Console.Error("%s", error_blob->GetBufferPointer());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
D3D12Context::ComPtr<ID3D12RootSignature> D3D12Context::CreateRootSignature(const D3D12_ROOT_SIGNATURE_DESC* desc)
|
||||
{
|
||||
ComPtr<ID3DBlob> blob = SerializeRootSignature(desc);
|
||||
if (!blob)
|
||||
return {};
|
||||
|
||||
ComPtr<ID3D12RootSignature> rs;
|
||||
const HRESULT hr =
|
||||
m_device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(rs.put()));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("CreateRootSignature() failed: %08X", hr);
|
||||
return {};
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
bool D3D12Context::SupportsTextureFormat(DXGI_FORMAT format)
|
||||
{
|
||||
constexpr u32 required = D3D12_FORMAT_SUPPORT1_TEXTURE2D | D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE;
|
||||
|
||||
D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {format};
|
||||
return SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support))) &&
|
||||
(support.Support1 & required) == required;
|
||||
}
|
||||
|
||||
bool D3D12Context::Create(IDXGIFactory5* dxgi_factory, IDXGIAdapter1* adapter, bool enable_debug_layer)
|
||||
{
|
||||
pxAssertRel(!g_d3d12_context, "No context exists");
|
||||
|
||||
g_d3d12_context.reset(new D3D12Context());
|
||||
if (!g_d3d12_context->CreateDevice(dxgi_factory, adapter, enable_debug_layer) ||
|
||||
!g_d3d12_context->CreateCommandQueue() || !g_d3d12_context->CreateAllocator() ||
|
||||
!g_d3d12_context->CreateFence() || !g_d3d12_context->CreateDescriptorHeaps() ||
|
||||
!g_d3d12_context->CreateCommandLists() || !g_d3d12_context->CreateTimestampQuery() ||
|
||||
!g_d3d12_context->CreateTextureStreamBuffer())
|
||||
{
|
||||
Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12Context::Destroy()
|
||||
{
|
||||
if (g_d3d12_context)
|
||||
g_d3d12_context.reset();
|
||||
}
|
||||
|
||||
u32 D3D12Context::GetAdapterVendorID() const
|
||||
{
|
||||
if (!m_adapter)
|
||||
return 0;
|
||||
|
||||
DXGI_ADAPTER_DESC desc;
|
||||
if (FAILED(m_adapter->GetDesc(&desc)))
|
||||
return 0;
|
||||
|
||||
return desc.VendorId;
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateDevice(IDXGIFactory5* dxgi_factory, IDXGIAdapter1* adapter, bool enable_debug_layer)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
// Enabling the debug layer will fail if the Graphics Tools feature is not installed.
|
||||
if (enable_debug_layer)
|
||||
{
|
||||
hr = D3D12GetDebugInterface(IID_PPV_ARGS(&m_debug_interface));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
m_debug_interface->EnableDebugLayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error("Debug layer requested but not available.");
|
||||
enable_debug_layer = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the actual device.
|
||||
hr = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Failed to create D3D12 device: %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// get adapter
|
||||
const LUID luid(m_device->GetAdapterLuid());
|
||||
if (FAILED(dxgi_factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(m_adapter.put()))))
|
||||
Console.Error("Failed to get lookup adapter by device LUID");
|
||||
|
||||
if (enable_debug_layer)
|
||||
{
|
||||
ComPtr<ID3D12InfoQueue> info_queue = m_device.try_query<ID3D12InfoQueue>();
|
||||
if (info_queue)
|
||||
{
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
|
||||
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
|
||||
}
|
||||
|
||||
D3D12_INFO_QUEUE_FILTER filter = {};
|
||||
std::array<D3D12_MESSAGE_ID, 5> id_list{
|
||||
D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,
|
||||
D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE,
|
||||
D3D12_MESSAGE_ID_CREATEGRAPHICSPIPELINESTATE_RENDERTARGETVIEW_NOT_SET,
|
||||
D3D12_MESSAGE_ID_CREATEINPUTLAYOUT_TYPE_MISMATCH,
|
||||
D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE,
|
||||
};
|
||||
filter.DenyList.NumIDs = static_cast<UINT>(id_list.size());
|
||||
filter.DenyList.pIDList = id_list.data();
|
||||
info_queue->PushStorageFilter(&filter);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateCommandQueue()
|
||||
{
|
||||
const D3D12_COMMAND_QUEUE_DESC queue_desc = {
|
||||
D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, D3D12_COMMAND_QUEUE_FLAG_NONE};
|
||||
HRESULT hr = m_device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&m_command_queue));
|
||||
pxAssertRel(SUCCEEDED(hr), "Create command queue");
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateAllocator()
|
||||
{
|
||||
D3D12MA::ALLOCATOR_DESC allocatorDesc = {};
|
||||
allocatorDesc.pDevice = m_device.get();
|
||||
allocatorDesc.pAdapter = m_adapter.get();
|
||||
allocatorDesc.Flags =
|
||||
D3D12MA::ALLOCATOR_FLAG_SINGLETHREADED |
|
||||
D3D12MA::ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED /* | D3D12MA::ALLOCATOR_FLAG_ALWAYS_COMMITTED*/;
|
||||
|
||||
const HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, m_allocator.put());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("D3D12MA::CreateAllocator() failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateFence()
|
||||
{
|
||||
HRESULT hr = m_device->CreateFence(m_completed_fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
|
||||
pxAssertRel(SUCCEEDED(hr), "Create fence");
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
m_fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
pxAssertRel(m_fence_event != NULL, "Create fence event");
|
||||
if (!m_fence_event)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateDescriptorHeaps()
|
||||
{
|
||||
static constexpr size_t MAX_SRVS = 32768;
|
||||
static constexpr size_t MAX_RTVS = 16384;
|
||||
static constexpr size_t MAX_DSVS = 16384;
|
||||
static constexpr size_t MAX_CPU_SAMPLERS = 1024;
|
||||
|
||||
if (!m_descriptor_heap_manager.Create(m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, MAX_SRVS, false) ||
|
||||
!m_rtv_heap_manager.Create(m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, MAX_RTVS, false) ||
|
||||
!m_dsv_heap_manager.Create(m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, MAX_DSVS, false) ||
|
||||
!m_sampler_heap_manager.Create(m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, MAX_CPU_SAMPLERS, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate null SRV descriptor for unbound textures.
|
||||
constexpr D3D12_SHADER_RESOURCE_VIEW_DESC null_srv_desc = {
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM, D3D12_SRV_DIMENSION_TEXTURE2D, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
|
||||
|
||||
if (!m_descriptor_heap_manager.Allocate(&m_null_srv_descriptor))
|
||||
{
|
||||
pxFailRel("Failed to allocate null descriptor");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device->CreateShaderResourceView(nullptr, &null_srv_desc, m_null_srv_descriptor.cpu_handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateCommandLists()
|
||||
{
|
||||
static constexpr size_t MAX_GPU_SRVS = 32768;
|
||||
static constexpr size_t MAX_GPU_SAMPLERS = 2048;
|
||||
|
||||
for (u32 i = 0; i < NUM_COMMAND_LISTS; i++)
|
||||
{
|
||||
CommandListResources& res = m_command_lists[i];
|
||||
HRESULT hr;
|
||||
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
hr = m_device->CreateCommandAllocator(
|
||||
D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(res.command_allocators[i].put()));
|
||||
pxAssertRel(SUCCEEDED(hr), "Create command allocator");
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, res.command_allocators[i].get(),
|
||||
nullptr, IID_PPV_ARGS(res.command_lists[i].put()));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Failed to create command list: %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close the command lists, since the first thing we do is reset them.
|
||||
hr = res.command_lists[i]->Close();
|
||||
pxAssertRel(SUCCEEDED(hr), "Closing new command list failed");
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!res.descriptor_allocator.Create(m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, MAX_GPU_SRVS))
|
||||
{
|
||||
Console.Error("Failed to create per frame descriptor allocator");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!res.sampler_allocator.Create(m_device.get(), MAX_GPU_SAMPLERS))
|
||||
{
|
||||
Console.Error("Failed to create per frame sampler allocator");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MoveToNextCommandList();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateTextureStreamBuffer()
|
||||
{
|
||||
return m_texture_stream_buffer.Create(TEXTURE_UPLOAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void D3D12Context::MoveToNextCommandList()
|
||||
{
|
||||
m_current_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
|
||||
m_current_fence_value++;
|
||||
|
||||
// We may have to wait if this command list hasn't finished on the GPU.
|
||||
CommandListResources& res = m_command_lists[m_current_command_list];
|
||||
WaitForFence(res.ready_fence_value, false);
|
||||
res.ready_fence_value = m_current_fence_value;
|
||||
res.init_command_list_used = false;
|
||||
|
||||
// Begin command list.
|
||||
res.command_allocators[1]->Reset();
|
||||
res.command_lists[1]->Reset(res.command_allocators[1].get(), nullptr);
|
||||
res.descriptor_allocator.Reset();
|
||||
if (res.sampler_allocator.ShouldReset())
|
||||
res.sampler_allocator.Reset();
|
||||
|
||||
if (res.has_timestamp_query)
|
||||
{
|
||||
// readback timestamp from the last time this cmdlist was used.
|
||||
// we don't need to worry about disjoint in dx12, the frequency is reliable within a single cmdlist.
|
||||
const u32 offset = (m_current_command_list * (sizeof(u64) * NUM_TIMESTAMP_QUERIES_PER_CMDLIST));
|
||||
const D3D12_RANGE read_range = {offset, offset + (sizeof(u64) * NUM_TIMESTAMP_QUERIES_PER_CMDLIST)};
|
||||
void* map;
|
||||
HRESULT hr = m_timestamp_query_buffer->Map(0, &read_range, &map);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
u64 timestamps[2];
|
||||
std::memcpy(timestamps, static_cast<const u8*>(map) + offset, sizeof(timestamps));
|
||||
m_accumulated_gpu_time +=
|
||||
static_cast<float>(static_cast<double>(timestamps[1] - timestamps[0]) / m_timestamp_frequency);
|
||||
|
||||
const D3D12_RANGE write_range = {};
|
||||
m_timestamp_query_buffer->Unmap(0, &write_range);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Warning("Map() for timestamp query failed: %08X", hr);
|
||||
}
|
||||
}
|
||||
|
||||
res.has_timestamp_query = m_gpu_timing_enabled;
|
||||
if (m_gpu_timing_enabled)
|
||||
{
|
||||
res.command_lists[1]->EndQuery(m_timestamp_query_heap.get(), D3D12_QUERY_TYPE_TIMESTAMP,
|
||||
m_current_command_list * NUM_TIMESTAMP_QUERIES_PER_CMDLIST);
|
||||
}
|
||||
|
||||
ID3D12DescriptorHeap* heaps[2] = {
|
||||
res.descriptor_allocator.GetDescriptorHeap(), res.sampler_allocator.GetDescriptorHeap()};
|
||||
res.command_lists[1]->SetDescriptorHeaps(std::size(heaps), heaps);
|
||||
|
||||
m_allocator->SetCurrentFrameIndex(static_cast<UINT>(m_current_fence_value));
|
||||
}
|
||||
|
||||
ID3D12GraphicsCommandList4* D3D12Context::GetInitCommandList()
|
||||
{
|
||||
CommandListResources& res = m_command_lists[m_current_command_list];
|
||||
if (!res.init_command_list_used)
|
||||
{
|
||||
HRESULT hr = res.command_allocators[0]->Reset();
|
||||
pxAssertMsg(SUCCEEDED(hr), "Reset init command allocator failed");
|
||||
|
||||
res.command_lists[0]->Reset(res.command_allocators[0].get(), nullptr);
|
||||
pxAssertMsg(SUCCEEDED(hr), "Reset init command list failed");
|
||||
res.init_command_list_used = true;
|
||||
}
|
||||
|
||||
return res.command_lists[0].get();
|
||||
}
|
||||
|
||||
bool D3D12Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
{
|
||||
CommandListResources& res = m_command_lists[m_current_command_list];
|
||||
HRESULT hr;
|
||||
|
||||
if (res.has_timestamp_query)
|
||||
{
|
||||
// write the timestamp back at the end of the cmdlist
|
||||
res.command_lists[1]->EndQuery(m_timestamp_query_heap.get(), D3D12_QUERY_TYPE_TIMESTAMP,
|
||||
(m_current_command_list * NUM_TIMESTAMP_QUERIES_PER_CMDLIST) + 1);
|
||||
res.command_lists[1]->ResolveQueryData(m_timestamp_query_heap.get(), D3D12_QUERY_TYPE_TIMESTAMP,
|
||||
m_current_command_list * NUM_TIMESTAMP_QUERIES_PER_CMDLIST, NUM_TIMESTAMP_QUERIES_PER_CMDLIST,
|
||||
m_timestamp_query_buffer.get(), m_current_command_list * (sizeof(u64) * NUM_TIMESTAMP_QUERIES_PER_CMDLIST));
|
||||
}
|
||||
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
hr = res.command_lists[0]->Close();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Closing init command list failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Close and queue command list.
|
||||
hr = res.command_lists[1]->Close();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Closing main command list failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
const std::array<ID3D12CommandList*, 2> execute_lists{res.command_lists[0].get(), res.command_lists[1].get()};
|
||||
m_command_queue->ExecuteCommandLists(static_cast<UINT>(execute_lists.size()), execute_lists.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<ID3D12CommandList*, 1> execute_lists{res.command_lists[1].get()};
|
||||
m_command_queue->ExecuteCommandLists(static_cast<UINT>(execute_lists.size()), execute_lists.data());
|
||||
}
|
||||
|
||||
// Update fence when GPU has completed.
|
||||
hr = m_command_queue->Signal(m_fence.get(), res.ready_fence_value);
|
||||
pxAssertRel(SUCCEEDED(hr), "Signal fence");
|
||||
|
||||
MoveToNextCommandList();
|
||||
if (wait_for_completion != WaitType::None)
|
||||
WaitForFence(res.ready_fence_value, wait_for_completion == WaitType::Spin);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12Context::InvalidateSamplerGroups()
|
||||
{
|
||||
for (CommandListResources& res : m_command_lists)
|
||||
res.sampler_allocator.InvalidateCache();
|
||||
}
|
||||
|
||||
void D3D12Context::DeferObjectDestruction(ID3D12DeviceChild* resource)
|
||||
{
|
||||
if (!resource)
|
||||
return;
|
||||
|
||||
resource->AddRef();
|
||||
m_command_lists[m_current_command_list].pending_resources.emplace_back(nullptr, resource);
|
||||
}
|
||||
|
||||
void D3D12Context::DeferResourceDestruction(D3D12MA::Allocation* allocation, ID3D12Resource* resource)
|
||||
{
|
||||
if (!resource)
|
||||
return;
|
||||
|
||||
if (allocation)
|
||||
allocation->AddRef();
|
||||
|
||||
resource->AddRef();
|
||||
m_command_lists[m_current_command_list].pending_resources.emplace_back(allocation, resource);
|
||||
}
|
||||
|
||||
void D3D12Context::DeferDescriptorDestruction(D3D12DescriptorHeapManager& manager, u32 index)
|
||||
{
|
||||
m_command_lists[m_current_command_list].pending_descriptors.emplace_back(manager, index);
|
||||
}
|
||||
|
||||
void D3D12Context::DeferDescriptorDestruction(D3D12DescriptorHeapManager& manager, D3D12DescriptorHandle* handle)
|
||||
{
|
||||
if (handle->index == D3D12DescriptorHandle::INVALID_INDEX)
|
||||
return;
|
||||
|
||||
m_command_lists[m_current_command_list].pending_descriptors.emplace_back(manager, handle->index);
|
||||
handle->Clear();
|
||||
}
|
||||
|
||||
void D3D12Context::DestroyPendingResources(CommandListResources& cmdlist)
|
||||
{
|
||||
for (const auto& dd : cmdlist.pending_descriptors)
|
||||
dd.first.Free(dd.second);
|
||||
cmdlist.pending_descriptors.clear();
|
||||
|
||||
for (const auto& it : cmdlist.pending_resources)
|
||||
{
|
||||
it.second->Release();
|
||||
if (it.first)
|
||||
it.first->Release();
|
||||
}
|
||||
cmdlist.pending_resources.clear();
|
||||
}
|
||||
|
||||
void D3D12Context::DestroyResources()
|
||||
{
|
||||
if (m_command_queue)
|
||||
{
|
||||
ExecuteCommandList(WaitType::Sleep);
|
||||
WaitForGPUIdle();
|
||||
}
|
||||
|
||||
m_texture_stream_buffer.Destroy(false);
|
||||
m_descriptor_heap_manager.Free(&m_null_srv_descriptor);
|
||||
m_timestamp_query_buffer.reset();
|
||||
m_timestamp_query_allocation.reset();
|
||||
m_sampler_heap_manager.Destroy();
|
||||
m_dsv_heap_manager.Destroy();
|
||||
m_rtv_heap_manager.Destroy();
|
||||
m_descriptor_heap_manager.Destroy();
|
||||
m_command_lists = {};
|
||||
m_current_command_list = 0;
|
||||
m_completed_fence_value = 0;
|
||||
m_current_fence_value = 0;
|
||||
if (m_fence_event)
|
||||
{
|
||||
CloseHandle(m_fence_event);
|
||||
m_fence_event = {};
|
||||
}
|
||||
|
||||
m_allocator.reset();
|
||||
m_command_queue.reset();
|
||||
m_debug_interface.reset();
|
||||
m_device.reset();
|
||||
}
|
||||
|
||||
void D3D12Context::WaitForFence(u64 fence, bool spin)
|
||||
{
|
||||
if (m_completed_fence_value >= fence)
|
||||
return;
|
||||
|
||||
if (spin)
|
||||
{
|
||||
u64 value;
|
||||
while ((value = m_fence->GetCompletedValue()) < fence)
|
||||
ShortSpin();
|
||||
m_completed_fence_value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try non-blocking check.
|
||||
m_completed_fence_value = m_fence->GetCompletedValue();
|
||||
if (m_completed_fence_value < fence)
|
||||
{
|
||||
// Fall back to event.
|
||||
HRESULT hr = m_fence->SetEventOnCompletion(fence, m_fence_event);
|
||||
pxAssertRel(SUCCEEDED(hr), "Set fence event on completion");
|
||||
WaitForSingleObject(m_fence_event, INFINITE);
|
||||
m_completed_fence_value = m_fence->GetCompletedValue();
|
||||
}
|
||||
}
|
||||
|
||||
// Release resources for as many command lists which have completed.
|
||||
u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
|
||||
for (u32 i = 0; i < NUM_COMMAND_LISTS; i++)
|
||||
{
|
||||
CommandListResources& res = m_command_lists[index];
|
||||
if (m_completed_fence_value < res.ready_fence_value)
|
||||
break;
|
||||
|
||||
DestroyPendingResources(res);
|
||||
index = (index + 1) % NUM_COMMAND_LISTS;
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12Context::WaitForGPUIdle()
|
||||
{
|
||||
u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
|
||||
for (u32 i = 0; i < (NUM_COMMAND_LISTS - 1); i++)
|
||||
{
|
||||
WaitForFence(m_command_lists[index].ready_fence_value, false);
|
||||
index = (index + 1) % NUM_COMMAND_LISTS;
|
||||
}
|
||||
}
|
||||
|
||||
bool D3D12Context::CreateTimestampQuery()
|
||||
{
|
||||
constexpr u32 QUERY_COUNT = NUM_TIMESTAMP_QUERIES_PER_CMDLIST * NUM_COMMAND_LISTS;
|
||||
constexpr u32 BUFFER_SIZE = sizeof(u64) * QUERY_COUNT;
|
||||
|
||||
const D3D12_QUERY_HEAP_DESC desc = {D3D12_QUERY_HEAP_TYPE_TIMESTAMP, QUERY_COUNT};
|
||||
HRESULT hr = m_device->CreateQueryHeap(&desc, IID_PPV_ARGS(m_timestamp_query_heap.put()));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("CreateQueryHeap() for timestamp failed with %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const D3D12MA::ALLOCATION_DESC allocation_desc = {D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_READBACK};
|
||||
const D3D12_RESOURCE_DESC resource_desc = {D3D12_RESOURCE_DIMENSION_BUFFER, 0, BUFFER_SIZE, 1, 1, 1,
|
||||
DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE};
|
||||
hr = m_allocator->CreateResource(&allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
|
||||
m_timestamp_query_allocation.put(), IID_PPV_ARGS(m_timestamp_query_buffer.put()));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("CreateResource() for timestamp failed with %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 frequency;
|
||||
hr = m_command_queue->GetTimestampFrequency(&frequency);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("GetTimestampFrequency() failed: %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_timestamp_frequency = static_cast<double>(frequency) / 1000.0;
|
||||
return true;
|
||||
}
|
||||
|
||||
float D3D12Context::GetAndResetAccumulatedGPUTime()
|
||||
{
|
||||
const float time = m_accumulated_gpu_time;
|
||||
m_accumulated_gpu_time = 0.0f;
|
||||
return time;
|
||||
}
|
||||
|
||||
void D3D12Context::SetEnableGPUTiming(bool enabled)
|
||||
{
|
||||
m_gpu_timing_enabled = enabled;
|
||||
}
|
||||
|
||||
bool D3D12Context::AllocatePreinitializedGPUBuffer(u32 size, ID3D12Resource** gpu_buffer,
|
||||
D3D12MA::Allocation** gpu_allocation, const std::function<void(void*)>& fill_callback)
|
||||
{
|
||||
// Try to place the fixed index buffer in GPU local memory.
|
||||
// Use the staging buffer to copy into it.
|
||||
const D3D12_RESOURCE_DESC rd = {D3D12_RESOURCE_DIMENSION_BUFFER, 0, size, 1, 1, 1, DXGI_FORMAT_UNKNOWN, {1, 0},
|
||||
D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE};
|
||||
|
||||
const D3D12MA::ALLOCATION_DESC cpu_ad = {D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_UPLOAD};
|
||||
|
||||
ComPtr<ID3D12Resource> cpu_buffer;
|
||||
ComPtr<D3D12MA::Allocation> cpu_allocation;
|
||||
HRESULT hr = m_allocator->CreateResource(
|
||||
&cpu_ad, &rd, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, cpu_allocation.put(), IID_PPV_ARGS(cpu_buffer.put()));
|
||||
pxAssertMsg(SUCCEEDED(hr), "Allocate CPU buffer");
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
static constexpr const D3D12_RANGE read_range = {};
|
||||
const D3D12_RANGE write_range = {0, size};
|
||||
void* mapped;
|
||||
hr = cpu_buffer->Map(0, &read_range, &mapped);
|
||||
pxAssertMsg(SUCCEEDED(hr), "Map CPU buffer");
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
fill_callback(mapped);
|
||||
cpu_buffer->Unmap(0, &write_range);
|
||||
|
||||
const D3D12MA::ALLOCATION_DESC gpu_ad = {D3D12MA::ALLOCATION_FLAG_COMMITTED, D3D12_HEAP_TYPE_DEFAULT};
|
||||
|
||||
hr = m_allocator->CreateResource(
|
||||
&gpu_ad, &rd, D3D12_RESOURCE_STATE_COMMON, nullptr, gpu_allocation, IID_PPV_ARGS(gpu_buffer));
|
||||
pxAssertMsg(SUCCEEDED(hr), "Allocate GPU buffer");
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
GetInitCommandList()->CopyBufferRegion(*gpu_buffer, 0, cpu_buffer.get(), 0, size);
|
||||
|
||||
D3D12_RESOURCE_BARRIER rb = {D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE};
|
||||
rb.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
rb.Transition.pResource = *gpu_buffer;
|
||||
rb.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; // COMMON -> COPY_DEST at first use.
|
||||
rb.Transition.StateAfter = D3D12_RESOURCE_STATE_INDEX_BUFFER;
|
||||
GetInitCommandList()->ResourceBarrier(1, &rb);
|
||||
|
||||
DeferResourceDestruction(cpu_allocation.get(), cpu_buffer.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
void D3D12::SetObjectName(ID3D12Object* object, const char* name)
|
||||
{
|
||||
object->SetName(StringUtil::UTF8StringToWideString(name).c_str());
|
||||
}
|
||||
|
||||
void D3D12::SetObjectNameFormatted(ID3D12Object* object, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
SetObjectName(object, StringUtil::StdStringFromFormatV(format, ap).c_str());
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,235 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "GS/Renderers/DX12/D3D12DescriptorHeapManager.h"
|
||||
#include "GS/Renderers/DX12/D3D12StreamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_5.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <wil/com.h>
|
||||
|
||||
struct IDXGIAdapter;
|
||||
struct IDXGIFactory;
|
||||
namespace D3D12MA
|
||||
{
|
||||
class Allocator;
|
||||
class Allocation;
|
||||
} // namespace D3D12MA
|
||||
|
||||
class D3D12Context
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
using ComPtr = wil::com_ptr_nothrow<T>;
|
||||
|
||||
enum : u32
|
||||
{
|
||||
/// Number of command lists. One is being built while the other(s) are executed.
|
||||
NUM_COMMAND_LISTS = 3,
|
||||
|
||||
/// Textures that don't fit into this buffer will be uploaded with a staging buffer.
|
||||
TEXTURE_UPLOAD_BUFFER_SIZE = 64 * 1024 * 1024,
|
||||
|
||||
/// Maximum number of samples in a single allocation group.
|
||||
SAMPLER_GROUP_SIZE = 2,
|
||||
|
||||
/// Start/End timestamp queries.
|
||||
NUM_TIMESTAMP_QUERIES_PER_CMDLIST = 2,
|
||||
};
|
||||
|
||||
~D3D12Context();
|
||||
|
||||
/// Creates new device and context.
|
||||
static bool Create(IDXGIFactory5* dxgi_factory, IDXGIAdapter1* adapter, bool enable_debug_layer);
|
||||
|
||||
/// Destroys active context.
|
||||
static void Destroy();
|
||||
|
||||
__fi IDXGIAdapter* GetAdapter() const { return m_adapter.get(); }
|
||||
__fi ID3D12Device* GetDevice() const { return m_device.get(); }
|
||||
__fi ID3D12CommandQueue* GetCommandQueue() const { return m_command_queue.get(); }
|
||||
__fi D3D12MA::Allocator* GetAllocator() const { return m_allocator.get(); }
|
||||
|
||||
/// Returns the PCI vendor ID of the device, if known.
|
||||
u32 GetAdapterVendorID() const;
|
||||
|
||||
/// Returns the current command list, commands can be recorded directly.
|
||||
ID3D12GraphicsCommandList4* GetCommandList() const
|
||||
{
|
||||
return m_command_lists[m_current_command_list].command_lists[1].get();
|
||||
}
|
||||
|
||||
/// Returns the init command list for uploading.
|
||||
ID3D12GraphicsCommandList4* GetInitCommandList();
|
||||
|
||||
/// Returns the per-frame SRV/CBV/UAV allocator.
|
||||
D3D12DescriptorAllocator& GetDescriptorAllocator()
|
||||
{
|
||||
return m_command_lists[m_current_command_list].descriptor_allocator;
|
||||
}
|
||||
|
||||
/// Returns the per-frame sampler allocator.
|
||||
D3D12GroupedSamplerAllocator<SAMPLER_GROUP_SIZE>& GetSamplerAllocator()
|
||||
{
|
||||
return m_command_lists[m_current_command_list].sampler_allocator;
|
||||
}
|
||||
|
||||
/// Invalidates GPU-side sampler caches for all command lists. Call after you've freed samplers,
|
||||
/// and are going to re-use the handles from GetSamplerHeapManager().
|
||||
void InvalidateSamplerGroups();
|
||||
|
||||
// Descriptor manager access.
|
||||
D3D12DescriptorHeapManager& GetDescriptorHeapManager() { return m_descriptor_heap_manager; }
|
||||
D3D12DescriptorHeapManager& GetRTVHeapManager() { return m_rtv_heap_manager; }
|
||||
D3D12DescriptorHeapManager& GetDSVHeapManager() { return m_dsv_heap_manager; }
|
||||
D3D12DescriptorHeapManager& GetSamplerHeapManager() { return m_sampler_heap_manager; }
|
||||
const D3D12DescriptorHandle& GetNullSRVDescriptor() const { return m_null_srv_descriptor; }
|
||||
D3D12StreamBuffer& GetTextureStreamBuffer() { return m_texture_stream_buffer; }
|
||||
|
||||
// Root signature access.
|
||||
ComPtr<ID3DBlob> SerializeRootSignature(const D3D12_ROOT_SIGNATURE_DESC* desc);
|
||||
ComPtr<ID3D12RootSignature> CreateRootSignature(const D3D12_ROOT_SIGNATURE_DESC* desc);
|
||||
|
||||
/// Fence value for current command list.
|
||||
u64 GetCurrentFenceValue() const { return m_current_fence_value; }
|
||||
|
||||
/// Last "completed" fence.
|
||||
u64 GetCompletedFenceValue() const { return m_completed_fence_value; }
|
||||
|
||||
/// Feature level to use when compiling shaders.
|
||||
D3D_FEATURE_LEVEL GetFeatureLevel() const { return m_feature_level; }
|
||||
|
||||
/// Test for support for the specified texture format.
|
||||
bool SupportsTextureFormat(DXGI_FORMAT format);
|
||||
|
||||
enum class WaitType
|
||||
{
|
||||
None, ///< Don't wait (async)
|
||||
Sleep, ///< Wait normally
|
||||
Spin, ///< Wait by spinning
|
||||
};
|
||||
|
||||
/// Executes the current command list.
|
||||
bool ExecuteCommandList(WaitType wait_for_completion);
|
||||
|
||||
/// Waits for a specific fence.
|
||||
void WaitForFence(u64 fence, bool spin);
|
||||
|
||||
/// Waits for any in-flight command buffers to complete.
|
||||
void WaitForGPUIdle();
|
||||
|
||||
/// Defers destruction of a D3D resource (associates it with the current list).
|
||||
void DeferObjectDestruction(ID3D12DeviceChild* resource);
|
||||
|
||||
/// Defers destruction of a D3D resource (associates it with the current list).
|
||||
void DeferResourceDestruction(D3D12MA::Allocation* allocation, ID3D12Resource* resource);
|
||||
|
||||
/// Defers destruction of a descriptor handle (associates it with the current list).
|
||||
void DeferDescriptorDestruction(D3D12DescriptorHeapManager& manager, u32 index);
|
||||
void DeferDescriptorDestruction(D3D12DescriptorHeapManager& manager, D3D12DescriptorHandle* handle);
|
||||
|
||||
float GetAndResetAccumulatedGPUTime();
|
||||
void SetEnableGPUTiming(bool enabled);
|
||||
|
||||
// Allocates a temporary CPU staging buffer, fires the callback with it to populate, then copies to a GPU buffer.
|
||||
bool AllocatePreinitializedGPUBuffer(u32 size, ID3D12Resource** gpu_buffer, D3D12MA::Allocation** gpu_allocation,
|
||||
const std::function<void(void*)>& fill_callback);
|
||||
|
||||
private:
|
||||
struct CommandListResources
|
||||
{
|
||||
std::array<ComPtr<ID3D12CommandAllocator>, 2> command_allocators;
|
||||
std::array<ComPtr<ID3D12GraphicsCommandList4>, 2> command_lists;
|
||||
D3D12DescriptorAllocator descriptor_allocator;
|
||||
D3D12GroupedSamplerAllocator<SAMPLER_GROUP_SIZE> sampler_allocator;
|
||||
std::vector<std::pair<D3D12MA::Allocation*, ID3D12DeviceChild*>> pending_resources;
|
||||
std::vector<std::pair<D3D12DescriptorHeapManager&, u32>> pending_descriptors;
|
||||
u64 ready_fence_value = 0;
|
||||
bool init_command_list_used = false;
|
||||
bool has_timestamp_query = false;
|
||||
};
|
||||
|
||||
D3D12Context();
|
||||
|
||||
bool CreateDevice(IDXGIFactory5* dxgi_factory, IDXGIAdapter1* adapter, bool enable_debug_layer);
|
||||
bool CreateCommandQueue();
|
||||
bool CreateAllocator();
|
||||
bool CreateFence();
|
||||
bool CreateDescriptorHeaps();
|
||||
bool CreateCommandLists();
|
||||
bool CreateTextureStreamBuffer();
|
||||
bool CreateTimestampQuery();
|
||||
void MoveToNextCommandList();
|
||||
void DestroyPendingResources(CommandListResources& cmdlist);
|
||||
void DestroyResources();
|
||||
|
||||
ComPtr<IDXGIAdapter> m_adapter;
|
||||
ComPtr<ID3D12Debug> m_debug_interface;
|
||||
ComPtr<ID3D12Device> m_device;
|
||||
ComPtr<ID3D12CommandQueue> m_command_queue;
|
||||
ComPtr<D3D12MA::Allocator> m_allocator;
|
||||
|
||||
ComPtr<ID3D12Fence> m_fence;
|
||||
HANDLE m_fence_event = {};
|
||||
u32 m_current_fence_value = 0;
|
||||
u64 m_completed_fence_value = 0;
|
||||
|
||||
std::array<CommandListResources, NUM_COMMAND_LISTS> m_command_lists;
|
||||
u32 m_current_command_list = NUM_COMMAND_LISTS - 1;
|
||||
|
||||
ComPtr<ID3D12QueryHeap> m_timestamp_query_heap;
|
||||
ComPtr<ID3D12Resource> m_timestamp_query_buffer;
|
||||
ComPtr<D3D12MA::Allocation> m_timestamp_query_allocation;
|
||||
double m_timestamp_frequency = 0.0;
|
||||
float m_accumulated_gpu_time = 0.0f;
|
||||
bool m_gpu_timing_enabled = false;
|
||||
|
||||
D3D12DescriptorHeapManager m_descriptor_heap_manager;
|
||||
D3D12DescriptorHeapManager m_rtv_heap_manager;
|
||||
D3D12DescriptorHeapManager m_dsv_heap_manager;
|
||||
D3D12DescriptorHeapManager m_sampler_heap_manager;
|
||||
D3D12DescriptorHandle m_null_srv_descriptor;
|
||||
D3D12StreamBuffer m_texture_stream_buffer;
|
||||
|
||||
D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<D3D12Context> g_d3d12_context;
|
||||
|
||||
namespace D3D12
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
|
||||
void SetObjectName(ID3D12Object* object, const char* name);
|
||||
void SetObjectNameFormatted(ID3D12Object* object, const char* format, ...);
|
||||
|
||||
#else
|
||||
|
||||
static inline void SetObjectName(ID3D12Object* object, const char* name)
|
||||
{
|
||||
}
|
||||
static inline void SetObjectNameFormatted(ID3D12Object* object, const char* format, ...)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace D3D12
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "GS/Renderers/DX12/D3D12StreamBuffer.h"
|
||||
#include "GS/Renderers/DX12/D3D12Context.h"
|
||||
#include "GS/Renderers/DX12/GSDevice12.h"
|
||||
|
||||
#include "common/Align.h"
|
||||
#include "common/Assertions.h"
|
||||
@@ -45,7 +45,7 @@ bool D3D12StreamBuffer::Create(u32 size)
|
||||
|
||||
wil::com_ptr_nothrow<ID3D12Resource> buffer;
|
||||
wil::com_ptr_nothrow<D3D12MA::Allocation> allocation;
|
||||
HRESULT hr = g_d3d12_context->GetAllocator()->CreateResource(&allocationDesc, &resource_desc,
|
||||
HRESULT hr = GSDevice12::GetInstance()->GetAllocator()->CreateResource(&allocationDesc, &resource_desc,
|
||||
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, allocation.put(), IID_PPV_ARGS(buffer.put()));
|
||||
pxAssertMsg(SUCCEEDED(hr), "Allocate buffer");
|
||||
if (FAILED(hr))
|
||||
@@ -155,7 +155,7 @@ void D3D12StreamBuffer::Destroy(bool defer)
|
||||
}
|
||||
|
||||
if (m_buffer && defer)
|
||||
g_d3d12_context->DeferResourceDestruction(m_allocation.get(), m_buffer.get());
|
||||
GSDevice12::GetInstance()->DeferResourceDestruction(m_allocation.get(), m_buffer.get());
|
||||
m_buffer.reset();
|
||||
m_allocation.reset();
|
||||
|
||||
@@ -172,7 +172,7 @@ void D3D12StreamBuffer::UpdateCurrentFencePosition()
|
||||
return;
|
||||
|
||||
// Has the offset changed since the last fence?
|
||||
const u64 fence = g_d3d12_context->GetCurrentFenceValue();
|
||||
const u64 fence = GSDevice12::GetInstance()->GetCurrentFenceValue();
|
||||
if (!m_tracked_fences.empty() && m_tracked_fences.back().first == fence)
|
||||
{
|
||||
// Still haven't executed a command buffer, so just update the offset.
|
||||
@@ -189,7 +189,7 @@ void D3D12StreamBuffer::UpdateGPUPosition()
|
||||
auto start = m_tracked_fences.begin();
|
||||
auto end = start;
|
||||
|
||||
const u64 completed_counter = g_d3d12_context->GetCompletedFenceValue();
|
||||
const u64 completed_counter = GSDevice12::GetInstance()->GetCompletedFenceValue();
|
||||
while (end != m_tracked_fences.end() && completed_counter >= end->first)
|
||||
{
|
||||
m_current_gpu_position = end->second;
|
||||
@@ -267,11 +267,11 @@ bool D3D12StreamBuffer::WaitForClearSpace(u32 num_bytes)
|
||||
|
||||
// Did any fences satisfy this condition?
|
||||
// Has the command buffer been executed yet? If not, the caller should execute it.
|
||||
if (iter == m_tracked_fences.end() || iter->first == g_d3d12_context->GetCurrentFenceValue())
|
||||
if (iter == m_tracked_fences.end() || iter->first == GSDevice12::GetInstance()->GetCurrentFenceValue())
|
||||
return false;
|
||||
|
||||
// Wait until this fence is signaled. This will fire the callback, updating the GPU position.
|
||||
g_d3d12_context->WaitForFence(iter->first, false);
|
||||
GSDevice12::GetInstance()->WaitForFence(iter->first, false);
|
||||
m_tracked_fences.erase(
|
||||
m_tracked_fences.begin(), m_current_offset == iter->second ? m_tracked_fences.end() : ++iter);
|
||||
m_current_offset = new_offset;
|
||||
|
||||