Compare commits

...
Author SHA1 Message Date
refractionpcsx2 abb32d8d84 GS: Replace a bunch of pointer logic in hot paths 2026-07-18 05:40:43 +01:00
refractionpcsx2 e5e1153719 GS: Reduce draw buffer copying 2026-07-17 22:27:34 +01:00
Ziemasandlightningterror 86d76bbf59 GameDB: Patch The Gift to fix sound RPC 2026-07-16 16:45:38 +02:00
refractionpcsx2andlightningterror 45fa6a8bb5 GS: Add special case CSM2 read for Breath of Fire Dragon Quarter 2026-07-16 16:44:29 +02:00
SternXDandlightningterror 223c2cba9a ImGui: Fix cut off legend in save state overlay 2026-07-16 16:42:02 +02:00
TJnotJTandlightningterror 562c32cd15 GS:HW: Fix ROV texture type conversion for temporary Z. 2026-07-16 16:41:31 +02:00
SternXDandlightningterror 876a0bfbd7 FullscreenUI: Fix nav loss after logging in to RA 2026-07-16 16:40:36 +02:00
chaoticgdandlightningterror 81558dc9c4 Qt: Silence warning when opening log window 2026-07-16 16:39:57 +02:00
PCSX2 BotandTy 2d62078b04 [ci skip] Qt: Update Base Translation. 2026-07-15 20:29:09 -04:00
RedPanda4552andlightningterror 474ad59818 USB: Copy Virtua Cop offsets to Re-Birth JP version 2026-07-15 16:23:29 +02:00
refractionpcsx2andlightningterror d75c8b1edc GS/TC: Invalidate rgb + alpha on contained target swizzle mismatch 2026-07-15 14:35:47 +02:00
refractionpcsx2andlightningterror d89b4b6fa1 GS/TC: Improve handling of different format overlap in preload 2026-07-15 14:35:47 +02:00
refractionpcsx2andlightningterror afb875f2c6 GS/HW: Improve clears when half is zero + alter mismatched DHC lookup
- DHC is double half clear :)
2026-07-15 14:35:47 +02:00
refractionpcsx2andlightningterror f24d44d470 GS: Make size macros const expressions 2026-07-15 14:35:47 +02:00
refractionpcsx2andlightningterror 4918b0aa09 GS/TC: Don't clear valid flags if render target is only partially cleared 2026-07-15 14:35:47 +02:00
a50ed4d05a GS:HW: Fix regression with prefer_reuse texture fetching flag.
Boolean value of the flag was not properly inverted in a refactor.

Co-authored-by: t654132
2026-07-15 14:34:40 +02:00
refractionpcsx2andlightningterror 19d08ba94f GS/TC: Only bilinear resize depth if downscaled 2026-07-15 14:34:02 +02:00
PCSX2 BotandTy a80b3144bb [ci skip] Qt: Update Base Translation. 2026-07-14 20:35:45 -04:00
chaoticgdandlightningterror 9f0206966b Debugger: Add navigation history with back and forward buttons 2026-07-15 00:29:33 +02:00
chaoticgdandlightningterror dcae14398a Debugger: Improve menu item sorting 2026-07-15 00:29:33 +02:00
TheLastRarandlightningterror 14164e6592 GS/OGL: Increase padding to fill ProgramSelector size 2026-07-15 00:27:42 +02:00
chaoticgdandGovanifY 873977d0b8 Debugger: Improve layout editor dialog 2026-07-14 20:00:52 +02:00
TundrakandGovanifY 78319ef0ba Pad: Fix enum entries check 2026-07-14 18:10:25 +02:00
Bobby SmithandTy d1beb6be9a USB: Add GunCon 2 values for the Japanese versions of Time Crisis II 2026-07-14 08:36:28 -04:00
PCSX2 Botandlightningterror 5df1662c1b [ci skip] Qt: Update Base Translation. 2026-07-13 23:45:30 +02:00
refractionpcsx2andlightningterror 45b8338f6d GS/HW: Adjust shuffle predetect for only texture formats under 32bit 2026-07-13 23:45:15 +02:00
lightningterror 92914cee40 GS: Bump shader cache version. 2026-07-12 21:43:59 +02:00
RedPanda4552andlightningterror 44079ebbe2 [ci skip] GitIgnore: Ignore Kate project files 2026-07-12 21:28:21 +02:00
refractionpcsx2andlightningterror 8d2d9ebb1d GS: Move readback image dump to Transfer Images parameter from Save RT 2026-07-12 21:27:13 +02:00
TJnotJTandlightningterror 4f62bff866 GS:DX:VK: Always make null texture with UAV usage. 2026-07-12 21:25:53 +02:00
TJnotJTandlightningterror 8510ec29ba GS/DX: Fix typo with primid init output. 2026-07-12 21:20:28 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cde272055f Deps/gha: Bump the ci-deps group across 1 directory with 2 updates (#14644)
Bumps the ci-deps group with 2 updates in the / directory:
[actions/cache](https://github.com/actions/cache) and
[indygreg/apple-code-sign-action](https://github.com/indygreg/apple-code-sign-action).

Updates `actions/cache` from 5 to 6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/releases">actions/cache's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update packages, migrate to ESM by <a
href="https://github.com/Samirat"><code>@​Samirat</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1760">actions/cache#1760</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v6.0.0">https://github.com/actions/cache/compare/v5...v6.0.0</a></p>
<h2>v5.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@​actions/cache</code> to v5.1.0 - handle read-only cache
access by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1775">actions/cache#1775</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.1.0">https://github.com/actions/cache/compare/v5...v5.1.0</a></p>
<h2>v5.0.5</h2>
<h2>What's Changed</h2>
<ul>
<li>Update ts-http-runtime dependency by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1747">actions/cache#1747</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.5">https://github.com/actions/cache/compare/v5...v5.0.5</a></p>
<h2>v5.0.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Add release instructions and update maintainer docs by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1696">actions/cache#1696</a></li>
<li>Potential fix for code scanning alert no. 52: Workflow does not
contain permissions by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1697">actions/cache#1697</a></li>
<li>Fix workflow permissions and cleanup workflow names / formatting by
<a href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1699">actions/cache#1699</a></li>
<li>docs: Update examples to use the latest version by <a
href="https://github.com/XZTDean"><code>@​XZTDean</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li>
<li>Fix proxy integration tests by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1701">actions/cache#1701</a></li>
<li>Fix cache key in examples.md for bun.lock by <a
href="https://github.com/RyPeck"><code>@​RyPeck</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li>
<li>Update dependencies &amp; patch security vulnerabilities by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1738">actions/cache#1738</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/XZTDean"><code>@​XZTDean</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li>
<li><a href="https://github.com/RyPeck"><code>@​RyPeck</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.4">https://github.com/actions/cache/compare/v5...v5.0.4</a></p>
<h2>v5.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.3">https://github.com/actions/cache/compare/v5...v5.0.3</a></p>
<h2>v.5.0.2</h2>
<h1>v5.0.2</h1>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's
changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h2>How to prepare a release</h2>
<blockquote>
<p>[!NOTE]
Relevant for maintainers with write access only.</p>
</blockquote>
<ol>
<li>Switch to a new branch from <code>main</code>.</li>
<li>Run <code>npm test</code> to ensure all tests are passing.</li>
<li>Update the version in <a
href="https://github.com/actions/cache/blob/main/package.json"><code>https://github.com/actions/cache/blob/main/package.json</code></a>.</li>
<li>Run <code>npm run build</code> to update the compiled files.</li>
<li>Update this <a
href="https://github.com/actions/cache/blob/main/RELEASES.md"><code>https://github.com/actions/cache/blob/main/RELEASES.md</code></a>
with the new version and changes in the <code>## Changelog</code>
section.</li>
<li>Run <code>licensed cache</code> to update the license report.</li>
<li>Run <code>licensed status</code> and resolve any warnings by
updating the <a
href="https://github.com/actions/cache/blob/main/.licensed.yml"><code>https://github.com/actions/cache/blob/main/.licensed.yml</code></a>
file with the exceptions.</li>
<li>Commit your changes and push your branch upstream.</li>
<li>Open a pull request against <code>main</code> and get it reviewed
and merged.</li>
<li>Draft a new release <a
href="https://github.com/actions/cache/releases">https://github.com/actions/cache/releases</a>
use the same version number used in <code>package.json</code>
<ol>
<li>Create a new tag with the version number.</li>
<li>Auto generate release notes and update them to match the changes you
made in <code>RELEASES.md</code>.</li>
<li>Toggle the set as the latest release option.</li>
<li>Publish the release.</li>
</ol>
</li>
<li>Navigate to <a
href="https://github.com/actions/cache/actions/workflows/release-new-action-version.yml">https://github.com/actions/cache/actions/workflows/release-new-action-version.yml</a>
<ol>
<li>There should be a workflow run queued with the same version
number.</li>
<li>Approve the run to publish the new version and update the major tags
for this action.</li>
</ol>
</li>
</ol>
<h2>Changelog</h2>
<h3>6.1.0</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v6.1.0 to pick up <a
href="https://redirect.github.com/actions/toolkit/pull/2435">actions/toolkit#2435
Handle cache write error due to read-only token</a></li>
<li>Switch redundant &quot;Cache save failed&quot; warning to debug log
in save-only</li>
</ul>
<h3>6.0.0</h3>
<ul>
<li>Updated <code>@actions/cache</code> to ^6.0.1,
<code>@actions/core</code> to ^3.0.1, <code>@actions/exec</code> to
^3.0.0, <code>@actions/io</code> to ^3.0.2</li>
<li>Migrated to ESM module system</li>
<li>Upgraded Jest to v30 and test infrastructure to be ESM
compatible</li>
</ul>
<h3>5.0.4</h3>
<ul>
<li>Bump <code>minimatch</code> to v3.1.5 (fixes ReDoS via globstar
patterns)</li>
<li>Bump <code>undici</code> to v6.24.1 (WebSocket decompression bomb
protection, header validation fixes)</li>
<li>Bump <code>fast-xml-parser</code> to v5.5.6</li>
</ul>
<h3>5.0.3</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<h3>5.0.2</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/cache/commit/55cc8345863c7cc4c66a329aec7e433d2d1c52a9"><code>55cc834</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1768">#1768</a>
from jasongin/readonly-cache</li>
<li><a
href="https://github.com/actions/cache/commit/d8cd72f230726cdf4457ebb61ec1b593a8d12337"><code>d8cd72f</code></a>
Bump <code>@​actions/cache</code> to v6.1.0 - handle cache write error
due to RO token</li>
<li><a
href="https://github.com/actions/cache/commit/2c8a9bd7457de244a408f35966fab2fb45fda9c8"><code>2c8a9bd</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1760">#1760</a>
from actions/samirat/esm_migration_and_package_update</li>
<li><a
href="https://github.com/actions/cache/commit/e9b91fdc3fea7d79165fceb79042ef45c2d51023"><code>e9b91fd</code></a>
Prettier fixes</li>
<li><a
href="https://github.com/actions/cache/commit/e4884b8ff7f92ef6b52c79eda480bbc86e685adb"><code>e4884b8</code></a>
Rebuild dist</li>
<li><a
href="https://github.com/actions/cache/commit/10baf0191a3c426ea0fa4a3253a5c04233b6e18f"><code>10baf01</code></a>
Fixed licenses</li>
<li><a
href="https://github.com/actions/cache/commit/e39b386c9004d72a15d864ade8c0b3a702d47a37"><code>e39b386</code></a>
Fix test mock return order</li>
<li><a
href="https://github.com/actions/cache/commit/b6928203372a8571ff984c0c883ef3a1adfb0c06"><code>b692820</code></a>
PR feedback</li>
<li><a
href="https://github.com/actions/cache/commit/60749128a44d25d3c520a489e576380cf00ff3f1"><code>6074912</code></a>
Rebuild dist bundles as ESM to match type:module</li>
<li><a
href="https://github.com/actions/cache/commit/5a912e8b4af820fa082a0e75cfd2c782f8fbfe0e"><code>5a912e8</code></a>
Fix lint and jest issues</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/cache/compare/v5...v6">compare
view</a></li>
</ul>
</details>
<br />

Updates `indygreg/apple-code-sign-action` from 1.1 to 1.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/indygreg/apple-code-sign-action/releases">indygreg/apple-code-sign-action's
releases</a>.</em></p>
<blockquote>
<h2>v1.2</h2>
<ul>
<li>Use Node 24</li>
<li>Upgraded a bunch of JS packages and GitHub Actions to newer
versions.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/5e7a4a01fe4dffe948ef3bcf1f4538d963cb0c72"><code>5e7a4a0</code></a>
ci: try to get superlint working</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/7f0907c84f8b68c8f1984ff263d324c1cce7d5fe"><code>7f0907c</code></a>
Sync linter CI job with reference repo</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/ff12e78172ee6d6ef5795e283781716beafa3218"><code>ff12e78</code></a>
Remove exports from index.js</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/770da749199c68745f62c806c8f124d9a2460b18"><code>770da74</code></a>
More export fixes</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/d518541e903902dbfbbad25890ec19b27dffea15"><code>d518541</code></a>
Fix import in index.js</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/438214a5fbcb443aefaa10b3c9224f872cfe27fd"><code>438214a</code></a>
use <code>import</code> instead of <code>require</code></li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/264357680eaff3f84804a0f31edb9137b57bcc34"><code>2643576</code></a>
Modernize action</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/7c7d8b0a07e0a8550c0785566284e4acda0cf836"><code>7c7d8b0</code></a>
node: upgrade vercel/ncc to latest</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/36ea96e14b7799106808f4e2e6811b84482faa64"><code>36ea96e</code></a>
node: make semver compatible package updates</li>
<li><a
href="https://github.com/indygreg/apple-code-sign-action/commit/379f0ae50edc7e725f0a29cb6e5c9d90f911124a"><code>379f0ae</code></a>
build(deps): bump actions/checkout from 6 to 7 (<a
href="https://redirect.github.com/indygreg/apple-code-sign-action/issues/79">#79</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/indygreg/apple-code-sign-action/compare/44d0985b7f4363198e80b6fea63ac3e9dd3e9957...5e7a4a01fe4dffe948ef3bcf1f4538d963cb0c72">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-12 14:02:45 -04:00
55 changed files with 1177 additions and 545 deletions
+3
View File
@@ -6,6 +6,9 @@ updates:
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
commit-message:
# Prefix all commit messages with "Deps/gha: "
prefix: "Deps/gha"
groups:
ci-deps:
patterns:
+2 -2
View File
@@ -92,7 +92,7 @@ jobs:
run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT
- name: ccache cache files
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: .ccache
key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
@@ -114,7 +114,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/deps
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/linux/build-dependencies-qt.sh', '.github/workflows/scripts/common/*.patch') }}
+3 -3
View File
@@ -99,7 +99,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/deps
key: ${{ inputs.os }} ${{ inputs.arch }} deps ${{ hashFiles('.github/workflows/scripts/macos/*', '.github/workflows/scripts/common/*.patch') }}
@@ -120,7 +120,7 @@ jobs:
run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT
- name: Cache ccache cache
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: .ccache
key: ${{ inputs.os }} ${{ inputs.arch }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
@@ -176,7 +176,7 @@ jobs:
- name: Sign the Application
if: ${{ inputs.sign_and_notarize == true && env.SIGN_KEY }}
uses: indygreg/apple-code-sign-action@44d0985b7f4363198e80b6fea63ac3e9dd3e9957
uses: indygreg/apple-code-sign-action@5e7a4a01fe4dffe948ef3bcf1f4538d963cb0c72
with:
input_path: 'PCSX2.app'
p12_file: cert.p12
+1 -1
View File
@@ -122,7 +122,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: deps
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/windows/build-dependencies.bat', '.github/workflows/scripts/common/*.patch') }}
+3
View File
@@ -63,6 +63,9 @@ oprofile_data/
*.kdev4
/.kdev4*
# Kate Projects
.kateproject*
# Resources and docs in /bin are tracked
/bin/**/*.dll
/bin/**/*.dmp
+8
View File
@@ -15134,6 +15134,14 @@ SLES-50276:
name: "The Gift"
name-sort: "Gift, The"
region: "PAL-E"
patches:
CC76CD02:
content: |-
// Sound RPC races because of shared send/receive buffers with badly
// placed wait loop, fix by forcing all sound rpc to be synchronous.
// Ideal fix would be moving the rpc wait loop though.
author=Ziemas
patch=0,EE,002159e0,word,00000000
SLES-50277:
name: "Red Faction"
region: "PAL-E"
+1 -1
View File
@@ -198,7 +198,7 @@ struct PS_OUTPUT
#if PS_RETURN_COLOR
#if PS_DATE == 1 || PS_DATE == 2
float c : SV_Target;
float c0 : SV_Target;
#else
float4 c0 : SV_Target0;
+1
View File
@@ -196,6 +196,7 @@ target_sources(pcsx2-qt PRIVATE
Debugger/ModuleModel.h
Debugger/ModuleView.cpp
Debugger/ModuleView.h
Debugger/NavigationHistoryStack.h
Debugger/RegisterView.cpp
Debugger/RegisterView.h
Debugger/RegisterView.ui
+24 -1
View File
@@ -206,6 +206,29 @@ void DebuggerView::updateStyleSheet()
setStyleSheet(stylesheet);
}
bool DebuggerView::supportsNavigation()
{
return false;
}
bool DebuggerView::canNavigateBack()
{
return false;
}
bool DebuggerView::canNavigateForward()
{
return false;
}
void DebuggerView::navigateBack()
{
}
void DebuggerView::navigateForward()
{
}
void DebuggerView::goToInDisassembler(u32 address, bool switch_to_tab)
{
DebuggerEvents::GoToAddress event;
@@ -268,7 +291,7 @@ std::vector<QAction*> DebuggerView::createEventActionsImplementation(
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
return QtHost::LocaleSensitiveCompare(lhs->displayNameWithoutSuffix(), rhs->displayNameWithoutSuffix()) < 0;
});
QMenu* submenu = nullptr;
+6
View File
@@ -153,6 +153,12 @@ public:
void updateStyleSheet();
virtual bool supportsNavigation();
virtual bool canNavigateBack();
virtual bool canNavigateForward();
virtual void navigateBack();
virtual void navigateForward();
static void goToInDisassembler(u32 address, bool switch_to_tab);
static void goToInMemoryView(u32 address, bool switch_to_tab);
+37
View File
@@ -115,6 +115,28 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)
setMenuWidget(m_dock_manager->createMenuBar(menu_bar));
connect(m_dock_manager, &DockManager::focusedViewForNavigationChanged,
this, &DebuggerWindow::updateNavigationButtons);
m_ui.actionNavigateBack->setEnabled(false);
m_ui.actionNavigateForward->setEnabled(false);
connect(m_ui.actionNavigateBack, &QAction::triggered, this, [this]() {
DebuggerView* view = m_dock_manager->focusedViewForNavigation();
if (!view)
return;
view->navigateBack();
});
connect(m_ui.actionNavigateForward, &QAction::triggered, this, [this]() {
DebuggerView* view = m_dock_manager->focusedViewForNavigation();
if (!view)
return;
view->navigateForward();
});
updateTheme();
Host::RunOnCPUThread([]() {
@@ -307,6 +329,21 @@ void DebuggerWindow::updateFromSettings()
}
}
void DebuggerWindow::updateNavigationButtons()
{
DebuggerView* view = m_dock_manager->focusedViewForNavigation();
if (view)
{
m_ui.actionNavigateBack->setEnabled(view->canNavigateBack());
m_ui.actionNavigateForward->setEnabled(view->canNavigateForward());
}
else
{
m_ui.actionNavigateBack->setEnabled(false);
m_ui.actionNavigateForward->setEnabled(false);
}
}
void DebuggerWindow::onVMStarting()
{
m_ui.actionRun->setEnabled(true);
+3 -1
View File
@@ -40,7 +40,9 @@ public:
void updateFromSettings();
public slots:
void updateNavigationButtons();
private slots:
void onVMStarting();
void onVMPaused();
void onVMResumed();
+63 -16
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>750</height>
<width>1322</width>
<height>974</height>
</rect>
</property>
<property name="windowTitle">
@@ -23,8 +23,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>21</height>
<width>1322</width>
<height>30</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -58,6 +58,9 @@
</property>
<addaction name="actionOnTop"/>
<addaction name="separator"/>
<addaction name="actionNavigateBack"/>
<addaction name="actionNavigateForward"/>
<addaction name="separator"/>
<addaction name="actionIncreaseFontSize"/>
<addaction name="actionDecreaseFontSize"/>
<addaction name="actionResetFontSize"/>
@@ -82,12 +85,25 @@
<addaction name="menuWindows"/>
<addaction name="menuLayouts"/>
</widget>
<widget class="QToolBar" name="toolBarNavigation">
<property name="windowTitle">
<string>Navigation</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionNavigateBack"/>
<addaction name="actionNavigateForward"/>
</widget>
<widget class="QToolBar" name="toolBarDebug">
<property name="windowTitle">
<string>Debug</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
@@ -105,7 +121,7 @@
<string>File</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
@@ -122,7 +138,7 @@
<string>System</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
@@ -138,7 +154,7 @@
<string>View</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
@@ -237,7 +253,7 @@
<string>Shut Down</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionReset">
@@ -248,7 +264,7 @@
<string>Reset</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionClose">
@@ -259,7 +275,7 @@
<string>Close</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionIncreaseFontSize">
@@ -270,7 +286,7 @@
<string>Increase Font Size</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionDecreaseFontSize">
@@ -284,7 +300,7 @@
<string>Ctrl+-</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionResetFontSize">
@@ -295,7 +311,7 @@
<string>Reset Font Size</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSettings">
@@ -306,7 +322,7 @@
<string>Settings</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionGameSettings">
@@ -317,7 +333,7 @@
<string>Game Settings</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionToolsDummy">
@@ -330,6 +346,37 @@
<string/>
</property>
</action>
<action name="actionNavigateBack">
<property name="icon">
<iconset theme="arrow-left-line"/>
</property>
<property name="text">
<string>Navigate Back</string>
</property>
<property name="toolTip">
<string>Navigate Back</string>
</property>
<property name="shortcut">
<string>Alt+Left</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionNavigateForward">
<property name="icon">
<iconset theme="arrow-right-line"/>
</property>
<property name="text">
<string>Navigate Forward</string>
</property>
<property name="shortcut">
<string>Alt+Right</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<resources/>
<connections/>
+56 -7
View File
@@ -76,7 +76,11 @@ bool DisassemblyView::fromJson(const JsonValueWrapper& json)
auto start_address = json.value().FindMember("startAddress");
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
{
m_visibleStart = start_address->value.GetUint() & ~3;
m_navigation_history.clear();
m_navigation_history.pushInstantly(m_visibleStart);
}
auto go_to_pc_on_pause = json.value().FindMember("goToPCOnPause");
if (go_to_pc_on_pause != json.value().MemberEnd() && go_to_pc_on_pause->value.IsBool())
@@ -388,6 +392,41 @@ QString DisassemblyView::GetLineDisasm(u32 address)
return QString("%1 %2").arg(lineInfo.name.c_str()).arg(lineInfo.params.c_str());
};
bool DisassemblyView::supportsNavigation()
{
return true;
}
bool DisassemblyView::canNavigateBack()
{
return m_navigation_history.canGoBack();
}
bool DisassemblyView::canNavigateForward()
{
return m_navigation_history.canGoForward();
}
void DisassemblyView::navigateBack()
{
std::optional<u32> address = m_navigation_history.back();
if (!address.has_value())
return;
m_visibleStart = *address;
update();
}
void DisassemblyView::navigateForward()
{
std::optional<u32> address = m_navigation_history.forward();
if (!address.has_value())
return;
m_visibleStart = *address;
update();
}
// Here we go!
void DisassemblyView::paintEvent(QPaintEvent* event)
{
@@ -629,19 +668,21 @@ void DisassemblyView::mouseDoubleClickEvent(QMouseEvent* event)
void DisassemblyView::wheelEvent(QWheelEvent* event)
{
if (event->angleDelta().y() < 0) // todo: max address bounds check?
{
if (event->angleDelta().y() < 0)
m_visibleStart += 4;
}
else if (event->angleDelta().y() && m_visibleStart > 0)
{
m_visibleStart -= 4;
}
m_navigation_history.pushWithDelay(m_visibleStart);
update();
}
void DisassemblyView::keyPressEvent(QKeyEvent* event)
{
// Alt is used for the global navigation shortcuts.
if (event->modifiers() & Qt::AltModifier)
return;
switch (event->key())
{
case Qt::Key_Up:
@@ -653,15 +694,19 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
// Auto scroll
if (m_visibleStart > m_selectedAddressStart)
m_visibleStart -= 4;
m_navigation_history.pushWithDelay(m_visibleStart);
break;
}
break;
case Qt::Key_PageUp:
{
m_selectedAddressStart -= m_visibleRows * 4;
m_selectedAddressEnd = m_selectedAddressStart;
m_visibleStart -= m_visibleRows * 4;
m_navigation_history.pushWithDelay(m_visibleStart);
break;
}
break;
case Qt::Key_Down:
{
m_selectedAddressEnd += 4;
@@ -673,6 +718,7 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
if (m_visibleStart + ((m_visibleRows - 1) * 4) < m_selectedAddressEnd)
m_visibleStart += 4;
m_navigation_history.pushWithDelay(m_visibleStart);
break;
}
case Qt::Key_PageDown:
@@ -680,6 +726,7 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
m_selectedAddressStart += m_visibleRows * 4;
m_selectedAddressEnd = m_selectedAddressStart;
m_visibleStart += m_visibleRows * 4;
m_navigation_history.pushWithDelay(m_visibleStart);
break;
}
case Qt::Key_G:
@@ -1014,6 +1061,8 @@ void DisassemblyView::gotoAddress(u32 address, bool should_set_focus)
m_selectedAddressStart = destAddress;
m_selectedAddressEnd = destAddress;
m_navigation_history.pushInstantly(m_visibleStart);
update();
if (should_set_focus)
setFocus();
+13 -2
View File
@@ -5,7 +5,8 @@
#include "ui_DisassemblyView.h"
#include "DebuggerView.h"
#include "Debugger/DebuggerView.h"
#include "Debugger/NavigationHistoryStack.h"
#include "pcsx2/DebugTools/DisassemblyManager.h"
@@ -26,6 +27,12 @@ public:
// Required for the breakpoint list (ugh wtf)
QString GetLineDisasm(u32 address);
bool supportsNavigation() override;
bool canNavigateBack() override;
bool canNavigateForward() override;
void navigateBack() override;
void navigateForward() override;
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
@@ -68,7 +75,7 @@ public slots:
private:
Ui::DisassemblyView m_ui;
u32 m_visibleStart = 0x100000; // The address of the first instruction shown.
u32 m_visibleStart = STARTING_ADDRESS; // The address of the first instruction shown.
u32 m_visibleRows;
u32 m_selectedAddressStart = 0;
u32 m_selectedAddressEnd = 0;
@@ -81,6 +88,8 @@ private:
bool m_goToProgramCounterOnPause = true;
DisassemblyManager m_disassemblyManager;
NavigationHistoryStack<u32> m_navigation_history{STARTING_ADDRESS};
QString GetDisassemblyTitleLine();
QColor GetDisassemblyTitleLineColor();
inline QString DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected);
@@ -96,4 +105,6 @@ private:
void setInstructions(u32 start, u32 end, u32 value);
bool AddressCanRestore(u32 start, u32 end);
bool FunctionCanRestore(u32 address);
static constexpr u32 STARTING_ADDRESS = 0x100000;
};
+39 -8
View File
@@ -339,15 +339,28 @@ void DockManager::createToolsMenu(QMenu* menu)
if (m_current_layout == DockLayout::INVALID_INDEX || !g_debugger_window)
return;
for (QToolBar* widget : g_debugger_window->findChildren<QToolBar*>())
std::vector<QToolBar*> toolbars;
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
toolbars.emplace_back(toolbar);
std::sort(toolbars.begin(), toolbars.end(), [](QToolBar* lhs, QToolBar* rhs) {
return QtHost::LocaleSensitiveCompare(lhs->windowTitle(), rhs->windowTitle()) < 0;
});
for (QToolBar* toolbar : toolbars)
{
QAction* action = menu->addAction(widget->windowTitle());
action->setText(widget->windowTitle());
QAction* action = menu->addAction(toolbar->windowTitle());
action->setText(toolbar->windowTitle());
action->setCheckable(true);
action->setChecked(widget->isVisible());
connect(action, &QAction::triggered, this, [widget]() {
widget->setVisible(!widget->isVisible());
action->setChecked(toolbar->isVisible());
connect(action, &QAction::triggered, this, [toolbar = QPointer<QToolBar>(toolbar)]() {
if (!toolbar)
return;
toolbar->setVisible(!toolbar->isVisible());
});
menu->addAction(action);
}
}
@@ -383,7 +396,7 @@ void DockManager::createWindowsMenu(QMenu* menu)
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
return QtHost::LocaleSensitiveCompare(lhs->displayNameWithoutSuffix(), rhs->displayNameWithoutSuffix()) < 0;
});
for (DebuggerView* widget : add_another_widgets)
@@ -472,7 +485,7 @@ void DockManager::createWindowsMenu(QMenu* menu)
if (lhs.display_name == rhs.display_name)
return lhs.suffix_number < rhs.suffix_number;
return lhs.display_name < rhs.display_name;
return QtHost::LocaleSensitiveCompare(lhs.display_name, rhs.display_name) < 0;
});
for (const DebuggerViewToggle& toggle : toggles)
@@ -862,6 +875,24 @@ std::optional<BreakPointCpu> DockManager::cpu()
return m_layouts.at(m_current_layout).cpu();
}
DebuggerView* DockManager::focusedViewForNavigation()
{
if (!m_focused_view_for_navigation)
return nullptr;
return m_focused_view_for_navigation;
}
void DockManager::onFocusedDockWidgetChanged(KDDockWidgets::QtWidgets::DockWidget* widget)
{
DebuggerView* view = qobject_cast<DebuggerView*>(widget->widget());
if (view && view->supportsNavigation() && view != m_focused_view_for_navigation)
{
m_focused_view_for_navigation = view;
emit focusedViewForNavigationChanged();
}
}
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
{
if (!g_debugger_window)
+10
View File
@@ -98,6 +98,14 @@ public:
std::optional<BreakPointCpu> cpu();
/// Returns the last focused view that supports back/forward navigation.
DebuggerView* focusedViewForNavigation();
void onFocusedDockWidgetChanged(KDDockWidgets::QtWidgets::DockWidget* widget);
Q_SIGNALS:
void focusedViewForNavigationChanged();
private:
static KDDockWidgets::Core::DockWidget* dockWidgetFactory(const QString& name);
static bool dragAboutToStart(KDDockWidgets::Core::Draggable* draggable);
@@ -108,4 +116,6 @@ private:
DockMenuBar* m_menu_bar = nullptr;
bool m_layout_locked = true;
QPointer<DebuggerView> m_focused_view_for_navigation;
};
+2
View File
@@ -82,6 +82,7 @@ const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUT
{"MemorySearchView", DefaultDockGroup::TOP_LEFT},
},
.toolbars = {
"toolBarNavigation",
"toolBarDebug",
"toolBarFile",
},
@@ -113,6 +114,7 @@ const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUT
{"MemorySearchView", DefaultDockGroup::TOP_LEFT},
},
.toolbars = {
"toolBarNavigation",
"toolBarDebug",
"toolBarFile",
},
+7
View File
@@ -82,6 +82,7 @@ DockWidget::DockWidget(
: KDDockWidgets::QtWidgets::DockWidget(unique_name, options, layout_saver_options, window_flags)
{
connect(this, &DockWidget::isOpenChanged, this, &DockWidget::openStateChanged);
connect(this, &DockWidget::isFocusedChanged, this, &DockWidget::focusStateChanged);
}
void DockWidget::openStateChanged(bool open)
@@ -95,6 +96,12 @@ void DockWidget::openStateChanged(bool open)
g_debugger_window->dockManager().destroyDebuggerView(uniqueName());
}
void DockWidget::focusStateChanged(bool focused)
{
if (focused && g_debugger_window)
g_debugger_window->dockManager().onFocusedDockWidgetChanged(this);
}
// *****************************************************************************
DockTitleBar::DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent)
+1
View File
@@ -63,6 +63,7 @@ public:
protected:
void openStateChanged(bool open);
void focusStateChanged(bool focused);
};
class DockTitleBar : public KDDockWidgets::QtWidgets::TitleBar
@@ -10,6 +10,12 @@
<height>150</height>
</rect>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>300</height>
</size>
</property>
<property name="windowTitle">
<string/>
</property>
@@ -19,7 +25,7 @@
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
<string>Name:</string>
</property>
<property name="buddy">
<cstring>nameEditor</cstring>
@@ -29,7 +35,7 @@
<item row="1" column="0">
<widget class="QLabel" name="cpuLabel">
<property name="text">
<string>Target</string>
<string>Target:</string>
</property>
<property name="buddy">
<cstring>cpuEditor</cstring>
@@ -39,7 +45,7 @@
<item row="2" column="0">
<widget class="QLabel" name="initialStateLabel">
<property name="text">
<string>Initial State</string>
<string>Initial State:</string>
</property>
<property name="buddy">
<cstring>initialStateEditor</cstring>
+56 -8
View File
@@ -19,9 +19,11 @@ using namespace QtUtils;
/*
MemoryViewTable
*/
void MemoryViewTable::UpdateStartAddress(u32 start)
void MemoryViewTable::UpdateStartAddress(u32 start, NavigationHistoryOperation history_operation)
{
startAddress = start & ~0xF;
navigation_history.push(startAddress, history_operation);
parent->update();
}
void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
@@ -41,6 +43,13 @@ void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
else
startAddress += 0x10;
}
else
{
return;
}
navigation_history.pushWithDelay(startAddress);
parent->update();
}
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu)
@@ -684,7 +693,7 @@ MemoryView::MemoryView(const DebuggerViewParameters& parameters)
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &MemoryView::customContextMenuRequested, this, &MemoryView::openContextMenu);
m_table.UpdateStartAddress(0x100000);
m_table.UpdateStartAddress(0x100000, NavigationHistoryOperation::INSTANT_PUSH);
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
update();
@@ -723,7 +732,10 @@ bool MemoryView::fromJson(const JsonValueWrapper& json)
auto start_address = json.value().FindMember("startAddress");
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
m_table.UpdateStartAddress(start_address->value.GetUint());
{
m_table.navigation_history.clear();
m_table.UpdateStartAddress(start_address->value.GetUint(), NavigationHistoryOperation::INSTANT_PUSH);
}
auto view_type = json.value().FindMember("viewType");
if (view_type != json.value().MemberEnd() && view_type->value.IsInt())
@@ -746,6 +758,40 @@ bool MemoryView::fromJson(const JsonValueWrapper& json)
return true;
}
bool MemoryView::supportsNavigation()
{
return true;
}
bool MemoryView::canNavigateBack()
{
return m_table.navigation_history.canGoBack();
}
bool MemoryView::canNavigateForward()
{
return m_table.navigation_history.canGoForward();
}
void MemoryView::navigateBack()
{
std::optional<u32> address = m_table.navigation_history.back();
if (!address.has_value())
return;
m_table.UpdateStartAddress(*address, NavigationHistoryOperation::NO_PUSH);
}
void MemoryView::navigateForward()
{
std::optional<u32> address = m_table.navigation_history.forward();
if (!address.has_value())
return;
m_table.UpdateStartAddress(*address, NavigationHistoryOperation::NO_PUSH);
}
void MemoryView::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
@@ -928,17 +974,20 @@ void MemoryView::wheelEvent(QWheelEvent* event)
{
if (event->angleDelta().y() < 0)
{
m_table.UpdateStartAddress(m_table.startAddress + 0x10);
m_table.UpdateStartAddress(m_table.startAddress + 0x10, NavigationHistoryOperation::DELAYED_PUSH);
}
else if (event->angleDelta().y() > 0)
{
m_table.UpdateStartAddress(m_table.startAddress - 0x10);
m_table.UpdateStartAddress(m_table.startAddress - 0x10, NavigationHistoryOperation::DELAYED_PUSH);
}
update();
}
void MemoryView::keyPressEvent(QKeyEvent* event)
{
// Alt is used for the global navigation shortcuts.
if (event->modifiers() & Qt::AltModifier)
return;
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0', cpu()))
{
switch (event->key())
@@ -960,9 +1009,8 @@ void MemoryView::keyPressEvent(QKeyEvent* event)
void MemoryView::gotoAddress(u32 address)
{
m_table.UpdateStartAddress(address & ~0xF);
m_table.UpdateStartAddress(address & ~0xF, NavigationHistoryOperation::INSTANT_PUSH);
m_table.selectedAddress = address;
update();
setFocus();
}
+10 -1
View File
@@ -6,6 +6,7 @@
#include "ui_MemoryView.h"
#include "Debugger/DebuggerView.h"
#include "Debugger/NavigationHistoryStack.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/DisassemblyManager.h"
@@ -93,7 +94,9 @@ public:
u32 selectedAddress = 0;
s32 selectedIndex = 0;
void UpdateStartAddress(u32 start);
NavigationHistoryStack<u32> navigation_history;
void UpdateStartAddress(u32 start, NavigationHistoryOperation history_operation);
void UpdateSelectedAddress(u32 selected, bool page = false);
void DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu);
void SelectAt(QPoint pos);
@@ -136,6 +139,12 @@ public:
void toJson(JsonValueWrapper& json) override;
bool fromJson(const JsonValueWrapper& json) override;
bool supportsNavigation() override;
bool canNavigateBack() override;
bool canNavigateForward() override;
void navigateBack() override;
void navigateForward() override;
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
+164
View File
@@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "Debugger/DebuggerWindow.h"
#include <QtCore/QTimer>
#include <deque>
enum class NavigationHistoryOperation
{
INSTANT_PUSH,
DELAYED_PUSH,
NO_PUSH
};
/// Data structure for storing navigation history, to be used for the back and
/// forward buttons.
template <typename Element>
class NavigationHistoryStack
{
public:
NavigationHistoryStack()
: m_position(m_elements.end())
{
m_timer.setInterval(DELAY_MILLISECONDS);
QObject::connect(&m_timer, &QTimer::timeout, [this]() {
pushInstantly(std::move(m_pending));
m_timer.stop();
});
}
NavigationHistoryStack(Element element)
: NavigationHistoryStack()
{
pushInstantly(std::move(element));
}
NavigationHistoryStack(const NavigationHistoryStack<Element>&) = delete;
NavigationHistoryStack<Element>& operator=(const NavigationHistoryStack<Element>&) = delete;
NavigationHistoryStack(NavigationHistoryStack<Element>&&) = delete;
NavigationHistoryStack<Element>& operator=(NavigationHistoryStack<Element>&&) = delete;
/// Push an element onto the stack.
void push(Element element, NavigationHistoryOperation operation)
{
switch (operation)
{
case NavigationHistoryOperation::INSTANT_PUSH:
pushInstantly(std::move(element));
break;
case NavigationHistoryOperation::DELAYED_PUSH:
pushWithDelay(std::move(element));
break;
case NavigationHistoryOperation::NO_PUSH:
break;
}
}
/// Wait a second, and then push an element onto the stack. If another
/// element is pushed, then that will take priority. This is useful if we're
/// storing a scroll position, and don't want the stack to be spammed with
/// new values when the user scrolls, for example.
void pushWithDelay(Element element)
{
m_pending = std::move(element);
m_timer.start();
}
/// Push an element onto the stack instantly.
void pushInstantly(Element element)
{
m_timer.stop();
if (m_position != m_elements.end() && element == *(m_position - 1))
return;
updateNavigationButtons();
m_elements.erase(m_position, m_elements.end());
m_elements.push_back(std::move(element));
if (m_elements.size() > MAX_ELEMENTS)
m_elements.pop_front();
m_position = m_elements.end();
}
/// Should to user have an option to go back?
bool canGoBack() const
{
return m_elements.begin() != m_elements.end() && m_position > m_elements.begin() + 1;
}
/// Should the user have an option to go forward?
bool canGoForward() const
{
return m_position != m_elements.end();
}
// Retrieve the current element.
std::optional<Element> current() const
{
if (m_position == m_elements.begin())
return std::nullopt;
return *(m_position - 1);
}
/// Go back one.
std::optional<Element> back()
{
updateNavigationButtons();
if (!canGoBack())
return std::nullopt;
m_position--;
return *(m_position - 1);
}
/// Go forward one.
std::optional<Element> forward()
{
updateNavigationButtons();
if (!canGoForward())
return std::nullopt;
Element element = *m_position;
m_position++;
return element;
}
/// Remove all elements.
void clear()
{
m_elements.clear();
m_position = m_elements.end();
m_timer.stop();
}
private:
void updateNavigationButtons()
{
QTimer::singleShot(0, []() {
if (g_debugger_window)
g_debugger_window->updateNavigationButtons();
});
}
std::deque<Element> m_elements;
std::deque<Element>::iterator m_position;
QTimer m_timer;
Element m_pending;
static constexpr int DELAY_MILLISECONDS = 1000;
static constexpr size_t MAX_ELEMENTS = 1000;
};
+1 -1
View File
@@ -216,7 +216,7 @@ void LogWindow::createUi()
m_newline_on_enter = state == Qt::CheckState::Checked;
});
m_input_hbox = new QHBoxLayout(this);
m_input_hbox = new QHBoxLayout;
m_input_hbox->addWidget(m_line_input, 1);
m_input_hbox->addWidget(m_local_echo_checkbox);
m_input_hbox->addWidget(m_newline_on_enter_checkbox);
File diff suppressed because it is too large Load Diff
+1
View File
@@ -246,6 +246,7 @@
<QtMoc Include="Debugger\StackView.h" />
<QtMoc Include="Debugger\ModuleModel.h" />
<QtMoc Include="Debugger\ModuleView.h" />
<QtMoc Include="Debugger\NavigationHistoryStack.h" />
<QtMoc Include="Debugger\ThreadModel.h" />
<QtMoc Include="Debugger\ThreadView.h" />
<ClInclude Include="Debugger\DebuggerSettingsManager.h" />
+3
View File
@@ -529,6 +529,9 @@
<QtMoc Include="Debugger\ModuleView.h">
<Filter>Debugger</Filter>
</QtMoc>
<QtMoc Include="Debugger\NavigationHistoryStack.h">
<Filter>Debugger</Filter>
</QtMoc>
<QtMoc Include="Debugger\ModuleModel.h">
<Filter>Debugger</Filter>
</QtMoc>
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000"><path d="M7.82843 10.9999H20V12.9999H7.82843L13.1924 18.3638L11.7782 19.778L4 11.9999L11.7782 4.22168L13.1924 5.63589L7.82843 10.9999Z"></path></svg>

After

Width:  |  Height:  |  Size: 222 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000"><path d="M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"></path></svg>

After

Width:  |  Height:  |  Size: 222 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff"><path d="M7.82843 10.9999H20V12.9999H7.82843L13.1924 18.3638L11.7782 19.778L4 11.9999L11.7782 4.22168L13.1924 5.63589L7.82843 10.9999Z"></path></svg>

After

Width:  |  Height:  |  Size: 222 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff"><path d="M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"></path></svg>

After

Width:  |  Height:  |  Size: 222 B

+6 -2
View File
@@ -2,12 +2,14 @@
<qresource>
<file>icons/AppIcon64.png</file>
<file>icons/black/index.theme</file>
<file>icons/black/svg/arrow-left-line.svg</file>
<file>icons/black/svg/arrow-left-right-line.svg</file>
<file>icons/black/svg/arrow-right-line.svg</file>
<file>icons/black/svg/artboard-2-line.svg</file>
<file>icons/black/svg/at.svg</file>
<file>icons/black/svg/band-aid-line.svg</file>
<file>icons/black/svg/booklet.svg</file>
<file>icons/black/svg/book.svg</file>
<file>icons/black/svg/booklet.svg</file>
<file>icons/black/svg/brush-line.svg</file>
<file>icons/black/svg/bug-line.svg</file>
<file>icons/black/svg/buzz-controller-line.svg</file>
@@ -113,12 +115,14 @@
<file>icons/QT.png</file>
<file>icons/update.png</file>
<file>icons/white/index.theme</file>
<file>icons/white/svg/arrow-left-line.svg</file>
<file>icons/white/svg/arrow-left-right-line.svg</file>
<file>icons/white/svg/arrow-right-line.svg</file>
<file>icons/white/svg/artboard-2-line.svg</file>
<file>icons/white/svg/at.svg</file>
<file>icons/white/svg/band-aid-line.svg</file>
<file>icons/white/svg/booklet.svg</file>
<file>icons/white/svg/book.svg</file>
<file>icons/white/svg/booklet.svg</file>
<file>icons/white/svg/brush-line.svg</file>
<file>icons/white/svg/bug-line.svg</file>
<file>icons/white/svg/buzz-controller-line.svg</file>
+28 -1
View File
@@ -385,7 +385,13 @@ void GSClut::Read32(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA)
case PSMT4HH:
clut += (TEX0.CSA & 15) << 4;
// TODO: merge these functions
ReadCLUT_T32_I4(clut, m_buff32);
// This is for a situation with Breath of Fire Dragon Quarter where it reinterprets the buffer under CSM2 after reading as CSM1.
if (TEX0.CSM == 1 && m_write.TEX0.CSM == 0)
ReadCLUT_T32_I4_Swizzled(clut, m_buff32);
else
ReadCLUT_T32_I4(clut, m_buff32);
ExpandCLUT64_T32_I8(m_buff32, (u64*)m_buff64); // sw renderer does not need m_buff64 anymore
break;
}
@@ -632,6 +638,27 @@ __forceinline void GSClut::WriteCLUT_T16_I4_CSM1(const u16* RESTRICT src, u16* R
}
}
// These functions are only used if the CLUT is in 32bit mode and it swaps to CSM2, a very obscure setup used by Breath of Fire Dragon Quarter.
// This doesn't handle offsetting the CLUT or anything crazy, that would be a lot more work
void GSClut::ReadCLUT_T32_I4_Swizzled(const u16* RESTRICT clut, u32* RESTRICT dst)
{
// Point to the base of the palette block
GSVector4i* s = (GSVector4i*)clut;
GSVector4i* d = (GSVector4i*)dst;
GSVector4i v0 = s[0];
GSVector4i v1 = s[2];
GSVector4i v2 = s[32];
GSVector4i v3 = s[34];
GSVector4i::sw16(v0, v2, v1, v3);
d[0] = v0;
d[1] = v1;
d[2] = v2;
d[3] = v3;
}
void GSClut::ReadCLUT_T32_I8(const u16* RESTRICT clut, u32* RESTRICT dst, int offset)
{
// Okay this deserves a small explanation
+1
View File
@@ -78,6 +78,7 @@ class alignas(32) GSClut final : public GSAlignedClass<32>
static void WriteCLUT_T32_I4_CSM1(const u32* RESTRICT src, u16* RESTRICT clut);
static void WriteCLUT_T16_I8_CSM1(const u16* RESTRICT src, u16* RESTRICT clut);
static void WriteCLUT_T16_I4_CSM1(const u16* RESTRICT src, u16* RESTRICT clut);
static void ReadCLUT_T32_I4_Swizzled(const u16* RESTRICT clut, u32* RESTRICT dst);
static void ReadCLUT_T32_I8(const u16* RESTRICT clut, u32* RESTRICT dst, int offset);
static void ReadCLUT_T32_I4(const u16* RESTRICT clut, u32* RESTRICT dst);
//static void ReadCLUT_T32_I4(const u16* RESTRICT clut, u32* RESTRICT dst32, u64* RESTRICT dst64);
+11 -11
View File
@@ -5,23 +5,23 @@
// clang-format off
#define VM_SIZE 4194304u
#define HALF_VM_SIZE (VM_SIZE / 2u)
#define GS_PAGE_SIZE 8192u
#define GS_BLOCK_SIZE 256u
#define GS_COLUMN_SIZE 64u
#define GS_BLOCKS_PER_PAGE (GS_PAGE_SIZE / GS_BLOCK_SIZE)
#define GS_MAX_PAGES (VM_SIZE / GS_PAGE_SIZE)
#define GS_MAX_BLOCKS (VM_SIZE / GS_BLOCK_SIZE)
#define GS_MAX_COLUMNS (VM_SIZE / GS_COLUMN_SIZE)
//if defined, will send much info in reply to the API title info queri from PCSX2
//default should be undefined
//#define GSTITLEINFO_API_FORCE_VERBOSE
#include "GSVector.h"
constexpr u32 VM_SIZE = 4194304u;
constexpr u32 HALF_VM_SIZE = (VM_SIZE / 2u);
constexpr u32 GS_PAGE_SIZE = 8192u;
constexpr u32 GS_BLOCK_SIZE = 256u;
constexpr u32 GS_COLUMN_SIZE = 64u;
constexpr u32 GS_BLOCKS_PER_PAGE = (GS_PAGE_SIZE / GS_BLOCK_SIZE);
constexpr u32 GS_MAX_PAGES = (VM_SIZE / GS_PAGE_SIZE);
constexpr u32 GS_MAX_BLOCKS = (VM_SIZE / GS_BLOCK_SIZE);
constexpr u32 GS_MAX_COLUMNS = (VM_SIZE / GS_COLUMN_SIZE);
#pragma pack(push, 1)
enum GS_PRIM
+210 -199
View File
@@ -289,55 +289,58 @@ void GSState::ResetDrawBufferIdx()
{
int entry_ptr = 0;
for (int i = 0; i < m_used_buffers_idx; i++)
if (GSConfig.UserHacks_DrawBuffering && m_used_buffers_idx > 1)
{
// There can be situations like VSync where it won't purge the draws, this is bad for us!
if (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx)
for (int i = 0; i < m_used_buffers_idx; i++)
{
if (m_index_buffers[i].tail == 0)
m_env_buffers[i].draw_rect = GSVector4i::zero();
if (entry_ptr == i && (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx))
// There can be situations like VSync where it won't purge the draws, this is bad for us!
if (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx)
{
if (m_index_buffers[i].tail == 0)
m_env_buffers[i].draw_rect = GSVector4i::zero();
if (entry_ptr == i && (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx))
{
entry_ptr++;
continue;
}
memcpy(m_vertex_buffers[entry_ptr].buff, m_vertex_buffers[i].buff, sizeof(GSVertex) * m_vertex_buffers[i].tail);
m_vertex_buffers[entry_ptr].head = m_vertex_buffers[i].head;
m_vertex_buffers[entry_ptr].tail = m_vertex_buffers[i].tail;
m_vertex_buffers[entry_ptr].next = m_vertex_buffers[i].next;
memcpy(m_index_buffers[entry_ptr].buff, m_index_buffers[i].buff, sizeof(u16) * m_index_buffers[i].tail);
m_index_buffers[entry_ptr].tail = m_index_buffers[i].tail;
if (m_vertex_buffers[entry_ptr].tail != 0)
{
memcpy(m_vertex_buffers[entry_ptr].xy, m_vertex_buffers[i].xy, sizeof(m_vertex_buffers[i].xy));
m_vertex_buffers[entry_ptr].xyhead = m_vertex_buffers[i].xyhead;
m_vertex_buffers[entry_ptr].xy_tail = m_vertex_buffers[i].xy_tail;
}
else
{
m_vertex_buffers[entry_ptr].xy_tail = 0;
}
memcpy(&m_env_buffers[entry_ptr], &m_env_buffers[i], sizeof(m_env_buffers[i]));
if (i == m_current_buffer_idx)
m_current_buffer_idx = entry_ptr;
entry_ptr++;
continue;
}
memcpy(m_vertex_buffers[entry_ptr].buff, m_vertex_buffers[i].buff, sizeof(GSVertex) * m_vertex_buffers[i].tail);
m_vertex_buffers[entry_ptr].head = m_vertex_buffers[i].head;
m_vertex_buffers[entry_ptr].tail = m_vertex_buffers[i].tail;
m_vertex_buffers[entry_ptr].next = m_vertex_buffers[i].next;
memcpy(m_index_buffers[entry_ptr].buff, m_index_buffers[i].buff, sizeof(u16) * m_index_buffers[i].tail);
m_index_buffers[entry_ptr].tail = m_index_buffers[i].tail;
if (m_vertex_buffers[entry_ptr].tail != 0)
if (i != (entry_ptr - 1))
{
memcpy(m_vertex_buffers[entry_ptr].xy, m_vertex_buffers[i].xy, sizeof(m_vertex_buffers[i].xy));
m_vertex_buffers[entry_ptr].xyhead = m_vertex_buffers[i].xyhead;
m_vertex_buffers[entry_ptr].xy_tail = m_vertex_buffers[i].xy_tail;
m_index_buffers[i].tail = 0;
memset(&m_env_buffers[i], 0, sizeof(GSDrawBufferEnv));
m_vertex_buffers[i].head = m_vertex_buffers[i].tail = m_vertex_buffers[i].next = 0;
m_vertex_buffers[i].xy_tail = 0;
}
else
{
m_vertex_buffers[entry_ptr].xy_tail = 0;
}
memcpy(&m_env_buffers[entry_ptr], &m_env_buffers[i], sizeof(m_env_buffers[i]));
if (i == m_current_buffer_idx)
m_current_buffer_idx = entry_ptr;
entry_ptr++;
}
if (i != (entry_ptr - 1))
{
m_index_buffers[i].tail = 0;
memset(&m_env_buffers[i], 0, sizeof(GSDrawBufferEnv));
m_vertex_buffers[i].head = m_vertex_buffers[i].tail = m_vertex_buffers[i].next = 0;
m_vertex_buffers[i].xy_tail = 0;
}
}
@@ -389,18 +392,11 @@ void GSState::ResetDrawBuffers()
// exclude_current is used if there is a flush for a reason other than the normal context change.
void GSState::FlushBuffers(bool flush_base_only, bool use_flush_reason, GSFlushReason flush_reason)
{
const int current_idx = m_current_buffer_idx;
bool restore_env = false;
if (m_used_buffers_idx > 0)
if (m_used_buffers_idx > 1)
{
if (m_used_buffers_idx > 1)
{
restore_env = true;
memcpy(&m_temp_env, &m_env, sizeof(m_env));
}
else if (m_index_buffers[0].tail == 0)
return;
const int current_idx = m_current_buffer_idx;
bool restore_env = true;
memcpy(&m_temp_env, &m_env, sizeof(m_env));
const int max_flushes = flush_base_only ? 1 : m_used_buffers_idx;
for (int i = 0; i < max_flushes; i++)
@@ -440,19 +436,26 @@ void GSState::FlushBuffers(bool flush_base_only, bool use_flush_reason, GSFlushR
else
FlushDraw(GSFlushReason::CONTEXTCHANGE);
}
// Restore the environment
m_current_buffer_idx = current_idx;
m_index = &m_index_buffers[m_current_buffer_idx];
m_vertex = &m_vertex_buffers[m_current_buffer_idx];
const int ctx = m_env_buffers[m_current_buffer_idx].m_backed_up_ctx;
std::memcpy(&m_prev_env, &m_env_buffers[m_current_buffer_idx].m_env, 88);
std::memcpy(&m_prev_env.CTXT[0], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[0], 96);
std::memcpy(&m_prev_env.CTXT[1], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[1], 96);
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset));
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor));
}
else
{
if (use_flush_reason || flush_reason == VSYNC)
FlushDraw(flush_reason);
else
FlushDraw(GSFlushReason::CONTEXTCHANGE);
}
// Restore the environment
m_current_buffer_idx = current_idx;
m_index = &m_index_buffers[m_current_buffer_idx];
m_vertex = &m_vertex_buffers[m_current_buffer_idx];
const int ctx = m_env_buffers[m_current_buffer_idx].m_backed_up_ctx;
std::memcpy(&m_prev_env, &m_env_buffers[m_current_buffer_idx].m_env, 88);
std::memcpy(&m_prev_env.CTXT[0], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[0], 96);
std::memcpy(&m_prev_env.CTXT[1], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[1], 96);
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset));
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor));
}
void GSState::PushBuffer()
@@ -504,6 +507,18 @@ void GSState::PushBuffer()
}
}
void GSState::SetDrawBufferEnv()
{
memcpy(&m_env_buffers[m_current_buffer_idx].m_env, &m_env, sizeof(GSDrawingEnvironment));
m_env_buffers[m_current_buffer_idx].m_backed_up_ctx = m_backed_up_ctx;
}
void GSState::SetDrawBuffDirty()
{
m_env_buffers[m_current_buffer_idx].m_dirty_regs = m_dirty_gs_regs;
m_env_buffers[m_current_buffer_idx].draw_rect = temp_draw_rect;
}
bool GSState::CanBufferNewDraw()
{
if (!GSConfig.UserHacks_DrawBuffering)
@@ -692,18 +707,6 @@ bool GSState::CanBufferNewDraw()
return true;
}
void GSState::SetDrawBufferEnv()
{
memcpy(&m_env_buffers[m_current_buffer_idx].m_env, &m_env, sizeof(GSDrawingEnvironment));
m_env_buffers[m_current_buffer_idx].m_backed_up_ctx = m_backed_up_ctx;
}
void GSState::SetDrawBuffDirty()
{
m_env_buffers[m_current_buffer_idx].m_dirty_regs = m_dirty_gs_regs;
m_env_buffers[m_current_buffer_idx].draw_rect = temp_draw_rect;
}
void GSState::ResetHandlers()
{
std::fill(std::begin(m_fpGIFPackedRegHandlers), std::end(m_fpGIFPackedRegHandlers), &GSState::GIFPackedRegHandlerNull);
@@ -1673,7 +1676,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0)
{
for (int b = 0; b < m_used_buffers_idx; b++)
{
GSDrawingEnvironment& buffered_env = m_env_buffers[b].m_env;
GSDrawingEnvironment& buffered_env = (m_current_buffer_idx == b) ? m_prev_env : m_env_buffers[b].m_env;
if ((buffered_env.PRIM.TME && (buffered_env.CTXT[buffered_env.PRIM.CTXT].TEX0.PSM & 0x7) >= 3) || (m_mem.m_clut.IsInvalid() & 2))
Flush(GSFlushReason::CLUTCHANGE);
}
@@ -2561,9 +2564,11 @@ void GSState::FlushPrim()
Console.Warning("GS: Possible invalid draw, Frame PSM %x ZPSM %x", m_context->FRAME.PSM, m_context->ZBUF.PSM);
}
#endif
GSVertexBuff& vtx_buff = *m_vertex;
GSIndexBuff& idx_buff = *m_index;
// Update scissor, it may have been modified by a previous draw
m_env.CTXT[PRIM->CTXT].UpdateScissor();
m_vt.Update(m_vertex->buff, m_index->buff, m_vertex->tail, m_index->tail, GSUtil::GetPrimClass(PRIM->PRIM));
m_vt.Update(vtx_buff.buff, idx_buff.buff, vtx_buff.tail, idx_buff.tail, GSUtil::GetPrimClass(PRIM->PRIM));
// Texel coordinate rounding
// Helps Manhunt (lights shining through objects).
@@ -2575,13 +2580,13 @@ void GSState::FlushPrim()
{
const bool is_sprite = GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS;
// ST's have the lowest 9 bits (or greater depending on exponent difference) rounding down (from hardware tests).
for (int i = m_index->tail - 1; i >= 0; i--)
for (int i = idx_buff.tail - 1; i >= 0; i--)
{
GSVertex* v = &m_vertex->buff[m_index->buff[i]];
GSVertex* v = &vtx_buff.buff[idx_buff.buff[i]];
// Only Q on the second vertex is valid
if (!(i & 1) && is_sprite)
v->RGBAQ.Q = m_vertex->buff[m_index->buff[i + 1]].RGBAQ.Q;
v->RGBAQ.Q = vtx_buff.buff[idx_buff.buff[i + 1]].RGBAQ.Q;
int T = std::bit_cast<int>(v->ST.T);
int Q = std::bit_cast<int>(v->RGBAQ.Q);
@@ -2634,7 +2639,7 @@ void GSState::FlushPrim()
Draw();
g_perfmon.Put(GSPerfMon::Draw, 1);
g_perfmon.Put(GSPerfMon::Prim, m_index->tail / GSUtil::GetVertexCount(PRIM->PRIM));
g_perfmon.Put(GSPerfMon::Prim, idx_buff.tail / GSUtil::GetVertexCount(PRIM->PRIM));
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
{
@@ -2646,15 +2651,15 @@ void GSState::FlushPrim()
}
}
m_index->tail = 0;
m_vertex->head = 0;
idx_buff.tail = 0;
vtx_buff.head = 0;
if (unused > 0)
{
memcpy(m_vertex->buff, buff, sizeof(GSVertex) * unused);
memcpy(vtx_buff.buff, buff, sizeof(GSVertex) * unused);
m_vertex->tail = unused;
m_vertex->next = next > head ? next - head : 0;
vtx_buff.tail = unused;
vtx_buff.next = next > head ? next - head : 0;
// If it's a Triangle fan the XY buffer needs to be updated to point to the correct head vert
// Jak 3 shadows get spikey (with autoflush) if you don't.
@@ -2662,22 +2667,22 @@ void GSState::FlushPrim()
{
for (u32 i = 0; i < unused; i++)
{
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&m_vertex->buff[i];
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&vtx_buff.buff[i];
GSVector4i v = vert_ptr[1];
v = v.xxxx().u16to32().sub32(m_xyof);
m_vertex->xy[i & 3] = v;
m_vertex->xy_tail = unused;
vtx_buff.xy[i & 3] = v;
vtx_buff.xy_tail = unused;
}
}
}
else
{
m_vertex->tail = 0;
m_vertex->next = 0;
vtx_buff.tail = 0;
vtx_buff.next = 0;
}
}
}
GSVector4i GSState::GetTEX0Rect(GSDrawingContext prev_ctx)
GSVector4i GSState::GetTEX0Rect(const GSDrawingContext& prev_ctx)
{
GSVector4i ret = GSVector4i::zero();
@@ -2723,27 +2728,19 @@ void GSState::CheckWriteOverlap(bool req_write, bool req_read)
const GIFRegBITBLTBUF& blit = m_env.BITBLTBUF;
const GSVector4i write_rect = GSVector4i(m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, m_env.TRXPOS.DSAX + w, m_env.TRXPOS.DSAY + h);
const u32 write_start_bp = GSLocalMemory::GetStartBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect);
const u32 write_end_bp = ((GSLocalMemory::GetEndBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
for (int i = 0; i < m_used_buffers_idx; i++)
{
GSIndexBuff* cur_index_buff = &m_index_buffers[i];
GSVertexBuff* cur_vertex_buff = &m_vertex_buffers[i];
const GSDrawingContext& prev_ctx = m_env_buffers[i].m_env.CTXT[m_env_buffers[i].m_backed_up_ctx];
const GSDrawingEnvironment& prev_env = m_env_buffers[i].m_env;
GSVector4i tex_rect = prev_env.PRIM.TME ? GetTEX0Rect(prev_ctx) : GSVector4i::zero();
const GSDrawingContext& prev_ctx = m_current_buffer_idx == i ? m_prev_env.CTXT[m_backed_up_ctx] :m_env_buffers[i].m_env.CTXT[m_env_buffers[i].m_backed_up_ctx];
const GSDrawingEnvironment& prev_env = m_current_buffer_idx == i ? m_prev_env : m_env_buffers[i].m_env;
if (cur_index_buff->tail > 0)
{
// Only flush on a NEW transfer if a pending one is using the same address or overlap.
// Check Fast & Furious (Hardare mode) and Assault Suits Valken (either renderer) and Tomb Raider - Angel of Darkness menu (TBP != DBP but overlaps).
// Cartoon Network overwrites its own Z buffer in the middle of a draw.
// Alias wraps its transfers, so be careful
const GSVector4i read_rect = GSVector4i(m_env.TRXPOS.SSAX, m_env.TRXPOS.SSAY, m_env.TRXPOS.SSAX + w, m_env.TRXPOS.SSAY + h);
if (req_write && prev_env.PRIM.TME)
{
GSVector4i tex_rect = prev_env.PRIM.TME ? GetTEX0Rect(prev_ctx) : GSVector4i::zero();
// Tex rect could be invalid showing 1024x1024 when it isn't. If the frame is only 1 page wide, it's either a big strip or a single page draw.
// This large texture causes misdetection of overlapping writes, causing our heuristics in the hardware renderer for future draws to be missing.
// Either way if we check the queued up coordinates, it should give us a fair idea. (Cabela's Trophy Bucks)
@@ -2842,6 +2839,11 @@ void GSState::CheckWriteOverlap(bool req_write, bool req_read)
}
}
// Only flush on a NEW transfer if a pending one is using the same address or overlap.
// Check Fast & Furious (Hardare mode) and Assault Suits Valken (either renderer) and Tomb Raider - Angel of Darkness menu (TBP != DBP but overlaps).
// Cartoon Network overwrites its own Z buffer in the middle of a draw.
// Alias wraps its transfers, so be careful
const GSVector4i read_rect = GSVector4i(m_env.TRXPOS.SSAX, m_env.TRXPOS.SSAY, m_env.TRXPOS.SSAX + w, m_env.TRXPOS.SSAY + h);
const u32 frame_mask = GSLocalMemory::m_psm[prev_ctx.FRAME.PSM].fmsk;
const bool frame_required = (!(prev_ctx.TEST.ATE && prev_ctx.TEST.ATST == 0 && (prev_ctx.TEST.AFAIL == 2 || prev_ctx.TEST.AFAIL == 0)) && ((prev_ctx.FRAME.FBMSK & frame_mask) != frame_mask)) || prev_ctx.TEST.DATE;
const GSVector4i draw_rect = (m_current_buffer_idx == i) ? temp_draw_rect : m_env_buffers[i].draw_rect;
@@ -2874,6 +2876,9 @@ void GSState::CheckWriteOverlap(bool req_write, bool req_read)
if (req_write)
{
const u32 write_start_bp = GSLocalMemory::GetStartBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect);
const u32 write_end_bp = ((GSLocalMemory::GetEndBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
// Invalid the CLUT if it crosses paths.
m_mem.m_clut.InvalidateRange(write_start_bp, write_end_bp);
}
@@ -2976,7 +2981,7 @@ void GSState::InitReadFIFO(u8* mem, int len)
// Read the image all in one go.
m_mem.ReadImageX(m_tr.x, m_tr.y, m_tr.buff, m_tr.total, m_env.BITBLTBUF, m_env.TRXPOS, m_env.TRXREG);
if (GSConfig.SaveRT && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
if (GSConfig.SaveTransferImages && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
{
const std::string s(GetDrawDumpPath(
"%05lld_read_%05x_%d_%s_%d_%d_%d_%d.bmp",
@@ -4771,12 +4776,12 @@ bool GSState::SpriteDrawWithoutGaps()
const GSVertex* v = &m_vertex->buff[0];
const int first_dpY = v[1].XYZ.Y - v[0].XYZ.Y;
const int first_dpX = v[1].XYZ.X - v[0].XYZ.X;
const u32 next_count = m_vertex->next;
// Horizontal Match.
if (((first_dpX + 8) >> 4) == m_r_no_scissor.z)
{
// Borrowed from MergeSprite() modified to calculate heights.
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < next_count; i += 2)
{
const int last_pY = v[i - 1].XYZ.Y;
const int dpY = v[i + 1].XYZ.Y - v[i].XYZ.Y;
@@ -4793,7 +4798,7 @@ bool GSState::SpriteDrawWithoutGaps()
{
// Borrowed from MergeSprite().
const int offset_X = m_context->XYOFFSET.OFX;
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < next_count; i += 2)
{
const int last_pX = v[i - 1].XYZ.X;
const int this_start_X = v[i].XYZ.X;
@@ -4810,7 +4815,7 @@ bool GSState::SpriteDrawWithoutGaps()
else
{
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)
if ((std::abs(dpY - first_dpY) >= 16 && (i + 2) < next_count) || std::abs(this_start_X - last_pX) >= 16)
return false;
}
}
@@ -4823,7 +4828,7 @@ bool GSState::SpriteDrawWithoutGaps()
{
int lastXEdge = std::max(v[1].XYZ.X, v[0].XYZ.X);
int lastYEdge = std::max(v[1].XYZ.Y, v[0].XYZ.Y);
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < next_count; i += 2)
{
const int dpY = v[i + 1].XYZ.Y - v[i].XYZ.Y;
@@ -5405,7 +5410,7 @@ __forceinline void GSState::CheckCLUTValidity(u32 prim)
for (int i = 0; i < m_used_buffers_idx; i++)
{
GSDrawingEnvironment& buffered_env = m_env_buffers[i].m_env;
GSDrawingEnvironment& buffered_env = (m_current_buffer_idx == i) ? m_prev_env : m_env_buffers[i].m_env;
const GSDrawingContext& ctx = buffered_env.CTXT[buffered_env.PRIM.CTXT];
if ((m_index_buffers[i].tail > 0 || (m_vertex_buffers[i].tail == n - 1)) && (GSLocalMemory::m_psm[ctx.TEX0.PSM].pal == 0 || !buffered_env.PRIM.TME))
{
@@ -5421,7 +5426,8 @@ __forceinline void GSState::CheckCLUTValidity(u32 prim)
if (prim != GS_POINTLIST || (m_index_buffers[i].tail > 1))
endbp = fpsm.info.bn(temp_draw_rect.z - 1, temp_draw_rect.w - 1, ctx.FRAME.Block(), ctx.FRAME.FBW);
m_mem.m_clut.InvalidateRange(startbp, endbp, true);
if (m_mem.m_clut.InvalidateRange(startbp, endbp, true))
break;
}
}
}
@@ -5434,6 +5440,8 @@ __forceinline void GSState::HandleAutoFlush()
if ((m_index->tail & 1) && (prim == GS_TRIANGLESTRIP || prim == GS_TRIANGLEFAN) && !m_texflush_flag)
return;
GSVertexBuff& vtx_buff = *m_vertex;
GSIndexBuff& idx_buff = *m_index;
// To briefly explain what's going on here, what we are checking for is draws over a texture when the source and destination are themselves.
// Because one page of the texture gets buffered in the Texture Cache (the PS2's one) if any of those pixels are overwritten, you still read the old data.
// So we need to calculate if a page boundary is being crossed for the format it is in and if the same part of the texture being written and read inside the draw.
@@ -5442,8 +5450,8 @@ __forceinline void GSState::HandleAutoFlush()
{
int n = 1;
u32 buff[3];
const u32 head = m_vertex->head;
const u32 tail = m_vertex->tail;
const u32 head = vtx_buff.head;
const u32 tail = vtx_buff.tail;
switch (prim)
{
@@ -5510,7 +5518,7 @@ __forceinline void GSState::HandleAutoFlush()
// Get the rest of the rect.
for (int i = 0; i < (n - 1); i++)
{
const GSVertex* v = &m_vertex->buff[buff[i]];
const GSVertex* v = &vtx_buff.buff[buff[i]];
xy_coord.x = (static_cast<int>(v->XYZ.X) - static_cast<int>(m_context->XYOFFSET.OFX)) >> 4;
xy_coord.y = (static_cast<int>(v->XYZ.Y) - static_cast<int>(m_context->XYOFFSET.OFY)) >> 4;
@@ -5562,7 +5570,7 @@ __forceinline void GSState::HandleAutoFlush()
return;
// Get the last texture position from the last draw.
const GSVertex* v = &m_vertex->buff[m_index->buff[m_index->tail - 1]];
const GSVertex* v = &vtx_buff.buff[idx_buff.buff[idx_buff.tail - 1]];
if (PRIM->FST)
{
@@ -5652,13 +5660,13 @@ __forceinline void GSState::HandleAutoFlush()
const GSVector2i offset = GSVector2i(m_context->XYOFFSET.OFX, m_context->XYOFFSET.OFY);
const GSVector4i scissor = m_context->scissor.in;
GSVector4i old_draw_rect = GSVector4i::zero();
int current_draw_end = m_index->tail;
int current_draw_end = idx_buff.tail;
while (current_draw_end >= n)
{
for (int i = current_draw_end - 1; i >= current_draw_end - n; i--)
{
const GSVertex* v = &m_vertex->buff[m_index->buff[i]];
const GSVertex* v = &vtx_buff.buff[idx_buff.buff[i]];
if (prim == GS_SPRITE && (i & 1))
{
@@ -5740,18 +5748,18 @@ __forceinline void GSState::HandleAutoFlush()
}
}
bool GSState::CheckOverlapVerts(u32 n)
__inline bool GSState::CheckOverlapVerts(u32 n)
{
if (!GSConfig.UserHacks_DrawBuffering)
return false;
GSVertexBuff& vtx_buff = *m_vertex;
GSIndexBuff& idx_buff = *m_index;
if (m_recent_buffer_switch && ((m_vertex->tail + 1) - m_vertex->head) == n)
if (m_recent_buffer_switch && ((vtx_buff.tail + 1) - vtx_buff.head) == n)
{
m_recent_buffer_switch = false;
if (m_used_buffers_idx > 1)
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &vtx_buff.buff[0];
GSVector2i cur_verts[3];
GSVector4i new_area = GSVector4i(m_v.XYZ.X - m_context->XYOFFSET.OFX, m_v.XYZ.Y - m_context->XYOFFSET.OFY).xyxy();
@@ -5759,11 +5767,11 @@ bool GSState::CheckOverlapVerts(u32 n)
for (u32 i = 0; i < (n - 1); i++)
{
const int pos = (m_vertex->tail - 1) - i;
const int pos = (vtx_buff.tail - 1) - i;
GSVector2i prev_vert;
if (m_env.PRIM.PRIM == GS_TRIANGLEFAN && i == (n - 2))
prev_vert = GSVector2i(v[m_vertex->head].XYZ.X - m_context->XYOFFSET.OFX, v[m_vertex->head].XYZ.X - m_context->XYOFFSET.OFY);
prev_vert = GSVector2i(v[vtx_buff.head].XYZ.X - m_context->XYOFFSET.OFX, v[vtx_buff.head].XYZ.X - m_context->XYOFFSET.OFY);
else
prev_vert = GSVector2i(v[pos].XYZ.X - m_context->XYOFFSET.OFX, v[pos].XYZ.Y - m_context->XYOFFSET.OFY);
@@ -5775,12 +5783,12 @@ bool GSState::CheckOverlapVerts(u32 n)
new_area.w = std::max(new_area.w, prev_vert.y);
}
if (m_index->tail > 0)
if (idx_buff.tail > 0)
{
u32 matching_verts = 0;
for (u32 i = 0; i < n; i++)
{
const u32 pos = m_index->buff[(m_index->tail - n) + i];
const u32 pos = idx_buff.buff[(idx_buff.tail - n) + i];
const GSVector2i prev_vert = GSVector2i(v[pos].XYZ.X - m_context->XYOFFSET.OFX, v[pos].XYZ.Y - m_context->XYOFFSET.OFY);
for (u32 j = 0; j < n; j++)
@@ -5811,12 +5819,12 @@ bool GSState::CheckOverlapVerts(u32 n)
}
}
/*const GSVertex* v = &m_vertex->buff[0];
/*const GSVertex* v = &vtx_buff.buff[0];
GSVector4i new_area = GSVector4i(m_v.XYZ.X - m_context->XYOFFSET.OFX, m_v.XYZ.Y - m_context->XYOFFSET.OFY).xyxy();
for (u32 i = 0; i < (n - 1); i++)
{
const int pos = m_index->buff[(m_index->tail - 1) - i];
const int pos = idx_buff.buff[(idx_buff.tail - 1) - i];
GSVector2i pre_vert = GSVector2i(v[pos].XYZ.X - m_context->XYOFFSET.OFX, v[pos].XYZ.Y - m_context->XYOFFSET.OFY);
new_area.x = std::min(new_area.x, pre_vert.x);
new_area.z = std::max(new_area.z, pre_vert.x);
@@ -5827,11 +5835,11 @@ bool GSState::CheckOverlapVerts(u32 n)
if (new_area.rintersect(temp_draw_rect).eq(new_area))
{
const int end_pos = m_index->tail - (n - 1);
const int end_pos = idx_buff.tail - (n - 1);
//Need to check if it's already drawn at this vector with this setup, if it has, it means one of the other draws might be drawing over it, which is a bad time for us, so best check.
for (int j = 0; j < end_pos; j+=n)
{
if (v[m_index->buff[j]].XYZ.X == m_v.XYZ.X && v[m_index->buff[j]].XYZ.Y == m_v.XYZ.Y)
if (v[idx_buff.buff[j]].XYZ.X == m_v.XYZ.X && v[idx_buff.buff[j]].XYZ.Y == m_v.XYZ.Y)
{
int min_point = std::max(j - 2, 0);
int match = 0;
@@ -5841,9 +5849,9 @@ bool GSState::CheckOverlapVerts(u32 n)
if (k == j)
continue;
if (v[m_index->buff[k]].XYZ.X == v[m_vertex->tail - 2].XYZ.X && v[m_index->buff[k]].XYZ.Y == v[m_vertex->tail - 2].XYZ.Y)
if (v[idx_buff.buff[k]].XYZ.X == v[vtx_buff.tail - 2].XYZ.X && v[idx_buff.buff[k]].XYZ.Y == v[vtx_buff.tail - 2].XYZ.Y)
match |= 1;
if (v[m_index->buff[k]].XYZ.X == v[m_vertex->tail - 1].XYZ.X && v[m_index->buff[k]].XYZ.Y == v[m_vertex->tail - 1].XYZ.Y)
if (v[idx_buff.buff[k]].XYZ.X == v[vtx_buff.tail - 1].XYZ.X && v[idx_buff.buff[k]].XYZ.Y == v[vtx_buff.tail - 1].XYZ.Y)
match |= 2;
}
@@ -5861,28 +5869,30 @@ __forceinline void GSState::VertexKick(u32 skip)
{
constexpr u32 n = NumIndicesForPrim(prim);
constexpr int primclass = GSUtil::GetPrimClass(prim);
GSVertexBuff& vtx_buff = *m_vertex;
GSIndexBuff& idx_buff = *m_index;
static_assert(n > 0);
pxAssert(m_vertex->tail < m_vertex->maxcount + 3);
pxAssert(vtx_buff.tail < vtx_buff. maxcount + 3);
if constexpr (prim == GS_INVALID)
{
m_vertex->tail = m_vertex->head;
vtx_buff.tail = vtx_buff.head;
return;
}
if (CheckOverlapVerts(n))
Flush(CONTEXTCHANGE);
if (GSConfig.UserHacks_DrawBuffering)
if (CheckOverlapVerts(n))
Flush(CONTEXTCHANGE);
if (auto_flush && skip == 0 && m_index->tail > 0 && ((m_vertex->tail + 1) - m_vertex->head) >= n)
if (auto_flush && skip == 0 && idx_buff.tail > 0 && ((vtx_buff.tail + 1) - vtx_buff.head) >= n)
{
HandleAutoFlush<prim>();
}
u32 head = m_vertex->head;
u32 tail = m_vertex->tail;
u32 next = m_vertex->next;
u32 xy_tail = m_vertex->xy_tail;
u32 head = vtx_buff.head;
u32 tail = vtx_buff.tail;
u32 next = vtx_buff.next;
u32 xy_tail = vtx_buff.xy_tail;
if (GSIsHardwareRenderer() && GSLocalMemory::m_psm[m_context->ZBUF.PSM].bpp == 32)
{
@@ -5897,7 +5907,7 @@ __forceinline void GSState::VertexKick(u32 skip)
const GSVector4i new_v0(m_v.m[0]);
const GSVector4i new_v1(m_v.m[1]);
GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex->buff[tail];
GSVector4i* RESTRICT tailptr = (GSVector4i*)&vtx_buff.buff[tail];
tailptr[0] = new_v0;
tailptr[1] = new_v1;
@@ -5905,41 +5915,29 @@ __forceinline void GSState::VertexKick(u32 skip)
// We maintain the X/Y coordinates for the last 4 vertices, as well as the head for triangle fans, so we can compute
// the min/max, and cull degenerate triangles, which saves draws in some cases. Why 4? Mod 4 is cheaper than Mod 3.
const GSVector4i xy = new_v1.xxxx().u16to32().sub32(m_xyof);
m_vertex->xy[xy_tail & 3] = xy;
vtx_buff.xy[xy_tail & 3] = xy;
// Backup head for triangle fans so we can read it later, otherwise it'll get lost after the 4th vertex.
if (prim == GS_TRIANGLEFAN && tail == head)
m_vertex->xyhead = xy;
vtx_buff.xyhead = xy;
m_vertex->tail = ++tail;
m_vertex->xy_tail = ++xy_tail;
vtx_buff.tail = ++tail;
vtx_buff.xy_tail = ++xy_tail;
const u32 m = tail - head;
if (m < n)
return;
if (m_index->tail == 0/* && ((m_backed_up_ctx != m_env.PRIM.CTXT) || m_dirty_gs_regs)*/)
{
const int ctx = m_env.PRIM.CTXT;
std::memcpy(&m_prev_env, &m_env, 88);
std::memcpy(&m_prev_env.CTXT[ctx], &m_env.CTXT[ctx], 96);
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env.CTXT[ctx].offset, sizeof(m_env.CTXT[ctx].offset));
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env.CTXT[ctx].scissor, sizeof(m_env.CTXT[ctx].scissor));
m_dirty_gs_regs = 0;
m_backed_up_ctx = m_env.PRIM.CTXT;
SetDrawBufferEnv();
}
// Skip draws when scissor is out of range (i.e. bottom-right is less than top-left), since everything will get clipped.
skip |= static_cast<u32>(m_scissor_invalid);
GSVector4i bbox;
if (skip == 0)
{
const GSVector4i v0 = m_vertex->xy[(xy_tail - 1) & 3];
const GSVector4i v1 = m_vertex->xy[(xy_tail - 2) & 3];
const GSVector4i v2 = (prim == GS_TRIANGLEFAN) ? m_vertex->xyhead : m_vertex->xy[(xy_tail - 3) & 3];
const GSVector4i v0 = vtx_buff.xy[(xy_tail - 1) & 3];
const GSVector4i v1 = vtx_buff.xy[(xy_tail - 2) & 3];
const GSVector4i v2 = (prim == GS_TRIANGLEFAN) ? vtx_buff.xyhead : vtx_buff.xy[(xy_tail - 3) & 3];
if constexpr (n == 1)
{
@@ -6004,14 +6002,14 @@ __forceinline void GSState::VertexKick(u32 skip)
case GS_LINELIST:
case GS_TRIANGLELIST:
case GS_SPRITE:
m_vertex->tail = head; // no need to check or grow the buffer length
vtx_buff.tail = head; // no need to check or grow the buffer length
break;
case GS_LINESTRIP:
case GS_TRIANGLESTRIP:
m_vertex->head = head + 1;
vtx_buff.head = head + 1;
[[fallthrough]];
case GS_TRIANGLEFAN:
if (tail >= m_vertex->maxcount)
if (tail >= vtx_buff.maxcount)
GrowVertexBuffer(); // in case too many vertices were skipped
break;
default:
@@ -6021,71 +6019,85 @@ __forceinline void GSState::VertexKick(u32 skip)
return;
}
if (tail >= m_vertex->maxcount)
if (idx_buff.tail == 0 /* && ((m_backed_up_ctx != m_env.PRIM.CTXT) || m_dirty_gs_regs)*/)
{
const int ctx = m_env.PRIM.CTXT;
std::memcpy(&m_prev_env, &m_env, 88);
std::memcpy(&m_prev_env.CTXT[ctx], &m_env.CTXT[ctx], 96);
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env.CTXT[ctx].offset, sizeof(m_env.CTXT[ctx].offset));
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env.CTXT[ctx].scissor, sizeof(m_env.CTXT[ctx].scissor));
m_dirty_gs_regs = 0;
m_backed_up_ctx = m_env.PRIM.CTXT;
if (GSConfig.UserHacks_DrawBuffering)
SetDrawBufferEnv();
}
if (tail >= vtx_buff.maxcount)
GrowVertexBuffer();
u16* RESTRICT buff = &m_index->buff[m_index->tail];
u16* RESTRICT buff = &idx_buff.buff[idx_buff.tail];
switch (prim)
{
case GS_POINTLIST:
buff[0] = static_cast<u16>(head + 0);
m_vertex->head = head + 1;
m_vertex->next = head + 1;
m_index->tail += 1;
vtx_buff.head = head + 1;
vtx_buff.next = head + 1;
idx_buff.tail += 1;
break;
case GS_LINELIST:
buff[0] = static_cast<u16>(head + 0);
buff[1] = static_cast<u16>(head + 1);
m_vertex->head = head + 2;
m_vertex->next = head + 2;
m_index->tail += 2;
vtx_buff.head = head + 2;
vtx_buff.next = head + 2;
idx_buff.tail += 2;
break;
case GS_LINESTRIP:
if (next < head)
{
m_vertex->buff[next + 0] = m_vertex->buff[head + 0];
m_vertex->buff[next + 1] = m_vertex->buff[head + 1];
vtx_buff.buff[next + 0] = vtx_buff.buff[head + 0];
vtx_buff.buff[next + 1] = vtx_buff.buff[head + 1];
head = next;
m_vertex->tail = next + 2;
vtx_buff.tail = next + 2;
}
buff[0] = static_cast<u16>(head + 0);
buff[1] = static_cast<u16>(head + 1);
m_vertex->head = head + 1;
m_vertex->next = head + 2;
m_index->tail += 2;
vtx_buff.head = head + 1;
vtx_buff.next = head + 2;
idx_buff.tail += 2;
break;
case GS_TRIANGLELIST:
buff[0] = static_cast<u16>(head + 0);
buff[1] = static_cast<u16>(head + 1);
buff[2] = static_cast<u16>(head + 2);
m_vertex->head = head + 3;
m_vertex->next = head + 3;
m_index->tail += 3;
vtx_buff.head = head + 3;
vtx_buff.next = head + 3;
idx_buff.tail += 3;
break;
case GS_TRIANGLESTRIP:
if (next < head)
{
m_vertex->buff[next + 0] = m_vertex->buff[head + 0];
m_vertex->buff[next + 1] = m_vertex->buff[head + 1];
m_vertex->buff[next + 2] = m_vertex->buff[head + 2];
vtx_buff.buff[next + 0] = vtx_buff.buff[head + 0];
vtx_buff.buff[next + 1] = vtx_buff.buff[head + 1];
vtx_buff.buff[next + 2] = vtx_buff.buff[head + 2];
head = next;
m_vertex->tail = next + 3;
vtx_buff.tail = next + 3;
}
buff[0] = static_cast<u16>(head + 0);
buff[1] = static_cast<u16>(head + 1);
buff[2] = static_cast<u16>(head + 2);
m_vertex->head = head + 1;
m_vertex->next = head + 3;
m_index->tail += 3;
vtx_buff.head = head + 1;
vtx_buff.next = head + 3;
idx_buff.tail += 3;
break;
case GS_TRIANGLEFAN:
// TODO: remove gaps, next == head && head < tail - 3 || next > head && next < tail - 2 (very rare)
buff[0] = static_cast<u16>(head + 0);
buff[1] = static_cast<u16>(tail - 2);
buff[2] = static_cast<u16>(tail - 1);
m_vertex->next = tail;
m_index->tail += 3;
vtx_buff.next = tail;
idx_buff.tail += 3;
break;
case GS_SPRITE:
buff[0] = static_cast<u16>(head + 0);
@@ -6093,11 +6105,11 @@ __forceinline void GSState::VertexKick(u32 skip)
// Update the first vert's Q for ease of doing Autoflush
if (!m_env.PRIM.FST)
m_vertex->buff[buff[0]].RGBAQ.Q = m_vertex->buff[buff[1]].RGBAQ.Q;
vtx_buff.buff[buff[0]].RGBAQ.Q = vtx_buff.buff[buff[1]].RGBAQ.Q;
m_vertex->head = head + 2;
m_vertex->next = head + 2;
m_index->tail += 2;
vtx_buff.head = head + 2;
vtx_buff.next = head + 2;
idx_buff.tail += 2;
break;
default:
ASSUME(0);
@@ -6105,14 +6117,14 @@ __forceinline void GSState::VertexKick(u32 skip)
// Update rectangle for the current draw. Needs exclusive endpoints.
const GSVector4i draw_rect = bbox.sra32<4>() + GSVector4i(0, 0, 1, 1);
if (m_index->tail != n)
if (idx_buff.tail != n)
temp_draw_rect = temp_draw_rect.runion(draw_rect);
else
temp_draw_rect = draw_rect;
temp_draw_rect = temp_draw_rect.rintersect(m_context->scissor.in);
constexpr u32 max_vertices = MaxVerticesForPrim(prim);
if (max_vertices != 0 && m_vertex->tail >= max_vertices)
if (max_vertices != 0 && vtx_buff.tail >= max_vertices)
Flush(VERTEXCOUNT);
}
@@ -6274,7 +6286,6 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL
const GSVector4i scissored_rc(int_rc.rintersect(m_context->scissor.in));
if (!int_rc.eq(scissored_rc))
{
const GSVertex* vert_first = &m_vertex->buff[m_index->buff[0]];
const GSVertex* vert_second = &m_vertex->buff[m_index->buff[1]];
const GSVertex* vert_third = &m_vertex->buff[m_index->buff[2]];
+1 -1
View File
@@ -506,7 +506,7 @@ public:
virtual void Move();
GSVector4i GetTEX0Rect(GSDrawingContext prev_ctx);
GSVector4i GetTEX0Rect(const GSDrawingContext& prev_ctx);
void CheckWriteOverlap(bool req_write, bool req_read);
void Write(const u8* mem, int len);
void Read(u8* mem, int len);
+1 -1
View File
@@ -805,7 +805,7 @@ GSTexture* GSDevice::CreateTexture(int w, int h, int mipmap_levels, GSTexture::F
{
pxAssert(mipmap_levels != 0 && (mipmap_levels < 0 || mipmap_levels <= GetMipmapLevelsForSize(w, h)));
const int levels = mipmap_levels < 0 ? GetMipmapLevelsForSize(w, h) : mipmap_levels;
return FetchSurface(GSTexture::Texture, w, h, levels, format, false, m_features.prefer_new_textures && prefer_reuse);
return FetchSurface(GSTexture::Texture, w, h, levels, format, false, !m_features.prefer_new_textures || prefer_reuse);
}
GSTexture* GSDevice::CreateTexture(const GSVector2i& size, int mipmap_levels, GSTexture::Format format, bool prefer_reuse)
+1 -1
View File
@@ -603,7 +603,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
}
// 1x1 dummy texture.
const GSTexture::Usage null_usage = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::Feedback;
const GSTexture::Usage null_usage = m_uav_texture ? GSTexture::ShaderWriteTarget : GSTexture::Feedback;
m_null_texture = CreateSurface(null_usage, 1, 1, 1, GSTexture::Format::Color);
if (!m_null_texture)
return false;
+2 -4
View File
@@ -2719,11 +2719,9 @@ GSDevice12::ComPtr<ID3DBlob> GSDevice12::GetUtilityPixelShader(const std::string
bool GSDevice12::CreateNullTexture()
{
const GSTexture::Usage null_access = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
const DXGI_FORMAT null_uav_format = m_features.rov ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_UNKNOWN;
m_null_texture =
GSTexture12::Create(null_access, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, null_uav_format);
GSTexture12::Create(GSTexture::ShaderWriteTarget, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UNORM);
if (!m_null_texture)
return false;
+108 -78
View File
@@ -222,7 +222,9 @@ void GSRendererHW::Lines2Sprites()
// each sprite converted to quad needs twice the space
while (m_vertex->tail * 2 > m_vertex->maxcount)
const int tail = m_vertex->tail * 2;
const int max_count = m_vertex->maxcount;
while (tail > max_count)
{
GrowVertexBuffer();
}
@@ -298,12 +300,13 @@ void GSRendererHW::Lines2Sprites()
void GSRendererHW::ExpandLineIndices()
{
const u32 process_count = (m_index->tail + 7) / 8 * 8;
GSIndexBuff& idx_buff = *m_index;
const u32 process_count = (idx_buff.tail + 7) / 8 * 8;
constexpr u32 expansion_factor = 3;
m_index->tail *= expansion_factor;
GSVector4i* end = reinterpret_cast<GSVector4i*>(m_index->buff);
GSVector4i* read = reinterpret_cast<GSVector4i*>(m_index->buff + process_count);
GSVector4i* write = reinterpret_cast<GSVector4i*>(m_index->buff + process_count * expansion_factor);
idx_buff.tail *= expansion_factor;
GSVector4i* end = reinterpret_cast<GSVector4i*>(idx_buff.buff);
GSVector4i* read = reinterpret_cast<GSVector4i*>(idx_buff.buff + process_count);
GSVector4i* write = reinterpret_cast<GSVector4i*>(idx_buff.buff + process_count * expansion_factor);
constexpr GSVector4i mask0 = GSVector4i::cxpr8(0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5);
constexpr GSVector4i mask1 = GSVector4i::cxpr8(6, 7, 4, 5, 6, 7, 6, 7, 8, 9, 8, 9, 10, 11, 8, 9);
@@ -1428,7 +1431,8 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
}
else
{
for (u32 i = 2; i < (m_vertex->tail & ~1); i++)
const u32 tail = m_vertex->tail & ~1;
for (u32 i = 2; i < tail; i++)
{
bool unique_found = false;
@@ -1469,7 +1473,8 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
}
else
{
for (u32 i = 2; i < (m_vertex->tail & ~1); i++)
const u32 tail = m_vertex->tail & ~1;
for (u32 i = 2; i < tail; i++)
{
bool unique_found = false;
@@ -2235,16 +2240,16 @@ void GSRendererHW::HandleManualDeswizzle()
// Check if it's doing manual deswizzling first (draws are 32x16), if they are, check if the Z is flat, if not,
// we're gonna have to get creative and swap around the quandrants, but that's a TODO.
GSVertex* v = &m_vertex->buff[0];
GSIndexBuff& idx_buff = *m_index;
// Check for page quadrant and compare it to the quadrant from the verts, if it does match then we need to do correction.
const GSVector2i page_quadrant = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs / 2;
if (PRIM->FST)
{
for (u32 i = 0; i < m_index->tail; i += 2)
for (u32 i = 0; i < idx_buff.tail; i += 2)
{
const u32 index_first = m_index->buff[i];
const u32 index_last = m_index->buff[i + 1];
const u32 index_first = idx_buff.buff[i];
const u32 index_last = idx_buff.buff[i + 1];
if ((abs((v[index_last].U) - (v[index_first].U)) >> 4) != page_quadrant.x || (abs((v[index_last].V) - (v[index_first].V)) >> 4) != page_quadrant.y)
return;
@@ -2252,10 +2257,10 @@ void GSRendererHW::HandleManualDeswizzle()
}
else
{
for (u32 i = 0; i < m_index->tail; i += 2)
for (u32 i = 0; i < idx_buff.tail; i += 2)
{
const u32 index_first = m_index->buff[i];
const u32 index_last = m_index->buff[i + 1];
const u32 index_first = idx_buff.buff[i];
const u32 index_last = idx_buff.buff[i + 1];
const u32 x = abs(((v[index_last].ST.S / v[index_last].RGBAQ.Q) * (1 << m_context->TEX0.TW)) - ((v[index_first].ST.S / v[index_first].RGBAQ.Q) * (1 << m_context->TEX0.TW)));
const u32 y = abs(((v[index_last].ST.T / v[index_last].RGBAQ.Q) * (1 << m_context->TEX0.TH)) - ((v[index_first].ST.T / v[index_first].RGBAQ.Q) * (1 << m_context->TEX0.TH)));
@@ -2756,7 +2761,8 @@ void GSRendererHW::RoundSpriteOffset()
void GSRendererHW::Draw()
{
static u32 num_skipped_channel_shuffle_draws = 0;
GSVertexBuff& vtx_buff = *m_vertex;
GSIndexBuff& idx_buff = *m_index;
// We mess with this state as an optimization, so take a copy and use that instead.
const GSDrawingContext* context = m_context;
m_cached_ctx.TEX0 = context->TEX0;
@@ -3183,7 +3189,7 @@ void GSRendererHW::Draw()
}
const u32 vert_index = (m_vt.m_primclass == GS_TRIANGLE_CLASS) ? 2 : 1;
u32 const_color = m_vertex->buff[m_index->buff[vert_index]].RGBAQ.U32[0];
u32 const_color = vtx_buff.buff[idx_buff.buff[vert_index]].RGBAQ.U32[0];
u32 fb_mask = m_cached_ctx.FRAME.FBMSK;
// If we could just check the colour, it would be great, but Echo Night decided it's going to set the alpha and green to 128, for some reason, and actually be 32bit, so it ruined my day.
@@ -3219,14 +3225,16 @@ void GSRendererHW::Draw()
m_cached_ctx.TEXA.TA0 = 0;
m_cached_ctx.TEXA.TA1 = 128;
m_cached_ctx.FRAME.PSM = (m_cached_ctx.FRAME.PSM & 2) ? m_cached_ctx.FRAME.PSM : PSMCT16;
m_vertex->buff[m_index->buff[1]].RGBAQ.U32[0] = const_color;
vtx_buff.buff[idx_buff.buff[1]].RGBAQ.U32[0] = const_color;
ReplaceVerticesWithSprite(m_r, GSVector2i(m_r.width(), m_r.height()));
}
// Be careful of being 1 pixel from filled.
const bool page_aligned = (m_r.w % pgs.y) == (pgs.y - 1) || (m_r.w % pgs.y) == 0;
const bool is_zero_color_clear = (GetConstantDirectWriteMemClearColor() == 0 && !preserve_rt_color && page_aligned);
const bool is_zero_depth_clear = (GetConstantDirectWriteMemClearDepth() == 0 && !preserve_depth && page_aligned);
const bool is_full_color_cover = !preserve_rt_color && page_aligned;
const bool is_full_depth_cover = !preserve_depth && page_aligned;
const bool is_zero_color_clear = (GetConstantDirectWriteMemClearColor() == 0 && is_full_color_cover);
const bool is_zero_depth_clear = (GetConstantDirectWriteMemClearDepth() == 0 && is_full_depth_cover);
bool gs_mem_cleared = false;
// If it's an invalid-sized draw, do the mem clear on the CPU, we don't want to create huge targets.
// If clearing to zero, don't bother creating the target. Games tend to clear more than they use, wasting VRAM/bandwidth.
@@ -3261,8 +3269,8 @@ void GSRendererHW::Draw()
}
gs_mem_cleared |= overwriting_whole_rt && overwriting_whole_ds && (!no_rt || !no_ds);
if (overwriting_whole_rt && overwriting_whole_ds &&
TryGSMemClear(no_rt, preserve_rt_color, is_zero_color_clear, rt_end_bp,
no_ds, preserve_depth, is_zero_depth_clear, ds_end_bp))
TryGSMemClear(no_rt, preserve_rt_color, is_full_color_cover, rt_end_bp,
no_ds, preserve_depth, is_full_depth_cover, ds_end_bp))
{
GL_INS("HW: Skipping (%d,%d=>%d,%d) draw at FBP %x/ZBP %x due to invalid height or zero clear.", m_r.x, m_r.y,
m_r.z, m_r.w, m_cached_ctx.FRAME.Block(), m_cached_ctx.ZBUF.Block());
@@ -3285,8 +3293,14 @@ void GSRendererHW::Draw()
}
}
CleanupDraw(false);
return;
no_rt |= is_zero_color_clear;
no_ds |= is_zero_depth_clear;
if (no_rt && no_ds)
{
CleanupDraw(false);
return;
}
}
}
@@ -3435,14 +3449,14 @@ void GSRendererHW::Draw()
bool shuffle_target = false;
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)))
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].trbpp <= 16 &&
(m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (idx_buff.tail % 6) == 0 && TrianglesAreQuads(true) && idx_buff.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.
if (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 &&
(m_index->tail >= (m_cached_ctx.TEX0.TBW * 2) || m_cached_ctx.TEX0.TBP0 == m_cached_ctx.FRAME.Block() || m_cached_ctx.CLAMP.WMS > CLAMP_CLAMP || m_cached_ctx.CLAMP.WMT > CLAMP_CLAMP))
(idx_buff.tail >= (m_cached_ctx.TEX0.TBW * 2) || m_cached_ctx.TEX0.TBP0 == m_cached_ctx.FRAME.Block() || m_cached_ctx.CLAMP.WMS > CLAMP_CLAMP || m_cached_ctx.CLAMP.WMT > CLAMP_CLAMP))
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &vtx_buff.buff[0];
const int first_x = std::clamp((static_cast<int>(((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8))) >> 4, 0, 2048);
const bool offset_last = PRIM->FST ? (v[1].U > v[0].U) : ((v[1].ST.S / v[1].RGBAQ.Q) > (v[0].ST.S / v[1].RGBAQ.Q));
@@ -3468,11 +3482,11 @@ void GSRendererHW::Draw()
{
bool shuffle_channel_reads = !m_cached_ctx.FRAME.FBMSK;
const u32 increment = (m_vt.m_primclass == GS_TRIANGLE_CLASS) ? 3 : 2;
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &vtx_buff.buff[0];
if (shuffle_channel_reads)
{
for (u32 i = 0; i < m_index->tail; i += increment)
for (u32 i = 0; i < idx_buff.tail; i += increment)
{
const int first_u = (PRIM->FST ? v[i].U : static_cast<int>(v[i].ST.S / v[(increment == 2) ? i + 1 : i].RGBAQ.Q)) >> 4;
const int second_u = (PRIM->FST ? v[i + 1].U : static_cast<int>(v[i + 1].ST.S / v[i + 1].RGBAQ.Q)) >> 4;
@@ -3821,9 +3835,10 @@ void GSRendererHW::Draw()
if (vertical_offset || horizontal_offset)
{
GSVertex* v = &m_vertex->buff[0];
GSVertex* v = &vtx_buff.buff[0];
const u32 tail = vtx_buff.tail;
for (u32 i = 0; i < m_vertex->tail; i++)
for (u32 i = 0; i < tail; i++)
{
v[i].XYZ.X += horizontal_offset << 4;
v[i].XYZ.Y += vertical_offset << 4;
@@ -4118,9 +4133,10 @@ void GSRendererHW::Draw()
if (vertical_offset || horizontal_offset)
{
GSVertex* v = &m_vertex->buff[0];
GSVertex* v = &vtx_buff.buff[0];
const u32 tail = vtx_buff.tail;
for (u32 i = 0; i < m_vertex->tail; i++)
for (u32 i = 0; i < tail; i++)
{
v[i].XYZ.X += horizontal_offset << 4;
v[i].XYZ.Y += vertical_offset << 4;
@@ -4579,13 +4595,13 @@ void GSRendererHW::Draw()
if (!m_texture_shuffle && !m_channel_shuffle)
{
// Try to turn blits in to single sprites, saves upscaling problems when striped clears/blits.
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_primitive_covers_without_gaps == NoGapsType::FullCover && m_index->tail > 2 && (!PRIM->TME || TextureCoversWithoutGapsNotEqual()) && m_vt.m_eq.rgba == 0xFFFF)
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_primitive_covers_without_gaps == NoGapsType::FullCover && idx_buff.tail > 2 && (!PRIM->TME || TextureCoversWithoutGapsNotEqual()) && m_vt.m_eq.rgba == 0xFFFF)
{
// Full final framebuffer only.
const GSVector2i fb_size = PCRTCDisplays.GetFramebufferSize(-1);
if (std::abs(fb_size.x - m_r.width()) <= 1 && std::abs(fb_size.y - m_r.height()) <= 1)
{
GSVertex* v = m_vertex->buff;
GSVertex* v = vtx_buff.buff;
v[0].XYZ.Z = v[1].XYZ.Z;
v[0].RGBAQ = v[1].RGBAQ;
@@ -4594,23 +4610,23 @@ void GSRendererHW::Draw()
m_vt.m_eq.z = true;
m_vt.m_eq.f = true;
v[1].XYZ.X = v[m_index->tail - 1].XYZ.X;
v[1].XYZ.Y = v[m_index->tail - 1].XYZ.Y;
v[1].XYZ.X = v[idx_buff.tail - 1].XYZ.X;
v[1].XYZ.Y = v[idx_buff.tail - 1].XYZ.Y;
if (PRIM->FST)
{
v[1].U = v[m_index->tail - 1].U;
v[1].V = v[m_index->tail - 1].V;
v[1].U = v[idx_buff.tail - 1].U;
v[1].V = v[idx_buff.tail - 1].V;
}
else
{
v[1].ST.S = v[m_index->tail - 1].ST.S;
v[1].ST.T = v[m_index->tail - 1].ST.T;
v[1].RGBAQ.Q = v[m_index->tail - 1].RGBAQ.Q;
v[1].ST.S = v[idx_buff.tail - 1].ST.S;
v[1].ST.T = v[idx_buff.tail - 1].ST.T;
v[1].RGBAQ.Q = v[idx_buff.tail - 1].RGBAQ.Q;
}
m_vertex->head = m_vertex->tail = m_vertex->next = 2;
m_index->tail = 2;
vtx_buff.head = vtx_buff.tail = vtx_buff.next = 2;
idx_buff.tail = 2;
}
}
@@ -5068,8 +5084,8 @@ void GSRendererHW::Draw()
// but it still needs to adjust native stuff from memory as it's not been compensated for upscaling (Dragon Quest 8 font for example).
if (CanUpscale() && (m_vt.m_primclass == GS_SPRITE_CLASS) && rt && rt->GetScale() > 1.0f)
{
const u32 count = m_vertex->next;
GSVertex* v = &m_vertex->buff[0];
const u32 count = vtx_buff.next;
GSVertex* v = &vtx_buff.buff[0];
// Hack to avoid vertical black line in various games (ace combat/tekken)
if (GSConfig.UserHacks_AlignSpriteX)
@@ -5296,33 +5312,35 @@ void GSRendererHW::Draw()
/// Verifies assumptions we expect to hold about indices
bool GSRendererHW::VerifyIndices()
{
GSIndexBuff& idx_buff = *m_index;
switch (m_vt.m_primclass)
{
case GS_SPRITE_CLASS:
if (m_index->tail % 2 != 0)
if (idx_buff.tail % 2 != 0)
return false;
[[fallthrough]];
case GS_POINT_CLASS:
// Expect indices to be flat increasing
for (u32 i = 0; i < m_index->tail; i++)
for (u32 i = 0; i < idx_buff.tail; i++)
{
if (m_index->buff[i] != i)
if (idx_buff.buff[i] != i)
return false;
}
break;
case GS_LINE_CLASS:
if (m_index->tail % 2 != 0)
if (idx_buff.tail % 2 != 0)
return false;
// Expect each line to be a pair next to each other
// VS expand relies on this!
for (u32 i = 0; i < m_index->tail; i += 2)
for (u32 i = 0; i < idx_buff.tail; i += 2)
{
if (m_index->buff[i] + 1 != m_index->buff[i + 1])
if (idx_buff.buff[i] + 1 != idx_buff.buff[i + 1])
return false;
}
break;
case GS_TRIANGLE_CLASS:
if (m_index->tail % 3 != 0)
if (idx_buff.tail % 3 != 0)
return false;
break;
case GS_INVALID_CLASS:
@@ -5343,15 +5361,17 @@ void GSRendererHW::HandleFlatShadedVertices()
if (!maybe_fix_vertices || dont_fix_vertices)
return;
const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
const u32 n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
GSVertexBuff& vtx_buff = *m_vertex;
GSIndexBuff& idx_buff = *m_index;
// If all vertices of each prim have the same color there is nothing to do.
bool prims_flat = true;
for (u32 i = 0; i < m_index->tail; i += n)
for (u32 i = 0; i < idx_buff.tail; i += n)
{
for (u32 j = 0; j < n - 1; j++)
{
if (m_vertex->buff[m_index->buff[i + j]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
if (vtx_buff.buff[idx_buff.buff[i + j]].RGBAQ.U32[0] != vtx_buff.buff[idx_buff.buff[i + n - 1]].RGBAQ.U32[0])
{
prims_flat = false;
break;
@@ -5364,21 +5384,22 @@ void GSRendererHW::HandleFlatShadedVertices()
return;
// De-index the vertices using the copy buffer
while (m_vertex->maxcount < m_index->tail)
while (vtx_buff.maxcount < idx_buff.tail)
GrowVertexBuffer();
for (int i = static_cast<int>(m_index->tail) - 1; i >= 0; i--)
for (int i = static_cast<int>(idx_buff.tail) - 1; i >= 0; i--)
{
m_vertex->buff_copy[i] = m_vertex->buff[m_index->buff[i]];
m_index->buff[i] = static_cast<u16>(i);
vtx_buff.buff_copy[i] = vtx_buff.buff[idx_buff.buff[i]];
idx_buff.buff[i] = static_cast<u16>(i);
}
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
std::swap(vtx_buff.buff, vtx_buff.buff_copy);
vtx_buff.head = vtx_buff.next = vtx_buff.tail = idx_buff.tail;
// Make all vertices the same color to simplify handling in expand shaders.
for (u32 i = 0; i < m_index->tail; i += n)
for (u32 i = 0; i < idx_buff.tail; i += n)
{
for (u32 j = 0; j < n - 1; j++)
m_vertex->buff[i + j].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
vtx_buff.buff[i + j].RGBAQ.U32[0] = vtx_buff.buff[i + n - 1].RGBAQ.U32[0];
}
}
@@ -7838,7 +7859,7 @@ void GSRendererHW::ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool
}
#if PCSX2_DEVBUILD
new_tex->SetDebugName(tgt->m_texture->GetDebugName());
new_tex->SetDebugName(old_tex->GetDebugName());
#endif
if (tgt->m_texture == old_tex)
@@ -7869,12 +7890,13 @@ void GSRendererHW::ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCa
// Convert depth to the proper type/format.
if (ds)
{
if (m_conf.ps.HasDepthROV() && !ds->m_texture->IsShaderWrite())
// Note: we must use m_conf.ds because it might be the temporary Z texture.
if (m_conf.ps.HasDepthROV() && !m_conf.ds->IsShaderWrite())
{
GL_PUSH("HW: Convert DepthStencil -> DepthColor for ROV.");
ConvertTextureTypeROVSingle(ds, true);
}
else if (!m_conf.ps.HasDepthROV() && !ds->m_texture->IsDepthStencil())
else if (!m_conf.ps.HasDepthROV() && !m_conf.ds->IsDepthStencil())
{
GL_PUSH("HW: Convert DepthColor -> DepthStencil for non-ROV.");
ConvertTextureTypeROVSingle(ds, false);
@@ -9860,11 +9882,13 @@ bool GSRendererHW::DetectStripedDoubleClear(bool& no_rt, bool& no_ds)
// and I could cheat and stop when we get a size that matches, but that might be a lucky misdetection, I don't wanna risk it.
int vertex_offset = 0;
int last_vertex = m_vertex->buff[0].XYZ.X;
const u32 tail = m_vertex->tail;
GSVertexBuff& vtx_buff = *m_vertex;
for (u32 i = 1; i < m_vertex->tail; i++)
for (u32 i = 1; i < tail; i++)
{
vertex_offset = std::max(static_cast<int>((m_vertex->buff[i].XYZ.X - last_vertex) >> 4), vertex_offset);
last_vertex = m_vertex->buff[i].XYZ.X;
vertex_offset = std::max(static_cast<int>((vtx_buff.buff[i].XYZ.X - last_vertex) >> 4), vertex_offset);
last_vertex = vtx_buff.buff[i].XYZ.X;
// Found a gap which is much bigger, no point continuing to scan.
if (vertex_offset > strip_size)
@@ -9975,9 +9999,11 @@ bool GSRendererHW::DetectDoubleHalfClear(bool& no_rt, bool& no_ds)
// path write out FRAME and Z separately, with their associated masks. Limit it to black to avoid false positives.
if (write_color == 0)
{
// Don't check the *entire* size for the end, as some games (X-Men) decide it's done with Z and starts overwriting
// the end with other things, which causes a resize, so the end point is no longer in the right place.
// So let's just check there's at least one page over the half way point, at worst it means 2 targets overlap.
const GSTextureCache::Target* base_tgt = g_texture_cache->GetExactTarget(base * GS_BLOCKS_PER_PAGE,
m_cached_ctx.FRAME.FBW, clear_depth ? GSTextureCache::DepthStencil : GSTextureCache::RenderTarget,
GSLocalMemory::GetEndBlockAddress(half * GS_BLOCKS_PER_PAGE, m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r));
m_cached_ctx.FRAME.FBW, clear_depth ? GSTextureCache::DepthStencil : GSTextureCache::RenderTarget, (half + 1) * GS_BLOCKS_PER_PAGE);
if (base_tgt)
{
GL_INS("HW: DetectDoubleHalfClear(): Invalidating targets at 0x%x/0x%x due to different formats, and clear to black.",
@@ -10598,12 +10624,13 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual()
const int first_dpX = v[1].XYZ.X - v[0].XYZ.X;
const int first_dtV = v[1].V - v[0].V;
const int first_dtU = v[1].U - v[0].U;
const u32 next_count = m_vertex->next;
// Horizontal Match.
if ((first_dpX >> 4) == m_r.z)
{
// Borrowed from MergeSprite() modified to calculate heights.
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < next_count; i += 2)
{
const int last_tV = v[i - 1].V;
const int dtV = v[i + 1].V - v[i].V;
@@ -10621,7 +10648,7 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual()
if ((first_dpY >> 4) == m_r.w)
{
// Borrowed from MergeSprite().
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < next_count; i += 2)
{
const int last_tU = v[i - 1].U;
const int this_start_U = v[i].U;
@@ -10877,10 +10904,13 @@ void GSRendererHW::OffsetDraw(s32 fbp_offset, s32 zbp_offset, s32 xoffset, s32 y
const s32 fp_xoffset = xoffset << 4;
const s32 fp_yoffset = yoffset << 4;
for (u32 i = 0; i < m_vertex->next; i++)
GSVertexBuff& vtx_buff = *m_vertex;
for (u32 i = 0; i < vtx_buff.next; i++)
{
m_vertex->buff[i].XYZ.X += fp_xoffset;
m_vertex->buff[i].XYZ.Y += fp_yoffset;
vtx_buff.buff[i].XYZ.X += fp_xoffset;
vtx_buff.buff[i].XYZ.Y += fp_yoffset;
}
m_vt.m_min.p.x += static_cast<float>(xoffset);
+59 -8
View File
@@ -3565,16 +3565,62 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
// Make sure there's sufficient compatibility/overlap between the old target and the new target
// to warrant loading from the old target.
const u32 old_buffer_width = std::max(1U, old_dst->m_TEX0.TBW);
if (old_dst->m_TEX0.PSM != dst->m_TEX0.PSM ||
!old_dst->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid))
{
if (old_dst->m_TEX0.TBP0 == dst->m_TEX0.TBP0 && dst_end_block >= old_dst->m_end_block && (!src || !src->m_target || src->m_from_target != old_dst))
{
InvalidateSourcesFromTarget(old_dst);
i = list.erase(j);
delete old_dst;
continue;
}
if (old_dst->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid))
{
const GSLocalMemory::psm_t& psm_o = GSLocalMemory::m_psm[old_dst->m_TEX0.PSM];
if (dst->m_TEX0.TBP0 > old_dst->m_TEX0.TBP0)
{
const int block_diff = dst->m_TEX0.TBP0 - old_dst->m_TEX0.TBP0;
const u32 old_pages_wide = old_buffer_width * 64 / psm_o.pgs.x;
if ((block_diff % (GS_BLOCKS_PER_PAGE * old_pages_wide)) == 0) // Check for left alignment.
{
const int new_height = block_diff / (GS_BLOCKS_PER_PAGE * old_pages_wide) * psm_o.pgs.y;
old_dst->m_valid = old_dst->m_valid.rintersect(GSVector4i(old_dst->m_valid.x, old_dst->m_valid.y, old_dst->m_valid.z, new_height));
old_dst->ResizeValidity(old_dst->m_valid);
}
}
else // new target is behind the old one, so need to move the start of the old one to the end block of the new.
{
const int block_diff = dst_end_block - old_dst->m_TEX0.TBP0;
const u32 old_pages_wide = old_buffer_width * 64 / psm_o.pgs.x;
if ((block_diff % (GS_BLOCKS_PER_PAGE * old_pages_wide)) == 0) // Check for left alignment.
{
const int change_height = block_diff / (GS_BLOCKS_PER_PAGE * old_pages_wide) * psm_o.pgs.y;
old_dst->m_valid = old_dst->m_valid.rintersect(GSVector4i(old_dst->m_valid.x, old_dst->m_valid.y, old_dst->m_valid.z, old_dst->m_valid.w - change_height));
old_dst->m_TEX0.TBP0 += block_diff;
const GSVector2i new_scaled_size = GSVector2i(old_dst->m_unscaled_size * old_dst->m_scale);
if (GSTexture* tex = g_gs_device->CreateCompatible(dst->m_texture, new_scaled_size, true))
{
const int height_offset = change_height * old_dst->m_scale;
g_gs_device->CopyRect(old_dst->m_texture, tex, GSVector4i(0, height_offset, old_dst->GetUnscaledWidth() * old_dst->m_scale, old_dst->GetUnscaledHeight() * old_dst->m_scale), 0, 0);
g_gs_device->Recycle(old_dst->m_texture);
old_dst->m_texture = tex;
}
}
}
}
i++;
continue;
}
const int page_diff = std::abs(static_cast<int>(old_dst->m_TEX0.TBP0 - dst->m_TEX0.TBP0)) >> 5;
const u32 new_buffer_width = std::max(1U, dst->m_TEX0.TBW);
const u32 old_buffer_width = std::max(1U, old_dst->m_TEX0.TBW);
// Handle cases where the buffer widths don't match.
if (new_buffer_width != old_buffer_width)
@@ -3592,7 +3638,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
const u32 old_pages_wide = old_buffer_width * 64 / psm_s.pgs.x;
if ((block_diff % (32 * old_pages_wide)) == 0) // Check for left alignment.
{
const int new_height = block_diff / (32 * old_pages_wide) * psm_s.pgs.y;
const int new_height = block_diff / (GS_BLOCKS_PER_PAGE * old_pages_wide) * psm_s.pgs.y;
old_dst->m_valid = old_dst->m_valid.rintersect(GSVector4i(0, 0, old_dst->GetUnscaledWidth(), new_height));
if (old_dst->m_valid.rempty())
{
@@ -4543,10 +4589,14 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
InvalidateSourcesFromTarget(t);
t->m_valid_alpha_low &= preserve_alpha;
t->m_valid_alpha_high &= preserve_alpha;
t->m_valid_rgb &= (fb_mask & 0x00FFFFFF) != 0;
t->m_was_dst_matched = false;
if (type == DepthStencil || start_bp == t->m_TEX0.TBP0 || (start_bp < t->m_TEX0.TBP0 && t->UnwrappedEndBlock() <= end_bp))
{
const bool compatible_channel_swizzle = GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[write_psm].bpp;
t->m_valid_alpha_low &= preserve_alpha && compatible_channel_swizzle;
t->m_valid_alpha_high &= preserve_alpha && compatible_channel_swizzle;
t->m_valid_rgb &= (fb_mask & 0x00FFFFFF) != 0 && compatible_channel_swizzle;
t->m_was_dst_matched = false;
}
// Don't keep partial depth buffers around.
if ((!t->m_valid_alpha_low && !t->m_valid_alpha_high && !t->m_valid_rgb) || type == DepthStencil)
@@ -8178,8 +8228,9 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca
{
// Can't do partial copies in DirectX for depth textures, and it's probably not ideal in other
// APIs either. So use a fullscreen quad setting depth instead.
// Use bilinear to avoid artifacts with upscaling. At native this is equivalent to nearest.
g_gs_device->StretchRectAuto(m_texture, tex, GSVector4(rc), Biln);
// Use bilinear to avoid artifacts with upscaling during native scaling.
const bool req_bilinear = m_downscaled && m_scale < g_gs_renderer->GetUpscaleMultiplier();
g_gs_device->StretchRectAuto(m_texture, tex, GSVector4(rc), req_bilinear ? Biln : Nearest);
}
else
{
+2 -1
View File
@@ -128,12 +128,13 @@ public:
{
PSSelector ps;
VSSelector vs;
u8 pad[3];
u8 pad[15];
__fi bool operator==(const ProgramSelector& p) const { return BitEqual(*this, p); }
__fi bool operator!=(const ProgramSelector& p) const { return !BitEqual(*this, p); }
};
static_assert(sizeof(ProgramSelector) == 32, "Program selector is 32 bytes");
static_assert(offsetof(ProgramSelector, pad) + sizeof(ProgramSelector::pad) == sizeof(ProgramSelector));
struct ProgramSelectorHash
{
+1 -2
View File
@@ -3906,8 +3906,7 @@ VkShaderModule GSDeviceVK::GetUtilityFragmentShader(const std::string& source, c
bool GSDeviceVK::CreateNullTexture()
{
GSTexture::Usage null_usage = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
m_null_texture = GSTextureVK::Create(null_usage, GSTexture::Format::Color, 1, 1, 1);
m_null_texture = GSTextureVK::Create(GSTexture::ShaderWriteTarget, GSTexture::Format::Color, 1, 1, 1);
if (!m_null_texture)
return false;
+2 -2
View File
@@ -380,8 +380,8 @@ bool FullscreenUI::HasActiveWindow()
bool FullscreenUI::AreAnyDialogsOpen()
{
return (s_save_state_selector_open || s_about_window_open || s_cover_downloader_open ||
s_input_binding_type != InputBindingInfo::Type::Unknown || ImGuiFullscreen::IsChoiceDialogOpen() ||
ImGuiFullscreen::IsFileSelectorOpen());
s_achievements_login_open || s_input_binding_type != InputBindingInfo::Type::Unknown ||
ImGuiFullscreen::IsChoiceDialogOpen() || ImGuiFullscreen::IsFileSelectorOpen());
}
void FullscreenUI::CheckForConfigChanges(const Pcsx2Config& old_config)
+7 -2
View File
@@ -4349,6 +4349,8 @@ void FullscreenUI::DrawAchievementsLoginWindow()
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
{
ResetFocusHere();
const float content_width = ImGui::GetContentRegionAvail().x;
ImGui::PushFont(g_large_font.first, g_large_font.second);
@@ -4448,6 +4450,8 @@ void FullscreenUI::DrawAchievementsLoginWindow()
s_achievements_login_username[0] = '\0';
s_achievements_login_password[0] = '\0';
QueueResetFocus(FocusResetType::PopupClosed);
};
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
@@ -4463,7 +4467,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
if ((ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) || WantsToCloseMenu()) && !s_achievements_login_logging_in)
CloseLoginPopup();
ImGui::PopStyleColor(3);
@@ -4580,7 +4584,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Cancel"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
if ((ImGui::Button(FSUI_CSTR("Cancel"), ImVec2(button_width, button_height)) || WantsToCloseMenu()) && !s_achievements_login_logging_in)
{
if (s_achievements_login_reason == Achievements::LoginRequestReason::TokenInvalid)
{
@@ -4803,6 +4807,7 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
s_achievements_login_show_dismiss = false;
s_achievements_login_open = true;
QueueResetFocus(FocusResetType::PopupOpened);
}
}
+2 -2
View File
@@ -1635,8 +1635,8 @@ void SaveStateSelectorUI::Draw()
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollbar))
{
// Leave 2 lines for the legend
const float legend_margin = ImGui::GetFontSize() * 3.0f + ImGui::GetStyle().ItemSpacing.y * 3.0f;
// Leave room for the legend.
const float legend_margin = ImGui::GetTextLineHeightWithSpacing() * 4.0f;
const float padding = 10.0f * scale;
ImGui::BeginChild("##item_list", ImVec2(0, -legend_margin), false,
+1 -1
View File
@@ -555,7 +555,7 @@ const Pad::ControllerInfo& PadDualshock2::GetInfo() const
void PadDualshock2::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
if (index >= Inputs::LENGTH)
{
return;
}
+1 -1
View File
@@ -258,7 +258,7 @@ const Pad::ControllerInfo& PadGuitar::GetInfo() const
void PadGuitar::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
if (index >= Inputs::LENGTH)
{
return;
}
+1 -1
View File
@@ -324,7 +324,7 @@ const Pad::ControllerInfo& PadJogcon::GetInfo() const
void PadJogcon::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
if (index >= Inputs::LENGTH)
{
return;
}
+1 -1
View File
@@ -307,7 +307,7 @@ const Pad::ControllerInfo& PadNegcon::GetInfo() const
void PadNegcon::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
if (index >= Inputs::LENGTH)
{
return;
}
+1 -1
View File
@@ -371,7 +371,7 @@ const Pad::ControllerInfo& PadPopn::GetInfo() const
void PadPopn::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
if (index >= Inputs::LENGTH)
{
return;
}
+1 -1
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 = 107; // Last changed in PR 14634
static constexpr u32 SHADER_CACHE_VERSION = 108; // Last changed in PR 14688
+3
View File
@@ -80,6 +80,8 @@ namespace usb_lightgun
{"SLUS-20669", 90.25f, 93.5f, 420, 132, 640, 240}, // Resident Evil - Dead Aim (U)
{"SLUS-20619", 90.25f, 91.75f, 453, 154, 640, 256}, // Starsky & Hutch (U)
{"SCES-50300", 90.25f, 102.75f, 390, 138, 640, 256}, // Time Crisis II (E)
{"SLPS-20122", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis II (J)
{"SLPS-20113", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis II (with GunCon 2) (J)
{"SLUS-20219", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis 2 (U)
{"SCES-51844", 90.25f, 102.75f, 390, 138, 640, 256}, // Time Crisis 3 (E)
{"SLUS-20645", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis 3 (U)
@@ -90,6 +92,7 @@ namespace usb_lightgun
{"SLPS-25077", 90.0f, 97.5f, 422, 118, 640, 240}, // Vampire Night (J)
{"SLUS-20221", 89.8f, 102.5f, 422, 124, 640, 228}, // Vampire Night (U)
{"SLES-51229", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop - Elite Edition (E,J) (480i)
{"SLPM-62205", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop Re-Birth (J) (480i)
// {"SLES-51229", 85.75f, 92.0f, 456, 164, 640, 256}, // Virtua Cop - Elite Edition (E,J) (480p)
};