Compare commits

...
76 Commits
Author SHA1 Message Date
Connor McLaughlinandGitHub 943359e328 CI: Fix incorrect commit message in base translation update 2023-07-17 23:31:02 +10:00
PCSX2 BotandConnor McLaughlin 92c54cc1f8 PAD: Update Base Translation 2023-07-17 23:28:02 +10:00
Stenzek 6ec0b44487 CI: Add daily base translation update 2023-07-17 23:24:37 +10:00
charlesthobeandConnor McLaughlin 85d2ca626f Common: Linux: fix potentially unsafe screensaver inhibitor 2023-07-17 20:11:32 +10:00
solid1353andConnor McLaughlin 68865a2bca Debugger/MemorySearch: search for byte array instead of converting byte array to string 2023-07-17 19:29:39 +10:00
solid1353andConnor McLaughlin 8362e6f435 Debugger Memory Search: add Array of byte type 2023-07-17 19:29:39 +10:00
JordanTheToasterandConnor McLaughlin 3804a58729 GameDB: Naruto Ultimate Ninja Fixes
Fixes for spikey SPS on characters with VU1 clamp extra plus sign.
2023-07-17 19:19:37 +10:00
TellowKrinkleandConnor McLaughlin fbb67a6ee9 Qt:Translation: Remove mnemonics from toolbar strings
They're not useful, and may make translators think they need to add extra characters in parentheses for the mnemonics to be placed on
2023-07-17 19:17:53 +10:00
TellowKrinkleandTellowKrinkle 19d8b59ca7 FSUI: Switch Secondary Light/Dark colors with Strong/Weak
Strong has higher than contrast against the background than the base color (lighter on dark theme, darker on light theme), weak is the opposite.
Previously the light color increased contrast in dark themes, but descreased contrast in light themes, which meant it wasn't suitable for the same things in the respective themes
2023-07-16 22:59:16 -05:00
TellowKrinkleandTellowKrinkle 7ca726492f FSUI: Remove PushSecondaryColor
It's not being used, and this way I can rename some colors to better describe our usage of them
2023-07-16 22:59:16 -05:00
TellowKrinkleandTellowKrinkle 96a43ca254 GS:MTL: Don't dispatch 0-element imgui draws
Metal validation doesn't like them
2023-07-16 22:59:16 -05:00
TellowKrinkleandTellowKrinkle bf34eb32ed FSUI: Fix background colors in light mode 2023-07-16 22:59:16 -05:00
TellowKrinkleandTellowKrinkle 433612cde0 Qt:Mac: Make sure settings windows are raised when requested 2023-07-16 22:12:13 -05:00
lightningterror 807db91140 GS: Bump shader cache version. 2023-07-16 12:45:42 +02:00
lightningterror 7791809224 GS-metal: Fix initialization shader warnings. 2023-07-16 12:45:42 +02:00
lightningterror 9b3d31cd47 GS-vk: Fix initialization shader warnings. 2023-07-16 12:45:42 +02:00
lightningterror 65a3a97ab2 GS-d3d: Fix initialization shader warnings. 2023-07-16 12:45:42 +02:00
lightningterror e3593574b0 GS-ogl: Fix initialization shader warnings. 2023-07-16 12:45:42 +02:00
refractionpcsx2 f81d391546 GS/HW: Use draw for valid height, preload cached size 2023-07-15 23:09:35 +01:00
JordanTheToasterandrefractionpcsx2 bd3b00793a GameDB: Various fixes
Fixes for missing sun in Ford Racing 2 and vertical lines in Digi World Collaboration SP.
2023-07-15 21:26:12 +01:00
kamfretozandConnor McLaughlin 757a72fb57 Qt: More icon treatments 2023-07-15 20:46:19 +10:00
kamfretozandConnor McLaughlin fbbec480af Qt: Add a reminder to New Input Profile dialog 2023-07-15 20:46:19 +10:00
Stenzek ba3b43c66c Qt: Show cheats/patches for all CRCs in game properties
Not just the primary/disc ELF.

Also get rid of duplicates from the list.
2023-07-15 12:08:53 +10:00
refractionpcsx2 e040b83b2c GS: Fix debug builds 2023-07-15 00:48:33 +01:00
refractionpcsx2 76f4a5b2fe UI: Add controller binding tooltips for additional controls 2023-07-14 13:48:24 +01:00
TellowKrinkleandlightningterror 4f4b9956b3 GS: Reduce cutoff for primid destination alpha 2023-07-14 13:44:08 +02:00
TellowKrinkleandlightningterror ba37a0f3a9 GS:MTL: Allow disabling fbfetch
For testing and stuff
2023-07-14 13:44:08 +02:00
AliandConnor McLaughlin d4acb95d7c Flatpak: Add the talk-name flag for org.freedesktop.Screensaver
This brings the functionality in line with the old Flatpak release. This allows PCSX2 to inhibit the screensaver or screen blanking without needing to touch the mouse/keyboard while the VM is running.
2023-07-14 14:53:05 +10:00
Stenzek 2887baefb8 GS/DX12: Combine GSDevice12 and D3D12Context 2023-07-14 12:21:51 +10:00
Stenzek 9678bf1e2f GS/Vulkan: Combine GSDeviceVK and VKContext
Less indirection.
2023-07-14 12:21:51 +10:00
Stenzek e2fc68ff2d GS/Vulkan: Use VK_EXT_attachment_feedback_loop_layout when supported 2023-07-13 22:35:34 +10:00
refractionpcsx2 4a7b641bc3 GameDB: Add HPO Special for LEGO Batman 2023-07-12 10:28:28 +01:00
refractionpcsx2 719b83bf42 GS: Only clamp tex coords to bounds in hardware. 2023-07-12 10:18:16 +01:00
Goatman13andrefractionpcsx2 f201a2e5dd GameDB: Fix patch for Michigan: Report from Hell 2023-07-11 20:09:29 +01:00
refractionpcsx2 fd6f47f5ca GS/HW: Tempfix alignment problem with texture replacements 2023-07-11 16:07:45 +01:00
Stenzekandrefractionpcsx2 8524fbbaf9 GS/HW: Decompress replacement textures to get alpha min/max 2023-07-11 13:30:47 +01:00
Stenzekandrefractionpcsx2 a371c5b7d0 GS/HW: Calculate alpha values for sources 2023-07-11 02:01:57 +01:00
refractionpcsx2 ef7ce4dfc8 GS/HW: Don't use DATE_one when alpha 128 (1.0) is masked 2023-07-11 02:01:57 +01:00
refractionpcsx2 189f6fa2a3 GS/HW: Calculate target alpha min/max 2023-07-11 02:01:57 +01:00
refractionpcsx2 472f4922bd GS/HW: Improve double half clear for strange strip drawing 2023-07-11 02:01:35 +01:00
SestainandGitHub 8ba2f342df OSD: Input recording overlap fixed (#9211) 2023-07-11 02:01:17 +01:00
JordanTheToasterandrefractionpcsx2 5d6b9d25bd Readme: Update OS requirements
Updates OS requirements to be in line with what we currently support and or the latest Windows in support at the time.
2023-07-10 18:19:02 +01:00
JordanTheToasterandrefractionpcsx2 1231499aaa GameDB: Various fixes
A lot of missing demo serials and some name fixes that bugged me.
2023-07-10 18:19:02 +01:00
Stenzek 9c4788432d GS/HW: Avoid tex-is-fb when incompatible clamp is used 2023-07-10 22:19:47 +10:00
lightningterrorandGitHub 4ded2caa0b GS-ogl: Comment out intel vendor set. (#9203)
Fixes Wunused-but-set-variable warning.
2023-07-10 07:30:15 +02:00
Stenzek 494afb9d43 Linux/CI: Switch to Ubuntu 22.04 and linuxdeploy 2023-07-10 14:16:41 +10:00
refractionpcsx2 c994e4828d Build: Restore old filenames temporarily 2023-07-09 23:18:43 +01:00
refractionpcsx2 e50f12a925 Updater: Make it not care about the Qt extension 2023-07-09 23:18:43 +01:00
refractionpcsx2 df75d70df4 VIF: Add missing internal DMA stall flag 2023-07-09 22:03:23 +01:00
Stenzekandrefractionpcsx2 c5aa04d4eb CI: Remove redundant GUI variables
And adjust release filenames.
2023-07-09 21:14:23 +01:00
Blackbird88andrefractionpcsx2 81ae1ce362 GameDB: NFS UG1 Blending 2023-07-09 17:10:24 +01:00
JordanTheToasterandrefractionpcsx2 e1921e660d GameDB: Various fixes
Fixes for Fifa Street 2 corrupted graphics and Onimusha Warlords.
2023-07-09 16:04:30 +01:00
KrossXandrefractionpcsx2 32aa24f6fd GS: Add fancy downsampling shaders. 2023-07-09 14:04:06 +01:00
refractionpcsx2 7fbc63b8e8 EE/IOP Sync: Optimize EE/IOP sync for more intelligent syncing 2023-07-09 13:53:00 +01:00
refractionpcsx2 443adcdd5b EE/IOP Sync: Calculate IOP interrupt delta after running IOP 2023-07-09 13:53:00 +01:00
refractionpcsx2 05b064d513 IOP/Counters: Fix off by 1 error 2023-07-09 13:53:00 +01:00
refractionpcsx2 b327033333 Counters: Process overflow before Target in case of Target = 0 2023-07-09 13:53:00 +01:00
refractionpcsx2 36c9b68b6c EE/Counters: Update HBlank before recalculating Next Event 2023-07-09 13:53:00 +01:00
refractionpcsx2 7d8d16091e EE/DMA: Try to avoid very small DMA loops 2023-07-09 13:53:00 +01:00
refractionpcsx2 c1c6bde429 Counters: Only schedule an EE interruption if they interrupt 2023-07-09 13:53:00 +01:00
Stenzek 8f0901e7a5 VMManager: Flag BIOS missing error for translation 2023-07-09 21:52:02 +10:00
Stenzek 6edba3820b GameList: Don't repeatedly scan invalid files every startup 2023-07-09 21:52:02 +10:00
Stenzek db1e1bcc1f CDVD: Rewrite ISO parser 2023-07-09 21:52:02 +10:00
Stenzek 51aeaeb508 CDVD: Ignore version information when loading ELF 2023-07-09 21:52:02 +10:00
Stenzek dcd0a1f002 GS/HW: Add render fix for complex moves
And use it to fix Growlanser.
2023-07-09 17:42:21 +10:00
Stenzek 819b61937f GS: Slim down GSState.h includes 2023-07-09 15:34:59 +10:00
Stenzek 9da7628083 GS: Move PCRTC function definitions to GSState.cpp 2023-07-09 15:34:59 +10:00
Ziemasandrefractionpcsx2 c5f47a8db3 mipsanalyst: sign extend 32 bit regs 2023-07-08 18:51:36 +01:00
Stenzek c2786b91ce GS/OGL: Fix sampling from cleared targets
And merge GLLoader into GSDeviceOGL while we're at it.
2023-07-09 00:46:52 +10:00
Stenzek 4290c16997 GS/DX11: Fix sampling from cleared targets 2023-07-09 00:46:52 +10:00
Stenzek 475e90b654 GS/HW: Don't invalidate buffers on partial clear 2023-07-09 00:45:16 +10:00
Stenzek ebb291e06d GS/DX12: Fix targets getting stuck undirtied 2023-07-08 14:33:21 +10:00
Stenzek a2c9069dda GameDB: Add eeCycleRate option
For those pesky games with cache/DMA issues only!
2023-07-08 14:28:30 +10:00
Stenzek 48cfe9ca73 Config: Make SpeedHack a scoped enum
And get rid of the redundant "SpeedHack" suffix on all values.
2023-07-08 14:28:30 +10:00
Stenzek b53e9856b8 USB/usb-mic: Fix buffer handling
- RingBuffer would store a size of zero if you wrote the entire
   the entire buffer in one call.

 - ResetBuffers() should be called before starting the stream,
   otherwise you risk a race where the callback happens before
   the buffer is allocated.

 - Fix incorrect latency being passed into Cubeb.
2023-07-08 14:27:51 +10:00
Stenzek 20420da326 Input/SDL: Don't pass stdio handle to SDL
Apparently on cmake, SDL isn't compiled with stdio support...
2023-07-08 14:21:02 +10:00
163 changed files with 9209 additions and 8153 deletions
@@ -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
+1 -6
View File
@@ -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: |
+2 -2
View File
@@ -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
+5 -6
View File
@@ -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
+5 -17
View File
@@ -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
+1 -2
View File
@@ -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
+4 -5
View File
@@ -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" "$@"
+67 -228
View File
@@ -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;
+6 -6
View File
@@ -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
-1
View File
@@ -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 }}
+2 -2
View File
@@ -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)_
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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);
+42
View File
@@ -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
+1 -1
View File
@@ -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;
+5 -3
View File
@@ -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;
+45
View File
@@ -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
+1 -1
View File
@@ -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;
+5 -3
View File
@@ -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;
+40
View File
@@ -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
+5 -5
View File
@@ -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);
+2
View File
@@ -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
+42 -36
View File
@@ -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
+12
View File
@@ -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
File diff suppressed because it is too large Load Diff
+199
View File
@@ -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
+2
View File
@@ -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" />
+6
View File
@@ -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">
+1 -8
View File
@@ -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;
+23 -9
View File
@@ -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:
+5
View File
@@ -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">
+10 -2
View File
@@ -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();
+11 -11
View File
@@ -274,7 +274,7 @@
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string comment="In Toolbar">Start &amp;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 &amp;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 &amp;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 &amp;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">&amp;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">&amp;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">&amp;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">&amp;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">&amp;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">&amp;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">&amp;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">
+7 -2
View File
@@ -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('&'))
+3 -3
View File
@@ -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."));
}
File diff suppressed because it is too large Load Diff
+9
View File
@@ -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

+4 -4
View File
@@ -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>
+15 -54
View 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;
}
+95 -75
View File
@@ -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();
+3 -1
View File
@@ -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();
+2
View File
@@ -15,6 +15,8 @@
#pragma once
#include "Common.h"
/*
Interrupts - values are flag bits.
+34 -36
View File
@@ -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;
}
-60
View File
@@ -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;
};
-322
View File
@@ -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(&sector[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, &sector[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);
}
}
-25
View File
@@ -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"
-37
View File
@@ -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;
}
-31
View File
@@ -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();
};
-262
View File
@@ -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;
}
-83
View File
@@ -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();
};
-47
View File
@@ -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(); }
};
-24
View File
@@ -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;
};
+315
View File
@@ -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*>(&sector_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*>(&sector_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;
}
+176
View File
@@ -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 = {};
};
+2 -17
View File
@@ -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
+16 -18
View File
@@ -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
+10 -3
View File
@@ -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;
+14 -7
View File
@@ -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;
}
+11 -3
View File
@@ -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
+13 -14
View File
@@ -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_;
+5 -4
View File
@@ -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();
+66 -8
View File
@@ -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));
+1
View File
@@ -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();
+1
View File
@@ -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"
+5
View File
@@ -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
+9
View File
@@ -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)
+572 -12
View File
@@ -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;
}
}
}
}
+29 -553
View File
@@ -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:
+24
View File
@@ -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)));
+8 -6
View File
@@ -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);
+2
View File
@@ -131,6 +131,8 @@ enum class PresentShader
TRIANGULAR_FILTER,
COMPLEX_FILTER,
LOTTES_FILTER,
SUPERSAMPLE_4xRGSS,
SUPERSAMPLE_AUTO,
Count
};
+6 -2
View File
@@ -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;
+14 -20
View File
@@ -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
-1
View File
@@ -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();
+20 -2
View File
@@ -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
+12 -1
View File
@@ -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
-686
View File
@@ -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
-235
View File
@@ -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;

Some files were not shown because too many files have changed in this diff Show More