Compare commits

..

109 Commits

Author SHA1 Message Date
JordanTheToaster
7e18c02c7e GameDB: Xenosaga Episode III fixes 2025-06-23 06:27:13 +02:00
twingofan
4a505cc239 GameDB: Add memcardFilters to Armored Core: Last Raven NTSC-U SLUS-21338. (#12891) 2025-06-23 06:26:16 +02:00
Kuan-Wei Chiu
35c81106c6 Fix invalid comparator in FullscreenUI game list sort
The game list comparator previously used >= when sorting by CRC in
descending order. This violates the requirements of a strict weak
ordering, as defined by the C++ standard, which mandates that the
comparator must be irreflexive: comp(x, x) must always return false.

Using >= causes comp(a, b) and comp(b, a) to both return true when
a == b, which breaks the irreflexivity and can invalidate the
assumptions made by std::sort. This may lead to undefined behavior,
including memory corruption or segmentation faults.

Replace >= with > to ensure the comparator satisfies the strict weak
ordering requirement, restoring correctness and stability in game list
sorting behavior.
2025-06-22 14:44:34 -04:00
Ziemas
1094222d3f Debugger: Don't check frame count in isAlive 2025-06-22 20:19:13 +02:00
TheLastRar
065d0db4c9 FSUI: Replace various icons with SVG files
Co-Authored-By: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
2025-06-22 20:15:03 +02:00
JordanTheToaster
bc51537080 GS/HW: Purge Hitman Blood Money CRC 2025-06-22 20:14:09 +02:00
JordanTheToaster
b7dfcb282b ImGuiOverlays: Minor OSD text changes 2025-06-22 20:14:09 +02:00
JordanTheToaster
78a9411766 GameDB: Various Fixes Part 5 2025-06-22 20:14:09 +02:00
TellowKrinkle
4724f67596 GS: Min alpha for AA1 is 0, not 128 2025-06-22 20:13:15 +02:00
JordanTheToaster
e0851bb86f [ci skip] Github: Update app bug report OS list 2025-06-21 22:00:10 -04:00
JordanTheToaster
08e68d9563 GitHub: Update bug report OS list 2025-06-21 15:50:02 -04:00
refractionpcsx2
0b6dccae51 GS/HW: Resize validity to draw size when most of target is covered 2025-06-21 05:52:50 +02:00
refractionpcsx2
60adfd5046 GS/TC: Fix region area on new source interpreted for shuffle 2025-06-21 05:52:50 +02:00
chaoticgd
3e782d355d Docs: Add missing third party license for KDBindings 2025-06-20 22:22:51 +02:00
PCSX2 Bot
22a7324b69 [ci skip] Qt: Update Base Translation. 2025-06-20 02:02:47 +02:00
refractionpcsx2
f3b4c50909 GS/HW: Improve Native Scaling detection + Include direct mem reads 2025-06-20 02:02:30 +02:00
lightningterror
a45f27e6e9 GS/DX: Don't output color for datm/stencil date shaders.
RTV(Render target view) is not bound so no need
to output anything, just clip/discard if needed.

Fixes dx11 api warning that pixel shader writes color
output but no rtv is bound.
2025-06-20 02:01:47 +02:00
lightningterror
2f84bf0cca GS/TC: Don't enable Frame buffer conversion on PSMT8.
We have dedicated shader now, no need.
2025-06-20 02:01:28 +02:00
refractionpcsx2
3e2c3e5075 GS/TC: Slight fix for rect translation with block offset 2025-06-20 02:00:51 +02:00
refractionpcsx2
154259d0a6 GS/HW: Don't resize target if only writing to alpha when RGB is valid 2025-06-20 02:00:51 +02:00
TellowKrinkle
7d2d05ff59 GS: Send all frames to capture, including skipped ones
You wanted those frames recorded right?  Also if you skip them you'll desync audio and everything will be sad.
2025-06-18 20:08:16 -04:00
TheLastRar
f7c587c9a8 CI: Restore repository check in Flathub publish action 2025-06-18 09:07:54 -04:00
PCSX2 Bot
9162b30b18 [ci skip] Qt: Update Base Translation. 2025-06-18 07:09:06 +02:00
chaoticgd
14cfe37c8b Debugger: Fix crash when breakpoint is hit before createMenuBar call 2025-06-17 04:39:32 +02:00
PCSX2 Bot
a27a0ab49d [ci skip] PAD: Update to latest controller database. 2025-06-17 04:38:20 +02:00
Ty
0742fe07ec [ci skip] Testing flathub versioning, please ignore 2025-06-15 14:53:55 -04:00
Ty
65a92470d3 CI: Fix flathub versioning on tagless commits 2025-06-15 14:26:08 -04:00
PCSX2 Bot
10dc1a2da2 [ci skip] Qt: Update Base Translation. 2025-06-14 21:50:20 +02:00
JordanTheToaster
aad2128c32 GSRunner: Fix broken compilation 2025-06-14 21:50:08 +02:00
refractionpcsx2
2640678cac GS/HW: Adjust which function gets used for clearing depth on perfect match 2025-06-14 01:30:00 +02:00
KamFretoZ
8b90e2d53d BPM: Fix CTD when exiting BPM in No GUI mode 2025-06-14 01:28:49 +02:00
KamFretoZ
f87175dc7f Host: Move Batch/NoGUI mode check to Host
Moved from QtHost to Host for more accesibility
2025-06-14 01:28:49 +02:00
refractionpcsx2
b30444c0d0 GS/HW: Fix bug and improve inside target tracking on HW moves 2025-06-12 23:01:36 +01:00
refractionpcsx2
c1da0caf15 GS/HW: Fix possible null reference causing a crash 2025-06-12 18:26:15 +01:00
lightningterror
c4a8fc5b71 Qt: Fix Winconsistent-missing-override warning. 2025-06-11 23:17:53 +02:00
GovanifY
8eb46b5a4c IopBios: remove clang deprecated carveout
we use snprintf now, so this isn't useful anymore.
2025-06-10 20:59:25 +02:00
GovanifY
8be16d1039 IopBios: do not overflow snprintf tmp buffer
We could otherwise overflow as snprintf does not return the number of
written bytes but the number of written bytes assuming an infinite
buffer.
2025-06-10 20:59:25 +02:00
refractionpcsx2
76e6208d1b GS/TC: Fix region for tex in rt depth 2025-06-10 20:12:41 +02:00
refractionpcsx2
bc09080ba5 GS/TC: Remove old inside check from source lookup 2025-06-10 20:12:41 +02:00
refractionpcsx2
a6eb257a3a GS/HW: Allow RT in RT offset on Z buffer if FRAME is disabled 2025-06-10 20:11:32 +02:00
PCSX2 Bot
e62450d255 [ci skip] PAD: Update to latest controller database. 2025-06-10 20:10:22 +02:00
GovanifY
1aa922f700 IopBios: allow %u string formatting in IOP kprintf 2025-06-10 14:47:27 +02:00
GovanifY
4c9d2f99b1 IopBios: truncate printf output if bigger than our buffer
I also increased the buffer size while we are at it to avoid breakage,
despite the obvious unintended allowed situations that we allowed up
until now

Reported-By: Michael Lappas
2025-06-10 14:47:27 +02:00
Ty Lamontagne
f6e899b570 Debugger: Update search results when we are _not_ going to remove them 2025-06-08 15:44:56 -04:00
refractionpcsx2
695c39fba2 GS/HW: Remove channel shuffle override from Namco CRC hack 2025-06-08 16:05:24 +02:00
refractionpcsx2
e5616cff98 GS/HW: Improve shuffle detection robustness 2025-06-08 13:51:19 +02:00
refractionpcsx2
76d5994c1e GS/TC: Improve rect block offset calculation for translation and invalidation 2025-06-08 13:51:19 +02:00
refractionpcsx2
cbb40832a1 GS/HW: Correct block offset target usage 2025-06-08 13:51:19 +02:00
refractionpcsx2
e4bdcde1ca GS/HW: Remove CRC hacks for Kunoichi and Sakura Wars 2025-06-08 13:51:19 +02:00
refractionpcsx2
863e119ff4 GS/HW: Allow conversion of colour to Z formats during HW move 2025-06-08 13:51:19 +02:00
refractionpcsx2
2ccf6dc872 GS: Add IsPageAlignedMasked to return an alignment mask 2025-06-08 13:51:19 +02:00
refractionpcsx2
24ebf1b4f1 GS/HW: Fix bug in target preloading causing misaligned base addresses 2025-06-08 13:51:19 +02:00
refractionpcsx2
ed2832434c GS/HW: Don't look up block offset targets on Exact target lookup 2025-06-08 13:51:19 +02:00
TJnotJT
5e160fca8f Tools: Small fix to gsrunner args. 2025-06-06 18:05:37 -04:00
JordanTheToaster
a0ef82e221 AudioStream: Fix typo with default output latency 2025-06-06 18:03:29 -04:00
icup321
3e7ac3d66c GameDB: Add native scaling to Scooby-Doo! Unmasked 2025-06-06 23:09:37 +02:00
JordanTheToaster
730e6fa737 GameDB: Simple 2000 Vol 92 Fixes 2025-06-06 23:09:26 +02:00
PCSX2 Bot
cdfcd9fddd [ci skip] Qt: Update Base Translation. 2025-06-06 02:31:08 +02:00
refractionpcsx2
b6930c10b9 GS/HW: Clean up target download formats 2025-06-05 13:51:21 +02:00
refractionpcsx2
852734580d GameDB: Update Harry Potter fixes. 2025-06-05 13:51:21 +02:00
refractionpcsx2
d1dc6a9c1d GS/HW: Add 16bit to 8bit conversion shader 2025-06-05 13:51:21 +02:00
Ty
4fa6d3ed3f GSRunner: Add type to shutdown message code 2025-06-04 20:25:32 -04:00
TellowKrinkle
92e190ad6c GSRunner: Fix surfaceless run on macOS 2025-06-04 20:25:32 -04:00
TellowKrinkle
da4fcffef4 Input: Fix crash when shutting down without initializing input 2025-06-04 20:25:32 -04:00
TellowKrinkle
1a5731dd8e MacOS: Better handle directories of non-bundle applications 2025-06-04 20:25:32 -04:00
TellowKrinkle
e764c5cd4e GSRunner: macOS support 2025-06-04 20:25:32 -04:00
TellowKrinkle
e23b247947 GSRunner: Use separate CPU thread 2025-06-04 20:25:32 -04:00
PCSX2 Bot
3d7792436f [ci skip] Qt: Update Base Translation. 2025-06-04 20:03:38 -04:00
chaoticgd
d8187fbea4 Deps: Specify minimum version of KDDockWidgets 2025-06-04 20:02:02 -04:00
chaoticgd
02259ad0a5 Debugger: Add include required for older versions of KDDockWidgets 2025-06-04 20:02:02 -04:00
TellowKrinkle
220a68df9a GS: Warn on texture replacement folder with wrong case 2025-06-04 19:58:41 -04:00
TellowKrinkle
2ced24f69e GS: Create texture dump directory if it doesn't exist 2025-06-04 19:58:41 -04:00
TellowKrinkle
ec91d0dc74 GS: Formatting 2025-06-04 19:58:41 -04:00
TheLastRar
46874f4673 Qt: Add workaround for incorrectly tinted icons after theme switch 2025-06-04 19:39:18 -04:00
TheLastRar
9eac47dc6c Qt: Fix selected gamelist icons being wrong colour after theme switch 2025-06-04 19:39:18 -04:00
RedDevilus
9e3fd5c2e0 CI: Fix flatpak
Try to fix build failing
2025-06-04 20:19:46 +01:00
refractionpcsx2
ae4be6e2b1 GS/CRC: Remove CRC for Simple 2000 Series Vol. 114, update GameDB Fixes 2025-06-04 18:35:00 +02:00
refractionpcsx2
434df49a7d GS/HW: Clear matched target on HW Move 2025-06-04 18:35:00 +02:00
JordanTheToaster
c939c0fcd5 GameDB: Re-add CRC to Death by Degrees 2025-06-04 18:33:36 +02:00
Berylskid
952c39f324 GameDB: Remove SoftwareRendererFMVHack from Armored Core 2 2025-06-03 13:44:40 +02:00
PCSX2 Bot
0fea7e2a70 [ci skip] Qt: Update Base Translation. 2025-06-03 03:40:13 +02:00
lightningterror
c1baab68d0 GS: Better handle hazards when dx12/vk device creation fails.
VK/DX12: Move CreateNullTexture before reading shader resource.
Fixes null pointer dereference.

VK: Check if vertex buffer is valid before binding.
Fixes vertex buffer validation error null handle.
2025-06-02 21:28:41 +02:00
PCSX2 Bot
ccef18f7a9 [ci skip] PAD: Update to latest controller database. 2025-06-02 19:08:36 +02:00
TheLastRar
8cb056bde3 Qt: Use DevicePixelRatioChange to detect DPR changes 2025-06-02 14:58:00 +02:00
TheLastRar
6ecaaee9e0 QtUtils: Remove redundant method 2025-06-02 14:58:00 +02:00
TheLastRar
c5f916bda0 Qt: Fix DPI icon scaling in various settings windows 2025-06-02 14:58:00 +02:00
TheLastRar
52a9a4649c Qt: Improve handling of DPI 2025-06-02 14:58:00 +02:00
refractionpcsx2
d2219b4dbd GS/TC: On RT->Z dst_match delete on format change if not a shuffle 2025-06-02 14:52:46 +02:00
refractionpcsx2
155f603245 GS/HW: Don't make new scaled targets on shuffles if source is downscaled 2025-06-02 14:52:46 +02:00
refractionpcsx2
cb7630a6ab GS/HW: Create new targets on shuffles when no target found
And get rid of some valid sizing thing which is just insane
2025-06-02 14:52:46 +02:00
refractionpcsx2
284fba1ce3 GS/TC: Don't allow Tex in RT 8bit textures from C24
C24 has no alpha channel, 8bit requires all.  This is still allowed for shuffles as that depends which channel it's reading.
2025-06-02 14:52:46 +02:00
refractionpcsx2
aa4bd6c88c GS/HW: Replace frame target if dirty data matches old format. 2025-06-02 14:52:46 +02:00
refractionpcsx2
623993930b GS/HW: Adjust depth size on clear if overlapping by 1 pixel
This is developer misunderstanding of how to use the SCISSOR register, which adds 1 on to the value when it's used, but they put 512x512, when they wanted 512x512.
2025-06-02 14:52:46 +02:00
refractionpcsx2
dbf2c854c6 GS/TC: Adopt valid rgb/alpha from preload merged targets 2025-06-02 14:52:46 +02:00
refractionpcsx2
25351bc05c GS/TC: Correct valid area checks for target combining 2025-06-02 14:52:46 +02:00
refractionpcsx2
27cc5f499c GS/HW: Fix up alpha blending checks 2025-06-02 14:52:46 +02:00
refractionpcsx2
b7c2f39a17 GS/TC: Further matching parameters on preload and tex in rt 2025-06-02 14:52:46 +02:00
refractionpcsx2
3541c1ccf8 GS/TC: Simplify and improve P8 texture conversion inside target 2025-06-02 14:52:46 +02:00
refractionpcsx2
7a05738d11 GS/TC: Allow matching on source if TEX == RT 2025-06-02 14:52:46 +02:00
refractionpcsx2
686220ae0c GS/TC: Improve copying of dst matched data 2025-06-02 14:52:46 +02:00
refractionpcsx2
cf380d36b9 GS/TC: Fix inside target alignment check for ExactTarget lookup 2025-06-02 14:52:46 +02:00
lightningterror
50bc0193ac Core: Bump savestate version.
[SAVEVERSION+]
2025-06-02 01:30:50 +02:00
refractionpcsx2
2162a72831 GS: Bump GS Dump version and add transfer parameters to dump 2025-06-02 00:40:03 +02:00
refractionpcsx2
313666f85b GS: Store entire GS transfer state at TRXDIR write 2025-06-02 00:40:03 +02:00
TheTechnician27
e9d79263b4 UI: Fix Discord Rich Presence not activating in FSUI 2025-06-01 23:21:39 +02:00
JordanTheToaster
4ede6d65fd GameDB: Fix half right issue with Xtreme Bowling 2025-06-01 13:28:25 -04:00
PCSX2 Bot
14d2eee371 [ci skip] Qt: Update Base Translation. 2025-05-31 20:46:33 -04:00
chaoticgd
717f370be0 Debugger: Improve DockTabBar ownership workaround 2025-05-31 03:30:13 +02:00
refractionpcsx2
d05e4b9727 GS/HW: Adjust SpriteNoGaps check for vertical strips 2025-05-31 03:25:47 +02:00
95 changed files with 4514 additions and 2829 deletions

View File

@@ -70,6 +70,8 @@ body:
- Windows 11
- Windows 10 (64bit)
- Linux (64bit) - Specify distro below
- macOS 26 (Tahoe)
- macOS 15 (Sequoia)
- macOS 14 (Sonoma)
- macOS 13 (Ventura)
- macOS 12 (Monterey)

View File

@@ -87,6 +87,8 @@ body:
- Windows 11
- Windows 10 (64bit)
- Linux (64bit) - Specify distro below
- macOS 26 (Tahoe)
- macOS 15 (Sequoia)
- macOS 14 (Sonoma)
- macOS 13 (Ventura)
- macOS 12 (Monterey)
@@ -104,14 +106,14 @@ body:
id: cpu
attributes:
label: CPU
placeholder: "Example: i5-7600"
placeholder: "Example: Intel i5 12400F"
validations:
required: true
- type: input
id: gpu
attributes:
label: GPU
placeholder: "Example: GTX 1070"
placeholder: "Example: Nvidia RTX 4060"
validations:
required: true
- type: textarea

View File

@@ -6,36 +6,45 @@ on:
workflow_dispatch: # As well as manually.
jobs:
check:
if: github.repository == 'PCSX2/pcsx2'
name: "Check if release is needed"
runs-on: ubuntu-latest
timeout-minutes: 180
outputs:
PCSX2_RELEASE: ${{ steps.getinfo.outputs.PCSX2_RELEASE }}
FLATHUB_RELEASE: ${{ steps.getinfo.outputs.FLATHUB_RELEASE }}
steps:
- name: Get latest tag and Flathub release
id: getinfo
env:
GH_TOKEN: ${{ github.token }}
run: |
PCSX2_RELEASE=$(gh api -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' /repos/PCSX2/pcsx2/releases | jq -r '.[0].tag_name')
FLATHUB_RELEASE=$(curl -L -s https://flathub.org/api/v2/appstream/net.pcsx2.PCSX2 | jq -r '.releases | max_by(.version) | .version')
echo "Latest PCSX2 release is: '${PCSX2_RELEASE}'"
echo "Latest Flathub release is: '${FLATHUB_RELEASE}'"
PCSX2_RELEASE=$(echo $PCSX2_RELEASE | sed 's/[^0-9]*//g')
FLATHUB_RELEASE=$(echo $FLATHUB_RELEASE | sed 's/[^0-9]*//g')
echo "PCSX2_RELEASE=${PCSX2_RELEASE}" >> "$GITHUB_OUTPUT"
echo "FLATHUB_RELEASE=${FLATHUB_RELEASE}" >> "$GITHUB_OUTPUT"
# check is disabled as the flathub api does not give us beta repository information
# Alternatively we can "flatpak remote-info or parse the appstream directly for the beta repo"
# Maybe in the future if we don't want to publish the same version twice if we get no commits
# for 24 hours.
# check:
# if: github.repository == 'PCSX2/pcsx2'
# name: "Check if release is needed"
# runs-on: ubuntu-latest
# timeout-minutes: 180
# outputs:
# PCSX2_RELEASE: ${{ steps.getinfo.outputs.PCSX2_RELEASE }}
# FLATHUB_RELEASE: ${{ steps.getinfo.outputs.FLATHUB_RELEASE }}
# steps:
# - name: Get latest tag and Flathub release
# id: getinfo
# env:
# GH_TOKEN: ${{ github.token }}
# run: |
# PCSX2_RELEASE=$(gh api -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' /repos/PCSX2/pcsx2/releases | jq -r '.[0].tag_name')
# FLATHUB_RELEASE=$(curl -L -s https://flathub.org/api/v2/appstream/net.pcsx2.PCSX2 | jq -r '.releases | max_by(.version) | .version')
# echo "Latest PCSX2 release is: '${PCSX2_RELEASE}'"
# echo "Latest Flathub release is: '${FLATHUB_RELEASE}'"
# PCSX2_RELEASE=$(echo $PCSX2_RELEASE | sed 's/[^0-9]*//g')
# FLATHUB_RELEASE=$(echo $FLATHUB_RELEASE | sed 's/[^0-9]*//g')
# echo "PCSX2_RELEASE=${PCSX2_RELEASE}" >> "$GITHUB_OUTPUT"
# echo "FLATHUB_RELEASE=${FLATHUB_RELEASE}" >> "$GITHUB_OUTPUT"
build:
needs: check
# needs: check
# outputs are automatically compared as strings. This doesn't work in our favour
# Use fromJson() to convert them to proper integers...
# see: https://github.com/github/docs/pull/25870
# and: https://github.com/orgs/community/discussions/57480
#if: fromJson(needs.check.outputs.FLATHUB_RELEASE) < fromJson(needs.check.outputs.PCSX2_RELEASE)
# if: fromJson(needs.check.outputs.FLATHUB_RELEASE) < fromJson(needs.check.outputs.PCSX2_RELEASE)
# As the check step is disabled, perform repository check here
if: github.repository == 'PCSX2/pcsx2'
name: "Build and publish Flatpak"
uses: ./.github/workflows/linux_build_flatpak.yml
with:

View File

@@ -54,19 +54,16 @@ jobs:
uses: actions/checkout@v4
with:
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
# 10 here, since the odds of having 10 untagged commits in a row should be slim to none
# This is required for the tagging logic in generate-metainfo.sh
fetch-depth: 10
fetch-tags: true
# Work around container ownership issue
- name: Set Safe Directory
shell: bash
run: git config --global --add safe.directory "*"
# Hackity hack. When running the workflow on a schedule, we don't have the tag,
# it doesn't fetch tags, therefore we don't get a version. So grab them manually.
# actions/checkout elides tags, fetch them primarily for releases
- name: Fetch tags
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
@@ -125,7 +122,7 @@ jobs:
cache: true
restore-cache: true
cache-key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} flatpak ${{ hashFiles('.github/workflows/scripts/linux/flatpak/**/*.json') }}
#- name: Validate build
# run: |
# flatpak-builder-lint repo repo

View File

@@ -11,10 +11,9 @@
},
"sources": [
{
"type": "git",
"type": "git",
"url": "https://github.com/sammycage/plutovg.git",
"tag": "v0.0.13",
"sha256": "5e4712cf873b0c7829a4a6157763e2ad3ac49164"
"tag": "v0.0.13"
}
],
"cleanup": [

View File

@@ -12,10 +12,9 @@
},
"sources": [
{
"type": "git",
"type": "git",
"url": "https://github.com/sammycage/plutosvg.git",
"tag": "v0.0.6",
"sha256": "c5388fa96feca1f1376a3d0485d5e35159452707"
"tag": "v0.0.6"
}
],
"cleanup": [

View File

@@ -12,12 +12,24 @@ GIT_DATE=$(git log -1 --pretty=%cd --date=iso8601)
GIT_VERSION=$(git tag --points-at HEAD)
GIT_HASH=$(git rev-parse HEAD)
if [[ "${GIT_VERSION}" == "" ]]; then
# In the odd event that we run this script before the release gets tagged.
GIT_VERSION=$(git describe --tags)
if [[ "${GIT_VERSION}" == "" ]]; then
GIT_VERSION=$(git rev-parse HEAD)
fi
if [[ -z "${GIT_VERSION}" ]]; then
if git branch -r --contains HEAD | grep -q 'origin/master'; then
# Our master doesn't have a tagged commit
# This happens when the commit is "ci skip"
# abbrev so we have just the latest tag
# ie v2.3.420 (Yes, that's the current master at the time of writing)
GIT_VERSION=$(git describe --tags --abbrev=0)
else
# We are probably building a PR
# Keep the short SHA in the version
# ie v2.3.420-1-g10dc1a2da
GIT_VERSION=$(git describe --tags)
fi
if [[ -z "${GIT_VERSION}" ]]; then
# Fallback to raw commit hash
GIT_VERSION=$(git rev-parse HEAD)
fi
fi
echo "GIT_DATE: ${GIT_DATE}"

View File

@@ -62,10 +62,12 @@ endif()
# gsrunner
if(ENABLE_GSRUNNER)
if (NOT WIN32)
message(WARNING "GSRunner is only supported on Windows and may not build on your system")
if (NOT WIN32 AND NOT APPLE)
message(WARNING "GSRunner is only supported on Windows and macOS and may not build on your system")
endif()
add_subdirectory(pcsx2-gsrunner)
else()
add_subdirectory(pcsx2-gsrunner EXCLUDE_FROM_ALL)
endif()
#-------------------------------------------------------------------------------

View File

@@ -24,6 +24,7 @@
<li><a href="#googletest">GoogleTest</a></li>
<li><a href="#harfbuzz">HarfBuzz</a></li>
<li><a href="#jsonformoderncpp">JSON for Modern C++</a></li>
<li><a href="#kdbindings">KDBindings</a></li>
<li><a href="#kddockwidgets">KDDockWidgets</a></li>
<li><a href="#kissfft">kissfft</a></li>
<li><a href="#libbacktrace">libbacktrace</a></li>
@@ -1910,6 +1911,7 @@ PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
</pre>
</div>
<!-- Transitive dependency of KDDockWidgets. -->
<div id="jsonformoderncpp">
<h3>JSON for Modern C++ - <a href="https://json.nlohmann.me">https://json.nlohmann.me</a></h3>
<pre>
@@ -1937,6 +1939,34 @@ SOFTWARE.
</pre>
</div>
<!-- Transitive dependency of KDDockWidgets. -->
<div id="kdbindings">
<h3>KDBindings - <a href="https://github.com/KDAB/KDBindings">https://github.com/KDAB/KDBindings</a></h3>
<pre>
MIT License
Copyright 2021 Klarälvdalens Datakonsult AB, a KDAB Group company &lt;info@kdab.com&gt;
Copyright 2021 Jeremy Burns
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the &quot;Software&quot;), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>
</div>
<div id="kddockwidgets">
<h3>KDDockWidgets - <a href="https://github.com/KDAB/KDDockWidgets">https://github.com/KDAB/KDDockWidgets</a></h3>
<pre>

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
width="800px"
height="800px"
viewBox="0 0 32 32"
version="1.1"
id="svg1"
sodipodi:docname="applications-system.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
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="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="0.97875"
inkscape:cx="399.48914"
inkscape:cy="400"
inkscape:window-width="1920"
inkscape:window-height="1010"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<defs
id="defs1" />
<path
d="M30.015 12.97l-2.567-0.569c-0.2-0.64-0.462-1.252-0.762-1.841l1.389-2.313c0.518-0.829 0.78-2.047 0-2.829l-1.415-1.414c-0.78-0.781-2.098-0.64-2.894-0.088l-2.251 1.434c-0.584-0.303-1.195-0.563-1.829-0.768l-0.576-2.598c-0.172-0.953-1.005-1.984-2.11-1.984h-2c-1.104 0-1.781 1.047-2 2l-0.642 2.567c-0.678 0.216-1.328 0.492-1.948 0.819l-2.308-1.47c-0.795-0.552-2.114-0.692-2.894 0.088l-1.415 1.414c-0.781 0.782-0.519 2 0 2.828l1.461 2.435c-0.274 0.552-0.517 1.123-0.705 1.72l-2.566 0.569c-0.953 0.171-1.984 1.005-1.984 2.109v2c0 1.105 1.047 1.782 2 2l2.598 0.649c0.179 0.551 0.404 1.080 0.658 1.593l-1.462 2.438c-0.518 0.828-0.78 2.047 0 2.828l1.415 1.414c0.78 0.782 2.098 0.64 2.894 0.089l2.313-1.474c0.623 0.329 1.277 0.608 1.96 0.823l0.64 2.559c0.219 0.953 0.896 2 2 2h2c1.105 0 1.938-1.032 2.11-1.985l0.577-2.604c0.628-0.203 1.23-0.459 1.808-0.758l2.256 1.438c0.796 0.552 2.114 0.692 2.895-0.089l1.415-1.414c0.78-0.782 0.518-2 0-2.828l-1.39-2.317c0.279-0.549 0.521-1.12 0.716-1.714l2.599-0.649c0.953-0.219 2-0.895 2-2v-2c0-1.104-1.031-1.938-1.985-2.11zM30.001 16.939c-0.085 0.061-0.245 0.145-0.448 0.192l-3.708 0.926-0.344 1.051c-0.155 0.474-0.356 0.954-0.597 1.428l-0.502 0.986 1.959 3.267c0.125 0.2 0.183 0.379 0.201 0.485l-1.316 1.314c-0.127-0.040-0.271-0.092-0.341-0.14l-3.292-2.099-1.023 0.529c-0.493 0.256-0.999 0.468-1.503 0.631l-1.090 0.352-0.824 3.723c-0.038 0.199-0.145 0.36-0.218 0.417h-1.8c-0.061-0.085-0.145-0.245-0.191-0.448l-0.921-3.681-1.066-0.338c-0.549-0.173-1.097-0.404-1.63-0.684l-1.028-0.543-3.293 2.099c-0.135 0.091-0.279 0.143-0.409 0.143l-1.311-1.276c0.018-0.104 0.072-0.274 0.181-0.449l2.045-3.408-0.487-0.98c-0.227-0.462-0.407-0.895-0.547-1.325l-0.343-1.052-3.671-0.918c-0.231-0.052-0.398-0.139-0.485-0.2v-1.86c0.001 0.001 0.002 0.001 0.005 0.001 0.034 0 0.198-0.117 0.335-0.142l3.772-0.835 0.346-1.103c0.141-0.449 0.333-0.917 0.588-1.43l0.487-0.98-2.024-3.373c-0.125-0.201-0.184-0.38-0.201-0.485l1.315-1.314c0.128 0.041 0.271 0.093 0.34 0.14l3.354 2.138 1.027-0.542c0.527-0.278 1.073-0.507 1.622-0.682l1.063-0.338 0.912-3.649c0.053-0.231 0.138-0.398 0.2-0.485h1.859c-0.014 0.020 0.115 0.195 0.142 0.339l0.84 3.794 1.089 0.352c0.511 0.165 1.023 0.38 1.523 0.639l1.023 0.532 3.224-2.053c0.135-0.092 0.279-0.143 0.409-0.143l1.313 1.276c-0.017 0.104-0.072 0.276-0.181 0.45l-1.98 3.296 0.505 0.988c0.273 0.533 0.48 1.033 0.635 1.529l0.346 1.104 3.697 0.82c0.224 0.041 0.398 0.171 0.434 0.241zM16.013 9.99c-3.321 0-6.023 2.697-6.023 6.010s2.702 6.010 6.023 6.010 6.023-2.697 6.023-6.009c0-3.313-2.702-6.010-6.023-6.010zM16 20c-2.205 0-4-1.794-4-4s1.794-4 4-4c2.206 0 4 1.794 4 4s-1.794 4-4 4z"
id="path1"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800px" height="800px" viewBox="0 0 72 72" enable-background="new 0 0 72 72" xml:space="preserve">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" width="800px" height="800px" viewBox="0 0 16 16" id="window-16px" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" id="SVGRoot" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" fill="#ffffff">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#ffffff">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800px" height="800px" viewBox="0 0 72 72" enable-background="new 0 0 72 72" xml:space="preserve">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,9 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#ffffff" width="800px" height="800px" viewBox="-2 -2 24 24" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin" class="jam jam-microchip">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -469,8 +469,10 @@
030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Windows,
03000000050b00000045000000000000,Nexus,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Windows,
03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
030000007e0500006920000000000000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Windows,
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,
030000007e0500007320000000000000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Windows,
030000007e0500001920000000000000,NSO N64 Controller,+rightx:b8,+righty:b2,-rightx:b3,-righty:b7,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Windows,
030000007e0500001720000000000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Windows,
03000000550900001472000000000000,NVIDIA Controller,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows,
@@ -1000,13 +1002,16 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X,
03000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c62400002b89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000853200008906000000010000,Nacon Revolution X Unlimited,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000632500007505000000020000,NeoGeo mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000921200004b46000003020000,NES 2-port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Mac OS X,
030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Mac OS X,
030000007e0500006920000001010000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Mac OS X,
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
030000007e0500000920000010020000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:Mac OS X,
050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:Mac OS X,
030000007e0500007320000001010000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Mac OS X,
030000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Mac OS X,
030000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000550900001472000025050000,NVIDIA Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X,
@@ -1283,7 +1288,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006f0e00008401000011010000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006f0e00008101000011010000,Faceoff Deluxe Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006f0e00008001000011010000,Faceoff Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03005036852100000201000010010000,Final Fantasy XIV Online Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000852100000201000010010000,FF GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
05000000b40400001224000001010000,Flydigi APEX 4,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b20,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000b40400001124000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000b40400001224000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@@ -1481,6 +1486,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
060000007e0500003713000000000000,Nintendo 3DS,a:b0,b:b1,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux,
03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b2,y:b3,platform:Linux,
030000007e0500006920000011010000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Linux,
060000004e696e74656e646f20537700,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
060000007e0500000620000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
060000007e0500000820000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
@@ -1495,6 +1501,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000007e0500003003000001000000,Nintendo Wii U Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
050000005a1d00000218000003000000,Nokia GC 5000,a:b9,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000000d0500000308000010010000,Nostromo n45 Dual Analog,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
030000007e0500007320000011010000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Linux,
030000007e0500001920000011810000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux,
050000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Linux,
050000007e0500001920000001800000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux,
@@ -1606,6 +1613,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
0300132d9b2800006500000001010000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800003200000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800006000000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800006100000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,
030000009b2800006400000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,
030000009b2800008000000020020000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux,
030000009b2800008000000001010000,Raphnet Wii Classic Adapter V3,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux,
03000000f8270000bf0b000011010000,Razer Kishi,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@@ -1802,6 +1811,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000130b000022050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,

View File

@@ -110,48 +110,24 @@ uint ps_convert_rgba8_16bits(PS_INPUT input) : SV_Target0
return ((i.x & 0x00F8u) >> 3) | ((i.y & 0x00F8u) << 2) | ((i.z & 0x00f8u) << 7) | ((i.w & 0x80u) << 8);
}
PS_OUTPUT ps_datm1(PS_INPUT input)
void ps_datm1(PS_INPUT input)
{
PS_OUTPUT output;
clip(sample_c(input.t).a - 127.5f / 255); // >= 0x80 pass
output.c = 0;
return output;
}
PS_OUTPUT ps_datm0(PS_INPUT input)
void ps_datm0(PS_INPUT input)
{
PS_OUTPUT output;
clip(127.5f / 255 - sample_c(input.t).a); // < 0x80 pass (== 0x80 should not pass)
output.c = 0;
return output;
}
PS_OUTPUT ps_datm1_rta_correction(PS_INPUT input)
void ps_datm1_rta_correction(PS_INPUT input)
{
PS_OUTPUT output;
clip(sample_c(input.t).a - 254.5f / 255); // >= 0x80 pass
output.c = 0;
return output;
}
PS_OUTPUT ps_datm0_rta_correction(PS_INPUT input)
void ps_datm0_rta_correction(PS_INPUT input)
{
PS_OUTPUT output;
clip(254.5f / 255 - sample_c(input.t).a); // < 0x80 pass (== 0x80 should not pass)
output.c = 0;
return output;
}
PS_OUTPUT ps_rta_correction(PS_INPUT input)
@@ -313,6 +289,127 @@ float ps_convert_rgb5a1_float16_biln(PS_INPUT input) : SV_Depth
SAMPLE_RGBA_DEPTH_BILN(rgb5a1_to_depth16);
}
PS_OUTPUT ps_convert_rgb5a1_8i(PS_INPUT input)
{
PS_OUTPUT output;
// Convert a RGB5A1 texture into a 8 bits packed texture
// Input column: 16x2 RGB5A1 pixels
// 0: 16 RGBA
// 1: 16 RGBA
// Output column: 16x4 Index pixels
// 0: 16 R5G2
// 1: 16 R5G2
// 2: 16 G2B5A1
// 3: 16 G2B5A1
uint2 pos = uint2(input.p.xy);
// Collapse separate R G B A areas into their base pixel
uint2 column = (pos & ~uint2(0u, 3u)) / uint2(1,2);
uint2 subcolumn = (pos & uint2(0u, 1u));
column.x -= (column.x / 128) * 64;
column.y += (column.y / 32) * 32;
uint PSM = uint(DOFFSET);
// Deal with swizzling differences
if ((PSM & 0x8) != 0) // PSMCT16S
{
if ((pos.x & 32) != 0)
{
column.y += 32; // 4 columns high times 4 to get bottom 4 blocks
column.x &= ~32;
}
if ((pos.x & 64) != 0)
{
column.x -= 32;
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((PSM & 0x30) != 0) // PSMZ16S - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 16;
}
}
else // PSMCT16
{
if ((pos.y & 32) != 0)
{
column.y -= 16;
column.x += 32;
}
if ((pos.x & 96) != 0)
{
uint multi = (pos.x & 96) / 32;
column.y += 16 * multi; // 4 columns high times 4 to get bottom 4 blocks
column.x -= (pos.x & 96);
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((PSM & 0x30) != 0) // PSMZ16 - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 32;
}
}
uint2 coord = column | subcolumn;
// Compensate for potentially differing page pitch.
uint SBW = uint(EMODA);
uint DBW = uint(EMODC);
uint2 block_xy = coord / uint2(64,64);
uint block_num = (block_xy.y * (DBW / 128)) + block_xy.x;
uint2 block_offset = uint2((block_num % (SBW / 64)) * 64, (block_num / (SBW / 64)) * 64);
coord = (coord % uint2(64, 64)) + block_offset;
// Apply offset to cols 1 and 2
uint is_col23 = pos.y & 4u;
uint is_col13 = pos.y & 2u;
uint is_col12 = is_col23 ^ (is_col13 << 1);
coord.x ^= is_col12; // If cols 1 or 2, flip bit 3 of x
float ScaleFactor = BGColor.x;
if (floor(ScaleFactor) != ScaleFactor)
coord = uint2(float2(coord) * ScaleFactor);
else
coord *= uint(ScaleFactor);
float4 pixel = Texture.Load(int3(int2(coord), 0));
uint4 denorm_c = (uint4)(pixel * 255.5f);
if ((pos.y & 2u) == 0u)
{
uint red = (denorm_c.r >> 3) & 0x1Fu;
uint green = (denorm_c.g >> 3) & 0x1Fu;
float sel0 = (float)(((green << 5) | red) & 0xFF) / 255.0f;
output.c = (float4)(sel0);
}
else
{
uint green = (denorm_c.g >> 3) & 0x1Fu;
uint blue = (denorm_c.b >> 3) & 0x1Fu;
uint alpha = denorm_c.a & 0x80u;
float sel0 = (float)((alpha | (blue << 2) | (green >> 3)) & 0xFF) / 255.0f;
output.c = (float4)(sel0);
}
return output;
}
PS_OUTPUT ps_convert_rgba_8i(PS_INPUT input)
{
PS_OUTPUT output;

View File

@@ -237,9 +237,131 @@ void ps_convert_rgb5a1_float16_biln()
}
#endif
#ifdef ps_convert_rgb5a1_8i
uniform uint SBW;
uniform uint DBW;
uniform uint PSM;
uniform float ScaleFactor;
void ps_convert_rgb5a1_8i()
{
// Convert a RGB5A1 texture into a 8 bits packed texture
// Input column: 16x2 RGB5A1 pixels
// 0: 16 RGBA
// 1: 16 RGBA
// Output column: 16x4 Index pixels
// 0: 16 R5G2
// 1: 16 R5G2
// 2: 16 G2B5A1
// 3: 16 G2B5A1
uvec2 pos = uvec2(gl_FragCoord.xy);
// Collapse separate R G B A areas into their base pixel
uvec2 column = (pos & ~uvec2(0u, 3u)) / uvec2(1,2);
uvec2 subcolumn = (pos & uvec2(0u, 1u));
column.x -= (column.x / 128) * 64;
column.y += (column.y / 32) * 32;
// Deal with swizzling differences
if ((PSM & 0x8) != 0) // PSMCT16S
{
if ((pos.x & 32) != 0)
{
column.y += 32; // 4 columns high times 4 to get bottom 4 blocks
column.x &= ~32;
}
if ((pos.x & 64) != 0)
{
column.x -= 32;
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((PSM & 0x30) != 0) // PSMZ16S - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 16;
}
}
else // PSMCT16
{
if ((pos.y & 32) != 0)
{
column.y -= 16;
column.x += 32;
}
if ((pos.x & 96) != 0)
{
uint multi = (pos.x & 96) / 32;
column.y += 16 * multi; // 4 columns high times 4 to get bottom 4 blocks
column.x -= (pos.x & 96);
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((PSM & 0x30) != 0) // PSMZ16 - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 32;
}
}
uvec2 coord = column | subcolumn;
// Compensate for potentially differing page pitch.
uvec2 block_xy = coord / uvec2(64u, 64u);
uint block_num = (block_xy.y * (DBW / 128u)) + block_xy.x;
uvec2 block_offset = uvec2((block_num % (SBW / 64u)) * 64u, (block_num / (SBW / 64u)) * 64u);
coord = (coord % uvec2(64u, 64u)) + block_offset;
// Apply offset to cols 1 and 2
uint is_col23 = pos.y & 4u;
uint is_col13 = pos.y & 2u;
uint is_col12 = is_col23 ^ (is_col13 << 1);
coord.x ^= is_col12; // If cols 1 or 2, flip bit 3 of x
if (floor(ScaleFactor) != ScaleFactor)
coord = uvec2(vec2(coord) * ScaleFactor);
else
coord *= uvec2(ScaleFactor);
vec4 pixel = texelFetch(TextureSampler, ivec2(coord), 0);
uvec4 denorm_c = uvec4(pixel * 255.5f);
if ((pos.y & 2u) == 0u)
{
uint red = (denorm_c.r >> 3) & 0x1Fu;
uint green = (denorm_c.g >> 3) & 0x1Fu;
float sel0 = float(((green << 5) | red) & 0xFF) / 255.0f;
SV_Target0 = vec4(sel0);
}
else
{
uint green = (denorm_c.g >> 3) & 0x1Fu;
uint blue = (denorm_c.b >> 3) & 0x1Fu;
uint alpha = denorm_c.a & 0x80u;
float sel0 = float((alpha | (blue << 2) | (green >> 3)) & 0xFF) / 255.0f;
SV_Target0 = vec4(sel0);
}
}
#endif
#ifdef ps_convert_rgba_8i
uniform uint SBW;
uniform uint DBW;
uniform uint PSM;
uniform float ScaleFactor;
void ps_convert_rgba_8i()

View File

@@ -104,7 +104,6 @@ void ps_datm1()
{
if(sample_c(v_tex).a < (127.5f / 255.0f)) // >= 0x80 pass
discard;
}
#endif
@@ -307,12 +306,138 @@ void ps_convert_rgb5a1_float16_biln()
}
#endif
#ifdef ps_convert_rgb5a1_8i
layout(push_constant) uniform cb10
{
uint SBW;
uint DBW;
uint PSM;
float cb_pad1;
float ScaleFactor;
vec3 cb_pad2;
};
void ps_convert_rgb5a1_8i()
{
// Convert a RGB5A1 texture into a 8 bits packed texture
// Input column: 16x2 RGB5A1 pixels
// 0: 16 RGBA
// 1: 16 RGBA
// Output column: 16x4 Index pixels
// 0: 16 R5G2
// 1: 16 R5G2
// 2: 16 G2B5A1
// 3: 16 G2B5A1
uvec2 pos = uvec2(gl_FragCoord.xy);
// Collapse separate R G B A areas into their base pixel
uvec2 column = (pos & ~uvec2(0u, 3u)) / uvec2(1,2);
uvec2 subcolumn = (pos & uvec2(0u, 1u));
column.x -= (column.x / 128) * 64;
column.y += (column.y / 32) * 32;
// Deal with swizzling differences
if ((PSM & 0x8) != 0) // PSMCT16S
{
if ((pos.x & 32) != 0)
{
column.y += 32; // 4 columns high times 4 to get bottom 4 blocks
column.x &= ~32;
}
if ((pos.x & 64) != 0)
{
column.x -= 32;
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((PSM & 0x30) != 0) // PSMZ16S - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 16;
}
}
else // PSMCT16
{
if ((pos.y & 32) != 0)
{
column.y -= 16;
column.x += 32;
}
if ((pos.x & 96) != 0)
{
uint multi = (pos.x & 96) / 32;
column.y += 16 * multi; // 4 columns high times 4 to get bottom 4 blocks
column.x -= (pos.x & 96);
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((PSM & 0x30) != 0) // PSMZ16 - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 32;
}
}
uvec2 coord = column | subcolumn;
// Compensate for potentially differing page pitch.
uvec2 block_xy = coord / uvec2(64u, 64u);
uint block_num = (block_xy.y * (DBW / 128u)) + block_xy.x;
uvec2 block_offset = uvec2((block_num % (SBW / 64u)) * 64u, (block_num / (SBW / 64u)) * 64u);
coord = (coord % uvec2(64u, 64u)) + block_offset;
// Apply offset to cols 1 and 2
uint is_col23 = pos.y & 4u;
uint is_col13 = pos.y & 2u;
uint is_col12 = is_col23 ^ (is_col13 << 1);
coord.x ^= is_col12; // If cols 1 or 2, flip bit 3 of x
if (floor(ScaleFactor) != ScaleFactor)
coord = uvec2(vec2(coord) * ScaleFactor);
else
coord *= uvec2(ScaleFactor);
vec4 pixel = texelFetch(samp0, ivec2(coord), 0);
uvec4 denorm_c = uvec4(pixel * 255.5f);
if ((pos.y & 2u) == 0u)
{
uint red = (denorm_c.r >> 3) & 0x1Fu;
uint green = (denorm_c.g >> 3) & 0x1Fu;
float sel0 = float(((green << 5) | red) & 0xFF) / 255.0f;
o_col0 = vec4(sel0);
}
else
{
uint green = (denorm_c.g >> 3) & 0x1Fu;
uint blue = (denorm_c.b >> 3) & 0x1Fu;
uint alpha = denorm_c.a & 0x80u;
float sel0 = float((alpha | (blue << 2) | (green >> 3)) & 0xFF) / 255.0f;
o_col0 = vec4(sel0);
}
}
#endif
#ifdef ps_convert_rgba_8i
layout(push_constant) uniform cb10
{
uint SBW;
uint DBW;
uvec2 cb_pad1;
uint PSM;
float cb_pad1;
float ScaleFactor;
vec3 cb_pad2;
};

View File

@@ -7,7 +7,7 @@ include(GNUInstallDirs)
# Misc option
#-------------------------------------------------------------------------------
option(ENABLE_TESTS "Enables building the unit tests" ON)
option(ENABLE_GSRUNNER "Enables building the GSRunner" OFF)
option(ENABLE_GSRUNNER "Enables building the GSRunner by default. It can still be built with `make pcsx2-gsrunner` otherwise." OFF)
option(LTO_PCSX2_CORE "Enable LTO/IPO/LTCG on the subset of pcsx2 that benefits most from it but not anything else")
option(USE_VTUNE "Plug VTUNE to profile GS JIT.")
option(PACKAGE_MODE "Use this option to ease packaging of PCSX2 (developer/distribution option)")

View File

@@ -120,7 +120,7 @@ add_subdirectory(3rdparty/demangler EXCLUDE_FROM_ALL)
add_subdirectory(3rdparty/ccc EXCLUDE_FROM_ALL)
# The docking system for the debugger.
find_package(KDDockWidgets-qt6 REQUIRED)
find_package(KDDockWidgets-qt6 2.0.0 REQUIRED)
# Add an extra include path to work around a broken include directive.
# TODO: Remove this the next time we update KDDockWidgets.
get_target_property(KDDOCKWIDGETS_INCLUDE_DIRECTORY KDAB::kddockwidgets INTERFACE_INCLUDE_DIRECTORIES)

View File

@@ -31,6 +31,19 @@ namespace CocoaTools
bool DelayedLaunch(std::string_view file);
/// Open a Finder window to the given URL
bool ShowInFinder(std::string_view file);
/// Get the path to the resources directory of the current application
std::optional<std::string> GetResourcePath();
/// Create a window
void* CreateWindow(std::string_view title, uint32_t width, uint32_t height);
/// Destroy a window
void DestroyWindow(void* window);
/// Make a WindowInfo from the given window
void GetWindowInfoFromWindow(WindowInfo* wi, void* window);
/// Run cocoa event loop
void RunCocoaEventLoop(bool wait_forever = false);
/// Posts an event to the main telling `RunCocoaEventLoop(true)` to exit
void StopMainThreadEventLoop();
}
#endif // __APPLE__

View File

@@ -224,5 +224,103 @@ bool CocoaTools::DelayedLaunch(std::string_view file)
bool CocoaTools::ShowInFinder(std::string_view file)
{
return [[NSWorkspace sharedWorkspace] selectFile:NSStringFromStringView(file)
inFileViewerRootedAtPath:nil];
inFileViewerRootedAtPath:@""];
}
std::optional<std::string> CocoaTools::GetResourcePath()
{ @autoreleasepool {
if (NSBundle* bundle = [NSBundle mainBundle])
{
NSString* rsrc = [bundle resourcePath];
NSString* root = [bundle bundlePath];
if ([rsrc isEqualToString:root])
rsrc = [rsrc stringByAppendingString:@"/resources"];
return [rsrc UTF8String];
}
return std::nullopt;
}}
// MARK: - GSRunner
void* CocoaTools::CreateWindow(std::string_view title, u32 width, u32 height)
{
if (!NSApp)
{
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp finishLaunching];
}
constexpr NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
NSScreen* mainScreen = [NSScreen mainScreen];
// Center the window on the screen, because why not
NSRect screenFrame = [mainScreen frame];
NSRect viewFrame = screenFrame;
viewFrame.size = NSMakeSize(width, height);
viewFrame.origin.x += (screenFrame.size.width - viewFrame.size.width) / 2;
viewFrame.origin.y += (screenFrame.size.height - viewFrame.size.height) / 2;
NSWindow* window = [[NSWindow alloc]
initWithContentRect:viewFrame
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[window setTitle:NSStringFromStringView(title)];
[window makeKeyAndOrderFront:window];
return (__bridge_retained void*)window;
}
void CocoaTools::DestroyWindow(void* window)
{
(void)(__bridge_transfer NSWindow*)window;
}
void CocoaTools::GetWindowInfoFromWindow(WindowInfo* wi, void* cf_window)
{
if (cf_window)
{
NSWindow* window = (__bridge NSWindow*)cf_window;
float scale = [window backingScaleFactor];
NSView* view = [window contentView];
NSRect dims = [view frame];
wi->type = WindowInfo::Type::MacOS;
wi->window_handle = (__bridge void*)view;
wi->surface_width = dims.size.width * scale;
wi->surface_height = dims.size.height * scale;
wi->surface_scale = scale;
}
else
{
wi->type = WindowInfo::Type::Surfaceless;
}
}
static constexpr short STOP_EVENT_LOOP = 0x100;
void CocoaTools::RunCocoaEventLoop(bool forever)
{
NSDate* end = forever ? [NSDate distantFuture] : [NSDate distantPast];
[NSApplication sharedApplication]; // Ensure NSApp is initialized
while (true)
{ @autoreleasepool {
NSEvent* ev = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:end
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (!ev || ([ev type] == NSEventTypeApplicationDefined && [ev subtype] == STOP_EVENT_LOOP))
break;
[NSApp sendEvent:ev];
}}
}
void CocoaTools::StopMainThreadEventLoop()
{ @autoreleasepool {
NSEvent* ev = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:{}
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:STOP_EVENT_LOOP
data1:0
data2:0];
[NSApp postEvent:ev atStart:NO];
}}

View File

@@ -16,6 +16,7 @@
#include "fmt/format.h"
#include "common/Assertions.h"
#include "common/CocoaTools.h"
#include "common/Console.h"
#include "common/CrashHandler.h"
#include "common/FileSystem.h"
@@ -56,7 +57,8 @@ namespace GSRunner
static bool CreatePlatformWindow();
static void DestroyPlatformWindow();
static std::optional<WindowInfo> GetPlatformWindowInfo();
static void PumpPlatformMessages();
static void PumpPlatformMessages(bool forever = false);
static void StopPlatformMessagePump();
} // namespace GSRunner
static constexpr u32 WINDOW_WIDTH = 640;
@@ -417,6 +419,16 @@ void Host::OnCreateMemoryCardOpenRequested()
// noop
}
bool Host::InBatchMode()
{
return false;
}
bool Host::InNoGUIMode()
{
return false;
}
bool Host::ShouldPreferHostFileSelector()
{
return false;
@@ -532,7 +544,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
{
std::string str(argv[++i]);
s_settings_interface.SetBoolValue("EmuCore/GS", "dump", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "DumpGSData", true);
if (str.find("rt") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveRT", true);
@@ -764,7 +776,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
return false;
}
if (s_settings_interface.GetBoolValue("EmuCore/GS", "dump") && !dumpdir.empty())
if (s_settings_interface.GetBoolValue("EmuCore/GS", "DumpGSData") && !dumpdir.empty())
{
if (s_settings_interface.GetStringValue("EmuCore/GS", "HWDumpDirectory").empty())
s_settings_interface.SetStringValue("EmuCore/GS", "HWDumpDirectory", dumpdir.c_str());
@@ -809,6 +821,22 @@ void GSRunner::DumpStats()
#define main real_main
#endif
static void CPUThreadMain(VMBootParameters* params) {
if (VMManager::Initialize(*params))
{
// run until end
GSDumpReplayer::SetLoopCount(s_loop_count);
VMManager::SetState(VMState::Running);
while (VMManager::GetState() == VMState::Running)
VMManager::Execute();
VMManager::Shutdown(false);
GSRunner::DumpStats();
}
VMManager::Internal::CPUThreadShutdown();
GSRunner::StopPlatformMessagePump();
}
int main(int argc, char* argv[])
{
CrashHandler::Install();
@@ -837,16 +865,9 @@ int main(int argc, char* argv[])
VMManager::ApplySettings();
GSDumpReplayer::SetIsDumpRunner(true);
if (VMManager::Initialize(params))
{
// run until end
GSDumpReplayer::SetLoopCount(s_loop_count);
VMManager::SetState(VMState::Running);
while (VMManager::GetState() == VMState::Running)
VMManager::Execute();
VMManager::Shutdown(false);
GSRunner::DumpStats();
}
std::thread cputhread(CPUThreadMain, &params);
GSRunner::PumpPlatformMessages(/*forever=*/true);
cputhread.join();
VMManager::Internal::CPUThreadShutdown();
GSRunner::DestroyPlatformWindow();
@@ -859,9 +880,6 @@ void Host::PumpMessagesOnCPUThread()
// update GS thread copy of frame number
MTGS::RunOnGSThread([frame_number = GSDumpReplayer::GetFrameNumber()]() { s_dump_frame_number = frame_number; });
MTGS::RunOnGSThread([loop_number = GSDumpReplayer::GetLoopCount()]() { s_loop_number = loop_number; });
// process any window messages (but we shouldn't really have any)
GSRunner::PumpPlatformMessages();
}
s32 Host::Internal::GetTranslatedStringImpl(
@@ -975,16 +993,32 @@ std::optional<WindowInfo> GSRunner::GetPlatformWindowInfo()
return wi;
}
void GSRunner::PumpPlatformMessages()
static constexpr int SHUTDOWN_MSG = WM_APP + 0x100;
static DWORD MainThreadID;
void GSRunner::PumpPlatformMessages(bool forever)
{
MSG msg;
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
while (true)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == SHUTDOWN_MSG)
return;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (!forever)
return;
WaitMessage();
}
}
void GSRunner::StopPlatformMessagePump()
{
PostThreadMessageW(MainThreadID, SHUTDOWN_MSG, 0, 0);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProcW(hwnd, msg, wParam, lParam);
@@ -1003,7 +1037,51 @@ int wmain(int argc, wchar_t** argv)
u8_argptrs.push_back(u8_args[i].data());
u8_argptrs.push_back(nullptr);
MainThreadID = GetCurrentThreadId();
return real_main(argc, u8_argptrs.data());
}
#endif // _WIN32
#elif defined(__APPLE__)
static void* s_window;
static WindowInfo s_wi;
bool GSRunner::CreatePlatformWindow()
{
pxAssertRel(!s_window, "Tried to create window when there already was one!");
s_window = CocoaTools::CreateWindow("PCSX2 GS Runner", WINDOW_WIDTH, WINDOW_HEIGHT);
CocoaTools::GetWindowInfoFromWindow(&s_wi, s_window);
PumpPlatformMessages();
return s_window;
}
void GSRunner::DestroyPlatformWindow()
{
if (s_window) {
CocoaTools::DestroyWindow(s_window);
s_window = nullptr;
}
}
std::optional<WindowInfo> GSRunner::GetPlatformWindowInfo()
{
WindowInfo wi;
if (s_window)
wi = s_wi;
else
wi.type = WindowInfo::Type::Surfaceless;
return wi;
}
void GSRunner::PumpPlatformMessages(bool forever)
{
CocoaTools::RunCocoaEventLoop(forever);
}
void GSRunner::StopPlatformMessagePump()
{
CocoaTools::StopMainThreadEventLoop();
}
#endif // _WIN32 / __APPLE__

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0+
#include "CoverDownloadDialog.h"
#include "QtUtils.h"
#include "pcsx2/GameList.h"
@@ -11,7 +12,7 @@ CoverDownloadDialog::CoverDownloadDialog(QWidget* parent /*= nullptr*/)
: QDialog(parent)
{
m_ui.setupUi(this);
m_ui.coverIcon->setPixmap(QIcon::fromTheme("artboard-2-line").pixmap(32));
QtUtils::SetScalableIcon(m_ui.coverIcon, QIcon::fromTheme(QStringLiteral("artboard-2-line")), QSize(32, 32));
updateEnabled();
connect(m_ui.start, &QPushButton::clicked, this, &CoverDownloadDialog::onStartClicked);

View File

@@ -142,12 +142,12 @@ void DockManager::switchToLayout(DockLayout::Index layout_index, bool blink_tab)
layout.thaw();
int tab_index = static_cast<int>(layout_index);
if (m_menu_bar && tab_index >= 0)
if (tab_index >= 0 && m_menu_bar)
m_menu_bar->onCurrentLayoutChanged(layout_index);
}
}
if (blink_tab)
if (blink_tab && m_menu_bar)
m_menu_bar->startBlink(m_current_layout);
}
@@ -512,7 +512,8 @@ void DockManager::newLayoutClicked()
{
// The plus button has just been made the current tab, so set it back to the
// one corresponding to the current layout again.
m_menu_bar->onCurrentLayoutChanged(m_current_layout);
if (m_menu_bar)
m_menu_bar->onCurrentLayoutChanged(m_current_layout);
auto name_validator = [this](const QString& name) {
return !hasNameConflict(name, DockLayout::INVALID_INDEX);

View File

@@ -149,7 +149,9 @@ DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
// that ends up taking ownerhsip of the style for the entire application!
if (QProxyStyle* proxy_style = qobject_cast<QProxyStyle*>(style()))
{
proxy_style->baseStyle()->setParent(qApp);
if (proxy_style->baseStyle() == qApp->style())
proxy_style->baseStyle()->setParent(qApp);
proxy_style->setBaseStyle(QStyleFactory::create(qApp->style()->name()));
}
}

View File

@@ -10,6 +10,7 @@
#include <kddockwidgets/Config.h>
#include <kddockwidgets/core/Group.h>
#include <kddockwidgets/core/Platform.h>
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
#include <kddockwidgets/qtwidgets/ViewFactory.h>

View File

@@ -311,7 +311,7 @@ void searchWorker(DebugInterface* cpu, std::vector<SearchResult>& searchResults,
const auto readValue = readValueAtAddress<T>(cpu, addr);
const bool doesMatch = handleSearchComparison(searchComparison, addr, &searchResult, searchValue, readValue);
if (!doesMatch)
if (doesMatch)
searchResult = MemorySearchView::SearchResult(addr, QVariant::fromValue(readValue), searchType);
return !doesMatch;

View File

@@ -52,12 +52,12 @@ DisplayWidget::~DisplayWidget()
int DisplayWidget::scaledWindowWidth() const
{
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioF())), 1);
}
int DisplayWidget::scaledWindowHeight() const
{
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioF())), 1);
}
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
@@ -268,7 +268,7 @@ bool DisplayWidget::event(QEvent* event)
if (!m_relative_mouse_enabled)
{
const qreal dpr = QtUtils::GetDevicePixelRatioForWidget(this);
const qreal dpr = devicePixelRatioF();
const QPoint mouse_pos = mouse_event->pos();
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
@@ -349,13 +349,12 @@ bool DisplayWidget::event(QEvent* event)
return true;
}
// According to https://bugreports.qt.io/browse/QTBUG-95925 the recommended practice for handling DPI change is responding to paint events
case QEvent::Paint:
case QEvent::DevicePixelRatioChange:
case QEvent::Resize:
{
QWidget::event(event);
const float dpr = QtUtils::GetDevicePixelRatioForWidget(this);
const float dpr = devicePixelRatioF();
const u32 scaled_width = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
const u32 scaled_height = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));

View File

@@ -25,17 +25,17 @@ static constexpr int COVER_ART_HEIGHT = 512;
static constexpr int COVER_ART_SPACING = 32;
static constexpr int MIN_COVER_CACHE_SIZE = 256;
static int DPRScale(int size, float dpr)
static int DPRScale(int size, qreal dpr)
{
return static_cast<int>(static_cast<float>(size) * dpr);
return static_cast<int>(static_cast<qreal>(size) * dpr);
}
static int DPRUnscale(int size, float dpr)
static int DPRUnscale(int size, qreal dpr)
{
return static_cast<int>(static_cast<float>(size) / dpr);
return static_cast<int>(static_cast<qreal>(size) / dpr);
}
static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, float dpr)
static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, qreal dpr)
{
const int dpr_expected_width = DPRScale(expected_width, dpr);
const int dpr_expected_height = DPRScale(expected_height, dpr);
@@ -71,9 +71,8 @@ static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_hei
}
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
const std::string& title)
qreal dpr, const std::string& title)
{
const float dpr = qApp->devicePixelRatio();
QPixmap pm(placeholder_pixmap.copy());
pm.setDevicePixelRatio(dpr);
if (pm.isNull())
@@ -113,9 +112,10 @@ const char* GameListModel::getColumnName(Column col)
return s_column_names[static_cast<int>(col)];
}
GameListModel::GameListModel(float cover_scale, bool show_cover_titles, QObject* parent /* = nullptr */)
GameListModel::GameListModel(float cover_scale, bool show_cover_titles, qreal dpr, QObject* parent /* = nullptr */)
: QAbstractTableModel(parent)
, m_show_titles_for_covers(show_cover_titles)
, m_dpr{dpr}
{
loadSettings();
loadCommonImages();
@@ -160,6 +160,13 @@ void GameListModel::updateCacheSize(int width, int height)
m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE)));
}
void GameListModel::setDevicePixelRatio(qreal dpr)
{
m_dpr = dpr;
loadCommonImages();
refreshCovers();
}
void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
{
// Why this counter: Every time we change the cover scale, we increment the counter variable. This way if the scale is changed
@@ -173,12 +180,11 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
const std::string cover_path(GameList::GetCoverImagePathForEntry(&entry));
if (!cover_path.empty())
{
const float dpr = qApp->devicePixelRatio();
image = QPixmap(QString::fromStdString(cover_path));
if (!image.isNull())
{
image.setDevicePixelRatio(dpr);
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), dpr);
image.setDevicePixelRatio(m_dpr);
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr);
}
}
}
@@ -186,7 +192,7 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
const std::string& title = entry.GetTitle(m_prefer_english_titles);
if (image.isNull())
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, title);
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, m_dpr, title);
if (m_cover_scale_counter.load(std::memory_order_acquire) != counter)
image = {};
@@ -575,10 +581,10 @@ QIcon GameListModel::getIconForRegion(GameList::Region region)
void GameListModel::loadThemeSpecificImages()
{
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
m_type_pixmaps[type] = getIconForType(static_cast<GameList::EntryType>(type)).pixmap(QSize(24, 24));
m_type_pixmaps[type] = getIconForType(static_cast<GameList::EntryType>(type)).pixmap(QSize(24, 24), m_dpr);
for (u32 i = 0; i < static_cast<u32>(GameList::Region::Count); i++)
m_region_pixmaps[i] = getIconForRegion(static_cast<GameList::Region>(i)).pixmap(QSize(36, 26));
m_region_pixmaps[i] = getIconForRegion(static_cast<GameList::Region>(i)).pixmap(QSize(36, 26), m_dpr);
}
void GameListModel::loadCommonImages()
@@ -587,7 +593,7 @@ void GameListModel::loadCommonImages()
const QString base_path(QtHost::GetResourcesBasePath());
for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++)
m_compatibility_pixmaps[i].load(QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(i - 1));
m_compatibility_pixmaps[i] = QIcon((QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(i - 1))).pixmap(QSize(88, 16), m_dpr);
m_placeholder_pixmap.load(QStringLiteral("%1/cover-placeholder.png").arg(base_path));
}

View File

@@ -43,7 +43,7 @@ public:
static QIcon getIconForType(GameList::EntryType type);
static QIcon getIconForRegion(GameList::Region region);
GameListModel(float cover_scale, bool show_cover_titles, QObject* parent = nullptr);
GameListModel(float cover_scale, bool show_cover_titles, qreal dpr, QObject* parent = nullptr);
~GameListModel();
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -71,6 +71,8 @@ public:
void refreshCovers();
void updateCacheSize(int width, int height);
void setDevicePixelRatio(qreal dpr);
Q_SIGNALS:
void coverScaleChanged();
@@ -94,6 +96,7 @@ private:
std::array<QPixmap, static_cast<u32>(GameList::Region::Count)> m_region_pixmaps;
QPixmap m_placeholder_pixmap;
QPixmap m_loading_pixmap;
qreal m_dpr;
std::array<QPixmap, static_cast<int>(GameList::CompatibilityRatingCount)> m_compatibility_pixmaps;
mutable LRUCache<std::string, QPixmap> m_cover_pixmap_cache;

View File

@@ -138,16 +138,15 @@ namespace
if (option.state & QStyle::State_Selected)
{
// See QItemDelegate::selectedPixmap()
QString key = QString::fromStdString(fmt::format("{:016X}-{:d}", pix.cacheKey(), enabled));
QColor color = option.palette.color(enabled ? QPalette::Normal : QPalette::Disabled, QPalette::Highlight);
color.setAlphaF(0.3f);
QString key = QString::fromStdString(fmt::format("{:016X}-{:d}-{:08X}", pix.cacheKey(), enabled, color.rgba()));
QPixmap pm;
if (!QPixmapCache::find(key, &pm))
{
QImage img = pix.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
QColor color = option.palette.color(enabled ? QPalette::Normal : QPalette::Disabled,
QPalette::Highlight);
color.setAlphaF(0.3f);
QPainter tinted_painter(&img);
tinted_painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
tinted_painter.fillRect(0, 0, img.width(), img.height(), color);
@@ -181,7 +180,7 @@ void GameListWidget::initialize()
{
const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f);
const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true);
m_model = new GameListModel(cover_scale, show_cover_titles, this);
m_model = new GameListModel(cover_scale, show_cover_titles, devicePixelRatioF(), this);
m_model->updateCacheSize(width(), height());
m_sort_model = new GameListSortModel(m_model);
@@ -559,6 +558,18 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
m_model->updateCacheSize(width(), height());
}
bool GameListWidget::event(QEvent* event)
{
if (event->type() == QEvent::DevicePixelRatioChange)
{
m_model->setDevicePixelRatio(devicePixelRatioF());
QWidget::event(event);
return true;
}
return QWidget::event(event);
}
void GameListWidget::resizeTableViewColumnsToFit()
{
QtUtils::ResizeColumnsForTableView(m_table_view, {

View File

@@ -92,7 +92,8 @@ public Q_SLOTS:
void refreshGridCovers();
protected:
void resizeEvent(QResizeEvent* event);
void resizeEvent(QResizeEvent* event) override;
bool event(QEvent* event) override;
private:
void loadTableViewColumnVisibilitySettings();

View File

@@ -43,7 +43,7 @@ void LogWindow::updateSettings()
{
std::unique_lock lock(s_log_mutex);
const bool new_enabled = Host::GetBaseBoolSettingValue("Logging", "EnableLogWindow", false) && !QtHost::InNoGUIMode();
const bool new_enabled = Host::GetBaseBoolSettingValue("Logging", "EnableLogWindow", false) && !Host::InNoGUIMode();
const bool attach_to_main = Host::GetBaseBoolSettingValue("Logging", "AttachLogWindowToMainWindow", true);
const bool curr_enabled = Log::IsHostOutputEnabled();

View File

@@ -1130,7 +1130,7 @@ bool MainWindow::shouldHideMainWindow() const
// NOTE: We can't use isRenderingToMain() here, because this happens post-fullscreen-switch.
return (Host::GetBoolSettingValue("UI", "HideMainWindowWhenRunning", false) && !g_emu_thread->shouldRenderToMain()) ||
(g_emu_thread->shouldRenderToMain() && (isRenderingFullscreen() || m_is_temporarily_windowed)) ||
QtHost::InNoGUIMode();
Host::InNoGUIMode();
}
bool MainWindow::shouldMouseLock() const
@@ -1306,7 +1306,7 @@ bool MainWindow::requestShutdown(bool allow_confirm, bool allow_save_to_state, b
// reshow the main window during display updates, because otherwise fullscreen transitions and renderer switches
// would briefly show and then hide the main window. So instead, we do it on shutdown, here. Except if we're in
// batch mode, when we're going to exit anyway.
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !g_emu_thread->isRunningFullscreenUI())
if (!isRenderingToMain() && isHidden() && !Host::InBatchMode() && !g_emu_thread->isRunningFullscreenUI())
updateWindowState(true);
// Clear the VM valid state early. That way we can't do anything in the UI if we take a while to shut down.
@@ -2060,7 +2060,7 @@ void MainWindow::onVMStopped()
updateInputRecordingActions(false);
// If we're closing or in batch mode, quit the whole application now.
if (m_is_closing || QtHost::InBatchMode())
if (m_is_closing || Host::InBatchMode())
{
quit();
return;

View File

@@ -602,7 +602,7 @@ void Host::CheckForSettingsChanges(const Pcsx2Config& old_config)
bool EmuThread::shouldRenderToMain() const
{
return !Host::GetBoolSettingValue("UI", "RenderToSeparateWindow", false) && !QtHost::InNoGUIMode();
return !Host::GetBoolSettingValue("UI", "RenderToSeparateWindow", false) && !Host::InNoGUIMode();
}
void EmuThread::toggleSoftwareRendering()
@@ -1268,7 +1268,7 @@ void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool def
// This will probably call shutdownVM() again, but by the time it runs, we'll have already shut down
// and it'll be a noop.
if (QtHost::InBatchMode())
if (Host::InBatchMode())
QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection, Q_ARG(bool, false));
}
}
@@ -1437,12 +1437,12 @@ void Host::CommitBaseSettingChanges()
}
}
bool QtHost::InBatchMode()
bool Host::InBatchMode()
{
return s_batch_mode;
}
bool QtHost::InNoGUIMode()
bool Host::InNoGUIMode()
{
return s_nogui_mode;
}

View File

@@ -245,12 +245,6 @@ namespace QtHost
/// Sets the icon theme, based on the current style (light/dark).
void SetIconThemeFromStyle();
/// Sets batch mode (exit after game shutdown).
bool InBatchMode();
/// Sets NoGUI mode (implys batch mode, does not display main window, exits on shutdown).
bool InNoGUIMode();
/// Returns true if the calling thread is the UI thread.
bool IsOnUIThread();

View File

@@ -254,15 +254,6 @@ namespace QtUtils
widget->resize(width, height);
}
qreal GetDevicePixelRatioForWidget(const QWidget* widget)
{
const QScreen* screen_for_ratio = widget->screen();
if (!screen_for_ratio)
screen_for_ratio = QGuiApplication::primaryScreen();
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
}
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget)
{
WindowInfo wi;
@@ -303,7 +294,7 @@ namespace QtUtils
}
#endif
const qreal dpr = GetDevicePixelRatioForWidget(widget);
const qreal dpr = widget->devicePixelRatioF();
wi.surface_width = static_cast<u32>(static_cast<qreal>(widget->width()) * dpr);
wi.surface_height = static_cast<u32>(static_cast<qreal>(widget->height()) * dpr);
wi.surface_scale = static_cast<float>(dpr);
@@ -374,4 +365,37 @@ namespace QtUtils
return true;
}
class IconVariableDpiFilter : QObject
{
public:
explicit IconVariableDpiFilter(QLabel* lbl, const QIcon& icon, const QSize& size, QObject* parent = nullptr)
: QObject(parent)
, m_lbl{lbl}
, m_icn{icon}
, m_size{size}
{
lbl->installEventFilter(this);
m_lbl->setPixmap(m_icn.pixmap(m_size, m_lbl->devicePixelRatioF()));
}
protected:
bool eventFilter(QObject* object, QEvent* event) override
{
if (object == m_lbl && event->type() == QEvent::DevicePixelRatioChange)
m_lbl->setPixmap(m_icn.pixmap(m_size, m_lbl->devicePixelRatioF()));
// Don't block the event
return false;
}
private:
QLabel* m_lbl;
QIcon m_icn;
QSize m_size;
};
void SetScalableIcon(QLabel* lbl, const QIcon& icon, const QSize& size)
{
new IconVariableDpiFilter(lbl, icon, size, lbl);
}
} // namespace QtUtils

View File

@@ -20,6 +20,7 @@ class QAction;
class QComboBox;
class QFileInfo;
class QFrame;
class QIcon;
class QLabel;
class QKeyEvent;
class QSlider;
@@ -82,9 +83,6 @@ namespace QtUtils
/// Adjusts the fixed size for a window if it's not resizeable.
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
/// Returns the pixel ratio/scaling factor for a widget.
qreal GetDevicePixelRatioForWidget(const QWidget* widget);
/// Returns the common window info structure for a Qt widget.
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget);
@@ -102,4 +100,8 @@ namespace QtUtils
bool IsLightTheme(const QPalette& palette);
bool IsCompositorManagerRunning();
/// Sets the scalable icon to a given label (svg icons, or icons with multiple size pixmaps)
/// The icon will then be reloaded on DPR changes using an event filter
void SetScalableIcon(QLabel* lbl, const QIcon& icon, const QSize& size);
} // namespace QtUtils

View File

@@ -3,6 +3,7 @@
#include "AchievementLoginDialog.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "pcsx2/Achievements.h"
@@ -15,7 +16,7 @@ AchievementLoginDialog::AchievementLoginDialog(QWidget* parent, Achievements::Lo
, m_reason(reason)
{
m_ui.setupUi(this);
m_ui.loginIcon->setPixmap(QIcon::fromTheme("login-box-line").pixmap(32));
QtUtils::SetScalableIcon(m_ui.loginIcon, QIcon::fromTheme(QStringLiteral("login-box-line")), QSize(32, 32));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Adjust text if needed based on reason.
@@ -116,7 +117,6 @@ void AchievementLoginDialog::processLoginResult(bool result, const QString& mess
g_emu_thread->resetVM();
}
}
}
done(0);

View File

@@ -342,7 +342,7 @@ void AudioSettingsWidget::onExpansionSettingsClicked()
QDialog dlg(QtUtils::GetRootWidget(this));
Ui::AudioExpansionSettingsDialog dlgui;
dlgui.setupUi(&dlg);
dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
QtUtils::SetScalableIcon(dlgui.icon, QIcon::fromTheme(QStringLiteral("volume-up-line")), QSize(32, 32));
SettingsInterface* sif = m_dialog->getSettingsInterface();
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.blockSize, "SPU2/Output", "ExpandBlockSize",
@@ -431,7 +431,7 @@ void AudioSettingsWidget::onStretchSettingsClicked()
QDialog dlg(QtUtils::GetRootWidget(this));
Ui::AudioStretchSettingsDialog dlgui;
dlgui.setupUi(&dlg);
dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("volume-up-line")).pixmap(32, 32));
QtUtils::SetScalableIcon(dlgui.icon, QIcon::fromTheme(QStringLiteral("volume-up-line")), QSize(32, 32));
SettingsInterface* sif = m_dialog->getSettingsInterface();
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.sequenceLength, "SPU2/Output", "StretchSequenceLengthMS",

View File

@@ -170,7 +170,7 @@ ControllerMouseSettingsDialog::ControllerMouseSettingsDialog(QWidget* parent, Co
SettingsInterface* sif = dialog->getProfileSettingsInterface();
m_ui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("mouse-line")).pixmap(32, 32));
QtUtils::SetScalableIcon(m_ui.icon, QIcon::fromTheme(QStringLiteral("mouse-line")), QSize(32, 32));
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXSpeedSlider, "Pad", "PointerXSpeed", 40.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYSpeedSlider, "Pad", "PointerYSpeed", 40.0f);
@@ -202,11 +202,10 @@ ControllerMappingSettingsDialog::ControllerMappingSettingsDialog(ControllerSetti
SettingsInterface* sif = parent->getProfileSettingsInterface();
m_ui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("settings-3-line")).pixmap(32, 32));
QtUtils::SetScalableIcon(m_ui.icon, QIcon::fromTheme(QStringLiteral("settings-3-line")), QSize(32, 32));
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.ignoreInversion, "InputSources", "IgnoreInversion", false);
connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept);
}
ControllerMappingSettingsDialog::~ControllerMappingSettingsDialog() = default;

View File

@@ -9,6 +9,7 @@
#include <QtWidgets/QPushButton>
#include "Settings/MemoryCardCreateDialog.h"
#include "QtUtils.h"
#include "pcsx2/SIO/Memcard/MemoryCardFile.h"
@@ -16,7 +17,7 @@ MemoryCardCreateDialog::MemoryCardCreateDialog(QWidget* parent /* = nullptr */)
: QDialog(parent)
{
m_ui.setupUi(this);
m_ui.icon->setPixmap(QIcon::fromTheme("memcard-line").pixmap(m_ui.icon->width()));
QtUtils::SetScalableIcon(m_ui.icon, QIcon::fromTheme(QStringLiteral("memcard-line")), QSize(m_ui.icon->width(), m_ui.icon->width()));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

View File

@@ -10,6 +10,7 @@
#include <QtCore/QFile>
#include <QtGui/QPalette>
#include <QtGui/QPixmapCache>
#include <QtWidgets/QApplication>
#include <QtWidgets/QStyle>
#include <QtWidgets/QStyleFactory>
@@ -43,6 +44,12 @@ void QtHost::UpdateApplicationTheme()
SetStyleFromSettings();
SetIconThemeFromStyle();
// Qt generates tinted versions of icons and stores them in QPixmapCache
// The key used does not seem to include the theme (or tint colour).
// This can cause icons tinted for wrong theme to be used for selected/disabled.
// As a workaround, reset the pixmap cache to clear icons tinted for the old theme.
QPixmapCache::clear();
}
bool QtHost::IsDarkApplicationTheme()

File diff suppressed because it is too large Load Diff

View File

@@ -1237,9 +1237,16 @@ fixup_file_properties(PCSX2)
force_include_last(PCSX2_FLAGS "/(usr|local)/include/?$")
if (APPLE)
find_library(APPKIT_LIBRARY AppKit)
find_library(IOKIT_LIBRARY IOKit)
find_library(METAL_LIBRARY Metal)
find_library(QUARTZCORE_LIBRARY QuartzCore)
target_link_libraries(PCSX2_FLAGS INTERFACE ${METAL_LIBRARY} ${QUARTZCORE_LIBRARY})
target_link_libraries(PCSX2_FLAGS INTERFACE
${APPKIT_LIBRARY}
${IOKIT_LIBRARY}
${METAL_LIBRARY}
${QUARTZCORE_LIBRARY}
)
endif()
set_property(GLOBAL PROPERTY PCSX2_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -44,7 +44,7 @@ bool DebugInterface::m_pause_on_entry = false;
bool DebugInterface::isAlive()
{
return VMManager::HasValidVM() && g_FrameCount > 0;
return VMManager::HasValidVM();
}
bool DebugInterface::isCpuPaused()

View File

@@ -446,6 +446,13 @@ std::vector<GSVector2i>* GSLocalMemory::GetPage2TileMap(const GIFRegTEX0& TEX0)
return p2t;
}
u32 GSLocalMemory::IsPageAlignedMasked(u32 psm, const GSVector4i& rc)
{
const psm_t& psm_s = m_psm[psm];
const GSVector4i pgmsk = GSVector4i(psm_s.pgs).xyxy() - GSVector4i(1);
return ((rc & pgmsk) == GSVector4i::zero()).mask();
}
bool GSLocalMemory::IsPageAligned(u32 psm, const GSVector4i& rc)
{
const psm_t& psm_s = m_psm[psm];

View File

@@ -524,6 +524,7 @@ public:
GSPixelOffset4* GetPixelOffset4(const GIFRegFRAME& FRAME, const GIFRegZBUF& ZBUF);
std::vector<GSVector2i>* GetPage2TileMap(const GIFRegTEX0& TEX0);
static bool HasOverlap(u32 src_bp, u32 src_bw, u32 src_psm, GSVector4i src_rect, u32 dst_bp, u32 dst_bw, u32 dst_psm, GSVector4i dst_rect);
static u32 IsPageAlignedMasked(u32 psm, const GSVector4i& rc);
static bool IsPageAligned(u32 psm, const GSVector4i& rc);
static u32 GetStartBlockAddress(u32 bp, u32 bw, u32 psm, GSVector4i rect);
static u32 GetEndBlockAddress(u32 bp, u32 bw, u32 psm, GSVector4i rect);

View File

@@ -32,7 +32,7 @@ static __fi bool IsFirstProvokingVertex()
return (GSIsHardwareRenderer() && !g_gs_device->Features().provoking_vertex_last);
}
constexpr int GSState::GetSaveStateSize()
constexpr int GSState::GetSaveStateSize(int version)
{
int size = 0;
@@ -78,6 +78,19 @@ constexpr int GSState::GetSaveStateSize()
size += sizeof(m_tr.x);
size += sizeof(m_tr.y);
if (version >= 9)
{
size += sizeof(m_tr.w);
size += sizeof(m_tr.h);
size += sizeof(m_tr.m_blit);
size += sizeof(m_tr.m_pos);
size += sizeof(m_tr.m_reg);
size += sizeof(m_tr.rect);
size += sizeof(m_tr.total);
size += sizeof(m_tr.start);
size += sizeof(m_tr.end);
size += sizeof(m_tr.write);
}
size += GSLocalMemory::m_vmsize;
size += (sizeof(GIFPath::tag) + sizeof(GIFPath::reg)) * 4 /* std::size(GSState::m_path) */; // std::size won't work without an instance.
size += sizeof(m_q);
@@ -1445,10 +1458,10 @@ void GSState::GIFRegHandlerTRXDIR(const GIFReg* RESTRICT r)
switch (m_env.TRXDIR.XDIR)
{
case 0: // host -> local
m_tr.Init(m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, m_env.BITBLTBUF, true);
m_tr.Init(m_env.TRXPOS, m_env.TRXREG, m_env.BITBLTBUF, true);
break;
case 1: // local -> host
m_tr.Init(m_env.TRXPOS.SSAX, m_env.TRXPOS.SSAY, m_env.BITBLTBUF, false);
m_tr.Init(m_env.TRXPOS, m_env.TRXREG, m_env.BITBLTBUF, false);
break;
case 2: // local -> local
CheckWriteOverlap(true, true);
@@ -1549,16 +1562,13 @@ void GSState::FlushWrite()
GSVector4i r;
r.left = m_env.TRXPOS.DSAX;
r.top = m_env.TRXPOS.DSAY;
r.right = r.left + m_env.TRXREG.RRW;
r.bottom = r.top + m_env.TRXREG.RRH;
r = m_tr.rect;
InvalidateVideoMem(m_env.BITBLTBUF, r);
const GSLocalMemory::writeImage wi = GSLocalMemory::m_psm[m_env.BITBLTBUF.DPSM].wi;
wi(m_mem, m_tr.x, m_tr.y, &m_tr.buff[m_tr.start], len, m_env.BITBLTBUF, m_env.TRXPOS, m_env.TRXREG);
wi(m_mem, m_tr.x, m_tr.y, &m_tr.buff[m_tr.start], len, m_tr.m_blit, m_tr.m_pos, m_tr.m_reg);
m_tr.start += len;
@@ -1931,12 +1941,9 @@ void GSState::Write(const u8* mem, int len)
if (m_env.TRXDIR.XDIR == 3)
return;
const int w = m_env.TRXREG.RRW;
const int h = m_env.TRXREG.RRH;
CheckWriteOverlap(true, false);
if (!m_tr.Update(w, h, GSLocalMemory::m_psm[m_env.BITBLTBUF.DPSM].trbpp, len))
if (!m_tr.Update(m_tr.w, m_tr.h, GSLocalMemory::m_psm[m_tr.m_blit.DPSM].trbpp, len))
{
m_env.TRXDIR.XDIR = 3;
return;
@@ -1949,10 +1956,7 @@ void GSState::Write(const u8* mem, int len)
{
GSVector4i r;
r.left = m_env.TRXPOS.DSAX;
r.top = m_env.TRXPOS.DSAY;
r.right = r.left + m_env.TRXREG.RRW;
r.bottom = r.top + m_env.TRXREG.RRH;
r = m_tr.rect;
s_last_transfer_draw_n = s_n;
// Store the transfer for preloading new RT's.
@@ -1974,15 +1978,15 @@ void GSState::Write(const u8* mem, int len)
GL_CACHE("Write! %u ... => 0x%x W:%d F:%s (DIR %d%d), dPos(%d %d) size(%d %d) draw %d", s_transfer_n,
blit.DBP, blit.DBW, psm_str(blit.DPSM),
m_env.TRXPOS.DIRX, m_env.TRXPOS.DIRY,
m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, w, h, s_n);
m_tr.m_pos.DIRX, m_tr.m_pos.DIRY,
m_tr.x, m_tr.y, m_tr.w, m_tr.h, s_n);
if (len >= m_tr.total)
{
// received all data in one piece, no need to buffer it
InvalidateVideoMem(blit, r);
psm.wi(m_mem, m_tr.x, m_tr.y, mem, m_tr.total, blit, m_env.TRXPOS, m_env.TRXREG);
psm.wi(m_mem, m_tr.x, m_tr.y, mem, m_tr.total, blit, m_tr.m_pos, m_tr.m_reg);
m_tr.start = m_tr.end = m_tr.total;
@@ -2334,7 +2338,7 @@ void GSState::ReadLocalMemoryUnsync(u8* mem, int qwc, GIFRegBITBLTBUF BITBLTBUF,
GSTransferBuffer tb;
if(m_tr.end >= m_tr.total || m_tr.write == true)
tb.Init(TRXPOS.SSAX, TRXPOS.SSAY, BITBLTBUF, false);
tb.Init(TRXPOS, TRXREG, BITBLTBUF, false);
int len = qwc * 16;
if (!tb.Update(w, h, bpp, len))
@@ -2578,13 +2582,14 @@ static void ReadState(T* dst, u8*& src, size_t len = sizeof(T))
int GSState::Freeze(freezeData* fd, bool sizeonly)
{
const u32 version = STATE_VERSION;
if (sizeonly)
{
fd->size = GetSaveStateSize();
fd->size = GetSaveStateSize(version);
return 0;
}
if (!fd->data || fd->size < GetSaveStateSize())
if (!fd->data || fd->size < GetSaveStateSize(version))
return -1;
Flush(GSFlushReason::SAVESTATE);
@@ -2593,7 +2598,6 @@ int GSState::Freeze(freezeData* fd, bool sizeonly)
ReadbackTextureCache();
u8* data = fd->data;
const u32 version = STATE_VERSION;
WriteState(data, &version);
WriteState(data, &m_env.PRIM);
@@ -2636,6 +2640,18 @@ int GSState::Freeze(freezeData* fd, bool sizeonly)
data += sizeof(GIFReg); // obsolite
WriteState(data, &m_tr.x);
WriteState(data, &m_tr.y);
// Version 9 up.
WriteState(data, &m_tr.w);
WriteState(data, &m_tr.h);
WriteState(data, &m_tr.m_blit);
WriteState(data, &m_tr.m_pos);
WriteState(data, &m_tr.m_reg);
WriteState(data, &m_tr.rect);
WriteState(data, &m_tr.total);
WriteState(data, &m_tr.start);
WriteState(data, &m_tr.end);
WriteState(data, &m_tr.write);
// End of version 9 changes.
WriteState(data, m_mem.m_vm8, m_mem.m_vmsize);
for (GIFPath& path : m_path)
@@ -2663,15 +2679,15 @@ int GSState::Defrost(const freezeData* fd)
if (!fd || !fd->data || fd->size == 0)
return -1;
if (fd->size < GetSaveStateSize())
return -1;
u8* data = fd->data;
u32 version;
ReadState(&version, data);
if (fd->size < GetSaveStateSize(version))
return -1;
if (version > STATE_VERSION)
{
Console.Error("GS: Savestate version is incompatible. Load aborted.");
@@ -2701,12 +2717,6 @@ int GSState::Defrost(const freezeData* fd)
ReadState(&m_env.TRXPOS, data);
ReadState(&m_env.TRXREG, data);
ReadState(&m_env.TRXREG, data); // obsolete
// Technically this value ought to be saved like m_tr.x/y (break
// compatibility) but so far only a single game (Motocross Mania) really
// depends on this value (i.e != BITBLTBUF) Savestates are likely done at
// VSYNC, so not in the middle of a texture transfer, therefore register
// will be set again properly
m_tr.m_blit = m_env.BITBLTBUF;
for (int i = 0; i < 2; i++)
{
@@ -2742,9 +2752,36 @@ int GSState::Defrost(const freezeData* fd)
data += sizeof(GIFReg); // obsolite
ReadState(&m_tr.x, data);
ReadState(&m_tr.y, data);
ReadState(m_mem.m_vm8, data, m_mem.m_vmsize);
m_tr.total = 0; // TODO: restore transfer state
if (version >= 9)
{
ReadState(&m_tr.w, data);
ReadState(&m_tr.h, data);
ReadState(&m_tr.m_blit, data);
ReadState(&m_tr.m_pos, data);
ReadState(&m_tr.m_reg, data);
ReadState(&m_tr.rect, data);
ReadState(&m_tr.total, data);
ReadState(&m_tr.start, data);
ReadState(&m_tr.end, data);
ReadState(&m_tr.write, data);
}
else
{
m_tr.w = m_env.TRXREG.RRW;
m_tr.h = m_env.TRXREG.RRH;
m_tr.m_blit = m_env.BITBLTBUF;
m_tr.m_pos = m_env.TRXPOS;
m_tr.m_reg = m_env.TRXREG;
// Assume the last transfer was a write (but nuke it).
m_tr.rect = GSVector4i(m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, m_env.TRXPOS.DSAX + m_tr.w, m_env.TRXPOS.DSAY + m_tr.h);
m_tr.total = 0;
m_tr.start = 0;
m_tr.end = 0;
m_tr.write = true;
}
ReadState(m_mem.m_vm8, data, m_mem.m_vmsize);
for (GIFPath& path : m_path)
{
@@ -3140,7 +3177,8 @@ bool GSState::SpriteDrawWithoutGaps()
}
else
{
if ((std::abs(dpX - first_dpX) >= 16 && (i + 2) < m_vertex.next) || std::abs(this_start_X - last_pX) >= 16)
const int dpY = v[i + 1].XYZ.Y - v[i].XYZ.Y;
if ((std::abs(dpY - first_dpY) >= 16 && (i + 2) < m_vertex.next) || std::abs(this_start_X - last_pX) >= 16)
return false;
}
}
@@ -4199,7 +4237,10 @@ void GSState::CalcAlphaMinMax(const int tex_alpha_min, const int tex_alpha_max)
if (IsCoverageAlpha())
{
min = 128;
// HW renderer doesn't currently support AA, so its min is 128.
// If we add AA support to the HW renderer, this will need to be changed.
// (Will probably only be supported with ROV/FBFetch so we would want to check for that.)
min = GSIsHardwareRenderer() ? 128 : 0;
max = 128;
}
else
@@ -4562,14 +4603,19 @@ GSState::GSTransferBuffer::~GSTransferBuffer()
_aligned_free(buff);
}
void GSState::GSTransferBuffer::Init(int tx, int ty, const GIFRegBITBLTBUF& blit, bool is_write)
void GSState::GSTransferBuffer::Init(GIFRegTRXPOS& TRXPOS, GIFRegTRXREG& TRXREG, const GIFRegBITBLTBUF& blit, bool is_write)
{
x = tx;
y = ty;
x = is_write ? TRXPOS.DSAX : TRXPOS.SSAX;
y = is_write ? TRXPOS.DSAY : TRXPOS.SSAY;
w = TRXREG.RRW;
h = TRXREG.RRH;
rect = GSVector4i(x, y, x + w, y + h);
total = 0;
start = 0;
end = 0;
m_blit = blit;
m_pos = TRXPOS;
m_reg = TRXREG;
write = is_write;
}

View File

@@ -21,7 +21,7 @@ public:
GSState();
virtual ~GSState();
static constexpr int GetSaveStateSize();
static constexpr int GetSaveStateSize(int version);
private:
// RESTRICT prevents multiple loads of the same part of the register when accessing its bitfields (the compiler is happy to know that memory writes in-between will not go there)
@@ -108,15 +108,19 @@ private:
struct GSTransferBuffer
{
int x = 0, y = 0;
int w = 0, h = 0;
int start = 0, end = 0, total = 0;
u8* buff = nullptr;
GSVector4i rect = GSVector4i::zero();
GIFRegBITBLTBUF m_blit = {};
GIFRegTRXPOS m_pos = {};
GIFRegTRXREG m_reg = {};
bool write = false;
GSTransferBuffer();
~GSTransferBuffer();
void Init(int tx, int ty, const GIFRegBITBLTBUF& blit, bool write);
void Init(GIFRegTRXPOS& TRXPOS, GIFRegTRXREG& TRXREG, const GIFRegBITBLTBUF& blit, bool is_write);
bool Update(int tw, int th, int bpp, int& len);
} m_tr;
@@ -259,7 +263,7 @@ public:
static int s_last_transfer_draw_n;
static int s_transfer_n;
static constexpr u32 STATE_VERSION = 8;
static constexpr u32 STATE_VERSION = 9;
enum REG_DIRTY
{

View File

@@ -68,6 +68,7 @@ const char* shaderName(ShaderConvert value)
case ShaderConvert::DEPTH_COPY: return "ps_depth_copy";
case ShaderConvert::DOWNSAMPLE_COPY: return "ps_downsample_copy";
case ShaderConvert::RGBA_TO_8I: return "ps_convert_rgba_8i";
case ShaderConvert::RGB5A1_TO_8I: return "ps_convert_rgb5a1_8i";
case ShaderConvert::CLUT_4: return "ps_convert_clut_4";
case ShaderConvert::CLUT_8: return "ps_convert_clut_8";
case ShaderConvert::YUV: return "ps_yuv";

View File

@@ -44,6 +44,7 @@ enum class ShaderConvert
DEPTH_COPY,
DOWNSAMPLE_COPY,
RGBA_TO_8I,
RGB5A1_TO_8I,
CLUT_4,
CLUT_8,
YUV,

View File

@@ -617,72 +617,71 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
EndPresentFrame();
PerformanceMetrics::Update(registers_written, fb_sprite_frame, skip_frame);
return;
}
if (!idle_frame)
g_gs_device->AgePool();
g_perfmon.EndFrame(idle_frame);
if ((g_perfmon.GetFrame() & 0x1f) == 0)
g_perfmon.Update();
// Little bit ugly, but we can't do CAS inside the render pass.
GSVector4i src_rect;
GSVector4 src_uv, draw_rect;
GSTexture* current = g_gs_device->GetCurrent();
if (current && !blank_frame)
else
{
src_rect = CalculateDrawSrcRect(current, m_real_size);
src_uv = GSVector4(src_rect) / GSVector4(current->GetSize()).xyxy();
draw_rect = CalculateDrawDstRect(g_gs_device->GetWindowWidth(), g_gs_device->GetWindowHeight(),
src_rect, current->GetSize(), s_display_alignment, g_gs_device->UsesLowerLeftOrigin(),
GetVideoMode() == GSVideoMode::SDTV_480P);
s_last_draw_rect = draw_rect;
if (!idle_frame)
g_gs_device->AgePool();
if (GSConfig.CASMode != GSCASMode::Disabled)
{
static bool cas_log_once = false;
if (g_gs_device->Features().cas_sharpening)
{
// sharpen only if the IR is higher than the display resolution
const bool sharpen_only = (GSConfig.CASMode == GSCASMode::SharpenOnly ||
(current->GetWidth() > g_gs_device->GetWindowWidth() &&
current->GetHeight() > g_gs_device->GetWindowHeight()));
g_gs_device->CAS(current, src_rect, src_uv, draw_rect, sharpen_only);
}
else if (!cas_log_once)
{
Host::AddIconOSDMessage("CASUnsupported", ICON_FA_EXCLAMATION_TRIANGLE,
TRANSLATE_SV("GS",
"CAS is not available, your graphics driver does not support the required functionality."),
10.0f);
cas_log_once = true;
}
}
}
g_perfmon.EndFrame(idle_frame);
if (BeginPresentFrame(false))
{
if ((g_perfmon.GetFrame() & 0x1f) == 0)
g_perfmon.Update();
// Little bit ugly, but we can't do CAS inside the render pass.
GSVector4i src_rect;
GSVector4 src_uv, draw_rect;
GSTexture* current = g_gs_device->GetCurrent();
if (current && !blank_frame)
{
const u64 current_time = Common::Timer::GetCurrentValue();
const float shader_time = static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - m_shader_time_start));
src_rect = CalculateDrawSrcRect(current, m_real_size);
src_uv = GSVector4(src_rect) / GSVector4(current->GetSize()).xyxy();
draw_rect = CalculateDrawDstRect(g_gs_device->GetWindowWidth(), g_gs_device->GetWindowHeight(),
src_rect, current->GetSize(), s_display_alignment, g_gs_device->UsesLowerLeftOrigin(),
GetVideoMode() == GSVideoMode::SDTV_480P);
s_last_draw_rect = draw_rect;
g_gs_device->PresentRect(current, src_uv, nullptr, draw_rect,
s_tv_shader_indices[GSConfig.TVShader], shader_time, GSConfig.LinearPresent != GSPostBilinearMode::Off);
if (GSConfig.CASMode != GSCASMode::Disabled)
{
static bool cas_log_once = false;
if (g_gs_device->Features().cas_sharpening)
{
// sharpen only if the IR is higher than the display resolution
const bool sharpen_only = (GSConfig.CASMode == GSCASMode::SharpenOnly ||
(current->GetWidth() > g_gs_device->GetWindowWidth() &&
current->GetHeight() > g_gs_device->GetWindowHeight()));
g_gs_device->CAS(current, src_rect, src_uv, draw_rect, sharpen_only);
}
else if (!cas_log_once)
{
Host::AddIconOSDMessage("CASUnsupported", ICON_FA_EXCLAMATION_TRIANGLE,
TRANSLATE_SV("GS", "CAS is not available, your graphics driver does not support the required functionality."),
10.0f);
cas_log_once = true;
}
}
}
EndPresentFrame();
if (BeginPresentFrame(false))
{
if (current && !blank_frame)
{
const u64 current_time = Common::Timer::GetCurrentValue();
const float shader_time = static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - m_shader_time_start));
if (GSConfig.OsdShowGPU)
PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime());
g_gs_device->PresentRect(current, src_uv, nullptr, draw_rect,
s_tv_shader_indices[GSConfig.TVShader], shader_time, GSConfig.LinearPresent != GSPostBilinearMode::Off);
}
EndPresentFrame();
if (GSConfig.OsdShowGPU)
PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime());
}
PerformanceMetrics::Update(registers_written, fb_sprite_frame, false);
}
PerformanceMetrics::Update(registers_written, fb_sprite_frame, false);
// snapshot
if (!m_snapshot.empty())
{

View File

@@ -1417,14 +1417,14 @@ void GSDevice11::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offs
{
float scale;
float pad1[3];
u32 SBW, DBW, pad3;
u32 SBW, DBW, SPSM;
};
const Uniforms cb = {sScale, {}, SBW, DBW};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM};
m_ctx->UpdateSubresource(m_merge.cb.get(), 0, nullptr, &cb, 0, 0);
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
const ShaderConvert shader = ShaderConvert::RGBA_TO_8I;
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
StretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
}

View File

@@ -703,11 +703,17 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
return false;
if (!CreateNullTexture())
{
Host::ReportErrorAsync("GS", "Failed to create dummy texture");
return false;
}
{
std::optional<std::string> shader = ReadShaderSource("shaders/dx11/tfx.fx");
if (!shader.has_value())
{
Host::ReportErrorAsync("GS", "Failed to read shaders/dx11/tfx.fxf.");
Host::ReportErrorAsync("GS", "Failed to read shaders/dx11/tfx.fx.");
return false;
}
@@ -717,12 +723,6 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
if (!m_shader_cache.Open(m_feature_level, GSConfig.UseDebugDevice))
Console.Warning("D3D12: Shader cache failed to open.");
if (!CreateNullTexture())
{
Host::ReportErrorAsync("GS", "Failed to create dummy texture");
return false;
}
if (!CreateRootSignatures())
{
Host::ReportErrorAsync("GS", "Failed to create pipeline layouts");
@@ -1486,15 +1486,15 @@ void GSDevice12::ConvertToIndexedTexture(
{
float scale;
float pad1[3];
u32 SBW, DBW, pad2;
u32 SBW, DBW, SPSM;
};
const Uniforms cb = {sScale, {}, SBW, DBW};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM};
SetUtilityRootSignature();
SetUtilityPushConstants(&cb, sizeof(cb));
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
const ShaderConvert shader = ShaderConvert::RGBA_TO_8I;
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
DoStretchRect(static_cast<GSTexture12*>(sTex), GSVector4::zero(), static_cast<GSTexture12*>(dTex), dRect,
m_convert[static_cast<int>(shader)].get(), false, true);
}

View File

@@ -184,40 +184,6 @@ bool GSHwHack::GSC_NamcoGames(GSRendererHW& r, int& skip)
{
if (skip == 0)
{
if (r.IsPossibleChannelShuffle() && !(RTBP0 & 31))
{
GSVertex* v = &r.m_vertex.buff[0];
// Make sure we're detecting the right effect.
if (((v[1].XYZ.X - v[0].XYZ.X) >> 4) != 8 || ((v[1].XYZ.Y - v[0].XYZ.Y) >> 4) != 14)
return false;
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget);
if (!rt)
return false;
GL_INS("GSC_NamcoGames(): HLE channel shuffle");
// have to set up the palette ourselves too, since GSC executes before it does
r.m_mem.m_clut.Read32(RTEX0, r.m_draw_env->TEXA);
std::shared_ptr<GSTextureCache::Palette> palette =
g_texture_cache->LookupPaletteObject(r.m_mem.m_clut, GSLocalMemory::m_psm[RTEX0.PSM].pal, true);
if (!palette)
return false;
GSHWDrawConfig& conf = r.BeginHLEHardwareDraw(
rt->GetTexture(), nullptr, rt->GetScale(), rt->GetTexture(), rt->GetScale(), rt->GetUnscaledRect());
conf.pal = palette->GetPaletteGSTexture();
conf.ps.channel = ChannelFetch_RGB;
conf.colormask.wa = false;
r.EndHLEHardwareDraw(false);
// 12 pages: 2 calls by channel, 3 channels, 1 blit
skip = 12 * (3 + 3 + 1);
return true;
}
if (!s_nativeres && r.PRIM->PRIM == GS_SPRITE && RTME && RTEX0.TFX == 1 && RFPSM == RTPSM && RTPSM == PSMCT32 && RFBMSK == 0xFF000000 && r.m_index.tail > 2)
{
GSVertex* v = &r.m_vertex.buff[0];
@@ -431,31 +397,6 @@ bool GSHwHack::GSC_TalesOfLegendia(GSRendererHW& r, int& skip)
return true;
}
bool GSHwHack::GSC_Kunoichi(GSRendererHW& r, int& skip)
{
if (skip == 0)
{
if (!RTME && (RFBP == 0x0 || RFBP == 0x00700 || RFBP == 0x00800) && RFPSM == PSMCT32 && RFBMSK == 0x00FFFFFF)
{
// Removes depth effects(shadows) not rendered correctly on all renders.
skip = 3;
}
if (RTME && (RFBP == 0x0700 || RFBP == 0) && RTBP0 == 0x0e00 && RTPSM == 0 && RFBMSK == 0)
{
skip = 1; // Removes black screen (not needed anymore maybe)?
}
}
else
{
if (RTME && (RFBP == 0x0e00) && RFPSM == PSMCT32 && RFBMSK == 0xFF000000)
{
skip = 0;
}
}
return true;
}
bool GSHwHack::GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, int& skip)
{
if (skip == 0)
@@ -505,27 +446,6 @@ bool GSHwHack::GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, int& skip)
return true;
}
bool GSHwHack::GSC_SakuraWarsSoLongMyLove(GSRendererHW& r, int& skip)
{
if (skip == 0)
{
if (RTME == 0 && RFBP != RTBP0 && RTBP0 && RFBMSK == 0x00FFFFFF)
{
skip = 3; // Remove darkness
}
else if (RTME == 0 && RFBP == RTBP0 && (RTBP0 == 0x1200 || RTBP0 == 0x1180 || RTBP0 == 0) && RFBMSK == 0x00FFFFFF)
{
skip = 3; // Remove darkness
}
else if (RTME && (RFBP == 0 || RFBP == 0x1180) && RFPSM == PSMCT32 && RTBP0 == 0x3F3F && RTPSM == PSMT8)
{
skip = 1; // Floodlight
}
}
return true;
}
bool GSHwHack::GSC_UltramanFightingEvolution(GSRendererHW& r, int& skip)
{
if (skip == 0)
@@ -557,26 +477,6 @@ bool GSHwHack::GSC_TalesofSymphonia(GSRendererHW& r, int& skip)
return true;
}
bool GSHwHack::GSC_Simple2000Vol114(GSRendererHW& r, int& skip)
{
if (skip == 0)
{
if (!s_nativeres && RTME == 0 && (RFBP == 0x1500) && (RTBP0 == 0x2c97 || RTBP0 == 0x2ace || RTBP0 == 0x03d0 || RTBP0 == 0x2448) && (RFBMSK == 0x0000))
{
// Don't enable hack on native res if crc is below aggressive.
// Upscaling issues, removes glow/blur effect which fixes ghosting.
skip = 1;
}
if (RTME && (RFBP == 0x0e00) && (RTBP0 == 0x1000) && (RFBMSK == 0x0000))
{
// Depth shadows.
skip = 1;
}
}
return true;
}
bool GSHwHack::GSC_UrbanReign(GSRendererHW& r, int& skip)
{
if (skip == 0)
@@ -922,23 +822,6 @@ bool GSHwHack::GSC_MetalGearSolid3(GSRendererHW& r, int& skip)
return true;
}
bool GSHwHack::GSC_HitmanBloodMoney(GSRendererHW& r, int& skip)
{
// The game does a stupid thing where it backs up the last 2 pages of the framebuffer with shuffles, uploads a CT32 texture to it
// then copies the RGB back (keeping the new alpha only). It's pretty gross, I have no idea why they didn't just upload a new alpha.
// This is a real pain to emulate with the current state of things, so let's just clear the dirty area from the upload and pretend it wasn't there.
// Catch the first draw of the copy back.
if (RFBP > 0 && RTPSM == PSMT8H && RFPSM == PSMCT32)
{
GSTextureCache::Target* target = g_texture_cache->FindOverlappingTarget(RFBP, RFBP + 1);
if (target)
target->m_dirty.clear();
}
return false;
}
bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const u32 n_vertices = r.m_vertex.next;
@@ -1413,13 +1296,10 @@ bool GSHwHack::MV_Ico(GSRendererHW& r)
#define CRC_F(name) { #name, &GSHwHack::name }
const GSHwHack::Entry<GSRendererHW::GSC_Ptr> GSHwHack::s_get_skip_count_functions[] = {
CRC_F(GSC_Kunoichi),
CRC_F(GSC_Manhunt2),
CRC_F(GSC_MidnightClub3),
CRC_F(GSC_SacredBlaze),
CRC_F(GSC_GuitarHero),
CRC_F(GSC_SakuraWarsSoLongMyLove),
CRC_F(GSC_Simple2000Vol114),
CRC_F(GSC_SFEX3),
CRC_F(GSC_DTGames),
CRC_F(GSC_TalesOfLegendia),
@@ -1431,7 +1311,6 @@ const GSHwHack::Entry<GSRendererHW::GSC_Ptr> GSHwHack::s_get_skip_count_function
CRC_F(GSC_NFSUndercover),
CRC_F(GSC_PolyphonyDigitalGames),
CRC_F(GSC_MetalGearSolid3),
CRC_F(GSC_HitmanBloodMoney),
CRC_F(GSC_Battlefield2),
// Channel Effect

View File

@@ -16,12 +16,9 @@ public:
static bool GSC_BlackAndBurnoutSky(GSRendererHW& r, int& skip);
static bool GSC_MidnightClub3(GSRendererHW& r, int& skip);
static bool GSC_TalesOfLegendia(GSRendererHW& r, int& skip);
static bool GSC_Kunoichi(GSRendererHW& r, int& skip);
static bool GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, int& skip);
static bool GSC_SakuraWarsSoLongMyLove(GSRendererHW& r, int& skip);
static bool GSC_UltramanFightingEvolution(GSRendererHW& r, int& skip);
static bool GSC_TalesofSymphonia(GSRendererHW& r, int& skip);
static bool GSC_Simple2000Vol114(GSRendererHW& r, int& skip);
static bool GSC_UrbanReign(GSRendererHW& r, int& skip);
static bool GSC_SteambotChronicles(GSRendererHW& r, int& skip);
static bool GSC_BlueTongueGames(GSRendererHW& r, int& skip);
@@ -29,7 +26,6 @@ public:
static bool GSC_Battlefield2(GSRendererHW& r, int& skip);
static bool GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip);
static bool GSC_MetalGearSolid3(GSRendererHW& r, int& skip);
static bool GSC_HitmanBloodMoney(GSRendererHW& r, int& skip);
static bool OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool OI_DBZBTGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);

View File

@@ -1599,6 +1599,13 @@ void GSRendererHW::FinishSplitClear()
m_split_clear_color = 0;
}
bool GSRendererHW::NeedsBlending()
{
const u32 temp_fbmask = (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) ? 0x00F8F8F8 : 0x00FFFFFF;
const bool FBMASK_skip = (m_cached_ctx.FRAME.FBMSK & temp_fbmask) == temp_fbmask;
return PRIM->ABE && !FBMASK_skip;
}
bool GSRendererHW::IsRTWritten()
{
const GIFRegTEST TEST = m_cached_ctx.TEST;
@@ -1637,12 +1644,12 @@ bool GSRendererHW::IsUsingCsInBlend()
{
const GIFRegALPHA ALPHA = m_context->ALPHA;
const bool blend_zero = (ALPHA.A == ALPHA.B || (ALPHA.C == 2 && ALPHA.FIX == 0) || (ALPHA.C == 0 && GetAlphaMinMax().max == 0));
return (PRIM->ABE && ((ALPHA.IsUsingCs() && !blend_zero) || m_context->ALPHA.D == 0));
return (NeedsBlending() && ((ALPHA.IsUsingCs() && !blend_zero) || m_context->ALPHA.D == 0));
}
bool GSRendererHW::IsUsingAsInBlend()
{
return (PRIM->ABE && m_context->ALPHA.IsUsingAs() && GetAlphaMinMax().max != 0);
return (NeedsBlending() && m_context->ALPHA.IsUsingAs() && GetAlphaMinMax().max != 0);
}
bool GSRendererHW::ChannelsSharedTEX0FRAME()
{
@@ -1906,7 +1913,7 @@ void GSRendererHW::SwSpriteRender()
const GSOffset spo = m_mem.GetOffset(m_context->TEX0.TBP0, m_context->TEX0.TBW, m_context->TEX0.PSM);
const GSOffset& dpo = m_context->offset.fb;
const bool alpha_blending_enabled = PRIM->ABE;
const bool alpha_blending_enabled = NeedsBlending();
const GSVertex& v = m_index.tail > 0 ? m_vertex.buff[m_index.buff[m_index.tail - 1]] : GSVertex(); // Last vertex if any.
const GSVector4i vc = GSVector4i(v.RGBAQ.R, v.RGBAQ.G, v.RGBAQ.B, v.RGBAQ.A) // 0x000000AA000000BB000000GG000000RR
@@ -2493,7 +2500,7 @@ void GSRendererHW::Draw()
return;
}
m_process_texture = PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_cached_ctx.TEX0.TCC) && !(no_rt && (!m_cached_ctx.TEST.ATE || m_cached_ctx.TEST.ATST <= ATST_ALWAYS));
m_process_texture = PRIM->TME && !(NeedsBlending() && m_context->ALPHA.IsBlack() && !m_cached_ctx.TEX0.TCC) && !(no_rt && (!m_cached_ctx.TEST.ATE || m_cached_ctx.TEST.ATST <= ATST_ALWAYS));
// We trigger the sw prim render here super early, to avoid creating superfluous render targets.
if (CanUseSwPrimRender(no_rt, no_ds, draw_sprite_tex && m_process_texture) && SwPrimRender(*this, true, true))
@@ -2855,7 +2862,9 @@ void GSRendererHW::Draw()
GIFRegTEX0 FRAME_TEX0;
bool shuffle_target = false;
if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 &&
const u32 page_alignment = GSLocalMemory::IsPageAlignedMasked(m_cached_ctx.TEX0.PSM, m_r);
const bool page_aligned = (page_alignment & 0xF0F0) != 0; // Make sure Y is page aligned.
if (!no_rt && page_aligned && m_cached_ctx.ZBUF.ZMSK && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 &&
(m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true) && m_index.tail > 6)))
{
// Tail check is to make sure we have enough strips to go all the way across the page, or if it's using a region clamp could be used to draw strips.
@@ -2927,12 +2936,12 @@ void GSRendererHW::Draw()
}
possible_shuffle = !no_rt && (((shuffle_target /*&& GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16*/) /*|| (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && ((m_cached_ctx.TEX0.PSM & 0x6) || m_cached_ctx.FRAME.PSM != m_cached_ctx.TEX0.PSM))*/) || IsPossibleChannelShuffle());
const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((PRIM->ABE && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_draw_env->TEXA.AEM;
const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((NeedsBlending() && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_draw_env->TEXA.AEM;
const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask();
const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000));
const bool texture_function_alpha = m_cached_ctx.TEX0.TFX != TFX_MODULATE || (color_mask & 0xF000);
const bool req_color = (texture_function_color && (!PRIM->ABE || (PRIM->ABE && IsUsingCsInBlend())) && (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color;
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((PRIM->ABE && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_color = (texture_function_color && (!PRIM->ABE || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp < 16 || (NeedsBlending() && IsUsingCsInBlend())) && (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color;
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((NeedsBlending() && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_alpha = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used;
// TODO: Be able to send an alpha of 1.0 (blended with vertex alpha maybe?) so we can avoid sending the texture, since we don't always need it.
@@ -3014,7 +3023,7 @@ void GSRendererHW::Draw()
// Urban Reign trolls by scissoring a draw to a target at 0x0-0x117F to 378x449 which ends up the size being rounded up to 640x480
// causing the buffer to expand to around 0x1400, which makes a later framebuffer at 0x1180 to fail to be created correctly.
// We can cheese this by checking if the Z is masked and the resultant colour is going to be black anyway.
const bool output_black = PRIM->ABE && ((m_context->ALPHA.A == 1 && m_context->ALPHA.D > 1) || (m_context->ALPHA.IsBlack() && m_context->ALPHA.D != 1)) && m_draw_env->COLCLAMP.CLAMP == 1;
const bool output_black = NeedsBlending() && ((m_context->ALPHA.A == 1 && m_context->ALPHA.D > 1) || (m_context->ALPHA.IsBlack() && m_context->ALPHA.D != 1)) && m_draw_env->COLCLAMP.CLAMP == 1;
const bool can_expand = !(m_cached_ctx.ZBUF.ZMSK && output_black);
// Estimate size based on the scissor rectangle and height cache.
@@ -3036,19 +3045,19 @@ void GSRendererHW::Draw()
if (scale_draw == 1)
{
target_scale = 1.0f;
m_downscale_source = src->m_from_target->GetScale() > 1.0f;
m_downscale_source = src->m_from_target ? src->m_from_target->GetScale() > 1.0f : false;
}
else
m_downscale_source = GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa.
m_downscale_source = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive || !src->m_from_target) ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa.
}
else
{
// if it's directly copying keep the scale - Ratchet and clank hits this, stops edge garbage happening.
// Keep it to small targets of 256 or lower.
if (scale_draw == -1 && src && src->m_from_target && src->m_from_target->m_downscaled && static_cast<int>(m_cached_ctx.FRAME.FBW * 64) <= (PCRTCDisplays.GetResolution().x >> 1) &&
(GSVector4i(m_vt.m_min.p).xyxy() == GSVector4i(m_vt.m_min.t).xyxy()).alltrue() && (GSVector4i(m_vt.m_max.p).xyxy() == GSVector4i(m_vt.m_max.t).xyxy()).alltrue())
if (scale_draw == -1 && src && (!src->m_from_target || (src->m_from_target && src->m_from_target->m_downscaled)) && ((static_cast<int>(m_cached_ctx.FRAME.FBW * 64) <= (PCRTCDisplays.GetResolution().x >> 1) &&
(GSVector4i(m_vt.m_min.p).xyxy() == GSVector4i(m_vt.m_min.t).xyxy()).alltrue() && (GSVector4i(m_vt.m_max.p).xyxy() == GSVector4i(m_vt.m_max.t).xyxy()).alltrue()) || possible_shuffle))
{
target_scale = src->m_from_target->GetScale();
target_scale = src->m_from_target ? src->m_from_target->GetScale() : 1.0f;
scale_draw = 1;
scaled_copy = true;
}
@@ -3064,7 +3073,7 @@ void GSRendererHW::Draw()
// This upscaling hack is for games which construct P8 textures by drawing a bunch of small sprites in C32,
// then reinterpreting it as P8. We need to keep the off-screen intermediate textures at native resolution,
// but not propagate that through to the normal render targets. Test Case: Crash Wrath of Cortex.
if (no_ds && src && !m_channel_shuffle && src->m_from_target && (GSConfig.UserHacks_NativePaletteDraw || (src->m_from_target->m_downscaled && scale_draw <= 1)) &&
if (no_ds && src && !m_channel_shuffle && src->m_from_target && (GSConfig.UserHacks_NativePaletteDraw || (src->m_target_direct && src->m_from_target->m_downscaled && scale_draw <= 1)) &&
src->m_scale == 1.0f && (src->m_TEX0.PSM == PSMT8 || src->m_TEX0.TBP0 == m_cached_ctx.FRAME.Block()))
{
GL_CACHE("HW: Using native resolution for target based on texture source");
@@ -3157,6 +3166,144 @@ void GSRendererHW::Draw()
}
}
}
if (no_rt && ds && ds->m_TEX0.TBP0 != m_cached_ctx.ZBUF.Block())
{
const GSLocalMemory::psm_t& zbuf_psm = GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM];
int vertical_offset = ((static_cast<int>(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(static_cast<int>(ds->m_TEX0.TBW), 1)) * zbuf_psm.pgs.y; // I know I could just not shift it..
int texture_offset = 0;
int horizontal_offset = ((static_cast<int>((m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)) / 32) % static_cast<int>(std::max(ds->m_TEX0.TBW, 1U))) * zbuf_psm.pgs.x;
// Used to reduce the offset made later in channel shuffles
m_target_offset = std::abs(static_cast<int>((m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)) >> 5);
if (vertical_offset < 0)
{
ds->m_TEX0.TBP0 = m_cached_ctx.ZBUF.Block();
GSVector2i new_size = ds->m_unscaled_size;
// Make sure to use the original format for the offset.
const int new_offset = std::abs((vertical_offset / zbuf_psm.pgs.y) * GSLocalMemory::m_psm[ds->m_TEX0.PSM].pgs.y);
texture_offset = new_offset;
new_size.y += new_offset;
const GSVector4i new_drect = GSVector4i(0, new_offset * ds->m_scale, new_size.x * ds->m_scale, new_size.y * ds->m_scale);
ds->ResizeTexture(new_size.x, new_size.y, true, true, new_drect);
if (src && src->m_from_target && src->m_from_target == ds && src->m_target_direct)
{
src->m_texture = ds->m_texture;
// If we've moved it and the source is expecting to be inside this target, we need to update the region to point to it.
int max_region_y = src->m_region.GetMaxY() + new_offset;
if (max_region_y == new_offset)
max_region_y = new_size.y;
src->m_region.SetY(src->m_region.GetMinY() + new_offset, max_region_y);
}
ds->m_valid.y += new_offset;
ds->m_valid.w += new_offset;
ds->m_drawn_since_read.y += new_offset;
ds->m_drawn_since_read.w += new_offset;
g_texture_cache->CombineAlignedInsideTargets(ds, src);
if (ds->m_dirty.size())
{
for (int i = 0; i < static_cast<int>(ds->m_dirty.size()); i++)
{
ds->m_dirty[i].r.y += new_offset;
ds->m_dirty[i].r.w += new_offset;
}
}
t_size.y += std::abs(vertical_offset);
vertical_offset = 0;
}
if (horizontal_offset < 0)
{
// Thankfully this doesn't really happen, but catwoman moves the framebuffer backwards 1 page with a channel shuffle, which is really messy and not easy to deal with.
// Hopefully the quick channel shuffle will just guess this and run with it.
ds->m_TEX0.TBP0 += horizontal_offset;
horizontal_offset = 0;
}
if (vertical_offset || horizontal_offset)
{
GSVertex* v = &m_vertex.buff[0];
for (u32 i = 0; i < m_vertex.tail; i++)
{
v[i].XYZ.X += horizontal_offset << 4;
v[i].XYZ.Y += vertical_offset << 4;
}
if (texture_offset && src && src->m_from_target && src->m_target_direct && src->m_from_target == ds)
{
GSVector4i src_region = src->GetRegionRect();
if (src_region.rempty())
{
src_region = GSVector4i::loadh(ds->m_unscaled_size);
src_region.y += texture_offset;
}
else
{
src_region.y += texture_offset;
src_region.w += texture_offset;
}
src->m_region.SetX(src_region.x, src_region.z);
src->m_region.SetY(src_region.y, src_region.w);
}
m_context->scissor.in.x += horizontal_offset;
m_context->scissor.in.z += horizontal_offset;
m_context->scissor.in.y += vertical_offset;
m_context->scissor.in.w += vertical_offset;
m_r.y += vertical_offset;
m_r.w += vertical_offset;
m_r.x += horizontal_offset;
m_r.z += horizontal_offset;
m_in_target_draw = ds->m_TEX0.TBP0 != m_cached_ctx.ZBUF.Block();
m_vt.m_min.p.x += horizontal_offset;
m_vt.m_max.p.x += horizontal_offset;
m_vt.m_min.p.y += vertical_offset;
m_vt.m_max.p.y += vertical_offset;
t_size.y = ds->m_unscaled_size.y - vertical_offset;
t_size.x = ds->m_unscaled_size.x - horizontal_offset;
}
// Don't resize if the BPP don't match.
GSVector2i new_size = GetValidSize(src, possible_shuffle);
if (new_size.x > ds->m_unscaled_size.x || new_size.y > ds->m_unscaled_size.y)
{
const u32 new_width = std::max(new_size.x, ds->m_unscaled_size.x);
const u32 new_height = std::max(new_size.y, ds->m_unscaled_size.y);
//DevCon.Warning("HW: Resizing texture %d x %d draw %d", ds->m_unscaled_size.x, new_height, s_n);
ds->ResizeTexture(new_width, new_height);
}
else if ((IsPageCopy() || is_possible_mem_clear) && m_r.width() <= zbuf_psm.pgs.x && m_r.height() <= zbuf_psm.pgs.y)
{
const int get_next_ctx = m_env.PRIM.CTXT;
const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx];
GSVector4i update_valid = GSVector4i::loadh(GSVector2i(horizontal_offset + GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y + vertical_offset));
ds->UpdateValidity(update_valid, true);
if (is_possible_mem_clear)
{
if ((horizontal_offset + GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.x) >= static_cast<int>(ds->m_TEX0.TBW * 64) && next_ctx.ZBUF.Block() == (m_cached_ctx.ZBUF.Block() + 0x20))
{
update_valid.x = 0;
update_valid.z = GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.x;
update_valid.y += GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y;
update_valid.w += GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y;
ds->UpdateValidity(update_valid, true);
}
}
}
}
}
if (!no_rt)
@@ -3258,7 +3405,7 @@ void GSRendererHW::Draw()
return;
}
rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src, possible_shuffle), (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale,
rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src, possible_shuffle), (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? ((src && src->m_from_target) ? src->m_from_target->GetScale() : (ds ? ds->m_scale : 1.0f)) : target_scale,
GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_color || possible_shuffle, lookup_rect, src);
if (!rt) [[unlikely]]
@@ -3887,11 +4034,13 @@ void GSRendererHW::Draw()
m_index.tail = 2;
}
}
const bool blending_cd = PRIM->ABE && !m_context->ALPHA.IsOpaque();
const bool blending_cd = NeedsBlending() && !m_context->ALPHA.IsOpaque();
bool valid_width_change = false;
if (rt && ((!is_possible_mem_clear || blending_cd) || rt->m_TEX0.PSM != FRAME_TEX0.PSM) && !m_in_target_draw)
{
valid_width_change = rt->m_TEX0.TBW != FRAME_TEX0.TBW;
const u32 frame_mask = (m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk);
valid_width_change = rt->m_TEX0.TBW != FRAME_TEX0.TBW && (frame_mask != (frame_psm.fmsk & 0x00FFFFFF) || rt->m_valid_rgb == false);
if (valid_width_change && !m_cached_ctx.ZBUF.ZMSK && (m_cached_ctx.FRAME.FBMSK & 0xFF000000))
{
// Alpha could be a font, and since the width is changing it's no longer valid.
@@ -3941,7 +4090,7 @@ void GSRendererHW::Draw()
// The FBW should also be okay, since it's coming from the source.
if (rt)
{
const bool update_fbw = (FRAME_TEX0.TBW != rt->m_TEX0.TBW || rt->m_TEX0.TBW == 1) && !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack());
const bool update_fbw = (FRAME_TEX0.TBW != rt->m_TEX0.TBW || rt->m_TEX0.TBW == 1) && !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!NeedsBlending() || IsOpaque() || m_context->ALPHA.IsBlack());
rt->m_TEX0.TBW = update_fbw ? ((src && src->m_from_target && src->m_32_bits_fmt) ? src->m_from_target->m_TEX0.TBW : FRAME_TEX0.TBW) : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW);
rt->m_TEX0.PSM = FRAME_TEX0.PSM;
}
@@ -4030,6 +4179,10 @@ void GSRendererHW::Draw()
const int new_w = std::min(2048, std::max(new_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds_size.x : 0)));
const int new_h = std::min(2048, std::max(new_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds_size.y : 0)));
const bool full_cover_clear = is_possible_mem_clear && GSLocalMemory::IsPageAligned(m_cached_ctx.FRAME.PSM, m_r) && m_r.x == 0 && m_r.y == 0 && !preserve_rt_rgb &&
!IsPageCopy() && m_r.width() == (m_cached_ctx.FRAME.FBW * 64);
if (rt)
{
const u32 old_end_block = rt->m_end_block;
@@ -4076,16 +4229,21 @@ void GSRendererHW::Draw()
g_texture_cache->AddDirtyRectTarget(rt, GSVector4i(rt->m_valid.x, rt->m_valid.w, rt->m_valid.z, new_h), rt->m_TEX0.PSM, rt->m_TEX0.TBW, mask, false);
g_texture_cache->GetTargetSize(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, 0, new_h);
}
rt->ResizeValidity(rt->m_valid.rintersect(rt->GetUnscaledRect()));
rt->ResizeDrawn(rt->m_drawn_since_read.rintersect(rt->GetUnscaledRect()));
const bool rt_cover = full_cover_clear && (m_r.height() + frame_psm.pgs.y) >= rt->m_valid.height();
rt->ResizeValidity(rt_cover ? update_rect : rt->m_valid.rintersect(rt->GetUnscaledRect()));
rt->ResizeDrawn(rt_cover ? update_rect : rt->m_drawn_since_read.rintersect(rt->GetUnscaledRect()));
}
const bool rt_update = can_update_size || (m_texture_shuffle && (src && rt && src->m_from_target != rt));
// If it's updating from a texture shuffle, limit the size to the source size.
if (rt_update && !can_update_size && src->m_from_target)
update_rect = update_rect.rintersect(src->m_from_target->m_valid);
if (rt_update && !can_update_size)
{
if (src->m_from_target)
update_rect = update_rect.rintersect(src->m_from_target->m_valid);
update_rect = update_rect.rintersect(GSVector4i::loadh(GSVector2i(new_w, new_h)));
}
// if frame is masked or afailing always to never write frame, wanna make sure we don't touch it. This might happen if DATE or Alpha Test is being used to write to Z.
const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY));
@@ -4151,16 +4309,18 @@ void GSRendererHW::Draw()
DevCon.Warning("HW: Temporary depth buffer creation failed.");
}
}
const bool z_masked = m_cached_ctx.ZBUF.ZMSK;
if (!m_texture_shuffle && !m_channel_shuffle)
{
ds->ResizeValidity(ds->GetUnscaledRect());
ds->ResizeDrawn(ds->GetUnscaledRect());
const bool z_cover = full_cover_clear && (m_r.height() + GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y) >= ds->m_valid.height();
ds->ResizeValidity(z_cover ? m_r : ds->GetUnscaledRect());
ds->ResizeDrawn(z_cover ? m_r : ds->GetUnscaledRect());
}
// Limit to 2x the vertical height of the resolution (for double buffering)
// Dark cloud writes to 424 when the buffer is only 416 high, but masks the Z.
// Updating the valid causes the Z to overlap the framebuffer, which is obviously incorrect.
const bool z_masked = m_cached_ctx.ZBUF.ZMSK;
const bool z_update = can_update_size && !z_masked;
if (rt && m_using_temp_z)
@@ -4170,6 +4330,7 @@ void GSRendererHW::Draw()
const int z_vertical_offset = ((static_cast<int>(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(static_cast<int>(ds->m_TEX0.TBW), 1)) * z_psm.pgs.y;
const GSVector4i ds_rect = m_r - GSVector4i(vertical_offset - z_vertical_offset);
ds->UpdateValidity(ds_rect, z_update && (can_update_size || (ds_rect.w <= (resolution.y * 2) && !m_texture_shuffle)));
ds->UpdateDrawn(ds_rect, z_update && (can_update_size || (ds_rect.w <= (resolution.y * 2) && !m_texture_shuffle)));
}
else
{
@@ -4456,6 +4617,7 @@ void GSRendererHW::Draw()
else if (was_written && g_texture_cache->GetTemporaryZ() != nullptr)
{
ds->UpdateValidity(real_rect, !z_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle)));
ds->UpdateDrawn(real_rect, !z_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle)));
GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo();
if (ds->m_TEX0.TBP0 == z_address_info.ZBP)
@@ -5197,7 +5359,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
m_r = GSVector4i(m_r.x & ~(frame_psm.pgs.x - 1), m_r.y & ~(frame_psm.pgs.y - 1), (m_r.z + (frame_psm.pgs.x - 1)) & ~(frame_psm.pgs.x - 1), (m_r.w + (frame_psm.pgs.y - 1)) & ~(frame_psm.pgs.y - 1));
// Hitman suffers from this, not sure on the exact scenario at the moment, but we need the barrier.
if (PRIM->ABE && m_context->ALPHA.IsCdInBlend())
if (NeedsBlending() && m_context->ALPHA.IsCdInBlend())
{
// Needed to enable IsFeedbackLoop.
m_conf.ps.channel_fb = 1;
@@ -5267,14 +5429,11 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
const bool AA1 = PRIM->AA1 && (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS);
// PABE: Check condition early as an optimization, no blending when As < 128.
// For Cs*As + Cd*(1 - As) if As is 128 then blending can be disabled as well.
const bool PABE_skip = PRIM->ABE && m_draw_env->PABE.PABE &&
const bool PABE_skip = m_draw_env->PABE.PABE &&
((GetAlphaMinMax().max < 128) || (GetAlphaMinMax().max == 128 && ALPHA.A == 0 && ALPHA.B == 1 && ALPHA.C == 0 && ALPHA.D == 1));
// FBMASK: Color is not written, no need to do blending.
const u32 temp_fbmask = m_conf.ps.dst_fmt == GSLocalMemory::PSM_FMT_16 ? 0x00F8F8F8 : 0x00FFFFFF;
const bool FBMASK_skip = (m_cached_ctx.FRAME.FBMSK & temp_fbmask) == temp_fbmask;
// No blending or coverage anti-aliasing so early exit
if (FBMASK_skip || PABE_skip || !(PRIM->ABE || AA1))
if (PABE_skip || !(NeedsBlending() || AA1))
{
m_conf.blend = {};
m_conf.ps.no_color1 = true;
@@ -6997,7 +7156,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
}
const int afail_type = m_cached_ctx.TEST.GetAFAIL(m_cached_ctx.FRAME.PSM);
if (m_cached_ctx.TEST.ATE && ((afail_type != AFAIL_FB_ONLY && afail_type != AFAIL_RGB_ONLY) || !PRIM->ABE || !IsUsingAsInBlend()))
if (m_cached_ctx.TEST.ATE && ((afail_type != AFAIL_FB_ONLY && afail_type != AFAIL_RGB_ONLY) || !NeedsBlending() || !IsUsingAsInBlend()))
{
const int aref = static_cast<int>(m_cached_ctx.TEST.AREF);
CorrectATEAlphaMinMax(m_cached_ctx.TEST.ATST, aref);
@@ -7934,7 +8093,7 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDrawAggressive()
if (m_cached_ctx.TEST.ATE)
return CLUTDrawTestResult::NotCLUTDraw;
if (PRIM->ABE)
if (NeedsBlending())
return CLUTDrawTestResult::NotCLUTDraw;
if (m_context->TEX1.MXL)
@@ -7997,13 +8156,13 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t
return false;
}
const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((PRIM->ABE && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_draw_env->TEXA.AEM;
const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((NeedsBlending() && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_draw_env->TEXA.AEM;
const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask();
const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000));
const bool texture_function_alpha = m_cached_ctx.TEX0.TFX != TFX_MODULATE || (color_mask & 0xF000);
const u32 fm_mask = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk;
const bool req_color = (texture_function_color && (!PRIM->ABE || (PRIM->ABE && IsUsingCsInBlend())) && (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF)) || need_aem_color;
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((PRIM->ABE && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000));
const bool req_color = (texture_function_color && (!PRIM->ABE || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp < 16 || (NeedsBlending() && IsUsingCsInBlend())) && (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF)) || need_aem_color;
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((NeedsBlending() && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000));
const bool req_alpha = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used;
if ((req_color && !src_target->m_valid_rgb) || (req_alpha && (!src_target->m_valid_alpha_low || !src_target->m_valid_alpha_high)))
@@ -8822,6 +8981,9 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual()
int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
{
if (GSConfig.UserHacks_NativeScaling == GSNativeScaling::Off)
return 0;
const GSVector2i draw_size = GSVector2i(m_vt.m_max.p.x - m_vt.m_min.p.x, m_vt.m_max.p.y - m_vt.m_min.p.y);
GSVector2i tex_size = GSVector2i(m_vt.m_max.t.x - m_vt.m_min.t.x, m_vt.m_max.t.y - m_vt.m_min.t.y);
@@ -8832,10 +8994,13 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
// Try to catch cases of stupid draws like Manhunt and Syphon Filter where they sample a single pixel.
// Also make sure it's grabbing most of the texture.
if (tex_size.x == 0 || tex_size.y == 0 || draw_size.x == 0 || draw_size.y == 0 || !is_target_src)
if (tex_size.x == 0 || tex_size.y == 0 || draw_size.x == 0 || draw_size.y == 0)
return 0;
if (is_target_src && src->m_from_target->m_downscaled && std::abs(draw_size.x - tex_size.x) <= 1 && std::abs(draw_size.y - tex_size.y) <= 1)
const bool no_resize = (std::abs(draw_size.x - tex_size.x) <= 1 && std::abs(draw_size.y - tex_size.y) <= 1);
const bool can_maintain = no_resize || (!is_target_src && m_index.tail == 2);
if (!src || ((!is_target_src || src->m_from_target->m_downscaled) && can_maintain))
return -1;
const GSDrawingContext& next_ctx = m_env.CTXT[m_env.PRIM.CTXT];
@@ -8848,8 +9013,9 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
const bool is_downscale = m_cached_ctx.TEX0.TBW >= m_cached_ctx.FRAME.FBW && draw_size.x <= (tex_size.x * 0.75f) && draw_size.y <= (tex_size.y * 0.75f);
// Check we're getting most of the texture and not just stenciling a part of it.
// Only allow non-bilineared downscales if it's most of the target (misdetections of shadows in Naruto, Transformers etc), otherwise it's fine.
const GSVector2i tex_size_half = GSVector2i((src->GetRegion().HasX() ? src->GetRegionSize().x : src->m_from_target->m_valid.width()) / 2, (src->GetRegion().HasY() ? src->GetRegionSize().y : src->m_from_target->m_valid.height()) / 2);
const bool possible_downscale = m_context->TEX1.MMAG == 1 || src->m_from_target->m_downscaled || tex_size.x >= tex_size_half.x || tex_size.y >= tex_size_half.y;
const GSVector4i src_valid = src->m_from_target ? src->m_from_target->m_valid : src->m_valid_rect;
const GSVector2i tex_size_half = GSVector2i((src->GetRegion().HasX() ? src->GetRegionSize().x : src_valid.width()) / 2, (src->GetRegion().HasY() ? src->GetRegionSize().y : src_valid.height()) / 2);
const bool possible_downscale = m_context->TEX1.MMIN == 1 || !src->m_from_target || src->m_from_target->m_downscaled || tex_size.x >= tex_size_half.x || tex_size.y >= tex_size_half.y;
if (is_downscale && (draw_size.x >= PCRTCDisplays.GetResolution().x || !possible_downscale))
return 0;
@@ -8867,6 +9033,39 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
return is_upscale ? 2 : 1;
}
// Last ditched check if it's doing a lot of small draws exactly the same which could be recursive lighting bloom.
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index.tail > 2 && !no_gaps_or_single_sprite && m_context->TEX1.MMAG == 1 && !m_context->ALPHA.IsOpaque())
{
GSVertex* v = &m_vertex.buff[0];
float tw = 1 << src->m_TEX0.TW;
float th = 1 << src->m_TEX0.TH;
const int first_u = (PRIM->FST) ? (v[1].U - v[0].U) >> 4 : std::floor(static_cast<int>(tw * v[1].ST.S) - static_cast<int>(tw * v[0].ST.S));
const int first_v = (PRIM->FST) ? (v[1].V - v[0].V) >> 4 : std::floor(static_cast<int>(th * v[1].ST.T) - static_cast<int>(th * v[0].ST.T));
const int first_x = (v[1].XYZ.X - v[0].XYZ.X) >> 4;
const int first_y = (v[1].XYZ.Y - v[0].XYZ.Y) >> 4;
if (first_x > first_u && first_y > first_v && !no_resize && std::abs(draw_size.x - first_x) <= 4 && std::abs(draw_size.y - first_y) <= 4)
{
for (u32 i = 2; i < m_index.tail; i += 2)
{
const int next_u = (PRIM->FST) ? (v[i + 1].U - v[i].U) >> 4 : std::floor(static_cast<int>(tw * v[i + 1].ST.S) - static_cast<int>(tw * v[i].ST.S));
const int next_v = (PRIM->FST) ? (v[i + 1].V - v[i].V) >> 4 : std::floor(static_cast<int>(th * v[i + 1].ST.T) - static_cast<int>(th * v[i].ST.T));
const int next_x = (v[i + 1].XYZ.X - v[i].XYZ.X) >> 4;
const int next_y = (v[i + 1].XYZ.Y - v[i].XYZ.Y) >> 4;
if (std::abs(draw_size.x - next_x) > 4 || std::abs(draw_size.y - next_y) > 4)
break;
if (next_u != first_u || next_v != first_v || next_x != first_x || next_y != first_y)
break;
if (i + 2 >= m_index.tail)
return 2;
}
}
}
return 0;
}

View File

@@ -129,6 +129,7 @@ private:
bool ContinueSplitClear();
void FinishSplitClear();
bool NeedsBlending();
bool IsRTWritten();
bool IsDepthAlwaysPassing();
bool IsUsingCsInBlend();

View File

@@ -228,8 +228,8 @@ bool GSTextureCache::CanTranslate(u32 bp, u32 bw, u32 spsm, GSVector4i r, u32 db
{
const GSVector2i src_page_size = GSLocalMemory::m_psm[spsm].pgs;
const GSVector2i dst_page_size = GSLocalMemory::m_psm[dpsm].pgs;
const bool bp_page_aligned_bp = ((bp & ~((1 << 5) - 1)) == bp) || bp == dbp;
const bool block_layout_match = GSLocalMemory::m_psm[spsm].bpp == GSLocalMemory::m_psm[dpsm].bpp;
const bool bp_page_aligned_bp = ((bp & ~((1 << 5) - 1)) == bp) || bp == dbp || (block_layout_match && bw == dbw);
const GSVector4i page_mask(GSVector4i((src_page_size.x - 1), (src_page_size.y - 1)).xyxy());
const GSVector4i masked_rect(r & ~page_mask);
const int src_pixel_width = static_cast<int>(bw * 64);
@@ -276,7 +276,7 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw
int page_offset = (static_cast<int>(sbp) - static_cast<int>(tbp)) >> 5;
int block_offset = (static_cast<int>(sbp) - static_cast<int>(tbp)) & 0x1F;
if (!(s_psm.bpp == t_psm.bpp))
if (!(s_psm.bpp == t_psm.bpp) || block_offset)
{
if (block_offset)
in_rect = in_rect.ralign<Align_Outside>(s_psm.bs);
@@ -299,6 +299,33 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw
DevCon.Warning("Error translating rect");
return GSVector4i::zero();
}
const bool block_matched_format = s_psm.bpp == t_psm.bpp && (sbw == tbw || sbw == 1);
// If there is block offset left over, try to adjust to that.
if (block_matched_format)
{
GSVector4i b2a_offset = GSVector4i::zero();
const GSVector4i target_rect = GSVector4i(0, 0, src_bw, 2048);
bool offset_found = false;
for (b2a_offset.x = target_rect.x; b2a_offset.x < target_rect.z; b2a_offset.x += s_psm.bs.x)
{
for (b2a_offset.y = target_rect.y; b2a_offset.y < target_rect.w; b2a_offset.y += s_psm.bs.y)
{
const u32 a_candidate_bp = s_psm.info.bn(b2a_offset.x, b2a_offset.y, tbp, sbw);
if (sbp == a_candidate_bp)
{
offset_found = true;
break;
}
}
if (offset_found)
break;
}
if (offset_found)
in_rect = (in_rect + b2a_offset.xyxy()).max_i32(GSVector4i(0));
}
}
GSVector4i new_rect = GSVector4i::zero();
@@ -397,7 +424,7 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw
}
}
}
else // Widths match
else if (!block_offset) // Widths match
{
const int horizontal_dst_page_offset = page_offset % clamped_tbw;
const int vertical_dst_page_offset = page_offset / clamped_tbw;
@@ -602,6 +629,8 @@ void GSTextureCache::DirtyRectByPage(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVe
{
RGBAMask rgba;
rgba._u32 = GSUtil::GetChannelMask(spsm);
// FIXME: This could be a problem if used when the valid area is smaller than dirty area and it needs the data during expansion on a later draw.
// This happens on Kamen Rider - Seigi no Keifu if the invalidatevideomem function was to use this function for depth clearing.
AddDirtyRectTarget(t, t->m_valid, t->m_TEX0.PSM, t->m_TEX0.TBW, rgba);
return;
}
@@ -618,7 +647,7 @@ void GSTextureCache::DirtyRectByPage(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVe
int block_offset = static_cast<int>(sbp) - static_cast<int>(target_bp);
// Different format needs to be page aligned, unless the block layout matches, then we can block align
// Might be able to translate the original rect.
if (!(src_info->bpp == dst_info->bpp))
if (!(src_info->bpp == dst_info->bpp) || block_offset)
{
const int src_bpp = src_info->bpp;
const bool column_align = !block_offset && src_r.z <= src_info->cs.x && src_r.w <= src_info->cs.y && src_info->depth == dst_info->depth;
@@ -741,7 +770,7 @@ void GSTextureCache::DirtyRectByPage(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVe
const int yblocks = in_rect.height() / src_info->bs.y;
// if !(block_offset & 0x7) is false, this is technically incorrect, but FFX hates it and starts making a mess, so it's better this way without adding complexity.
// TODO maybe: Add per block invalidation? ugh, would have to keep that to small blocks. 2 blocks in the case of FFX.
if ((xblocks <= 4 && yblocks <= 2) || req_depth_offset)
if ((xblocks <= (src_info->pgs.x / src_info->bs.x) && yblocks <= (src_info->pgs.y / src_info->bs.y)) || req_depth_offset)
{
GSVector4i b2a_offset = GSVector4i::zero();
const GSVector4i target_rect = GSVector4i(0, 0, src_width, 2048);
@@ -1165,8 +1194,8 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c
if (inside_target)
{
// Need to set it up as a region target.
src->m_region.SetX(block_boundary_rect.x, src->m_from_target->m_valid.z);
src->m_region.SetY(block_boundary_rect.y, src->m_from_target->m_valid.w);
src->m_region.SetX(block_boundary_rect.x, std::max(src->m_from_target->m_valid.z, block_boundary_rect.z));
src->m_region.SetY(block_boundary_rect.y, std::max(src->m_from_target->m_valid.w, block_boundary_rect.w));
}
if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0))
@@ -1279,11 +1308,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
// Make sure it is page aligned, otherwise things get messy with the pixel order (Tomb Raider Legend).
if (t->m_used)
{
// If the BP is block offset it's unlikely to be a target, but a target can be made by a HW move, so we need to check for a match.
// Good for Baldurs Gate Dark Alliance (HW Move), bad for Tomb Raider Legends (just offset).
if (((bp & (BLOCKS_PER_PAGE - 1)) != (t->m_TEX0.TBP0 & (BLOCKS_PER_PAGE - 1))) && (bp & (BLOCKS_PER_PAGE - 1)))
continue;
//const bool overlaps = t->Inside(bp, bw, psm, block_boundary_rect);
const bool overlaps = t->Overlaps(bp, bw, psm, block_boundary_rect);
// Try to make sure the target has available what we need, be careful of self referencing frames with font in the alpha.
@@ -1293,6 +1317,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
if (!overlaps || (found_t && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)))
continue;
// If the BP is offset in to a page and the format does not match, trying to match up the correct position is very difficult since we don't swizzle.
// Tomb Raider Legends does a block level BP in PSMT8 over a C16 target, which is just a nightmare to get right.
// Baldurs Gate used to have this too, but now we can translate HW moves inside targets when the format matches.
if (((bp & (BLOCKS_PER_PAGE - 1)) != (t->m_TEX0.TBP0 & (BLOCKS_PER_PAGE - 1))) && (bp & (BLOCKS_PER_PAGE - 1)) && !CanTranslate(bp, bw, psm, block_boundary_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW))
continue;
const bool width_match = (std::max(64U, bw * 64U) >> GSLocalMemory::m_psm[psm].info.pageShiftX()) ==
(std::max(64U, t->m_TEX0.TBW * 64U) >> GSLocalMemory::m_psm[t->m_TEX0.PSM].info.pageShiftX());
@@ -1596,14 +1626,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
{
// It is a complex to convert the code in shader. As a reference, let's do it on the CPU,
// it will be slow but can work even with upscaling, also fine tune it so it's not enabled when not needed.
if (psm == PSMT4 || (GSConfig.UserHacks_CPUFBConversion && psm == PSMT8 && (!possible_shuffle || GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != 32)) ||
(psm == PSMT8H && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16))
if (psm == PSMT4 || (psm == PSMT8H && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16))
{
// Forces 4-bit and 8-bit frame buffer conversion to be done on the CPU instead of the GPU, but performance will be slower.
// There is no dedicated shader to handle 4-bit conversion (Beyond Good and Evil and Stuntman).
// Enable readbacks on PSMT4 as we don't have a dedicated shader (Beyond Good and Evil and Stuntman).
// Enable readbacks on PSMT8H 16bit as we don't have a dedicated shader (History Channel - Battle for the Pacific, Sea World - Shamu's Big Adventure).
// Note: Stuntman no longer hits the PSMT4 code path.
// Direct3D10/11 and OpenGL support 8-bit fb conversion but don't render some corner cases properly (Harry Potter games).
// The hack can fix glitches in some games.
// Note2: Harry Potter is now properly handled with shader conversion and no need to enable frame buffer conversion.
if (!t->m_drawn_since_read.rempty())
{
t->UnscaleRTAlpha();
@@ -1617,8 +1645,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
{
const bool outside_target = !t->Overlaps(bp, bw, psm, r);
// We don't have a shader for this.
if (!possible_shuffle && TEX0.PSM == PSMT8 && ((GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != 32) || outside_target))
if (!possible_shuffle && TEX0.PSM == PSMT8 && outside_target)
{
continue;
}
@@ -1647,7 +1674,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
// Make sure the texture actually is INSIDE the RT, it's possibly not valid if it isn't.
// Also check BP >= TBP, create source isn't equpped to expand it backwards and all data comes from the target. (GH3)
else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets &&
(GSLocalMemory::m_psm[color_psm].bpp >= 16 || (/*possible_shuffle &&*/ GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) && // Channel shuffles or non indexed lookups.
(GSLocalMemory::m_psm[color_psm].bpp >= 16 || (/*possible_shuffle &&*/ GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp >= 16)) && // Channel shuffles or non indexed lookups.
t->m_age <= 1 && (!found_t || t->m_last_draw > dst->m_last_draw) /*&& CanTranslate(bp, bw, psm, block_boundary_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW)*/)
{
u32 rt_tbw = std::max(1U, t->m_TEX0.TBW);
@@ -1665,9 +1692,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
}
// Keep note that 2 bw is basically 1 normal page, as bw is in 64 pixels, and 8bit pages are 128 pixels wide, aka 2 bw.
// Also check for 4HH/HL and 8H which use the alpha channel, if the page order is wrong this can cause problems as well (Jak X font).
else if (!possible_shuffle && GSLocalMemory::m_psm[psm].trbpp <= 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 &&
(!(block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y && ((GSLocalMemory::m_psm[psm].bpp == 32) ? bw : ((bw + 1) / 2)) <= t->m_TEX0.TBW) &&
!(((GSLocalMemory::m_psm[psm].bpp == 32) ? bw : ((bw + 1) / 2)) == rt_tbw)))
else if (!possible_shuffle && GSLocalMemory::m_psm[psm].trbpp <= 8 && (GSUtil::GetChannelMask(t->m_TEX0.PSM) != 0xF ||
((GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != 16 || GSLocalMemory::m_psm[psm].bpp < 16) && (!(block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y &&
((GSLocalMemory::m_psm[psm].bpp == 32) ? bw : ((bw + 1) / 2)) <= t->m_TEX0.TBW) &&
!(((GSLocalMemory::m_psm[psm].bpp == 32) ? bw : ((bw + 1) / 2)) == rt_tbw)))))
{
DbgCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM);
continue;
@@ -1772,10 +1800,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, rt_tbw == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32))
continue;
// Be careful of shuffles where it can shuffle the width of the target, even though it may not have all been drawn to.
if (!possible_shuffle && !t->Inside(bp, bw, psm, block_boundary_rect))
continue;
x_offset = rect.x;
y_offset = rect.y;
dst = t;
@@ -1987,6 +2011,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
src = CreateMergedSource(TEX0, TEXA, region, dst->m_scale);
}
GSVector4i rect = r;
if (!src)
{
#ifdef ENABLE_OGL_DEBUG
@@ -2004,8 +2030,61 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
GL_CACHE("TC: src miss (0x%x, 0x%x, %s)", TEX0.TBP0, psm_s.pal > 0 ? TEX0.CBP : 0, psm_str(TEX0.PSM));
}
#endif
// This is for the condition where the target doesn't exist on a shuffle and it needs to load from memory.
// The Godfather clears the depth buffer with a normal clear, so our depth target gets deleted, then because it finds no target
// it assumes it really is 16bits, causing the texture to be full of garbage, and our shuffle handling becomes a mess.
// In this case it's actually C24, but let's just assume it means C32, it shouldn't matter in this case.
GIFRegTEX0 src_TEX0 = TEX0;
if (possible_shuffle && !dst && psm_s.bpp == 16)
{
if (frame.FBW == src_TEX0.TBW && frame.FBW <= 14)
{
rect.y /= 2;
rect.w /= 2;
src = CreateSource(TEX0, TEXA, dst, x_offset, y_offset, lod, &r, gpu_clut, region);
if (region.HasY())
{
const u32 min_y = region.GetMinY() / 2;
const u32 max_y = region.GetMaxY() / 2;
region.ClearY();
region.SetY(min_y, max_y);
}
}
else
{
rect.x /= 2;
rect.z /= 2;
if (region.HasX())
{
const u32 min_x = region.GetMinX() / 2;
const u32 max_x = region.GetMaxX() / 2;
region.ClearX();
region.SetX(min_x, max_x);
}
}
if (TEX0.TBP0 == frame.Block())
{
GIFRegTEX0 target_TEX0;
target_TEX0.TBP0 = frame.Block();
target_TEX0.PSM = PSMCT32;
target_TEX0.TBW = frame.FBW;
if (target_TEX0.TBW > 14)
target_TEX0.TBW /= 2;
dst = g_texture_cache->CreateTarget(target_TEX0, GSVector2i(rect.z, rect.w), GSVector2i(rect.z, rect.w), GSRendererHW::GetInstance()->GetUpscaleMultiplier(),
GSLocalMemory::m_psm[TEX0.PSM].depth ? DepthStencil : RenderTarget, true, 0, false, true, possible_shuffle, rect, nullptr);
}
else
{
src_TEX0.PSM = PSMCT32;
}
}
src = CreateSource(src_TEX0, TEXA, dst, x_offset, y_offset, lod, &rect, gpu_clut, region);
if (!src) [[unlikely]]
return nullptr;
}
@@ -2070,7 +2149,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
AttachPaletteToSource(src, psm_s.pal, true, true);
}
src->Update(r);
src->Update(rect);
return src;
}
@@ -2304,10 +2383,11 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y));
const bool ds_offset = !ds || offset != 0;
const bool is_double_buffer = TEX0.TBP0 == ((((t->m_end_block + 1) - t->m_TEX0.TBP0) / 2) + t->m_TEX0.TBP0);
const bool source_match = src && src->m_TEX0.TBP0 == bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t;
// if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems.
// This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now.
const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32)));
if (no_target_or_newer && is_aligned_ok && width_match && overlaps && (is_shuffle || ds_offset || is_double_buffer))
if (source_match || (no_target_or_newer && is_aligned_ok && width_match && overlaps && (is_shuffle || ds_offset || is_double_buffer)))
{
const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM];
@@ -2414,11 +2494,24 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// If frame is old and dirty, probably modified by the EE, so kill the wrong dimension version.
if (!t->m_dirty.empty())
{
DevCon.Warning("Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
// It's dirty with the data we want at the right width, so just change it to that.
// Prince of Persia - Sands of Time
if (t->m_dirty.size() == 1 && t->m_dirty[0].bw == TEX0.TBW)
{
t->m_TEX0.TBW = TEX0.TBW;
t->m_valid = dirty_rect;
t->m_end_block = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_valid);
t->m_drawn_since_read = GSVector4i::zero();
}
else
{
DevCon.Warning("Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
}
}
dst = t;
@@ -2678,7 +2771,9 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// If our RGB was invalidated, we need to pull it from depth.
// Terminator 3 will reuse our dst_matched target with the RGB masked, then later use the full ARGB area, so we need to update the depth.
const bool preserve_target = preserve_rgb || preserve_alpha;
if (type == RenderTarget && (preserve_target || !dst->m_valid.rintersect(draw_rect).eq(dst->m_valid)) &&
const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk;
if ((preserve_target || !dst->m_valid.rintersect(draw_rect).eq(dst->m_valid)) &&
!dst->m_valid_rgb && !FullRectDirty(dst, 0x7) &&
(GSLocalMemory::m_psm[TEX0.PSM].trbpp < 24 || fbmask != 0x00FFFFFFu))
{
@@ -2687,28 +2782,54 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
if (!is_frame)
{
GL_CACHE("TC: Attempt to repopulate RGB for %s[%x]", to_string(type), dst->m_TEX0.TBP0);
for (Target* dst_match : m_dst[DepthStencil])
for (Target* dst_match : m_dst[1 - type])
{
if (dst_match->m_TEX0.TBP0 != TEX0.TBP0 || !dst_match->m_valid_rgb)
if (dst_match->m_TEX0.TBP0 != dst->m_TEX0.TBP0 || !dst_match->m_valid_rgb)
continue;
dst->m_was_dst_matched = true;
dst->m_TEX0.TBW = dst_match->m_TEX0.TBW;
// Force the valid rect to the new size in case of shrinkage.
dst->m_valid = dst_match->m_valid;
dst->UpdateValidity(dst_match->m_valid);
if (!CopyRGBFromDepthToColor(dst, dst_match))
if (type == RenderTarget)
{
// Needed new texture and memory allocation failed.
return nullptr;
dst_match->m_valid_rgb = (fbmask & mask) == (mask & 0x00FFFFFFu);
dst->m_was_dst_matched = true;
if (!CopyRGBFromDepthToColor(dst, dst_match))
{
// Needed new texture and memory allocation failed.
return nullptr;
}
}
else
{
dst_match->m_valid_rgb &= (fbmask & mask) == (mask & 0x00FFFFFFu);
dst->Update();
if (!dst->ResizeTexture(dst_match->m_unscaled_size.x, dst_match->m_unscaled_size.y))
{
// Needed new texture and memory allocation failed.
return nullptr;
}
const ShaderConvert shader = (GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 16) ? ShaderConvert::RGB5A1_TO_FLOAT16 :
(GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 32) ? ShaderConvert::RGBA8_TO_FLOAT32 :
ShaderConvert::RGBA8_TO_FLOAT24;
g_gs_device->StretchRect(dst_match->m_texture, GSVector4(0, 0, 1, 1),
dst->m_texture, GSVector4(dst->GetUnscaledRect()) * GSVector4(dst->GetScale()), shader, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
dst_match->m_valid_rgb = !used;
dst_match->m_was_dst_matched = true;
dst->m_valid_rgb = true;
dst->m_32_bits_fmt = dst_match->m_32_bits_fmt;
}
break;
}
}
const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk;
if (!dst->m_valid_rgb && ((fbmask & 0x00FFFFFF) & mask) != (mask & 0x00FFFFFF))
{
GL_CACHE("TC: Cannot find RGB target for %s[%x], clearing.", to_string(type), dst->m_TEX0.TBP0);
@@ -2820,7 +2941,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
const GSLocalMemory::psm_t& t_psm_s = GSLocalMemory::m_psm[t->m_TEX0.PSM];
if (t_psm_s.bpp != psm_s.bpp)
{
bool remove_target = possible_clear;
bool remove_target = possible_clear || (used && !is_shuffle);
// If we have a BW change, and it's not a multiple of 2 (for a shuffle), the game's going to get a jigsaw
// puzzle of pages and can't be expecting to have legitimate data. Tokimeki Memorial 3 reuses a BW 17
@@ -3031,7 +3152,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe
{
// Not having this valid could make things explode, but I do enjoy watching the world burn (and this is actually more correct).
const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk;
dst->m_valid_rgb = GSLocalMemory::m_psm[TEX0.PSM].depth || ((fbmask & 0x00FFFFFF) & mask) != (mask & 0x00FFFFFF) || (dst->m_dirty.GetDirtyChannels() & 0x7);
dst->m_valid_rgb |= GSLocalMemory::m_psm[TEX0.PSM].depth || ((fbmask & 0x00FFFFFF) & mask) != (mask & 0x00FFFFFF) || (dst->m_dirty.GetDirtyChannels() & 0x7);
// If there is an opposite target without valid RGB, we need to match them up
auto& rev_list = m_dst[1 - type];
@@ -3053,8 +3174,8 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe
// If the alpha is masked and preloaded, we need to say it's valid else textures might fail to use the whole texture if RGB is valid.
if (((fbmask & 0xFF000000) & mask) != (mask & 0xFF000000) && bpp != 24)
{
dst->m_valid_alpha_high = (~(fbmask & mask) & 0xf0000000) & mask;
dst->m_valid_alpha_low = (~(fbmask & mask) & 0x0f000000) & mask;
dst->m_valid_alpha_high |= ((~(fbmask & mask) & 0xf0000000) & mask) != 0;
dst->m_valid_alpha_low |= ((~(fbmask & mask) & 0x0f000000) & mask) != 0;
if (bpp == 16)
dst->m_valid_alpha_low = dst->m_valid_alpha_high;
@@ -3077,8 +3198,8 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe
// If the format, and location doesn't overlap
if (TEX0.TBP0 == iter->blit.DBP && GSUtil::HasCompatibleBits(iter->blit.DPSM, TEX0.PSM) && (iter->blit.DBW == dst->m_TEX0.TBW || (transfer_end >= tex_end && (iter->blit.DBW * 64) == iter->rect.z)))
{
dst->m_valid_alpha_high = iter->blit.DPSM != PSMT4HL;
dst->m_valid_alpha_low = iter->blit.DPSM != PSMT4HH;
dst->m_valid_alpha_high |= iter->blit.DPSM != PSMT4HL;
dst->m_valid_alpha_low |= iter->blit.DPSM != PSMT4HH;
break;
}
@@ -3260,6 +3381,12 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
}
}
const GSVector4i dst_valid = dst->m_valid.rempty() ? GSVector4i::loadh(valid_size) : dst->m_valid;
u32 dst_end_block = GSLocalMemory::GetEndBlockAddress(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid);
if (dst_end_block < dst->m_TEX0.TBP0)
dst_end_block += MAX_BLOCKS;
// Can't do channel writes to depth targets, and DirectX can't partial copy depth targets.
if (psm_s.depth == 0)
{
@@ -3271,7 +3398,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
auto j = i;
Target* t = *j;
if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) &&
if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid) &&
static_cast<int>(((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) / 32) % std::max(dst->m_TEX0.TBW, 1U)) <= std::max(0, static_cast<int>(dst->m_TEX0.TBW - t->m_TEX0.TBW)))
{
const u32 buffer_width = std::max(1U, dst->m_TEX0.TBW);
@@ -3314,10 +3441,10 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
return hw_clear.value_or(false);
}
// The new texture is behind it but engulfs the whole thing, shrink the new target so it grows in the HW Draw resize.
else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (dst->UnwrappedEndBlock() + 1) > t->m_TEX0.TBP0)
else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && dst_end_block > t->m_TEX0.TBP0)
{
const int rt_pages = ((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5;
const int overlapping_pages = std::min(rt_pages, static_cast<int>((dst->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5);
const int overlapping_pages = std::min(rt_pages, static_cast<int>(dst_end_block - t->m_TEX0.TBP0) >> 5);
const int overlapping_pages_height = ((overlapping_pages + (buffer_width - 1)) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
if (overlapping_pages_height == 0 || (overlapping_pages % buffer_width))
@@ -3355,6 +3482,10 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
t->Update();
dst->Update();
dst->m_valid_rgb |= t->m_valid_rgb;
dst->m_valid_alpha_low |= t->m_valid_alpha_low;
dst->m_valid_alpha_high |= t->m_valid_alpha_high;
// Clamp it if it gets too small, shouldn't happen but stranger things have happened.
if (copy_width < 0)
{
@@ -3408,14 +3539,40 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
{
auto j = i;
Target* t = *j;
if (t != dst && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) && GSUtil::HasSharedBits(dst->m_TEX0.PSM, t->m_TEX0.PSM))
if (t != dst && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid) && GSUtil::HasSharedBits(dst->m_TEX0.PSM, t->m_TEX0.PSM))
{
if (dst->m_TEX0.TBP0 > t->m_TEX0.TBP0 && (((dst->m_TEX0.TBP0 - t->m_TEX0.TBP0) >> 5) % std::max(t->m_TEX0.TBW, 1U)) == 0)
if (dst->m_TEX0.TBP0 > t->m_TEX0.TBP0 && dst->m_TEX0.TBW == t->m_TEX0.TBW &&
((((dst->m_TEX0.TBP0 - t->m_TEX0.TBP0) >> 5) % std::max(t->m_TEX0.TBW, 1U)) + (dst_valid.z / 64)) <= dst->m_TEX0.TBW)
{
int height_adjust = (((dst->m_TEX0.TBP0 - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
// Probably a render target which was previously a Z.
if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->Inside(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) &&
GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp)
{
dst->m_TEX0.TBP0 = t->m_TEX0.TBP0;
dst->m_valid = t->m_valid;
dst->m_drawn_since_read = t->m_drawn_since_read;
dst->m_end_block = t->m_end_block;
dst->m_valid_rgb = true;
t->m_valid_rgb = false;
t->m_was_dst_matched = true;
t->m_valid.w = std::min(height_adjust, t->m_valid.w);
t->ResizeValidity(t->m_valid);
dst->ResizeTexture(t->m_unscaled_size.x, t->m_unscaled_size.y);
const ShaderConvert shader = (GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 16) ? ShaderConvert::RGB5A1_TO_FLOAT16 :
(GSLocalMemory::m_psm[dst->m_TEX0.PSM].trbpp == 32) ? ShaderConvert::RGBA8_TO_FLOAT32 : ShaderConvert::RGBA8_TO_FLOAT24;
g_gs_device->StretchRect(t->m_texture, GSVector4(0,0,1,1),
dst->m_texture, GSVector4(t->GetUnscaledRect()) * GSVector4(dst->GetScale()), shader, false);
break;
}
else
{
const int height_adjust = (((dst->m_TEX0.TBP0 - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
t->m_valid.w = std::min(height_adjust, t->m_valid.w);
t->ResizeValidity(t->m_valid);
}
}
else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % std::max(t->m_TEX0.TBW, 1U)) == 0)
{
@@ -3428,15 +3585,15 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
continue;
}
int height_adjust = ((((dst->m_end_block + 1) - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
const int height_adjust = ((((dst_end_block + 31) - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
if (height_adjust < t->m_unscaled_size.y)
{
t->m_TEX0.TBP0 = dst->m_end_block + 1;
t->m_TEX0.TBP0 = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, GSVector4i(0, height_adjust, t->m_valid.z, t->m_valid.w));
t->m_valid.w -= height_adjust;
t->ResizeValidity(t->m_valid);
GSTexture* tex = (type == RenderTarget) ?
GSTexture* tex = (t->m_type == RenderTarget) ?
g_gs_device->CreateRenderTarget(t->m_texture->GetWidth(),
t->m_texture->GetHeight(), GSTexture::Format::Color, true) :
g_gs_device->CreateDepthStencil(t->m_texture->GetWidth(),
@@ -3462,7 +3619,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
{
if (src && src->m_target && src->m_from_target == t)
{
src->m_from_target = t;
src->m_from_target = nullptr;
src->m_texture = t->m_texture;
src->m_target_direct = false;
src->m_shared_texture = false;
@@ -3941,7 +4098,7 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
{
if (write_bw == t->m_TEX0.TBW && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[write_psm].bpp)
{
RGBAMask mask;
mask._u32 = GSUtil::GetChannelMask(write_psm);
AddDirtyRectTarget(t, invalidate_r, t->m_TEX0.PSM, t->m_TEX0.TBW, mask, false);
@@ -3952,6 +4109,23 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
}
}
// This is an annoying edge case where developers don't know how to use SCISSOR correctly, so it's one pixel over size, making the end block too late.
// In this case we *don't* want to nuke the depth, but just adjust the size so it's not 1 pixel over.
// Prince of Persia - Sands of Time suffers from this.
if (type == DepthStencil && t->m_TEX0.TBP0 < start_bp && t->m_end_block > start_bp)
{
const GSVector4i masked_valid = GSVector4i(t->m_valid.x, t->m_valid.y, t->m_valid.z & ~1, t->m_valid.w & ~1);
const u32 reduced_endblock = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, masked_valid);
if (reduced_endblock <= start_bp)
{
t->ResizeValidity(masked_valid);
t->ResizeDrawn(masked_valid);
++i;
continue;
}
}
InvalidateSourcesFromTarget(t);
t->m_valid_alpha_low &= preserve_alpha;
@@ -4207,7 +4381,9 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
InvalidateTemporaryZ();
}
if (GSLocalMemory::m_psm[psm].depth)
// If we're dealing with quadrant draws, we need to position them correctly (Final Fantasy X).
if (GSLocalMemory::m_psm[psm].depth &&
r.width() <= (GSLocalMemory::m_psm[psm].pgs.x >> 1) && r.height() <= (GSLocalMemory::m_psm[psm].pgs.y >> 1))
DirtyRectByPage(bp, psm, bw, t, r);
else
AddDirtyRectTarget(t, r, psm, bw, rgba);
@@ -4601,7 +4777,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
// Not even going to go down the rabbit hole of palette formats on the GPU.. We shouldn't have any targets with P4/P8 anyway.
const GSLocalMemory::psm_t& spsm_s = GSLocalMemory::m_psm[SPSM];
const GSLocalMemory::psm_t& dpsm_s = GSLocalMemory::m_psm[DPSM];
if (SPSM != DPSM || ((spsm_s.pal + dpsm_s.pal) != 0 && !alpha_only))
if (GSLocalMemory::m_psm[SPSM].bpp != GSLocalMemory::m_psm[DPSM].bpp || ((spsm_s.pal + dpsm_s.pal) != 0 && !alpha_only))
{
GL_CACHE("TC: Skipping HW move from 0x%X to 0x%X with SPSM=%s DPSM=%s", SBP, DBP, psm_str(SPSM), psm_str(DPSM));
return false;
@@ -4617,6 +4793,12 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
return false;
}
bool req_resize = false;
// Save for later in case of page copy.
const u32 start_SBP = SBP;
const u32 start_DBP = DBP;
if (m_expected_src_bp == static_cast<int>(SBP) && m_expected_dst_bp == static_cast<int>(DBP))
{
// Get the new position so we can work out the offset.
@@ -4627,17 +4809,18 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
sy += rect_offset.y;
dx += rect_offset.x;
dy += rect_offset.y;
req_resize = true;
GL_INS("TC: Detected striped move, realigning from SBP %x->%x DBP %x->%x", SBP, m_remembered_src_bp, DBP, m_remembered_dst_bp);
SBP = m_remembered_src_bp;
DBP = m_remembered_dst_bp;
m_expected_src_bp = -1;
m_remembered_src_bp = -1;
m_expected_dst_bp = -1;
m_remembered_dst_bp = -1;
}
m_expected_src_bp = -1;
m_remembered_src_bp = -1;
m_expected_dst_bp = -1;
m_remembered_dst_bp = -1;
// Look for an exact match on the targets.
GSTextureCache::Target* src = GetExactTarget(SBP, SBW, spsm_s.depth ? DepthStencil : RenderTarget, SBP);
GSTextureCache::Target* dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget, DBP);
@@ -4672,9 +4855,21 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
const GSVector2i target_size = GetTargetSize(DBP, DBW, DPSM, Common::AlignUpPow2(w, 64), h);
dst = LookupTarget(new_TEX0, target_size, src->m_scale, src->m_type);
if (!dst)
dst = CreateTarget(new_TEX0, target_size, target_size, src->m_scale, src->m_type);
{
dst = Target::Create(new_TEX0, target_size.x, target_size.y, src->m_scale, GSLocalMemory::m_psm[DPSM].depth, true);
if (!dst) [[unlikely]]
return false;
}
else // Expand if necessary (Silent hill 4 takes an old target which is smaller).
dst->ResizeTexture(std::max(dst->m_unscaled_size.x, target_size.x), std::max(dst->m_unscaled_size.y, target_size.y));
{
req_resize = true;
// If it was matched to an old target, make sure to clear the other type and update its information.
if (dst->m_was_dst_matched)
{
dst->m_TEX0 = new_TEX0;
}
}
if (!dst)
return false;
@@ -4686,6 +4881,24 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
if (!src || !dst || src->m_scale != dst->m_scale)
return false;
// If we have an offset, adjust the base positions
if (src->m_TEX0.TBP0 != SBP)
{
const GSVector4i offset = TranslateAlignedRectByPage(src, SBP, SPSM, SBW, GSVector4i(0, 1).xxyy(), false);
sx += offset.x;
sy += offset.y;
}
if (dst->m_TEX0.TBP0 != DBP)
{
const GSVector4i offset = TranslateAlignedRectByPage(dst, DBP, DPSM, DBW, GSVector4i(0, 1).xxyy(), false);
dx += offset.x;
dy += offset.y;
req_resize = true;
}
// Scale coordinates.
const float scale = src->m_scale;
const int scaled_sx = static_cast<int>(sx * scale);
@@ -4699,6 +4912,11 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
if ((scaled_sx + scaled_w) > src->m_texture->GetWidth() || (scaled_sy + scaled_h) > src->m_texture->GetHeight())
return false;
if (req_resize)
{
const GSVector2i target_size = GetTargetSize(DBP, DBW, DPSM, Common::AlignUpPow2(dx + w, 64), dx + h);
dst->ResizeTexture(std::max(dst->m_unscaled_size.x, target_size.x), std::max(dst->m_unscaled_size.y, target_size.y));
}
// We don't want to copy "old" data that the game has overwritten with writes,
// so flush any overlapping dirty area.
src->UpdateIfDirtyIntersects(GSVector4i(sx, sy, sx + w, sy + h));
@@ -4707,6 +4925,9 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
// we're not going to be able to use it as a source.
dst->Update();
// Invalidate any opposite targets.
g_texture_cache->InvalidateVideoMemType(GSTextureCache::DepthStencil - dst->m_type, dst->m_TEX0.TBP0);
// Expand the target when we used a more conservative size.
const int required_dh = scaled_dy + scaled_h;
if ((scaled_dx + scaled_w) <= dst->m_texture->GetWidth() && required_dh > dst->m_texture->GetHeight())
@@ -4795,9 +5016,30 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
{
if (SPSM == PSMT8H && SPSM == DPSM)
{
ShaderConvert shader = ShaderConvert::COPY;
const GSVector4 src_rect = GSVector4(scaled_sx, scaled_sy, scaled_sx + scaled_w, scaled_sy + scaled_h) / (GSVector4(src->m_texture->GetSize()).xyxy());
const GSVector4 dst_rect = GSVector4(scaled_dx, scaled_dy, (scaled_dx + scaled_w), (scaled_dy + scaled_h));
g_gs_device->StretchRect(src->m_texture, src_rect, dst->m_texture, dst_rect, false, false, false, true);
g_gs_device->StretchRect(src->m_texture, src_rect, dst->m_texture, dst_rect, false, false, false, true, shader);
}
else if (src->m_type != dst->m_type)
{
ShaderConvert shader = dst->m_type ? ShaderConvert::RGBA8_TO_FLOAT32 : ShaderConvert::FLOAT32_TO_RGBA8;
switch (dpsm_s.trbpp)
{
case 24:
shader = dst->m_type ? ShaderConvert::RGBA8_TO_FLOAT24 : ShaderConvert::FLOAT32_TO_RGB8;
break;
case 16:
shader = dst->m_type ? ShaderConvert::RGB5A1_TO_FLOAT16 : ShaderConvert::FLOAT16_TO_RGB5A1;
break;
default:
break;
}
const GSVector4 src_rect = GSVector4(scaled_sx, scaled_sy, scaled_sx + scaled_w, scaled_sy + scaled_h) / (GSVector4(src->m_texture->GetSize()).xyxy());
g_gs_device->StretchRect(src->m_texture, src_rect, dst->m_texture, GSVector4(scaled_sx, scaled_sy, scaled_sx + scaled_w, scaled_sy + scaled_h), shader, false);
}
else
{
@@ -4825,20 +5067,29 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
dst->m_alpha_range |= src->m_alpha_range;
}
if (GSLocalMemory::IsPageAligned(src->m_TEX0.PSM, GSVector4i(sx, sy, sx + w, sy + h)) && (w == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.x || h == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.y))
u32 page_mask = GSLocalMemory::IsPageAlignedMasked(src->m_TEX0.PSM, GSVector4i(sx, sy, sx + w, sy + h));
if (((page_mask & 0x0f0f) == 0x0f0f || (page_mask & 0xf0f0) == 0xf0f0) && (w == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.x || h == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.y))
{
// Vertical Strips
if (w == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.x)
// Page copy
if (w == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.x && h == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.y)
{
m_expected_src_bp = start_SBP + BLOCKS_PER_PAGE;
m_expected_dst_bp = start_DBP + BLOCKS_PER_PAGE;
}
// Vertical Strips.
else if (w == GSLocalMemory::m_psm[src->m_TEX0.PSM].pgs.x)
{
m_expected_src_bp = GSLocalMemory::GetStartBlockAddress(src->m_TEX0.TBP0, src->m_TEX0.TBW, src->m_TEX0.PSM, GSVector4i(sx + w, 0, sx + w + w, h));
m_expected_dst_bp = GSLocalMemory::GetStartBlockAddress(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, GSVector4i(dx + w, 0, dx + w + w, h));
}
// Horizontal Strips.
else
{
m_expected_src_bp = GSLocalMemory::GetStartBlockAddress(src->m_TEX0.TBP0, src->m_TEX0.TBW, src->m_TEX0.PSM, GSVector4i(0, sy + h, w, sy + h + h));
m_expected_dst_bp = GSLocalMemory::GetStartBlockAddress(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, GSVector4i(0, dy + h, w, dy + h + h));
}
// Only check the source, the destination might need expanding.
if (static_cast<u32>(m_expected_src_bp) < src->UnwrappedEndBlock() && static_cast<u32>(m_expected_src_bp) >= src->m_TEX0.TBP0)
{
m_remembered_src_bp = src->m_TEX0.TBP0;
@@ -4860,7 +5111,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
// Invalidate any sources that overlap with the target (since they're now stale).
InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(DBP, DBW, DPSM), GSVector4i(dx, dy, dx + w, dy + h), false);
CombineAlignedInsideTargets(dst);
return true;
}
@@ -5047,7 +5298,7 @@ GSTextureCache::Target* GSTextureCache::GetExactTarget(u32 BP, u32 BW, int type,
{
Target* t = *it;
const u32 tgt_bw = std::max(t->m_TEX0.TBW, 1U);
if ((t->m_TEX0.TBP0 == BP || (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->m_TEX0.TBP0 < BP && ((BP >> 5) % tgt_bw) == 0)) && tgt_bw == BW && t->UnwrappedEndBlock() >= end_bp)
if ((t->m_TEX0.TBP0 == BP || (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->m_TEX0.TBP0 < BP && !(BP & 0x1f) && (((BP - t->m_TEX0.TBP0) >> 5) % tgt_bw) == 0)) && tgt_bw == BW && t->UnwrappedEndBlock() >= end_bp)
{
rts.MoveFront(it.Index());
return t;
@@ -5524,7 +5775,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
if (is_8bits)
{
GL_INS("TC: Reading RT as a packed-indexed 8 bits format");
shader = ShaderConvert::RGBA_TO_8I;
shader = GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16 ? ShaderConvert::RGB5A1_TO_8I : ShaderConvert::RGBA_TO_8I;
}
#ifdef ENABLE_OGL_DEBUG
@@ -5592,11 +5843,13 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
}
else
{
const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM];
const GSLocalMemory::psm_t& t_psm = GSLocalMemory::m_psm[dst->m_TEX0.PSM];
u32 dst_pages = (dst->m_unscaled_size.x / t_psm.pgs.x) * (dst->m_unscaled_size.y / t_psm.pgs.y);
src->m_unscaled_size.x = std::max(static_cast<int>(TEX0.TBW) * (s_psm.pgs.x / 2), (s_psm.pgs.x / 2));
src->m_unscaled_size.y = std::max(static_cast<int>(dst_pages / std::max((TEX0.TBW / 2U), 1U)) * s_psm.pgs.y, s_psm.pgs.y);
// We're inside the target, so conversion needs to happen on the entire target so we can offset properly.
src->m_unscaled_size.x = dst->m_unscaled_size.x * 2;
if (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 32)
src->m_unscaled_size.y = dst->m_unscaled_size.y * 2;
else
src->m_unscaled_size.y = dst->m_unscaled_size.y;
new_size.x = src->m_unscaled_size.x;
new_size.y = src->m_unscaled_size.y;
}
@@ -5760,24 +6013,16 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
sTex = dst->m_texture;
}
const u32 destination_tbw = (dst->m_TEX0.TBP0 == TEX0.TBP0) ? (std::max<u32>(TEX0.TBW, 1u) * 64) : std::max<u32>(dst->m_TEX0.TBW, 1u) * 128;
g_gs_device->ConvertToIndexedTexture(sTex, dst->m_scale, x_offset, y_offset,
std::max<u32>(dst->m_TEX0.TBW, 1u) * 64, dst->m_TEX0.PSM, dTex,
std::max<u32>(TEX0.TBW, 1u) * 64, TEX0.PSM);
destination_tbw, TEX0.PSM);
// Adjust the region for the newly translated rect.
u32 const dst_y_height = GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y;
u32 const src_y_height = GSLocalMemory::m_psm[TEX0.PSM].pgs.y;
u32 const dst_page_offset = (y_offset / dst_y_height) * std::max(dst->m_TEX0.TBW, 1U);
y_offset = (dst_page_offset / (std::max(TEX0.TBW / 2U, 1U))) * src_y_height;
// Adjust to match a PSMT8 texture (coordinates are double C32, we shouldn't be converting from anything else).
x_offset *= 2;
if (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 32)
y_offset *= 2;
u32 const src_page_width = GSLocalMemory::m_psm[TEX0.PSM].pgs.x;
x_offset = (x_offset / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.x) * GSLocalMemory::m_psm[TEX0.PSM].pgs.x;
if (x_offset >= static_cast<int>(std::max(TEX0.TBW, 1U) * src_page_width))
{
const u32 adjust = x_offset / src_page_width;
y_offset += adjust * GSLocalMemory::m_psm[TEX0.PSM].pgs.y;
x_offset -= src_page_width * adjust;
}
src->m_region.SetX(x_offset, x_offset + tw);
src->m_region.SetY(y_offset, y_offset + th);
@@ -6563,6 +6808,8 @@ void GSTextureCache::Read(Target* t, const GSVector4i& r)
{
case PSMCT32:
case PSMCT24:
case PSMZ32:
case PSMZ24:
{
// If we're downloading a depth buffer that's been reinterpreted as a color
// format, convert it to integer. The format/swizzle is likely wrong, but it's
@@ -6588,27 +6835,11 @@ void GSTextureCache::Read(Target* t, const GSVector4i& r)
case PSMCT16:
case PSMCT16S:
{
fmt = GSTexture::Format::UInt16;
ps_shader = is_depth ? ShaderConvert::FLOAT32_TO_16_BITS : ShaderConvert::RGBA8_TO_16_BITS;
dltex = &m_uint16_download_texture;
}
break;
case PSMZ32:
case PSMZ24:
{
fmt = GSTexture::Format::UInt32;
ps_shader = ShaderConvert::FLOAT32_TO_32_BITS;
dltex = &m_uint32_download_texture;
}
break;
case PSMZ16:
case PSMZ16S:
{
fmt = GSTexture::Format::UInt16;
ps_shader = ShaderConvert::FLOAT32_TO_16_BITS;
ps_shader = is_depth ? ShaderConvert::FLOAT32_TO_16_BITS : ShaderConvert::RGBA8_TO_16_BITS;
dltex = &m_uint16_download_texture;
}
break;

View File

@@ -45,6 +45,9 @@ public:
bool HasY() const { return static_cast<u32>(bits >> 32) != 0; }
bool HasEither() const { return (bits != 0); }
void ClearX() { bits &= ~0xFFFFFFFFULL; }
void ClearY() { bits &= 0xFFFFFFFFULL; }
void SetX(s32 min, s32 max) { bits |= (static_cast<u64>(static_cast<u16>(min)) | (static_cast<u64>(static_cast<u16>(max) << 16))); }
void SetY(s32 min, s32 max) { bits |= ((static_cast<u64>(static_cast<u16>(min)) << 32) | (static_cast<u64>(static_cast<u16>(max)) << 48)); }

View File

@@ -270,56 +270,57 @@ std::string GSTextureReplacements::GetDumpFilename(const TextureName& name, u32
return ret;
const std::string game_dir(GetGameTextureDirectory());
if (!FileSystem::DirectoryExists(game_dir.c_str()))
const std::string game_subdir(Path::Combine(game_dir, TEXTURE_DUMP_SUBDIRECTORY_NAME));
if (!FileSystem::DirectoryExists(game_subdir.c_str()))
{
// create both dumps and replacements
if (!FileSystem::CreateDirectoryPath(game_dir.c_str(), false) ||
!FileSystem::EnsureDirectoryExists(Path::Combine(game_dir, "dumps").c_str(), false) ||
!FileSystem::EnsureDirectoryExists(Path::Combine(game_dir, "replacements").c_str(), false))
!FileSystem::EnsureDirectoryExists(game_subdir.c_str(), false) ||
!FileSystem::EnsureDirectoryExists(Path::Combine(game_dir, TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME).c_str(), false))
{
// if it fails to create, we're not going to be able to use it anyway
return ret;
}
}
const std::string game_subdir(Path::Combine(game_dir, TEXTURE_DUMP_SUBDIRECTORY_NAME));
std::string filename;
if (name.HasRegion())
{
if (name.HasPalette())
{
filename = (level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.region_width, name.region_height, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.region_width, name.region_height, name.bits);
filename = (level > 0)
? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.region_width, name.region_height, name.bits, level)
: StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.region_width, name.region_height, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_REGION_FORMAT_STRING "-mip%u.png", name.TEX0Hash,
name.region_width, name.region_height, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_REGION_FORMAT_STRING ".png", name.TEX0Hash,
name.region_width, name.region_height, name.bits);
filename = (level > 0)
? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.region_width, name.region_height, name.bits, level)
: StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_FORMAT_STRING ".png",
name.TEX0Hash, name.region_width, name.region_height, name.bits);
}
}
else
{
if (name.HasPalette())
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.bits);
filename = (level > 0)
? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.bits, level)
: StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
filename = (level > 0)
? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.bits, level)
: StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING ".png",
name.TEX0Hash, name.bits);
}
}
@@ -349,6 +350,28 @@ void GSTextureReplacements::GameChanged()
ClearDumpedTextureList();
}
/// If the given file exists in the given directory, but with a different case than the original file, write its path to `*output` and return true.
static bool GetWrongCasePath(std::string* output, const char* dir, std::string_view file, FileSystem::FindResultsArray* reuseme)
{
if (FileSystem::FindFiles(dir, "*", FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, reuseme))
{
for (const FILESYSTEM_FIND_DATA& fd : *reuseme)
{
std::string_view name = Path::GetFileName(fd.FileName);
if (name.size() != file.size())
continue;
if (0 == strncmp(name.data(), file.data(), name.size()))
continue;
if (0 == StringUtil::Strncasecmp(name.data(), file.data(), name.size()))
{
*output = fd.FileName;
return true;
}
}
}
return false;
}
void GSTextureReplacements::ReloadReplacementMap()
{
SyncWorkerThread();
@@ -368,9 +391,27 @@ void GSTextureReplacements::ReloadReplacementMap()
if (s_current_serial.empty() || !GSConfig.LoadTextureReplacements)
return;
const std::string replacement_dir(Path::Combine(GetGameTextureDirectory(), TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME));
const std::string texture_dir = GetGameTextureDirectory();
const std::string replacement_dir(Path::Combine(texture_dir, TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME));
FileSystem::FindResultsArray files;
// For some reason texture pack authors think it's a good idea to rename the replacements directory to something with the wrong case...
std::string wrong_case_path;
const std::string* right_case_path = nullptr;
if (GetWrongCasePath(&wrong_case_path, EmuFolders::Textures.c_str(), s_current_serial, &files))
right_case_path = &texture_dir;
else if (GetWrongCasePath(&wrong_case_path, texture_dir.c_str(), TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME, &files))
right_case_path = &replacement_dir;
if (right_case_path)
{
Host::AddKeyedOSDMessage("TextureReplacementDirCaseMismatch",
fmt::format(TRANSLATE_FS("TextureReplacement", "Texture replacement directory {} will not work on case sensitive filesystems.\n"
"Rename it to {} to remove this warning."),
wrong_case_path, *right_case_path),
Host::OSD_WARNING_DURATION);
}
if (!FileSystem::FindFiles(replacement_dir.c_str(), "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RECURSIVE, &files))
return;

View File

@@ -1713,12 +1713,12 @@ void GSDeviceMTL::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX,
void GSDeviceMTL::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM)
{ @autoreleasepool {
const ShaderConvert shader = ShaderConvert::RGBA_TO_8I;
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
id<MTLRenderPipelineState> pipeline = m_convert_pipeline[static_cast<int>(shader)];
if (!pipeline)
[NSException raise:@"StretchRect Missing Pipeline" format:@"No pipeline for %d", static_cast<int>(shader)];
GSMTLIndexedConvertPSUniform uniform = { sScale, SBW, DBW };
GSMTLIndexedConvertPSUniform uniform = { sScale, SBW, DBW, SPSM };
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, pipeline, false, LoadAction::DontCareIfFull, &uniform, sizeof(uniform));

View File

@@ -4,12 +4,19 @@
#include "GSMTLDeviceInfo.h"
#include "GS/GS.h"
#include "common/Console.h"
#include "common/Path.h"
#ifdef __APPLE__
static id<MTLLibrary> loadMainLibrary(id<MTLDevice> dev, NSString* name)
{
NSString* path = [[NSBundle mainBundle] pathForResource:name ofType:@"metallib"];
if (!path)
{
std::string ssname = std::string([name UTF8String]) + ".metallib";
std::string sspath = Path::Combine(EmuFolders::Resources, ssname);
path = [[NSString alloc] initWithBytes:sspath.data() length:sspath.length() encoding:NSUTF8StringEncoding];
}
return path ? [dev newLibraryWithFile:path error:nullptr] : nullptr;
}
@@ -24,6 +31,8 @@ static MRCOwned<id<MTLLibrary>> loadMainLibrary(id<MTLDevice> dev)
if (@available(macOS 10.14, iOS 12.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal21"))
return MRCTransfer(lib);
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"default"))
return MRCTransfer(lib);
return MRCTransfer([dev newDefaultLibrary]);
}

View File

@@ -64,6 +64,7 @@ struct GSMTLIndexedConvertPSUniform
float scale;
uint sbw;
uint dbw;
uint psm;
};
struct GSMTLDownsamplePSUniform

View File

@@ -296,6 +296,121 @@ fragment DepthOut ps_convert_rgb5a1_float16_biln(ConvertShaderData data [[stage_
return res.sample_biln<rgb5a1_to_depth16>(data.t);
}
fragment float4 ps_convert_rgb5a1_8i(ConvertShaderData data [[stage_in]], DirectReadTextureIn<float> res,
constant GSMTLIndexedConvertPSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{
// Convert a RGB5A1 texture into a 8 bits packed texture
// Input column: 16x2 RGB5A1 pixels
// 0: 16 RGBA
// 1: 16 RGBA
// Output column: 16x4 Index pixels
// 0: 16 R5G2
// 1: 16 R5G2
// 2: 16 G2B5A1
// 3: 16 G2B5A1
uint2 pos = uint2(data.p.xy);
// Collapse separate R G B A areas into their base pixel
uint2 column = (pos & ~uint2(0u, 3u)) / uint2(1,2);
uint2 subcolumn = (pos & uint2(0u, 1u));
column.x -= (column.x / 128) * 64;
column.y += (column.y / 32) * 32;
// Deal with swizzling differences
if ((uniform.psm & 0x8) != 0) // PSMCT16S
{
if ((pos.x & 32) != 0)
{
column.y += 32; // 4 columns high times 4 to get bottom 4 blocks
column.x &= ~32;
}
if ((pos.x & 64) != 0)
{
column.x -= 32;
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((uniform.psm & 0x30) != 0) // PSMZ16S - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 16;
}
}
else // PSMCT16
{
if ((pos.y & 32) != 0)
{
column.y -= 16;
column.x += 32;
}
if ((pos.x & 96) != 0)
{
uint multi = (pos.x & 96) / 32;
column.y += 16 * multi; // 4 columns high times 4 to get bottom 4 blocks
column.x -= (pos.x & 96);
}
if (((pos.x & 16) != 0) != ((pos.y & 16) != 0))
{
column.x ^= 16;
column.y ^= 8;
}
if ((uniform.psm & 0x30) != 0) // PSMZ16 - Untested but hopefully ok if anything uses it.
{
column.x ^= 32;
column.y ^= 32;
}
}
uint2 coord = column | subcolumn;
// Compensate for potentially differing page pitch.
uint2 block_xy = coord / uint2(64, 64);
uint block_num = (block_xy.y * (uniform.dbw / 128)) + block_xy.x;
uint2 block_offset = uint2((block_num % (uniform.sbw / 64)) * 64, (block_num / (uniform.sbw / 64)) * 64);
coord = (coord % uint2(64, 64)) + block_offset;
// Apply offset to cols 1 and 2
uint is_col23 = pos.y & 4;
uint is_col13 = pos.y & 2;
uint is_col12 = is_col23 ^ (is_col13 << 1);
coord.x ^= is_col12; // If cols 1 or 2, flip bit 3 of x
if (any(floor(uniform.scale) != uniform.scale))
coord = uint2(float2(coord) * uniform.scale);
else
coord = mul24(coord, uint2(uniform.scale));
float4 pixel = res.tex.read(coord);
uint4 denorm_c = (uint4)(pixel * 255.5f);
if ((pos.y & 2u) == 0u)
{
uint red = (denorm_c.r >> 3) & 0x1Fu;
uint green = (denorm_c.g >> 3) & 0x1Fu;
float sel0 = (float)(((green << 5) | red) & 0xFF) / 255.0f;
return float4(sel0);
}
else
{
uint green = (denorm_c.g >> 3) & 0x1Fu;
uint blue = (denorm_c.b >> 3) & 0x1Fu;
uint alpha = denorm_c.a & 0x80u;
float sel0 = (float)((alpha | (blue << 2) | (green >> 3)) & 0xFF) / 255.0f;
return float4(sel0);
}
}
fragment float4 ps_convert_rgba_8i(ConvertShaderData data [[stage_in]], DirectReadTextureIn<float> res,
constant GSMTLIndexedConvertPSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{

View File

@@ -350,10 +350,11 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
return false;
m_convert.ps[i].SetFormattedName("Convert pipe %s", name);
if (static_cast<ShaderConvert>(i) == ShaderConvert::RGBA_TO_8I)
if (static_cast<ShaderConvert>(i) == ShaderConvert::RGBA_TO_8I || static_cast<ShaderConvert>(i) == ShaderConvert::RGB5A1_TO_8I)
{
m_convert.ps[i].RegisterUniform("SBW");
m_convert.ps[i].RegisterUniform("DBW");
m_convert.ps[i].RegisterUniform("PSM");
m_convert.ps[i].RegisterUniform("ScaleFactor");
}
else if (static_cast<ShaderConvert>(i) == ShaderConvert::YUV)
@@ -1594,12 +1595,13 @@ void GSDeviceOGL::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 off
{
CommitClear(sTex, false);
const ShaderConvert shader = ShaderConvert::RGBA_TO_8I;
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
GLProgram& prog = m_convert.ps[static_cast<int>(shader)];
prog.Bind();
prog.Uniform1ui(0, SBW);
prog.Uniform1ui(1, DBW);
prog.Uniform1f(2, sScale);
prog.Uniform1ui(2, SPSM);
prog.Uniform1f(3, sScale);
OMSetDepthStencilState(m_convert.dss);
OMSetBlendState(false);

View File

@@ -2059,6 +2059,12 @@ bool GSDeviceVK::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
return false;
}
if (!CreateNullTexture())
{
Host::ReportErrorAsync("GS", "Failed to create dummy texture");
return false;
}
{
std::optional<std::string> shader = ReadShaderSource("shaders/vulkan/tfx.glsl");
if (!shader.has_value())
@@ -2070,12 +2076,6 @@ bool GSDeviceVK::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
m_tfx_source = std::move(*shader);
}
if (!CreateNullTexture())
{
Host::ReportErrorAsync("GS", "Failed to create dummy texture");
return false;
}
if (!CreatePipelineLayouts())
{
Host::ReportErrorAsync("GS", "Failed to create pipeline layouts");
@@ -3158,15 +3158,16 @@ void GSDeviceVK::ConvertToIndexedTexture(
{
u32 SBW;
u32 DBW;
u32 pad1[2];
u32 PSM;
u32 pad1[1];
float ScaleFactor;
float pad2[3];
};
const Uniforms uniforms = {SBW, DBW, {}, sScale, {}};
const Uniforms uniforms = {SBW, DBW, SPSM, {}, sScale, {}};
SetUtilityPushConstants(&uniforms, sizeof(uniforms));
const ShaderConvert shader = ShaderConvert::RGBA_TO_8I;
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
DoStretchRect(static_cast<GSTextureVK*>(sTex), GSVector4::zero(), static_cast<GSTextureVK*>(dTex), dRect,
m_convert[static_cast<int>(shader)], false, true);
@@ -5291,8 +5292,12 @@ void GSDeviceVK::SetPipeline(VkPipeline pipeline)
void GSDeviceVK::SetInitialState(VkCommandBuffer cmdbuf)
{
const VkDeviceSize buffer_offset = 0;
vkCmdBindVertexBuffers(cmdbuf, 0, 1, m_vertex_stream_buffer.GetBufferPtr(), &buffer_offset);
VkBuffer buffer = *m_vertex_stream_buffer.GetBufferPtr();
if (buffer != VK_NULL_HANDLE)
{
constexpr VkDeviceSize buffer_offset = 0;
vkCmdBindVertexBuffers(cmdbuf, 0, 1, &buffer, &buffer_offset);
}
}
__ri void GSDeviceVK::ApplyBaseState(u32 flags, VkCommandBuffer cmdbuf)

View File

@@ -67,6 +67,12 @@ namespace Host
bool ConfirmMessage(const std::string_view title, const std::string_view message);
bool ConfirmFormattedMessage(const std::string_view title, const char* format, ...);
/// Sets batch mode (exit after game shutdown).
bool InBatchMode();
/// Sets NoGUI mode (implys batch mode, does not display main window, exits on shutdown).
bool InNoGUIMode();
/// Opens a URL, using the default application.
void OpenURL(const std::string_view url);

View File

@@ -857,7 +857,7 @@ void AudioStreamParameters::LoadSave(SettingsWrapper& wrap, const char* section)
wrap.EnumEntry(section, "ExpansionMode", expansion_mode, &AudioStream::ParseExpansionMode, &AudioStream::GetExpansionModeName, DEFAULT_EXPANSION_MODE);
minimal_output_latency = wrap.EntryBitBool(section, "OutputLatencyMinimal", DEFAULT_OUTPUT_LATENCY_MINIMAL);
buffer_ms = static_cast<u16>(std::clamp<int>(wrap.EntryBitfield(section, "BufferMS", buffer_ms, DEFAULT_BUFFER_MS), 0, std::numeric_limits<u16>::max()));
output_latency_ms = static_cast<u16>(std::clamp<int>(wrap.EntryBitfield(section, "OutputLatencyMS", buffer_ms, DEFAULT_OUTPUT_LATENCY_MS), 0, std::numeric_limits<u16>::max()));
output_latency_ms = static_cast<u16>(std::clamp<int>(wrap.EntryBitfield(section, "OutputLatencyMS", output_latency_ms, DEFAULT_OUTPUT_LATENCY_MS), 0, std::numeric_limits<u16>::max()));
stretch_sequence_length_ms = static_cast<u16>(std::clamp<int>(wrap.EntryBitfield(section, "StretchSequenceLengthMS", DEFAULT_STRETCH_SEQUENCE_LENGTH), 0, std::numeric_limits<u16>::max()));
stretch_seekwindow_ms = static_cast<u16>(std::clamp<int>(wrap.EntryBitfield(section, "StretchSeekWindowMS", DEFAULT_STRETCH_SEEKWINDOW), 0, std::numeric_limits<u16>::max()));

View File

@@ -139,6 +139,7 @@ using ImGuiFullscreen::GetCachedTextureAsync;
using ImGuiFullscreen::GetPlaceholderTexture;
using ImGuiFullscreen::GetQueuedFocusResetType;
using ImGuiFullscreen::HorizontalMenuItem;
using ImGuiFullscreen::HorizontalMenuSvgItem;
using ImGuiFullscreen::IsFocusResetQueued;
using ImGuiFullscreen::IsGamepadInputSource;
using ImGuiFullscreen::LayoutScale;
@@ -271,8 +272,6 @@ namespace FullscreenUI
static std::array<std::shared_ptr<GSTexture>, static_cast<u32>(GameDatabaseSchema::Compatibility::Perfect)>
s_game_compatibility_textures;
static std::shared_ptr<GSTexture> s_banner_texture;
static std::shared_ptr<GSTexture> s_fallback_disc_texture;
static std::shared_ptr<GSTexture> s_fallback_exe_texture;
static std::vector<std::unique_ptr<GSTexture>> s_cleanup_textures;
//////////////////////////////////////////////////////////////////////////
@@ -476,9 +475,13 @@ namespace FullscreenUI
static void DrawGameListSettingsWindow();
static void SwitchToGameList();
static void PopulateGameListEntryList();
static GSTexture* GetTextureForGameListEntryType(GameList::EntryType type);
static GSTexture* GetTextureForGameListEntryType(GameList::EntryType type, const ImVec2& size, SvgScaling mode = SvgScaling::Stretch);
static GSTexture* GetGameListCover(const GameList::Entry* entry);
static GSTexture* GetCoverForCurrentGame();
static void DrawGameCover(const GameList::Entry* entry, const ImVec2& size);
static void DrawGameCover(const GameList::Entry* entry, ImDrawList* draw_list, const ImVec2& min, const ImVec2& max);
// For when we have no GameList entry
static void DrawFallbackCover(const ImVec2& size);
static void DrawFallbackCover(ImDrawList* draw_list, const ImVec2& min, const ImVec2& max);
// Lazily populated cover images.
static std::unordered_map<std::string, std::string> s_cover_image_map;
@@ -1107,9 +1110,6 @@ void FullscreenUI::ReturnToMainWindow()
bool FullscreenUI::LoadResources()
{
s_fallback_disc_texture = LoadTexture("fullscreenui/media-cdrom.png");
s_fallback_exe_texture = LoadTexture("fullscreenui/applications-system.png");
return LoadSvgResources();
}
@@ -1128,8 +1128,6 @@ bool FullscreenUI::LoadSvgResources()
void FullscreenUI::DestroyResources()
{
s_fallback_exe_texture.reset();
s_fallback_disc_texture.reset();
s_banner_texture.reset();
for (auto& tex : s_game_compatibility_textures)
tex.reset();
@@ -1457,27 +1455,26 @@ void FullscreenUI::DrawLandingWindow()
{
ResetFocusHere();
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/game-list.png"), FSUI_CSTR("Game List"),
if (HorizontalMenuSvgItem("fullscreenui/game-list.svg", FSUI_CSTR("Game List"),
FSUI_CSTR("Launch a game from images scanned from your game directories.")))
{
SwitchToGameList();
}
if (HorizontalMenuItem(
GetCachedTexture("fullscreenui/media-cdrom.png"), FSUI_CSTR("Start Game"),
if (HorizontalMenuSvgItem("fullscreenui/media-cdrom.svg", FSUI_CSTR("Start Game"),
FSUI_CSTR("Launch a game from a file, disc, or starts the console without any disc inserted.")))
{
s_current_main_window = MainWindowType::StartGame;
QueueResetFocus(FocusResetType::WindowChanged);
}
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/applications-system.png"), FSUI_CSTR("Settings"),
if (HorizontalMenuSvgItem("fullscreenui/applications-system.svg", FSUI_CSTR("Settings"),
FSUI_CSTR("Changes settings for the application.")))
{
SwitchToSettings();
}
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit"),
if (HorizontalMenuSvgItem("fullscreenui/exit.svg", FSUI_CSTR("Exit"),
FSUI_CSTR("Return to desktop mode, or exit the application.")) ||
(!AreAnyDialogsOpen() && WantsToCloseMenu()))
{
@@ -1536,26 +1533,25 @@ void FullscreenUI::DrawStartGameWindow()
{
ResetFocusHere();
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-file.png"), FSUI_CSTR("Start File"),
if (HorizontalMenuSvgItem("fullscreenui/start-file.svg", FSUI_CSTR("Start File"),
FSUI_CSTR("Launch a game by selecting a file/disc image.")))
{
DoStartFile();
}
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/drive-cdrom.png"), FSUI_CSTR("Start Disc"),
if (HorizontalMenuSvgItem("fullscreenui/drive-cdrom.svg", FSUI_CSTR("Start Disc"),
FSUI_CSTR("Start a game from a disc in your PC's DVD drive.")))
{
DoStartDisc();
}
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-bios.png"), FSUI_CSTR("Start BIOS"),
if (HorizontalMenuSvgItem("fullscreenui/start-bios.svg", FSUI_CSTR("Start BIOS"),
FSUI_CSTR("Start the console without any disc inserted.")))
{
DoStartBIOS();
}
// https://www.iconpacks.net/free-icon/arrow-back-3783.html
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"),
if (HorizontalMenuSvgItem("fullscreenui/back-icon.svg", FSUI_CSTR("Back"),
FSUI_CSTR("Return to the previous menu.")) ||
(!AreAnyDialogsOpen() && WantsToCloseMenu()))
{
@@ -1602,12 +1598,11 @@ void FullscreenUI::DrawExitWindow()
ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor);
if (BeginHorizontalMenu("exit_window", menu_pos, menu_size, 3))
if (BeginHorizontalMenu("exit_window", menu_pos, menu_size, (Host::InNoGUIMode()) ? 2 : 3))
{
ResetFocusHere();
// https://www.iconpacks.net/free-icon/arrow-back-3783.html
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"),
if (HorizontalMenuSvgItem("fullscreenui/back-icon.svg", FSUI_CSTR("Back"),
FSUI_CSTR("Return to the previous menu.")) ||
WantsToCloseMenu())
{
@@ -1615,16 +1610,19 @@ void FullscreenUI::DrawExitWindow()
QueueResetFocus(FocusResetType::WindowChanged);
}
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit PCSX2"),
if (HorizontalMenuSvgItem("fullscreenui/exit.svg", FSUI_CSTR("Exit PCSX2"),
FSUI_CSTR("Completely exits the application, returning you to your desktop.")))
{
DoRequestExit();
}
if (HorizontalMenuItem(GetCachedTexture("fullscreenui/desktop-mode.png"), FSUI_CSTR("Desktop Mode"),
FSUI_CSTR("Exits Big Picture mode, returning to the desktop interface.")))
if (!Host::InNoGUIMode())
{
DoDesktopMode();
if (HorizontalMenuSvgItem("fullscreenui/desktop-mode.svg", FSUI_CSTR("Desktop Mode"),
FSUI_CSTR("Exits Big Picture mode, returning to the desktop interface.")))
{
DoDesktopMode();
}
}
}
EndHorizontalMenu();
@@ -3559,7 +3557,7 @@ void FullscreenUI::DrawInterfaceSettingsPage()
MenuHeading(FSUI_CSTR("Integration"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_USER_CIRCLE, "Enable Discord Presence"),
FSUI_CSTR("Shows the game you are currently playing as part of your profile on Discord."), "UI", "DiscordPresence", false);
FSUI_CSTR("Shows the game you are currently playing as part of your profile on Discord."), "EmuCore", "EnableDiscordPresence", false);
MenuHeading(FSUI_CSTR("Game Display"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TV, "Start Fullscreen"),
@@ -5460,14 +5458,18 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
}
DrawShadowedText(dl, g_medium_font, subtitle_pos, text_color, s_current_game_subtitle.c_str());
GSTexture* const cover = GetCoverForCurrentGame();
const ImVec2 image_min(display_size.x - LayoutScale(10.0f + image_width),
display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height) - rp_height);
const ImVec2 image_max(image_min.x + LayoutScale(image_width), image_min.y + LayoutScale(image_height) + rp_height);
const ImRect image_rect(CenterImage(
ImRect(image_min, image_max), ImVec2(static_cast<float>(cover->GetWidth()), static_cast<float>(cover->GetHeight()))));
dl->AddImage(reinterpret_cast<ImTextureID>(cover->GetNativeHandle()), image_rect.Min, image_rect.Max);
{
auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(s_current_disc_path.c_str());
if (entry)
DrawGameCover(entry, dl, image_min, image_max);
else
DrawFallbackCover(dl, image_min, image_max);
}
}
// current time / play time
@@ -6283,7 +6285,7 @@ void FullscreenUI::PopulateGameListEntryList()
case 4: // CRC
{
if (lhs->crc != rhs->crc)
return reverse ? (lhs->crc >= rhs->crc) : (lhs->crc < rhs->crc);
return reverse ? (lhs->crc > rhs->crc) : (lhs->crc < rhs->crc);
}
break;
@@ -6455,8 +6457,6 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
if (!visible)
continue;
GSTexture* cover_texture = GetGameListCover(entry);
summary.clear();
if (entry->serial.empty())
fmt::format_to(std::back_inserter(summary), "{} - ", GameList::RegionToString(entry->region, true));
@@ -6466,11 +6466,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
const std::string_view filename(Path::GetFileName(entry->path));
summary.append(filename);
const ImRect image_rect(CenterImage(ImRect(bb.Min, bb.Min + image_size),
ImVec2(static_cast<float>(cover_texture->GetWidth()), static_cast<float>(cover_texture->GetHeight()))));
ImGui::GetWindowDrawList()->AddImage(reinterpret_cast<ImTextureID>(cover_texture->GetNativeHandle()),
image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
DrawGameCover(entry, ImGui::GetWindowDrawList(), bb.Min, bb.Min + image_size);
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f);
@@ -6511,18 +6507,13 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
if (BeginFullscreenColumnWindow(-530.0f, 0.0f, "game_list_info", UIPrimaryDarkColor))
{
const GSTexture* cover_texture =
selected_entry ? GetGameListCover(selected_entry) : GetTextureForGameListEntryType(GameList::EntryType::Count);
if (cover_texture)
{
const ImRect image_rect(CenterImage(LayoutScale(ImVec2(275.0f, 400.0f)),
ImVec2(static_cast<float>(cover_texture->GetWidth()), static_cast<float>(cover_texture->GetHeight()))));
const ImVec2 image_size = LayoutScale(ImVec2(275.0f, 400.0f));
ImGui::SetCursorPos(LayoutScale(ImVec2(128.0f, 20.0f)));
ImGui::SetCursorPos(LayoutScale(ImVec2(128.0f, 20.0f)) + image_rect.Min);
ImGui::Image(reinterpret_cast<ImTextureID>(selected_entry ? GetGameListCover(selected_entry)->GetNativeHandle() :
GetTextureForGameListEntryType(GameList::EntryType::Count)->GetNativeHandle()),
image_rect.GetSize());
}
if (selected_entry)
DrawGameCover(selected_entry, image_size);
else
DrawFallbackCover(image_size);
const float work_width = ImGui::GetCurrentWindow()->WorkRect.GetWidth();
constexpr float field_margin_y = 10.0f;
@@ -6690,12 +6681,7 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size)
bb.Min += style.FramePadding;
bb.Max -= style.FramePadding;
const GSTexture* const cover_texture = GetGameListCover(entry);
const ImRect image_rect(CenterImage(ImRect(bb.Min, bb.Min + image_size),
ImVec2(static_cast<float>(cover_texture->GetWidth()), static_cast<float>(cover_texture->GetHeight()))));
ImGui::GetWindowDrawList()->AddImage(reinterpret_cast<ImTextureID>(cover_texture->GetNativeHandle()),
image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
DrawGameCover(entry, ImGui::GetWindowDrawList(), bb.Min, bb.Min + image_size);
const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max);
const std::string_view title(std::string_view(entry->GetTitle(true)).substr(0, 31));
@@ -6984,33 +6970,104 @@ GSTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry)
cover_it = s_cover_image_map.emplace(entry->path, std::move(cover_path)).first;
}
GSTexture* tex = (!cover_it->second.empty()) ? GetCachedTextureAsync(cover_it->second.c_str()) : nullptr;
return tex ? tex : GetTextureForGameListEntryType(entry->type);
return (!cover_it->second.empty()) ? GetCachedTextureAsync(cover_it->second.c_str()) : nullptr;
}
GSTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::EntryType type)
GSTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::EntryType type, const ImVec2& size, SvgScaling mode)
{
switch (type)
{
case GameList::EntryType::ELF:
return s_fallback_exe_texture.get();
return GetCachedSvgTexture("fullscreenui/applications-system.svg", size, mode);
case GameList::EntryType::PS1Disc:
case GameList::EntryType::PS2Disc:
default:
return s_fallback_disc_texture.get();
return GetCachedSvgTexture("fullscreenui/media-cdrom.svg", size, mode);
}
}
GSTexture* FullscreenUI::GetCoverForCurrentGame()
void FullscreenUI::DrawGameCover(const GameList::Entry* entry, const ImVec2& size)
{
auto lock = GameList::GetLock();
// Used in DrawGameList (selected preview)
const GSTexture* cover_texture = GetGameListCover(entry);
const GameList::Entry* entry = GameList::GetEntryForPath(s_current_disc_path.c_str());
if (!entry)
return s_fallback_disc_texture.get();
pxAssert(ImGui::GetCurrentContext()->Style.ImageBorderSize == 0);
const ImVec2 origin = ImGui::GetCursorPos();
return GetGameListCover(entry);
if (cover_texture)
{
const ImRect image_rect(CenterImage(size,
ImVec2(static_cast<float>(cover_texture->GetWidth()), static_cast<float>(cover_texture->GetHeight()))));
ImGui::SetCursorPos(origin + image_rect.Min);
ImGui::Image(reinterpret_cast<ImTextureID>(cover_texture->GetNativeHandle()), image_rect.GetSize());
}
else
{
const float min_size = std::min(size.x, size.y);
const ImVec2 image_square(min_size, min_size);
GSTexture* const icon_texture = GetTextureForGameListEntryType(entry->type, image_square);
const ImRect image_rect(CenterImage(size, image_square));
DrawSvgTexture(icon_texture, image_square);
}
// Pretend the image we drew was the the size passed to us
ImGui::SetCursorPos(origin);
ImGui::Dummy(size);
}
void FullscreenUI::DrawGameCover(const GameList::Entry* entry, ImDrawList* draw_list, const ImVec2& min, const ImVec2& max)
{
// Used in DrawPauseMenu, DrawGameList (list item), DrawGameGrid
const GSTexture* cover_texture = GetGameListCover(entry);
if (cover_texture)
{
const ImRect image_rect(CenterImage(ImRect(min, max),
ImVec2(static_cast<float>(cover_texture->GetWidth()), static_cast<float>(cover_texture->GetHeight()))));
draw_list->AddImage(reinterpret_cast<ImTextureID>(cover_texture->GetNativeHandle()),
image_rect.Min, image_rect.Max);
}
else
{
const float min_size = std::min(max.x - min.x, max.y - min.y);
const ImVec2 image_square(min_size, min_size);
const ImRect image_rect(CenterImage(ImRect(min, max), image_square));
DrawListSvgTexture(draw_list, GetTextureForGameListEntryType(entry->type, image_square, SvgScaling::Fit),
image_rect.Min, image_rect.Max);
}
}
void FullscreenUI::DrawFallbackCover(const ImVec2& size)
{
pxAssert(ImGui::GetCurrentContext()->Style.ImageBorderSize == 0);
const ImVec2 origin = ImGui::GetCursorPos();
const float min_size = std::min(size.x, size.y);
const ImVec2 image_square(min_size, min_size);
GSTexture* const icon_texture = GetTextureForGameListEntryType(GameList::EntryType::PS2Disc, image_square);
const ImRect image_rect(CenterImage(size, image_square));
DrawSvgTexture(icon_texture, image_square);
// Pretend the image we drew was the the size passed to us
ImGui::SetCursorPos(origin);
ImGui::Dummy(size);
}
void FullscreenUI::DrawFallbackCover(ImDrawList* draw_list, const ImVec2& min, const ImVec2& max)
{
const float min_size = std::min(max.x - min.x, max.y - min.y);
const ImVec2 image_square(min_size, min_size);
const ImRect image_rect(CenterImage(ImRect(min, max), image_square));
DrawListSvgTexture(draw_list, GetTextureForGameListEntryType(GameList::EntryType::PS2Disc, image_square, SvgScaling::Fit),
image_rect.Min, image_rect.Max);
}
//////////////////////////////////////////////////////////////////////////

View File

@@ -1992,7 +1992,7 @@ void ImGuiFullscreen::EndHorizontalMenu()
EndFullscreenWindow();
}
bool ImGuiFullscreen::HorizontalMenuItem(GSTexture* icon, const char* title, const char* description)
bool ImGuiFullscreen::HorizontalMenuItem(GSTexture* icon, const ImVec2& icon_uv0, const ImVec2& icon_uv1, const char* title, const char* description)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
@@ -2031,7 +2031,7 @@ bool ImGuiFullscreen::HorizontalMenuItem(GSTexture* icon, const char* title, con
const ImVec2 icon_pos = bb.Min + ImVec2((avail_width - icon_size) * 0.5f, 0.0f);
ImDrawList* dl = ImGui::GetWindowDrawList();
dl->AddImage(reinterpret_cast<ImTextureID>(icon->GetNativeHandle()), icon_pos, icon_pos + ImVec2(icon_size, icon_size));
dl->AddImage(reinterpret_cast<ImTextureID>(icon->GetNativeHandle()), icon_pos, icon_pos + ImVec2(icon_size, icon_size), icon_uv0, icon_uv1);
ImFont* title_font = g_large_font;
const ImVec2 title_size = title_font->CalcTextSizeA(title_font->FontSize, avail_width, 0.0f, title);
@@ -2056,6 +2056,22 @@ bool ImGuiFullscreen::HorizontalMenuItem(GSTexture* icon, const char* title, con
return pressed;
}
bool ImGuiFullscreen::HorizontalMenuItem(GSTexture* icon, const char* title, const char* description)
{
return HorizontalMenuItem(icon, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), title, description);
}
bool ImGuiFullscreen::HorizontalMenuSvgItem(const char* svg_path, const char* title, const char* description, SvgScaling mode)
{
const ImVec2 icon_size = LayoutScale(150.0f, 150.0f);
GSTexture* padded_texture = GetCachedSvgTexture(svg_path, icon_size, mode);
const ImVec2 padded_size(padded_texture->GetWidth(), padded_texture->GetHeight());
const ImVec2 uv1 = icon_size / padded_size;
return HorizontalMenuItem(padded_texture, ImVec2(0.0f, 0.0f), uv1, title, description);
}
void ImGuiFullscreen::PopulateFileSelectorItems()
{
s_file_selector_items.clear();

View File

@@ -235,7 +235,9 @@ namespace ImGuiFullscreen
bool BeginHorizontalMenu(const char* name, const ImVec2& position, const ImVec2& size, u32 num_items);
void EndHorizontalMenu();
bool HorizontalMenuItem(GSTexture* icon, const ImVec2& icon_uv0, const ImVec2& icon_uv1, const char* title, const char* description);
bool HorizontalMenuItem(GSTexture* icon, const char* title, const char* description);
bool HorizontalMenuSvgItem(const char* svg_path, const char* title, const char* description, SvgScaling mode = SvgScaling::Stretch);
using FileSelectorCallback = std::function<void(const std::string& path)>;
using FileSelectorFilters = std::vector<std::string>;

View File

@@ -416,10 +416,10 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
APPEND("B={} PL={} ", static_cast<unsigned>(GSConfig.AccurateBlendingUnit), static_cast<unsigned>(GSConfig.TexturePreloading));
if (GSConfig.GPUPaletteConversion)
APPEND("PT ");
APPEND("PLTX ");
if (GSConfig.HWDownloadMode != GSHardwareDownloadMode::Enabled)
APPEND("DL={} ", static_cast<unsigned>(GSConfig.HWDownloadMode));
APPEND("HWDM={} ", static_cast<unsigned>(GSConfig.HWDownloadMode));
if (GSConfig.HWMipmap)
APPEND("MM ");
@@ -466,7 +466,7 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
if (GSConfig.UserHacks_CPUFBConversion)
APPEND("FBC ");
if (GSConfig.UserHacks_ReadTCOnClose)
APPEND("FTC ");
APPEND("RTOC ");
if (GSConfig.UserHacks_DisableDepthSupport)
APPEND("DDC ");
if (GSConfig.UserHacks_DisablePartialInvalidation)

View File

@@ -1615,7 +1615,7 @@ void InputManager::CloseSources()
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i]->IsInitialized())
if (s_input_sources[i] && s_input_sources[i]->IsInitialized())
{
s_input_sources[i]->Shutdown();
}

View File

@@ -21,6 +21,7 @@
#include <cstring>
#include <fmt/format.h>
#include <sys/stat.h>
#include <algorithm>
#include <fcntl.h>
@@ -641,7 +642,7 @@ namespace R3000A
for (size_t i = 0; i < handles.size(); i++)
{
if (handles[i].fd_index == (u32) fd - firstfd)
if (handles[i].fd_index == (u32)fd - firstfd)
{
handles.erase(handles.begin() + i);
break;
@@ -928,12 +929,6 @@ namespace R3000A
{
int Kprintf_HLE()
{
// Using sprintf here is a bit nasty, but it has a large buffer..
// Don't feel like rewriting it.
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
// Emulate the expected Kprintf functionality:
iopMemWrite32(sp, a0);
@@ -951,8 +946,12 @@ namespace R3000A
if (!ConsoleLogging.iopConsole.IsActive())
return 1;
char tmp[1024], tmp2[1024];
// maximum allowed size for our buffer before we truncate
const unsigned int max_len = 4096;
char tmp[max_len], tmp2[max_len];
char* ptmp = tmp;
unsigned int printed_bytes = 0;
int remaining_buf = max_len - 1;
int n = 1, i = 0, j = 0;
while (fmt[i])
@@ -963,21 +962,32 @@ namespace R3000A
j = 0;
tmp2[j++] = '%';
_start:
switch (fmt[++i])
// let's check whether this is our null terminator
// before allowing the parser to proceed
if (fmt[i + 1])
{
case '.':
case 'l':
tmp2[j++] = fmt[i];
goto _start;
default:
if (fmt[i] >= '0' && fmt[i] <= '9')
{
switch (fmt[++i])
{
case '.':
case 'l':
if (j >= max_len)
break;
tmp2[j++] = fmt[i];
goto _start;
}
break;
default:
if (fmt[i] >= '0' && fmt[i] <= '9')
{
if (j >= max_len)
break;
tmp2[j++] = fmt[i];
goto _start;
}
break;
}
}
if (j >= max_len)
break;
tmp2[j++] = fmt[i];
tmp2[j] = 0;
@@ -985,7 +995,9 @@ namespace R3000A
{
case 'f':
case 'F':
ptmp += sprintf(ptmp, tmp2, (float)iopMemRead32(sp + n * 4));
printed_bytes = std::min(remaining_buf, snprintf(ptmp, remaining_buf, tmp2, (float)iopMemRead32(sp + n * 4)));
remaining_buf -= printed_bytes;
ptmp += printed_bytes;
n++;
break;
@@ -995,7 +1007,9 @@ namespace R3000A
case 'E':
case 'g':
case 'G':
ptmp += sprintf(ptmp, tmp2, (double)iopMemRead32(sp + n * 4));
printed_bytes = std::min(remaining_buf, snprintf(ptmp, remaining_buf, tmp2, (double)iopMemRead32(sp + n * 4)));
remaining_buf -= printed_bytes;
ptmp += printed_bytes;
n++;
break;
@@ -1007,19 +1021,27 @@ namespace R3000A
case 'O':
case 'x':
case 'X':
ptmp += sprintf(ptmp, tmp2, (u32)iopMemRead32(sp + n * 4));
case 'u':
case 'U':
printed_bytes = std::min(remaining_buf, snprintf(ptmp, remaining_buf, tmp2, (u32)iopMemRead32(sp + n * 4)));
remaining_buf -= printed_bytes;
ptmp += printed_bytes;
n++;
break;
case 'c':
ptmp += sprintf(ptmp, tmp2, (u8)iopMemRead32(sp + n * 4));
printed_bytes = std::min(remaining_buf, snprintf(ptmp, remaining_buf, tmp2, (u8)iopMemRead32(sp + n * 4)));
remaining_buf -= printed_bytes;
ptmp += printed_bytes;
n++;
break;
case 's':
{
std::string s = iopMemReadString(iopMemRead32(sp + n * 4));
ptmp += sprintf(ptmp, tmp2, s.data());
printed_bytes = std::min(remaining_buf, snprintf(ptmp, remaining_buf, tmp2, s.data()));
remaining_buf -= printed_bytes;
ptmp += printed_bytes;
n++;
}
break;
@@ -1043,10 +1065,6 @@ namespace R3000A
iopConLog(ShiftJIS_ConvertString(tmp, 1023));
return 1;
#ifdef __clang__
#pragma clang diagnostic pop
#endif
}
} // namespace sysmem

View File

@@ -1898,6 +1898,7 @@ Pcsx2Config::Pcsx2Config()
UseSavestateSelector = true;
BackupSavestate = true;
WarnAboutUnsafeSettings = true;
EnableDiscordPresence = false;
ManuallySetRealTimeClock = false;
// To be moved to FileMemoryCard pluign (someday)
@@ -2083,17 +2084,16 @@ void Pcsx2Config::ClearInvalidPerGameConfiguration(SettingsInterface* si)
void EmuFolders::SetAppRoot()
{
std::string program_path = FileSystem::GetProgramPath();
Console.WriteLnFmt("Program Path: {}", program_path);
AppRoot = Path::Canonicalize(Path::GetDirectory(program_path));
#ifdef __APPLE__
const auto bundle_path = CocoaTools::GetNonTranslocatedBundlePath();
if (bundle_path.has_value())
{
// On macOS, override with the bundle path if launched from a bundle.
program_path = bundle_path.value();
AppRoot = StringUtil::EndsWithNoCase(*bundle_path, ".app") ? Path::GetDirectory(*bundle_path) : *bundle_path;
}
#endif
Console.WriteLnFmt("Program Path: {}", program_path);
AppRoot = Path::Canonicalize(Path::GetDirectory(program_path));
// logging of directories in case something goes wrong super early
Console.WriteLnFmt("AppRoot Directory: {}", AppRoot);
@@ -2110,8 +2110,10 @@ bool EmuFolders::SetResourcesDirectory()
#endif
#else
// On macOS, this is in the bundle resources directory.
const std::string program_path = FileSystem::GetProgramPath();
Resources = Path::Canonicalize(Path::Combine(Path::GetDirectory(program_path), "../Resources"));
if (auto resources = CocoaTools::GetResourcePath())
Resources = *resources;
else
Resources = Path::Combine(AppRoot, "resources");
#endif
Console.WriteLnFmt("Resources Directory: {}", Resources);

View File

@@ -25,7 +25,7 @@ enum class FreezeAction
// [SAVEVERSION+]
// This informs the auto updater that the users savestates will be invalidated.
static const u32 g_SaveVersion = (0x9A53 << 16) | 0x0000;
static const u32 g_SaveVersion = (0x9A54 << 16) | 0x0000;
// the freezing data between submodules and core

View File

@@ -3,4 +3,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
static constexpr u32 SHADER_CACHE_VERSION = 65;
static constexpr u32 SHADER_CACHE_VERSION = 67;

View File

@@ -56,6 +56,16 @@ void Host::OpenURL(const std::string_view url)
{
}
bool Host::InBatchMode()
{
return false;
}
bool Host::InNoGUIMode()
{
return false;
}
bool Host::CopyTextToClipboard(const std::string_view text)
{
return false;