Compare commits

..
Author SHA1 Message Date
refractionpcsx2 846784650d GS: Optimize draw buffering pointer logic and some small tweaks 2026-07-18 14:52:46 +01:00
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
TellowKrinkleandGitHub 5e3ad282a8 common: Fix build on ryml < 0.11 2026-07-11 23:13:53 -05:00
PCSX2 Botandlightningterror 091b3a8694 [ci skip] Qt: Update Base Translation. 2026-07-12 02:18:27 +02:00
KamFretoZandlightningterror 42ce97aca5 Qt: Clean up Setup Wizard's labels 2026-07-11 15:41:27 +02:00
KamFretoZandlightningterror a4df5c300e Qt/Launcher: Fix StartupWMClass Value 2026-07-11 15:41:27 +02:00
KamFretoZandlightningterror 4bf51e735a Qt: Add Create PCSX2 shortcut option to setup wizard 2026-07-11 15:41:27 +02:00
KamFretoZandlightningterror d7e7c8164c Qt: Decouple CreateShortcut from the ShortcutCreationDialog 2026-07-11 15:41:27 +02:00
TJnotJTandlightningterror eb5e3fcd13 GS/HW: Make all vertices the same color in certain flat shading cases.
Fixes issues with AA1 with flat shading.
2026-07-10 10:16:51 +02:00
TheLastRarandTy 8f8968cbb1 Deps: Revert workaround for MSVC ICE with PlutoVG
This reverts commit 7ade24fc77.
2026-07-09 18:40:47 -04:00
SilentandTy ac83e93426 GameDB: Update Gran Turismo entries to use anchors and overrides 2026-07-09 15:53:38 -04:00
SilentandTy 78d0cf4a4f YAML: Add support for resolving anchors, and use it for GameDB 2026-07-09 15:53:38 -04:00
lightningterror 2444c6090b GS: Fix Compiler warnings. 2026-07-09 18:06:50 +02:00
JordanTheToasterandlightningterror 7e49bc5a50 3rdparty: Update Vulkan-Headers to v1.4.356 2026-07-09 12:05:39 +02:00
JordanTheToasterandlightningterror 457719d0cc Deps: Update libjpegturbo to 3.2.0 2026-07-09 12:05:39 +02:00
JordanTheToasterandlightningterror c3e63d5a0e 3rdparty: Update CPUInfo to commit b1a5d63 2026-07-09 12:05:39 +02:00
TJnotJTandlightningterror 8c1b3f8ce3 QT: Remove ROV tabstop in graphics advanced section.
No longer used in this part of the GUI.
2026-07-09 11:44:18 +02:00
TJnotJTandlightningterror 55b37b794c GS:DX12: Index pipelines in correct shader cache table. 2026-07-09 11:43:44 +02:00
TJnotJTandlightningterror 1aca396e34 GS/DX12: Make sure null texture has correct descriptor type for slot. 2026-07-09 11:43:17 +02:00
PCSX2 BotandTy cb000a5101 [ci skip] Qt: Update Base Translation. 2026-07-08 20:48:48 -04:00
chaoticgdandlightningterror 1ae2a96747 Debugger: Allow strings to be edited from the symbol trees 2026-07-08 15:02:23 +02:00
SternXDandlightningterror 6834086c37 Achievements: Improve subset sidebar nav/focus behavior 2026-07-08 15:00:49 +02:00
SternXDandlightningterror b33b493c6e FullscreenUI: Use locale time on landing page instead of 24 hour 2026-07-08 15:00:49 +02:00
SternXDandlightningterror 437fe1e442 FullscreenUI: Move game info and cover art to top left of pause menu 2026-07-08 15:00:49 +02:00
TellowKrinkleandlightningterror 2a348dcff9 GS:MTL: Add ROV support for RW Textures Tier 1 hardware 2026-07-08 14:08:29 +02:00
TellowKrinkleandlightningterror 2821986fb2 GS:MTL: Implement ROV 2026-07-08 14:08:29 +02:00
TellowKrinkleandlightningterror 1a13f9707b GS:MTL: GPU ROV feature detection 2026-07-08 14:08:29 +02:00
TellowKrinkleandlightningterror ff1b47942c GS:MTL: Reduce unneccessary texture rebinds 2026-07-08 14:08:29 +02:00
TellowKrinkleandlightningterror 13e4973b48 GS:MTL: Fix texture flags 2026-07-08 14:08:29 +02:00
raid273andlightningterror fd46054fb7 GameDB: Remove cpuSpriteRender from Mana-Khemia 2026-07-04 21:51:16 +02:00
raid273andlightningterror f618f39bee GameDB: Various fixes 2026-07-04 21:51:16 +02:00
JordanTheToasterandlightningterror 603486ad2f Deps: Update SDL3 to v3.4.12 2026-07-04 21:49:31 +02:00
lightningterror a4b11651a4 GS: Better handle imgui texture creation hazards. 2026-07-04 20:40:55 +02:00
PCSX2 BotandTy b6e9a04433 [ci skip] Qt: Update Base Translation. 2026-07-03 20:22:06 -04:00
MrlinkwiiandTy 05a2bbbf35 GameDB: remove gamefixes from silent hill 3 2026-07-03 16:09:21 -04:00
Oleg TolmatcevandTy 16b6572826 Misc: another COM code cleanup 2026-07-03 16:08:58 -04:00
MrlinkwiiandTy 806f06fba6 Achievements: Rename softcore to Casual 2026-07-03 16:07:16 -04:00
TJnotJTandlightningterror 7a90caedec GS/HW: Force depth ROV if alpha test needs depth feedback. 2026-07-02 19:46:47 +02:00
TJnotJTandlightningterror 017b4d07da GS/HW: Improve ROV discard behavior.
Try to avoid color/depth write when it's discarded.
2026-07-02 19:46:47 +02:00
lightningterror 05015bc476 GS/HW: Better handle some texture creation hazards.
BeginDSAsRT and ConvertTextureTypeROVSingle make sure the textures are actually created.
2026-07-02 11:24:08 +02:00
lightningterror 1f0f3b92b3 GS/DX12: Expand the fb optimization hit detection. 2026-07-02 11:21:49 +02:00
JordanTheToasterandGovanifY fe3ce7f578 GameDB: Various Gran Turismo fixes 2026-07-01 11:40:37 +02:00
PCSX2 Botandlightningterror 51c995168a [ci skip] PAD: Update to latest controller database. 2026-06-29 18:57:33 +02:00
PCSX2 BotandTy f83e4ff4bf [ci skip] Qt: Update Base Translation. 2026-06-28 20:26:52 -04:00
chaoticgdandTy b67e793283 PINE: Disallow access to pages with custom VTLB handlers 2026-06-28 16:46:13 -04:00
chaoticgdandTy d87ec54a5f VTLB: Rewrite vtlb_ramRead function template
Previously the declaration didn't match the definition, so you couldn't
actually call it.
2026-06-28 16:46:13 -04:00
TJnotJTlightningterrorrefractionpcsx2
a6b1040caf GS: Update target TEX0 on a move if buffer width is increased.
Co-authored-by: refractionpcsx2
2026-06-28 21:51:10 +02:00
TheLastRarandlightningterror 9650c4b79a GS/HW: Tweak AF shader 2026-06-28 21:46:04 +02:00
wxvuandTy 3f8eb5adf0 GameDB: Various fixes 2026-06-28 13:58:29 -04:00
TJnotJTandTy 99a78f9a55 GS: Add GPU pipeline stats to OSD.
Adds vertex shader and pixel shader invocations.
2026-06-28 12:39:56 -04:00
TJnotJTandTy c3ace64205 GS/HW: Change prefer_unused_texture parameter to prefer_reuse in GSDevice::FetchSurface().
prefer_reuse is the opposite of prefer_unused_texture, so all corresponding arguments are inverted.
2026-06-28 12:13:38 -04:00
TJnotJTandTy 3b2c1ec6e3 GS/DX12: Fix DX12 renderpass restart optimization rebase error. 2026-06-28 12:13:38 -04:00
TJnotJTandTy dba432639e GS/VK: Detect max supported framebuffer dimensions for null framebuffer. 2026-06-28 12:13:38 -04:00
TJnotJTandTy 166c4491c2 GS/HW: ROV activation revisions.
Force color ROV if depth ROV is used.
Only force color/depth ROV if the draw actually uses both color/depth (fixes a bug with temporary DS for stencil being used as a ROV).
Simplify the ROV activation heuristic a bit (removed unused variables, simplify barrier counting, etc.)
2026-06-28 12:13:38 -04:00
TJnotJTTyTellowKrinkle
d1cd5a62ed GS/HW: ROV revisions.
Use GSTexture::FeedbackTarget RTs by default (not GSTexture::ShaderWriteTarget).
Do ROV type conversion for both color and depth (if needed).
Make sure the formats for ROV actually support UAV/storage image.
Change 'PSSetUnorderedAccess' to 'PSSetROVs' for clarity.
DX12: Use enum names instead of raw constants in PSSetShaderResource().
Some string format cleanup to GL_*() macros.
VK: Do not enable ROV if FB fetch is available.
Other misc cosmetic changes such as whitespace, etc.

Co-authored-by: TellowKrinkle
2026-06-28 12:13:38 -04:00
TJnotJTTyTellowKrinkle
b11a406519 GS: Change GSTexture::Type to GSTexture::Usage
Usage fits more cleanly with API flags.
Also separates out feedback and write usage from render target.

Co-authored-by: TellowKrinkle
2026-06-28 12:13:38 -04:00
TJnotJTTyTellowKrinkle
c7042e1877 GS/HW: Use CreateCompatible() in texture cache to propagate correct formats.
Co-authored-by: TellowKrinkle
2026-06-28 12:13:38 -04:00
DevloonandTy 896e0b82c2 Qt: show full game titles in grid mode.
Added UI element with new icon to toggle showing full game titles by wrapping around to the next line.
2026-06-28 11:55:18 -04:00
chaoticgdandTy 7856757087 AudioStream: Don't compare uninitialized padding 2026-06-27 16:51:29 -04:00
chaoticgdandTy b0ce5d7582 Debugger: Fix some uninitialized variables 2026-06-27 16:51:29 -04:00
ZiemasandTy e23416aa92 IR3000A: Use SmallString with Zydis formatter 2026-06-27 14:26:08 -04:00
ZiemasandTy 9a9cfca706 IR5900: Use SmallString with Zydis formatter 2026-06-27 14:26:08 -04:00
ZiemasandTy 59feb88ef7 IR5900: fix recMemcheck to not cache loaded reg
This functions calls out of the JIT, so it will get stomped on by
something else.
2026-06-27 14:26:08 -04:00
ZiemasandTy 247cda0e26 IR3000A: DUMP_BLOCKS: Symbolize text ptr relative 2026-06-27 14:26:08 -04:00
ZiemasandTy 9f3233224e IR5900: DUMP_BLOCKS: Symbolize text ptr relative 2026-06-27 14:26:08 -04:00
PCSX2 Botandlightningterror ef14238b4e [ci skip] Qt: Update Base Translation. 2026-06-27 09:02:47 +02:00
lightningterror 13b7345189 GS/HW: Fix bbox when using aa1.
Suggested by TJ.
2026-06-27 09:02:11 +02:00
195 changed files with 7581 additions and 4844 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
@@ -23,11 +23,11 @@ QTAPNG=1.3.0
FFMPEG=8.1
LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
LIBJPEGTURBO=3.1.4.1
LIBJPEGTURBO=3.2.0
LIBPNG=1.6.58
LIBWEBP=1.6.0
NVENC=13.0.19.0
SDL=SDL3-3.4.10
SDL=SDL3-3.4.12
LZ4=1.10.0
VULKAN=1.4.328.1
ZSTD=1.5.7
@@ -57,10 +57,10 @@ f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
96e5c2d7f2c482a60d5804da48a2eb9a0db0719b2c65dcc169fbfdcf37f3a45d libbacktrace-$LIBBACKTRACE.tar.gz
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
28eb403f51f0f7405249132cecfe82ea5c0ef97f1b32c5a65828814ae0d34775 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 $SDL.tar.gz
f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7 $SDL.tar.gz
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
13da39edb3a40ed9713ae390ca89faa2f1202c9dda869ef306a8d4383e242bee nv-codec-headers-$NVENC.tar.gz
@@ -14,8 +14,8 @@
"sources": [
{
"type": "archive",
"url": "https://libsdl.org/release/SDL3-3.4.10.tar.gz",
"sha256": "12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785"
"url": "https://libsdl.org/release/SDL3-3.4.12.tar.gz",
"sha256": "f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7"
}
],
"cleanup": [
@@ -3,7 +3,7 @@ Version=1.0
Terminal=false
Type=Application
Name=PCSX2
StartupWMClass=PCSX2
StartupWMClass=pcsx2-qt
GenericName=PlayStation 2 Emulator
Comment=Sony PlayStation 2 emulator
Exec=pcsx2-qt
@@ -47,12 +47,12 @@ QT=6.11.1
QTAPNG=1.3.0
FREETYPE=2.14.3
SDL=SDL3-3.4.10
SDL=SDL3-3.4.12
HARFBUZZ=14.2.0
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.58
LIBJPEGTURBO=3.1.4.1
LIBJPEGTURBO=3.2.0
LIBWEBP=1.6.0
FFMPEG=8.1
MOLTENVK=1.4.1
@@ -93,14 +93,14 @@ b2bf6c6845ac175ed7f819145483ba4676f617aaa6a5012c8efee63c8bbac413 qtimageformats
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f freetype-$FREETYPE.tar.xz
12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 $SDL.tar.gz
f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7 $SDL.tar.gz
c652d5d94971031654ab3989891a490a895d3e3f2b71171c62692b28e94b1b93 harfbuzz-$HARFBUZZ.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
28eb403f51f0f7405249132cecfe82ea5c0ef97f1b32c5a65828814ae0d34775 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c MoltenVK-$MOLTENVK.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
@@ -24,12 +24,12 @@ QT=6.11.1
QTAPNG=1.3.0
FREETYPE=2.14.3
SDL=SDL3-3.4.10
SDL=SDL3-3.4.12
HARFBUZZ=14.2.0
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.58
LIBJPEGTURBO=3.1.4.1
LIBJPEGTURBO=3.2.0
LIBWEBP=1.6.0
FFMPEG=8.1
MOLTENVK=1.4.1
@@ -69,14 +69,14 @@ b2bf6c6845ac175ed7f819145483ba4676f617aaa6a5012c8efee63c8bbac413 qtimageformats
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f freetype-$FREETYPE.tar.xz
12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 $SDL.tar.gz
f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7 $SDL.tar.gz
c652d5d94971031654ab3989891a490a895d3e3f2b71171c62692b28e94b1b93 harfbuzz-$HARFBUZZ.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
28eb403f51f0f7405249132cecfe82ea5c0ef97f1b32c5a65828814ae0d34775 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
+4 -4
View File
@@ -60,8 +60,8 @@ set QTAPNG=1.3.0
set FREETYPE=2.14.3
set HARFBUZZ=14.2.0
set SDL=SDL3-3.4.10
set LIBJPEGTURBO=3.1.4.1
set SDL=SDL3-3.4.12
set LIBJPEGTURBO=3.2.0
set LIBPNG=1658
set LIBPNGLONG=1.6.58
set LZ4=1.10.0
@@ -92,9 +92,9 @@ call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip bb2f83255706b1c92d731541c7cefaf98bb5b93e8f76d16f6deda05225ff20ee || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1658.zip b32f170855dbbe3e6d9e645af40b538137041773672c3ba3e02db5816c82d376 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" be8dafef3ecc956e736cfd1c242c10737f3ad589a866f25ba437b7f2f6d5ced2 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 3d4de8967a49c0451e775a0c1e9022092c19fdef41ba38a83fcf031c5a6496e2 || goto error
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://github.com/madler/zlib/releases/download/v%ZLIB%/zlib%ZLIBSHORT%.zip" e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
+4 -5
View File
@@ -77,8 +77,8 @@ set LIBX264=b35605ace3ddf7c1a5d67a2eb553f034aef41d55
set FREETYPE=2.14.3
set HARFBUZZ=14.2.0
set SDL=SDL3-3.4.10
set LIBJPEGTURBO=3.1.4.1
set SDL=SDL3-3.4.12
set LIBJPEGTURBO=3.2.0
set LIBPNG=1658
set LIBPNGLONG=1.6.58
set LZ4=1.10.0
@@ -120,9 +120,9 @@ call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip bb2f83255706b1c92d731541c7cefaf98bb5b93e8f76d16f6deda05225ff20ee || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1658.zip b32f170855dbbe3e6d9e645af40b538137041773672c3ba3e02db5816c82d376 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" be8dafef3ecc956e736cfd1c242c10737f3ad589a866f25ba437b7f2f6d5ced2 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 3d4de8967a49c0451e775a0c1e9022092c19fdef41ba38a83fcf031c5a6496e2 || goto error
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://github.com/madler/zlib/releases/download/v%ZLIB%/zlib%ZLIBSHORT%.zip" e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
@@ -471,7 +471,6 @@ echo "Building PlutoVG..."
rmdir /S /Q "plutovg-%PLUTOVG%"
%SEVENZIP% x "plutovg-%PLUTOVG%.zip" || goto error
cd "plutovg-%PLUTOVG%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\plutovg-workaround-msvc-ice.patch" || goto error
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DPLUTOVG_BUILD_EXAMPLES=OFF -B build -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
@@ -1,52 +0,0 @@
diff --git a/source/plutovg-font.c b/source/plutovg-font.c
index 0ad89b1..110de7f 100644
--- a/source/plutovg-font.c
+++ b/source/plutovg-font.c
@@ -836,7 +836,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
size_t family_index = 0;
if(unicode_family_name) {
const stbtt_uint8* family_name = unicode_family_name;
- while(family_length) {
+ //while(family_length) {
+ for (int i = family_length; i > 0; i -= 2) {
stbtt_uint16 ch = family_name[0] * 256 + family_name[1];
if(ch < 0x80) {
entry->family[family_index++] = ch;
@@ -853,7 +854,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
entry->family[family_index++] = (0x80 + ((c) & 0x3f));
family_name += 2;
- family_length -= 2;
+ //family_length -= 2;
+ i -= 2;
} else {
entry->family[family_index++] = (0xe0 + (ch >> 12));
entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f));
@@ -861,7 +863,7 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
}
family_name += 2;
- family_length -= 2;
+ //family_length -= 2;
}
entry->family[family_index] = '\0';
@@ -902,7 +904,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
};
const stbtt_uint8* family_name = roman_family_name;
- while(family_length) {
+ //while(family_length) {
+ for (int i2 = family_length; i2 > 0; i2--) {
stbtt_uint16 ch = MAC_ROMAN_TABLE[family_name[0]];
if(ch < 0x80) {
entry->family[family_index++] = ch;
@@ -916,7 +919,7 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
}
family_name += 1;
- family_length -= 1;
+ //family_length -= 1;
}
entry->family[family_index] = '\0';
+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
+2
View File
@@ -361,6 +361,8 @@ enum cpuinfo_uarch {
cpuinfo_uarch_raptor_cove = 0x0010020F,
/** Intel Redwood Cove microarchitecture (Granite Rapids). */
cpuinfo_uarch_redwood_cove = 0x00100210,
/** Intel Coyote Cove microarchitecture. */
cpuinfo_uarch_coyote_cove = 0x00100211,
/** Pentium 4 with Willamette, Northwood, or Foster cores. */
cpuinfo_uarch_willamette = 0x00100300,
+3
View File
@@ -1,3 +1,6 @@
/* for syscall() */
#define _DEFAULT_SOURCE
#include <stdbool.h>
#include <stddef.h>
+2
View File
@@ -36,6 +36,7 @@ enum cpuinfo_arm_chipset_vendor {
cpuinfo_arm_chipset_vendor_texas_instruments,
cpuinfo_arm_chipset_vendor_unisoc,
cpuinfo_arm_chipset_vendor_wondermedia,
cpuinfo_arm_chipset_vendor_google,
cpuinfo_arm_chipset_vendor_max,
};
@@ -73,6 +74,7 @@ enum cpuinfo_arm_chipset_series {
cpuinfo_arm_chipset_series_unisoc_t,
cpuinfo_arm_chipset_series_unisoc_ums,
cpuinfo_arm_chipset_series_wondermedia_wm,
cpuinfo_arm_chipset_series_google_tensor,
cpuinfo_arm_chipset_series_max,
};
+1 -1
View File
@@ -366,7 +366,7 @@ CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_f
CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_hardware_chipname(
const char ro_hardware_chipname[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]);
CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_soc_model(
const char ro_soc_model[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]);
const char soc_model[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]);
#else
CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_linux_decode_chipset_from_proc_cpuinfo_revision(
const char proc_cpuinfo_revision[restrict static CPUINFO_REVISION_VALUE_MAX]);
+61 -10
View File
@@ -3492,9 +3492,20 @@ struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_chipname(
};
}
/*
* Decodes chipset name from ro.soc.model Android system property.
*
* @param[in] soc_model - ro.soc.model value.
*
* @returns Decoded chipset name. If chipset could not be decoded, the resulting
* structure would use `unknown` vendor and series identifiers.
*/
struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_soc_model(
const char soc_model[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]) {
struct cpuinfo_arm_chipset chipset;
struct cpuinfo_arm_chipset chipset = {
.vendor = cpuinfo_arm_chipset_vendor_unknown,
.series = cpuinfo_arm_chipset_series_unknown,
};
const size_t soc_model_length = strnlen(soc_model, CPUINFO_BUILD_PROP_VALUE_MAX);
const char* soc_model_end = soc_model + soc_model_length;
@@ -3516,10 +3527,28 @@ struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_soc_model(
return chipset;
}
return (struct cpuinfo_arm_chipset){
.vendor = cpuinfo_arm_chipset_vendor_unknown,
.series = cpuinfo_arm_chipset_series_unknown,
};
if (soc_model[0] != '\0') {
if (strncmp(soc_model, "Tensor", 6) == 0) {
chipset.vendor = cpuinfo_arm_chipset_vendor_google;
chipset.series = cpuinfo_arm_chipset_series_google_tensor;
const char* suffix_start = soc_model + 6;
while (*suffix_start == ' ') {
suffix_start++;
}
const size_t suffix_length = strnlen(suffix_start, CPUINFO_ARM_CHIPSET_SUFFIX_MAX);
if (suffix_length > 0) {
strncpy(chipset.suffix, suffix_start, suffix_length);
}
return chipset;
} else if (strncmp(soc_model, "GS201", 5) == 0) {
chipset.vendor = cpuinfo_arm_chipset_vendor_google;
chipset.series = cpuinfo_arm_chipset_series_google_tensor;
strncpy(chipset.suffix, "G2", 2);
return chipset;
}
}
return chipset;
}
#endif /* __ANDROID__ */
@@ -3858,6 +3887,7 @@ static const char* chipset_vendor_string[cpuinfo_arm_chipset_vendor_max] = {
[cpuinfo_arm_chipset_vendor_texas_instruments] = "Texas Instruments",
[cpuinfo_arm_chipset_vendor_unisoc] = "Unisoc",
[cpuinfo_arm_chipset_vendor_wondermedia] = "WonderMedia",
[cpuinfo_arm_chipset_vendor_google] = "Google",
};
/* Map from ARM chipset series ID to its string representation */
@@ -3895,6 +3925,7 @@ static const char* chipset_series_string[cpuinfo_arm_chipset_series_max] = {
[cpuinfo_arm_chipset_series_unisoc_t] = "T",
[cpuinfo_arm_chipset_series_unisoc_ums] = "UMS",
[cpuinfo_arm_chipset_series_wondermedia_wm] = "WM",
[cpuinfo_arm_chipset_series_google_tensor] = "Tensor",
};
/* Convert chipset name represented by cpuinfo_arm_chipset structure to a string
@@ -3913,14 +3944,35 @@ void cpuinfo_arm_chipset_to_string(
const char* vendor_string = chipset_vendor_string[vendor];
const char* series_string = chipset_series_string[series];
const uint32_t model = chipset->model;
const size_t suffix_length = strnlen(chipset->suffix, CPUINFO_ARM_CHIPSET_SUFFIX_MAX);
if (model == 0) {
if (series == cpuinfo_arm_chipset_series_unknown) {
strncpy(name, vendor_string, CPUINFO_ARM_CHIPSET_NAME_MAX);
if (suffix_length > 0) {
if (series == cpuinfo_arm_chipset_series_unknown) {
snprintf(
name,
CPUINFO_ARM_CHIPSET_NAME_MAX,
"%s %.*s",
vendor_string,
(int)suffix_length,
chipset->suffix);
} else {
snprintf(
name,
CPUINFO_ARM_CHIPSET_NAME_MAX,
"%s %s %.*s",
vendor_string,
series_string,
(int)suffix_length,
chipset->suffix);
}
} else {
snprintf(name, CPUINFO_ARM_CHIPSET_NAME_MAX, "%s %s", vendor_string, series_string);
if (series == cpuinfo_arm_chipset_series_unknown) {
strncpy(name, vendor_string, CPUINFO_ARM_CHIPSET_NAME_MAX);
} else {
snprintf(name, CPUINFO_ARM_CHIPSET_NAME_MAX, "%s %s", vendor_string, series_string);
}
}
} else {
const size_t suffix_length = strnlen(chipset->suffix, CPUINFO_ARM_CHIPSET_SUFFIX_MAX);
snprintf(
name,
CPUINFO_ARM_CHIPSET_NAME_MAX,
@@ -4051,7 +4103,6 @@ static enum cpuinfo_arm_chipset_vendor disambiguate_chipset_vendor(
(vendor_a == cpuinfo_arm_chipset_vendor_spreadtrum && vendor_b == cpuinfo_arm_chipset_vendor_unisoc)) {
return cpuinfo_arm_chipset_vendor_unisoc;
}
return cpuinfo_arm_chipset_vendor_unknown;
}
+3 -3
View File
@@ -256,12 +256,12 @@ void cpuinfo_arm_linux_init(void) {
}
#if defined(__ANDROID__)
struct cpuinfo_android_properties android_properties;
struct cpuinfo_android_properties android_properties = {0};
cpuinfo_arm_android_parse_properties(&android_properties);
#else
char proc_cpuinfo_hardware[CPUINFO_HARDWARE_VALUE_MAX];
char proc_cpuinfo_hardware[CPUINFO_HARDWARE_VALUE_MAX] = {0};
#endif
char proc_cpuinfo_revision[CPUINFO_REVISION_VALUE_MAX];
char proc_cpuinfo_revision[CPUINFO_REVISION_VALUE_MAX] = {0};
if (!cpuinfo_arm_linux_parse_proc_cpuinfo(
#if defined(__ANDROID__)
+49
View File
@@ -11,6 +11,11 @@ void cpuinfo_arm_decode_vendor_uarch(
#endif /* CPUINFO_ARCH_ARM */
enum cpuinfo_vendor vendor[RESTRICT_STATIC 1],
enum cpuinfo_uarch uarch[RESTRICT_STATIC 1]) {
/* Ensure the out-parameters are always initialized, including for
* implementers that are not handled in the switch below. */
*vendor = cpuinfo_vendor_unknown;
*uarch = cpuinfo_uarch_unknown;
switch (midr_get_implementer(midr)) {
case 'A':
*vendor = cpuinfo_vendor_arm;
@@ -438,6 +443,50 @@ void cpuinfo_arm_decode_vendor_uarch(
midr_get_part(midr));
}
break;
case 'a':
*vendor = cpuinfo_vendor_apple;
switch (midr_get_part(midr)) {
case 0x022:
case 0x024:
case 0x028:
*uarch = cpuinfo_uarch_icestorm;
break;
case 0x023:
case 0x025:
case 0x029:
*uarch = cpuinfo_uarch_firestorm;
break;
case 0x032:
case 0x034:
case 0x038:
*uarch = cpuinfo_uarch_blizzard;
break;
case 0x033:
case 0x035:
case 0x039:
*uarch = cpuinfo_uarch_avalanche;
break;
case 0x042:
case 0x044:
case 0x048:
*uarch = cpuinfo_uarch_coll_sawtooth;
break;
case 0x043:
case 0x045:
case 0x049:
*uarch = cpuinfo_uarch_coll_everest;
break;
case 0x052:
*uarch = cpuinfo_uarch_donan_sawtooth;
break;
case 0x053:
*uarch = cpuinfo_uarch_donan_everest;
break;
default:
cpuinfo_log_warning(
"unknown Apple CPU part 0x%03" PRIx32 " ignored", midr_get_part(midr));
}
break;
#if CPUINFO_ARCH_ARM
case 'V':
*vendor = cpuinfo_vendor_marvell;
+9
View File
@@ -307,6 +307,15 @@ static bool transform_token(char* token_start, char* token_end, struct parser_st
}
break;
case 4:
/*
* Erase everything starting with "with" on AMD
* processors, e.g. "AMD Ryzen 5 PRO 6650U with Radeon
* Graphics"
*/
if (erase_matching(token_start, token_length, "with")) {
return false;
}
/* Remember to erase "Dual Core" in "AMD Athlon(tm) 64
* X2 Dual Core Processor 3800+" */
if (memcmp(token_start, "Dual", token_length) == 0) {
+7
View File
@@ -270,8 +270,15 @@ enum cpuinfo_uarch cpuinfo_x86_decode_uarch(
return cpuinfo_uarch_prescott;
}
break;
case 0x12:
switch (model_info->model) {
case 0x01: // Nova Lake P-core (Coyote Cove)
return cpuinfo_uarch_coyote_cove;
}
break;
}
break;
case cpuinfo_vendor_amd:
switch (model_info->family) {
#if CPUINFO_ARCH_X86
-2
View File
@@ -265,8 +265,6 @@ BOOL CALLBACK cpuinfo_x86_windows_init(PINIT_ONCE init_once, PVOID parameter, PV
* APIC order */
const uint32_t core_id = cores_count++;
uint32_t smt_id = 0;
/* Reconstruct core part of APIC ID */
const uint32_t core_apic_id = (core_id & core_bits_mask) << x86_processor.topology.core_bits_offset;
/* Iterate processor groups and set the core & SMT parts of APIC
* ID */
for (uint32_t i = 0; i < core_info->Processor.GroupCount; i++) {
+4 -3
View File
@@ -1,12 +1,13 @@
Copyright 2015-2023 The Khronos Group Inc.
Copyright 2015-2026 The Khronos Group Inc.
Files in this repository fall under one of these licenses:
- `Apache-2.0`
- `MIT`
Note: With the exception of `parse_dependency.py` the files using `MIT` license
also fall under `Apache-2.0`. Example:
Note: With the exception of `parse_dependency.py`, which is based on an
external file, files using the `MIT` license also fall under `Apache-2.0`.
Example:
```
SPDX-License-Identifier: Apache-2.0 OR MIT
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -2
View File
@@ -2,8 +2,7 @@
* Copyright 2015-2023 The Khronos Group Inc.
* Copyright 2015-2023 Valve Corporation
* Copyright 2015-2023 LunarG, Inc.
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#pragma once
+1 -2
View File
@@ -2,8 +2,7 @@
* Copyright 2015-2023 The Khronos Group Inc.
* Copyright 2015-2023 Valve Corporation
* Copyright 2015-2023 LunarG, Inc.
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#pragma once
+1 -2
View File
@@ -3,8 +3,7 @@
//
/*
** Copyright 2014-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
+1 -2
View File
@@ -3,8 +3,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#include "vk_platform.h"
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1262,6 +1262,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00001530000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001630000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001730000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
06000000c82d00000320000007010000,8BitDo Ultimate Wired Controller for Xbox,a:b0,b:b1,x:b2,y:b3,back:b6,guide:b8,start:b7,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
03000000c82d00000121000011010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000c82d00000121000000010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000a00500003232000001000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
+89 -101
View File
@@ -192,13 +192,13 @@ struct PS_INPUT
#ifdef PIXEL_SHADER
struct PS_OUTPUT_REAL
struct PS_OUTPUT
{
#define NUM_RTS 0
#if PS_RETURN_COLOR
#if PS_DATE == 1 || PS_DATE == 2
float c : SV_Target;
float c0 : SV_Target;
#else
float4 c0 : SV_Target0;
@@ -231,20 +231,6 @@ struct PS_OUTPUT_REAL
#undef NUM_RTS
};
struct PS_OUTPUT
{
#if !PS_NO_COLOR
#if PS_DATE == 1 || PS_DATE == 2
float c;
#else
float4 c0;
#if !PS_NO_COLOR1
float4 c1;
#endif
#endif
#endif
};
Texture2D<float4> Texture : register(t0);
Texture2D<float4> Palette : register(t1);
#if !PS_ROV_COLOR
@@ -361,23 +347,28 @@ bool2 nan_or_inf(float2 xy)
float4 sample_c_af(float2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = any(nan_or_inf(uv)) ? float2(0, 0) : uv;
uv = any(nan_or_inf(uv)) ? float2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
float2 sz;
Texture.GetDimensions(sz.x, sz.y);
float2 dX = ddx(uv) * sz;
float2 dY = ddy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(nan_or_inf(dX) | nan_or_inf(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -385,42 +376,49 @@ float4 sample_c_af(float2 uv, float uv_w)
float A = dX.y * dX.y + dY.y * dY.y;
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
float C = dX.x * dX.x + dY.x * dY.x;
float f = (dX.x * dY.y - dY.x * dX.y);
float F = f * f;
float p = A - C;
float q = A + C;
float t = sqrt(p * p + B * B);
float sqrt_num_plus = sqrt(F * (t + p));
float sqrt_num_minus = sqrt(F * (t - p));
float inv_sqrt_denom_plus = rsqrt(t * (q + t));
float inv_sqrt_denom_minus = rsqrt(t * (q - t));
float signB = sign(B);
float2 new_dX = float2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrt_num_plus * inv_sqrt_denom_plus,
sqrt_num_minus * inv_sqrt_denom_plus * signB
);
float2 new_dY = float2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrt_num_minus * inv_sqrt_denom_minus * -signB,
sqrt_num_plus * inv_sqrt_denom_minus
);
d_inf_nan = any(nan_or_inf(new_dX) | nan_or_inf(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
float2 aniso_line;
if (length_major <= 1.0f)
{
// A zero length_major would result in NaN Lod and break sampling.
@@ -428,57 +426,44 @@ float4 sample_c_af(float2 uv, float uv_w)
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = float2(0, 0);
aniso_line = float2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
float2 aniso_line_dir = float2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
float2 aniso_line_dir = is_major_x ? dX : dY;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_ANISOTROPIC_FILTERING;
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0; // No Lod
float lod = 0.0f; // No Lod
#endif
float4 colour;
if (aniso_ratio == 1.0f)
colour = Texture.SampleLevel(TextureSampler, uv, lod);
else
{
float4 num = float4(0, 0, 0, 0);
for (int i = 0; i < aniso_ratio; i++)
{
float2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f);
float2 segment = (2.0f * aniso_line) / aniso_ratio;
int aniso_ratio_i = (int)aniso_ratio;
for (int i = 0; i < aniso_ratio_i; i++)
{
float2 d = -aniso_line + (0.5f + i) * segment;
float2 uv_sample = uv + d;
float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod);
num += sample_colour;
@@ -1327,13 +1312,17 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy)
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
#define DISCARD rov_discard = true
#define DISCARD { rov_discard_color = true; rov_discard_depth = true; }
#define DISCARD_COLOR rov_discard_color = true
#define DISCARD_DEPTH rov_discard_depth = true
#else
#define DISCARD discard
#define DISCARD discard
#define DISCARD_COLOR o_col0 = RtLoad(input.p.xy)
#define DISCARD_DEPTH input.p.z = DepthLoad(input.p.xy)
#endif
#if (PS_RETURN_COLOR || PS_RETURN_DEPTH)
PS_OUTPUT_REAL ps_main(PS_INPUT input)
PS_OUTPUT ps_main(PS_INPUT input)
#else
void ps_main(PS_INPUT input)
#endif
@@ -1352,7 +1341,8 @@ void ps_main(PS_INPUT input)
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
bool rov_discard = false;
bool rov_discard_color = false;
bool rov_discard_depth = false;
#endif
// Use ROV discard macro for since we cannot do
@@ -1459,19 +1449,29 @@ if (bad)
discard;
#endif
PS_OUTPUT output;
// Output values
#if !PS_NO_COLOR
#if PS_DATE == 1 || PS_DATE == 2
float o_col0;
#else
float4 o_col0;
#if !PS_NO_COLOR1
float4 o_col1;
#endif
#endif
#endif
// Get first primitive that will write a failling alpha value
#if PS_DATE == 1
// DATM == 0
// Pixel with alpha equal to 1 will failed (128-255)
output.c = (C.a > 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
o_col0 = (C.a > 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
#elif PS_DATE == 2
// DATM == 1
// Pixel with alpha equal to 0 will failed (0-127)
output.c = (C.a < 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
o_col0 = (C.a < 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
#else
// Not primid DATE setup
@@ -1548,26 +1548,26 @@ if (bad)
// Output color scaling
#if !PS_NO_COLOR
output.c0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f;
output.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f;
o_col0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f;
o_col0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f;
#if !PS_NO_COLOR1
output.c1 = alpha_blend;
o_col1 = alpha_blend;
#endif
#endif // !PS_NO_COLOR
// Alpha test with feedback
#if PS_AFAIL == AFAIL_FB_ONLY
if (!atst_pass)
input.p.z = DepthLoad(input.p.xy);
DISCARD_DEPTH;
#elif PS_AFAIL == AFAIL_ZB_ONLY
if (!atst_pass)
output.c0 = RtLoad(input.p.xy);
DISCARD_COLOR;
#elif PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
if (!atst_pass)
{
output.c0.a = RtLoad(input.p.xy).a;
o_col0.a = RtLoad(input.p.xy).a; // discard alpha
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
input.p.z = DepthLoad(input.p.xy);
DISCARD_DEPTH;
#endif
}
#endif
@@ -1580,46 +1580,39 @@ if (bad)
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
if (!bool(input.interior))
input.p.z = DepthLoad(input.p.xy); // No depth update for triangle edges.
DISCARD_DEPTH; // No depth update for triangle edges.
#endif
#if (PS_RETURN_COLOR || PS_RETURN_DEPTH)
// Output struct with the actual system values output semantics.
PS_OUTPUT_REAL output_real;
PS_OUTPUT output;
#endif
// Color write back
#if PS_RETURN_COLOR
#if PS_DATE == 1 || PS_DATE == 2
output_real.c = output.c;
#else
output_real.c0 = output.c0;
#if !PS_NO_COLOR1
output_real.c1 = output.c1;
#endif
output.c0 = o_col0;
#if !PS_NO_COLOR1
output.c1 = o_col1;
#endif
#elif PS_RETURN_COLOR_ROV
output.c0 = (rov_discard | (FbMask == 0xFFu)) ? RtLoad(input.p.xy) : output.c0;
RtWrite(input.p.xy, output.c0);
o_col0 = (FbMask == 0xFFu) ? RtLoad(input.p.xy) : o_col0; // channel masking
if (!rov_discard_color)
RtWrite(input.p.xy, o_col0);
#endif
// Depth write back
#if PS_RETURN_DEPTH
output_real.depth = input.p.z;
output.depth = input.p.z;
#if SW_DEPTH && PS_NO_COLOR1 && PS_DEPTH_FEEDBACK_SUPPORT == 2
// Output color clone for feedback.
output_real.depth_color = input.p.z;
output.depth_color = input.p.z;
#endif
#elif PS_RETURN_DEPTH_ROV
#if SW_DEPTH
input.p.z = rov_discard ? DepthLoad(input.p.xy) : input.p.z;
#endif
DepthWrite(input.p.xy, input.p.z);
if (!rov_discard_depth)
DepthWrite(input.p.xy, input.p.z);
#endif
#if (PS_RETURN_COLOR || PS_RETURN_DEPTH)
return output_real;
return output;
#endif
}
@@ -2021,11 +2014,6 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
vtx.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vtx.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (first vertex in DX)
vtx.c = i0 == 0 ? vtx.c : (i1 == 0 ? other.c : opposite.c);
#endif
}
return vtx;
+43 -45
View File
@@ -220,22 +220,27 @@ float manual_lod(float uv_w)
vec4 sample_c_af(vec2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0, 0) : uv;
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
vec2 sz = textureSize(TextureSampler, 0);
vec2 dX = dFdx(uv) * sz;
vec2 dY = dFdy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -243,38 +248,46 @@ vec4 sample_c_af(vec2 uv, float uv_w)
float A = dX.y * dX.y + dY.y * dY.y;
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
float C = dX.x * dX.x + dY.x * dY.x;
float f = (dX.x * dY.y - dY.x * dX.y);
float F = f * f;
float p = A - C;
float q = A + C;
float t = sqrt(p * p + B * B);
float signB = sign(B);
float denom_plus = t * (q + t);
float denom_minus = t * (q - t);
float sqrtA = sqrt(F * (t + p));
float sqrtB = sqrt(F * (t - p));
float inv_sqrt_denom_plus = inversesqrt(denom_plus);
float inv_sqrt_denom_minus = inversesqrt(denom_minus);
vec2 new_dX = vec2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrtA * inv_sqrt_denom_plus,
sqrtB * inv_sqrt_denom_plus * signB
);
vec2 new_dY = vec2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrtB * inv_sqrt_denom_minus * -signB,
sqrtA * inv_sqrt_denom_minus
);
d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
@@ -286,57 +299,42 @@ vec4 sample_c_af(vec2 uv, float uv_w)
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = vec2(0, 0);
aniso_line = vec2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
vec2 aniso_line_dir = vec2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
vec2 aniso_line_dir = is_major_x ? dX : dY;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_ANISOTROPIC_FILTERING;
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0; // No Lod
float lod = 0.0f; // No Lod
#endif
vec4 colour;
if (aniso_ratio == 1.0f)
colour = textureLod(TextureSampler, uv, lod);
else
{
vec4 num = vec4(0, 0, 0, 0);
vec4 num = vec4(0.0f, 0.0f, 0.0f, 0.0f);
vec2 segment = (2.0f * aniso_line) / aniso_ratio;
for (int i = 0; i < aniso_ratio; i++)
{
vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
{
vec2 d = -aniso_line + (0.5f + i) * segment;
vec2 uv_sample = uv + d;
vec4 sample_colour = textureLod(TextureSampler, uv_sample, lod);
num += sample_colour;
@@ -455,11 +455,6 @@ void main()
VSout.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
VSout.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (last vertex in GL)
vtx.c = i0 == 2 ? vtx.c : (i1 == 2 ? other.c : opposite.c);
#endif
}
#endif
+61 -63
View File
@@ -450,11 +450,6 @@ void main()
vsOut.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vsOut.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (last vertex in VK)
vtx.c = i0 == 2 ? vtx.c : (i1 == 2 ? other.c : opposite.c);
#endif
}
#endif
@@ -728,22 +723,27 @@ float manual_lod(float uv_w)
vec4 sample_c_af(vec2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0, 0) : uv;
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
vec2 sz = textureSize(Texture, 0);
vec2 dX = dFdx(uv) * sz;
vec2 dY = dFdy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -758,31 +758,40 @@ vec4 sample_c_af(vec2 uv, float uv_w)
float q = A + C;
float t = sqrt(p * p + B * B);
float signB = sign(B);
float denom_plus = t * (q + t);
float denom_minus = t * (q - t);
float sqrtA = sqrt(F * (t + p));
float sqrtB = sqrt(F * (t - p));
float inv_sqrt_denom_plus = inversesqrt(denom_plus);
float inv_sqrt_denom_minus = inversesqrt(denom_minus);
vec2 new_dX = vec2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrtA * inv_sqrt_denom_plus,
sqrtB * inv_sqrt_denom_plus * signB
);
vec2 new_dY = vec2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrtB * inv_sqrt_denom_minus * -signB,
sqrtA * inv_sqrt_denom_minus
);
d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
@@ -794,57 +803,42 @@ vec4 sample_c_af(vec2 uv, float uv_w)
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = vec2(0, 0);
aniso_line = vec2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
vec2 aniso_line_dir = vec2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
vec2 aniso_line_dir = is_major_x ? dX : dY;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_ANISOTROPIC_FILTERING;
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0; // No Lod
float lod = 0.0f; // No Lod
#endif
vec4 colour;
if (aniso_ratio == 1.0f)
colour = textureLod(Texture, uv, lod);
else
{
vec4 num = vec4(0, 0, 0, 0);
vec4 num = vec4(0.0f, 0.0f, 0.0f, 0.0f);
vec2 segment = (2.0f * aniso_line) / aniso_ratio;
for (int i = 0; i < aniso_ratio; i++)
{
vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
{
vec2 d = -aniso_line + (0.5f + i) * segment;
vec2 uv_sample = uv + d;
vec4 sample_colour = textureLod(Texture, uv_sample, lod);
num += sample_colour;
@@ -1721,9 +1715,13 @@ layout(early_fragment_tests) in;
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
#define DISCARD rov_discard = true
#define DISCARD { rov_discard_color = true; rov_discard_depth = true; }
#define DISCARD_COLOR rov_discard_color = true
#define DISCARD_DEPTH rov_discard_depth = true
#else
#define DISCARD discard
#define DISCARD discard
#define DISCARD_COLOR o_col0 = sample_from_rt()
#define DISCARD_DEPTH input_z = sample_from_depth()
#endif
void main()
@@ -1748,7 +1746,8 @@ void main()
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
bool rov_discard = gl_HelperInvocation;
bool rov_discard_color = gl_HelperInvocation;
bool rov_discard_depth = gl_HelperInvocation;
#endif
#if PS_ZTST == ZTST_GEQUAL
@@ -1941,16 +1940,16 @@ void main()
// Alpha test with feedback
#if PS_AFAIL == AFAIL_FB_ONLY
if (!atst_pass)
input_z = sample_from_depth();
DISCARD_DEPTH;
#elif PS_AFAIL == AFAIL_ZB_ONLY
if (!atst_pass)
o_col0 = sample_from_rt();
DISCARD_COLOR;
#elif (PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
if (!atst_pass)
{
o_col0.a = sample_from_rt().a;
o_col0.a = sample_from_rt().a; // discard alpha
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
input_z = sample_from_depth();
DISCARD_DEPTH;
#endif
}
#endif
@@ -1962,24 +1961,23 @@ void main()
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
if (!bool(vsIn.interior))
input_z = sample_from_depth(); // No depth update for triangle edges.
DISCARD_DEPTH; // No depth update for triangle edges.
#endif
// Writing back color (result already written to o_col0 for non-ROV)
#if PS_RETURN_COLOR_ROV
bvec4 discard_channels = bvec4(uvec4(rov_discard) | uvec4(equal(FbMask, uvec4(0xFFu))));
o_col0 = mix(o_col0, sample_from_rt(), discard_channels);
o_col0 = mix(o_col0, sample_from_rt(), equal(FbMask, uvec4(0xFFu))); // channel masking
imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0);
if (!rov_discard_color)
imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0);
#endif
// Writing back depth
#if PS_RETURN_DEPTH
gl_FragDepth = input_z;
#elif PS_RETURN_DEPTH_ROV
input_z = rov_discard ? sample_from_depth() : input_z;
imageStore(DepthImageRov, ivec2(gl_FragCoord.xy), vec4(input_z, 0, 0, 1.0f));
if (!rov_discard_depth)
imageStore(DepthImageRov, ivec2(gl_FragCoord.xy), vec4(input_z, 0, 0, 1.0f));
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
+39
View File
@@ -34,6 +34,34 @@ Value MemoryInterface::Read(u32 address, bool* valid)
return Value(0);
}
std::optional<std::string> MemoryInterface::ReadString(u32 address, u32 max_size, ReadStringFlags flags)
{
std::string string;
for (u32 i = 0; i < max_size; i++)
{
bool valid;
char c = Read8(address + i, &valid);
if (!valid)
return std::nullopt;
if (c == '\0')
return string;
else if (!(flags & ALLOW_NON_PRINTABLE_CHARACTERS) && (c < ' ' || c > '~'))
return std::nullopt;
string += c;
}
if (flags & ALLOW_LONG_STRINGS)
{
string += '~';
return string;
}
return std::nullopt;
}
template <MemoryAccessType Value>
bool MemoryInterface::Write(u32 address, Value value)
{
@@ -62,6 +90,17 @@ bool MemoryInterface::Write(u32 address, Value value)
return false;
}
bool MemoryInterface::WriteString(u32 address, std::string_view string)
{
if (!WriteBytes(address, string.data(), static_cast<u32>(string.size())))
return false;
if (!Write8(address + static_cast<u32>(string.size()), '\0'))
return false;
return true;
}
bool MemoryInterface::IdempotentWrite8(u32 address, u8 value)
{
bool valid;
+16 -2
View File
@@ -5,6 +5,9 @@
#include "Pcsx2Types.h"
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
template <typename Value>
@@ -31,16 +34,27 @@ public:
template <MemoryAccessType Value>
Value Read(u32 address, bool* valid = nullptr);
enum ReadStringFlags
{
NO_FLAGS = 0,
ALLOW_LONG_STRINGS = 1 << 0,
ALLOW_NON_PRINTABLE_CHARACTERS = 1 << 1
};
std::optional<std::string> ReadString(u32 address, u32 max_size = 256, ReadStringFlags flags = NO_FLAGS);
virtual bool Write8(u32 address, u8 value) = 0;
virtual bool Write16(u32 address, u16 value) = 0;
virtual bool Write32(u32 address, u32 value) = 0;
virtual bool Write64(u32 address, u64 value) = 0;
virtual bool Write128(u32 address, u128 value) = 0;
virtual bool WriteBytes(u32 address, void* src, u32 size) = 0;
virtual bool WriteBytes(u32 address, const void* src, u32 size) = 0;
template <MemoryAccessType Value>
bool Write(u32 address, Value value);
bool WriteString(u32 address, std::string_view string);
bool IdempotentWrite8(u32 address, u8 value);
bool IdempotentWrite16(u32 address, u16 value);
bool IdempotentWrite32(u32 address, u32 value);
@@ -51,5 +65,5 @@ public:
template <MemoryAccessType Value>
bool IdempotentWrite(u32 address, Value value);
virtual bool CompareBytes(u32 address, void* src, u32 size) = 0;
virtual bool CompareBytes(u32 address, const void* src, u32 size) = 0;
};
+102 -30
View File
@@ -17,7 +17,7 @@ struct RapidYAMLContext
Error* error = nullptr;
};
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors)
{
RapidYAMLContext context;
context.error = error;
@@ -28,63 +28,135 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
callbacks.set_user_data(static_cast<void*>(&context));
callbacks.set_error_basic([](ryml::csubstr msg, const ryml::ErrorDataBasic& errdata, void* user_data) {
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_basic_format(std::move(callback), msg, errdata);
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_basic_format(std::move(callback), msg, errdata);
// We might have already returned, so don't try to recover.
pxFailRel(description.c_str());
std::abort();
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
if (context != nullptr)
{
std::longjmp(context->env, 1);
}
else
{
std::terminate();
}
});
callbacks.set_error_parse([](ryml::csubstr msg, const ryml::ErrorDataParse& errdata, void* user_data) {
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_parse_format(std::move(callback), msg, errdata);
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_parse_format(std::move(callback), msg, errdata);
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
Error::SetString(context->error, std::move(description));
std::longjmp(context->env, 1);
if (context != nullptr)
{
std::longjmp(context->env, 2);
}
else
{
std::terminate();
}
});
callbacks.set_error_visit([](ryml::csubstr msg, const ryml::ErrorDataVisit& errdata, void* user_data) {
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_visit_format(std::move(callback), msg, errdata);
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_visit_format(std::move(callback), msg, errdata);
// We've probably already returned, so don't try to recover.
pxFailRel(description.c_str());
std::abort();
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
if (context != nullptr)
{
std::longjmp(context->env, 3);
}
else
{
std::terminate();
}
});
#else
callbacks.m_user_data = static_cast<void*>(&context);
callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location location, void* user_data) {
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
Error::SetString(context->error, std::string(msg, msg_len));
std::longjmp(context->env, 1);
if (RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data))
{
Error::SetString(context->error, std::string(msg, msg_len));
std::longjmp(context->env, 1);
}
else
{
std::string description(msg, msg_len);
pxFailRel(description.c_str());
}
};
#endif
ryml::EventHandlerTree event_handler(callbacks);
ryml::Parser parser(&event_handler);
ryml::Tree tree;
ryml::Tree tree(callbacks);
// The only options RapidYAML provides for recovering from errors are
// throwing an exception or using setjmp/longjmp. Since we have exceptions
// disabled we have to use the latter option.
if (setjmp(context.env))
if (setjmp(context.env) != 0)
return std::nullopt;
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
if (resolve_anchors)
{
tree.resolve();
}
// Callbacks passed to ryml::Tree are used for value parsing errors later,
// so we need to clear the context before it goes out of scope.
#if RYML_VERSION_MAJOR > 0 || RYML_VERSION_MINOR >= 11
callbacks.set_user_data(nullptr);
#else
callbacks.m_user_data = nullptr;
#endif
tree.callbacks(callbacks);
return tree;
}
+3 -1
View File
@@ -14,4 +14,6 @@
/// parsing errors (as is recommended by the documentation for cases where
/// exceptions are disabled). The file_name parameter is only used for error
/// messages, which are returned via the error parameter.
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error);
/// If resolve_anchors is set to true, YAML anchors and aliases will be resolved.
/// It's a potentially slow operation, so it's opt-in.
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors = false);
+1
View File
@@ -163,6 +163,7 @@
<ClInclude Include="WindowInfo.h" />
<ClInclude Include="YAML.h" />
<ClInclude Include="Threading.h" />
<ClInclude Include="EnumOps.h" />
<ClInclude Include="emitter\implement\avx.h" />
<ClInclude Include="emitter\implement\bmi.h" />
<ClInclude Include="emitter\instructions.h" />
+3 -3
View File
@@ -101,7 +101,7 @@ static u64 s_total_barriers = 0;
static u64 s_total_copies = 0;
static u64 s_total_uploads = 0;
static u64 s_total_readbacks = 0;
static u64 s_total_depth_copies_rov = 0;
static u64 s_total_copies_rov = 0;
static u64 s_total_draws_rov = 0;
static u64 s_total_barriers_rov = 0;
static u32 s_total_frames = 0;
@@ -298,7 +298,7 @@ void Host::BeginPresentFrame()
update_stat(GSPerfMon::TextureCopies, s_total_copies, s_last_copies);
update_stat(GSPerfMon::TextureUploads, s_total_uploads, s_last_uploads);
update_stat(GSPerfMon::Readbacks, s_total_readbacks, s_last_readbacks);
update_stat(GSPerfMon::DepthCopiesROV, s_total_depth_copies_rov, s_last_depth_copies_rov);
update_stat(GSPerfMon::TextureCopiesROV, s_total_copies_rov, s_last_depth_copies_rov);
update_stat(GSPerfMon::DrawCallsROV, s_total_draws_rov, s_last_draws_rov);
update_stat(GSPerfMon::BarriersROV, s_total_barriers_rov, s_last_barriers_rov);
@@ -917,7 +917,7 @@ void GSRunner::DumpStats()
Console.WriteLn(fmt::format("@HWSTAT@ Copies: {} (avg {})", s_total_copies, static_cast<u64>(std::ceil(s_total_copies / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Uploads: {} (avg {})", s_total_uploads, static_cast<u64>(std::ceil(s_total_uploads / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Readbacks: {} (avg {})", s_total_readbacks, static_cast<u64>(std::ceil(s_total_readbacks / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Depth Copies (ROV): {} (avg {})", s_total_depth_copies_rov, static_cast<u64>(std::ceil(s_total_depth_copies_rov / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Copies (ROV): {} (avg {})", s_total_copies_rov, static_cast<u64>(std::ceil(s_total_copies_rov / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Draws Calls (ROV): {} (avg {})", s_total_draws_rov, static_cast<u64>(std::ceil(s_total_draws_rov / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Barriers (ROV): {} (avg {})", s_total_barriers_rov, static_cast<u64>(std::ceil(s_total_barriers_rov / static_cast<double>(s_total_drawn_frames)))));
if (s_perf_enable)
+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();
}
+19 -10
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"
@@ -50,15 +51,15 @@ private:
QWidget* parent;
MemoryViewType displayType = MemoryViewType::BYTE;
bool littleEndian = true;
u32 rowCount;
u32 rowVisible;
s32 rowHeight;
u32 rowCount = 0;
u32 rowVisible = 0;
s32 rowHeight = 0;
// Stuff used for selection handling
// This gets set every paint and depends on the window size / current display mode (1byte,2byte,etc)
s32 valuexAxis; // Where the hexadecimal view begins
s32 textXAxis; // Where the text view begins
s32 row1YAxis; // Where the first row starts
s32 valuexAxis = 0; // Where the hexadecimal view begins
s32 textXAxis = 0; // Where the text view begins
s32 row1YAxis = 0; // Where the first row starts
s32 segmentXAxis[16]; // Where the segments begin
bool selectedText = false; // Whether the user has clicked on text or hex
@@ -89,11 +90,13 @@ public:
{
}
u32 startAddress;
u32 selectedAddress;
s32 selectedIndex;
u32 startAddress = 0;
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;
};
@@ -7,6 +7,8 @@
#include "Debugger/SymbolTree/SymbolTreeModel.h"
#include "Debugger/SymbolTree/TypeString.h"
#include "common/StringUtil.h"
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
@@ -44,12 +46,28 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
QVariant value = node->readValueAsVariant(physical_type, m_cpu, database);
const ccc::ast::Node& type = *logical_type->physical_type(database).first;
switch (type.descriptor)
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;;
if (array.element_count > 0 &&
element_type.name == "char" &&
node->location.type == SymbolTreeLocation::MEMORY &&
!value.isNull())
{
QLineEdit* editor = new QLineEdit(parent);
editor->setText(value.toString());
result = editor;
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtin = type.as<ccc::ast::BuiltIn>();
const ccc::ast::BuiltIn& builtin = physical_type.as<ccc::ast::BuiltIn>();
switch (builtin.bclass)
{
@@ -111,7 +129,7 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
}
case ccc::ast::ENUM:
{
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
const ccc::ast::Enum& enumeration = physical_type.as<ccc::ast::Enum>();
QComboBox* combo_box = new QComboBox(parent);
bool named = false;
@@ -186,12 +204,30 @@ void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel*
if (!logical_type)
return;
const ccc::ast::Node& type = *logical_type->physical_type(database).first;
switch (type.descriptor)
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
QLineEdit* line_edit = qobject_cast<QLineEdit*>(editor);
if (array.element_count > 0 &&
element_type.name == "char" &&
node->location.type == SymbolTreeLocation::MEMORY &&
line_edit)
{
value = line_edit->text();
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtin = type.as<ccc::ast::BuiltIn>();
const ccc::ast::BuiltIn& builtin = physical_type.as<ccc::ast::BuiltIn>();
switch (builtin.bclass)
{
+60 -10
View File
@@ -67,7 +67,7 @@ bool SymbolTreeNode::writeToVM(
if (logical_type)
{
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
writeValueFromVariant(m_value, physical_type, cpu);
writeValueFromVariant(m_value, physical_type, cpu, database);
}
data_changed |= updateDisplayString(cpu, database, display_options);
@@ -76,10 +76,29 @@ bool SymbolTreeNode::writeToVM(
return data_changed;
}
QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const
QVariant SymbolTreeNode::readValueAsVariant(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const
{
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
std::optional<std::string> string = cpu.ReadString(location.address, 256);
if (string.has_value())
return QString::fromStdString(*string);
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtIn = physical_type.as<ccc::ast::BuiltIn>();
@@ -134,10 +153,36 @@ QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type,
return QVariant();
}
bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const
bool SymbolTreeNode::writeValueFromVariant(
QVariant value,
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const
{
if (value.isNull())
return false;
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
QByteArray byte_array = value.toString().toLatin1();
std::string_view view(
byte_array.data(),
std::min(static_cast<s32>(byte_array.size()), array.element_count - 1));
cpu.WriteString(location.address, view);
break;
}
return false;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& built_in = physical_type.as<ccc::ast::BuiltIn>();
@@ -191,6 +236,7 @@ bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node&
return false;
}
}
break;
}
case ccc::ast::ENUM:
@@ -266,11 +312,14 @@ QString SymbolTreeNode::generateDisplayString(
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
if (element_type.name == "char" && location.type == SymbolTreeLocation::MEMORY)
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
char* string = cpu.stringFromPointer(location.address);
if (string)
return QString("\"%1\"").arg(string);
std::optional<std::string> string = cpu.ReadString(
location.address, 256, MemoryInterface::ALLOW_LONG_STRINGS);
if (string.has_value())
return QString("\"%1\"").arg(*string);
}
QString result;
@@ -412,9 +461,10 @@ QString SymbolTreeNode::generateDisplayString(
if (pointer_or_reference.is_pointer && value_type.name == "char")
{
const char* string = cpu.stringFromPointer(address);
if (string)
result += QString(" \"%1\"").arg(string);
std::optional<std::string> string = cpu.ReadString(
address, 256, MemoryInterface::ALLOW_LONG_STRINGS);
if (string.has_value())
result += QString(" \"%1\"").arg(*string);
}
else if (depth == 0)
{
+11 -2
View File
@@ -62,11 +62,20 @@ public:
const ccc::SymbolDatabase& database,
const SymbolTreeDisplayOptions& display_options);
QVariant readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const;
bool writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const;
QVariant readValueAsVariant(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const;
bool writeValueFromVariant(
QVariant value,
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const;
bool updateDisplayString(
DebugInterface& cpu, const ccc::SymbolDatabase& database, const SymbolTreeDisplayOptions& display);
QString generateDisplayString(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
+29 -4
View File
@@ -73,9 +73,10 @@ const char* GameListModel::getColumnName(const Column col)
return s_column_names[static_cast<int>(col)];
}
GameListModel::GameListModel(const float cover_scale, const bool show_cover_titles, const qreal dpr, QObject* parent /* = nullptr */)
GameListModel::GameListModel(const float cover_scale, const bool show_cover_titles, bool show_full_cover_titles, const qreal dpr, QObject* parent /* = nullptr */)
: QAbstractTableModel(parent)
, m_show_titles_for_covers(show_cover_titles)
, m_show_full_titles_for_covers(show_full_cover_titles)
, m_dpr{dpr}
{
loadSettings();
@@ -315,9 +316,33 @@ QVariant GameListModel::data(const QModelIndex& index, const int role) const
switch (index.column())
{
case Column_Cover:
return QSize(static_cast<int>(static_cast<float>(SIZE_HINT_WIDTH) * m_cover_scale),
static_cast<int>(static_cast<float>(m_show_titles_for_covers ? SIZE_HINT_HEIGHT_TITLES : SIZE_HINT_HEIGHT) * m_cover_scale));
{
const int cover_width = static_cast<int>(static_cast<float>(SIZE_HINT_WIDTH) * m_cover_scale);
const int cover_height = static_cast<int>(static_cast<float>(SIZE_HINT_HEIGHT) * m_cover_scale);
if (m_show_full_titles_for_covers && m_show_titles_for_covers)
{
// Find height needed for the longest title at the current font/width
const int text_width = static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale);
QFont font;
font.setPointSizeF(20.0f * m_cover_scale);
const QFontMetrics fm(font);
int max_text_height = fm.height();
const auto lock = GameList::GetLock();
const u32 count = GameList::GetEntryCount();
for (u32 i = 0; i < count; i++)
{
const GameList::Entry* entry = GameList::GetEntryByIndex(i);
if (!entry) continue;
const QString title = QString::fromStdString(entry->GetTitle(m_prefer_english_titles));
const QRect bound = fm.boundingRect(QRect(0, 0, text_width, 0),
Qt::TextWordWrap | Qt::AlignHCenter, title);
max_text_height = std::max(max_text_height, bound.height());
}
return QSize(cover_width, cover_height + max_text_height);
}
return QSize(cover_width, static_cast<int>(static_cast<float>(m_show_titles_for_covers ? SIZE_HINT_HEIGHT_TITLES : SIZE_HINT_HEIGHT) * m_cover_scale));
}
default:
return QVariant();
}
@@ -354,7 +379,7 @@ bool GameListModel::titlesLessThan(const int left_row, const int right_row) cons
const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
return QtHost::LocaleSensitiveCompare(QString::fromStdString(left->GetTitleSort(m_prefer_english_titles)),
QString::fromStdString(right->GetTitleSort(m_prefer_english_titles))) < 0;
QString::fromStdString(right->GetTitleSort(m_prefer_english_titles))) < 0;
}
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, const int column) const
+5 -1
View File
@@ -43,7 +43,7 @@ public:
static QIcon getIconForType(GameList::EntryType type);
static QIcon getIconForRegion(GameList::Region region);
GameListModel(float cover_scale, bool show_cover_titles, qreal dpr, QObject* parent = nullptr);
GameListModel(float cover_scale, bool show_cover_titles, bool show_full_cover_titles, qreal dpr, QObject* parent = nullptr);
~GameListModel();
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -63,6 +63,9 @@ public:
bool getShowCoverTitles() const { return m_show_titles_for_covers; }
void setShowCoverTitles(bool enabled) { m_show_titles_for_covers = enabled; }
bool getShowFullCoverTitles() const { return m_show_full_titles_for_covers; }
void setShowFullCoverTitles(bool enabled) { m_show_full_titles_for_covers = enabled; }
float getCoverScale() const { return m_cover_scale; }
void setCoverScale(float scale);
int getCoverArtWidth() const;
@@ -89,6 +92,7 @@ private:
float m_cover_scale = 0.0f;
std::atomic<u32> m_cover_scale_counter{0};
bool m_show_titles_for_covers = false;
bool m_show_full_titles_for_covers = false;
bool m_prefer_english_titles = false;
std::array<QString, Column_Count> m_column_display_names;
+29 -1
View File
@@ -239,7 +239,8 @@ void GameListWidget::initialize()
{
const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f);
const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true);
m_model = new GameListModel(cover_scale, show_cover_titles, devicePixelRatioF(), this);
const bool show_full_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowFullCoverTitles", true);
m_model = new GameListModel(cover_scale, show_cover_titles, show_full_cover_titles, devicePixelRatioF(), this);
m_model->updateCacheSize(width(), height());
m_sort_model = new GameListSortModel(m_model);
@@ -268,6 +269,7 @@ void GameListWidget::initialize()
connect(m_ui.viewGameGrid, &QPushButton::clicked, this, &GameListWidget::showGameGrid);
connect(m_ui.gridScale, &QSlider::valueChanged, this, &GameListWidget::gridIntScale);
connect(m_ui.viewGridTitles, &QPushButton::toggled, this, &GameListWidget::setShowCoverTitles);
connect(m_ui.viewFullGridTitles, &QPushButton::toggled, this, &GameListWidget::setShowFullCoverTitles);
connect(m_ui.filterType, &QComboBox::currentIndexChanged, this, [this](int index) {
m_sort_model->setFilterType((index == 0) ? GameList::EntryType::Count : static_cast<GameList::EntryType>(index - 1));
});
@@ -348,6 +350,7 @@ void GameListWidget::initialize()
m_list_view->setFrameStyle(QFrame::NoFrame);
m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_list_view->verticalScrollBar()->setSingleStep(15);
m_list_view->setWordWrap(show_full_cover_titles);
onCoverScaleChanged();
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
@@ -542,6 +545,11 @@ bool GameListWidget::getShowGridCoverTitles() const
return m_model->getShowCoverTitles();
}
bool GameListWidget::getShowGridFullCoverTitles() const
{
return m_model->getShowFullCoverTitles();
}
void GameListWidget::refresh(bool invalidate_cache, bool popup_on_error)
{
cancelRefresh();
@@ -776,6 +784,21 @@ void GameListWidget::setShowCoverTitles(bool enabled)
emit layoutChange();
}
void GameListWidget::setShowFullCoverTitles(bool enabled)
{
if (m_model->getShowFullCoverTitles() == enabled)
return;
Host::SetBaseBoolSettingValue("UI", "GameListShowFullCoverTitles", enabled);
Host::CommitBaseSettingChanges();
m_model->setShowFullCoverTitles(enabled);
m_list_view->setWordWrap(enabled);
if (isShowingGameGrid())
m_model->refresh();
updateToolbar();
emit layoutChange();
}
void GameListWidget::updateToolbar()
{
const bool grid_view = Host::GetBaseBoolSettingValue("UI", "GameListGridView", false);
@@ -791,12 +814,17 @@ void GameListWidget::updateToolbar()
QSignalBlocker sb(m_ui.viewGridTitles);
m_ui.viewGridTitles->setChecked(m_model->getShowCoverTitles());
}
{
QSignalBlocker sb(m_ui.viewFullGridTitles);
m_ui.viewFullGridTitles->setChecked(m_model->getShowFullCoverTitles());
}
{
QSignalBlocker sb(m_ui.gridScale);
m_ui.gridScale->setValue(static_cast<int>(m_model->getCoverScale() * 100.0f));
}
m_ui.viewGridTitles->setEnabled(grid_view);
m_ui.viewFullGridTitles->setEnabled(grid_view && m_model->getShowCoverTitles());
m_ui.gridScale->setEnabled(grid_view);
}
+2
View File
@@ -61,6 +61,7 @@ public:
bool isShowingGameList() const;
bool isShowingGameGrid() const;
bool getShowGridCoverTitles() const;
bool getShowGridFullCoverTitles() const;
std::optional<GameList::Entry> getSelectedEntry() const;
@@ -95,6 +96,7 @@ public Q_SLOTS:
void showGameList();
void showGameGrid();
void setShowCoverTitles(bool enabled);
void setShowFullCoverTitles(bool enabled);
void gridZoomIn();
void gridZoomOut();
void gridIntScale(int int_scale);
+26
View File
@@ -120,6 +120,31 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="viewFullGridTitles">
<property name="minimumSize">
<size>
<width>32</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Show Full Titles</string>
</property>
<property name="text">
<string>Show Full Titles</string>
</property>
<property name="icon">
<iconset theme="price-tag-2-line"/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="gridScale">
<property name="minimumSize">
@@ -218,6 +243,7 @@
<tabstop>viewGameList</tabstop>
<tabstop>viewGameGrid</tabstop>
<tabstop>viewGridTitles</tabstop>
<tabstop>viewFullGridTitles</tabstop>
<tabstop>gridScale</tabstop>
<tabstop>filterType</tabstop>
<tabstop>filterRegion</tabstop>
+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);
+9 -3
View File
@@ -221,11 +221,13 @@ void MainWindow::setupAdditionalUi()
}
const bool show_game_grid = Host::GetBaseBoolSettingValue("UI", "GameListGridView", false);
updateGameGridActions(show_game_grid);
const bool show_grid_cover_titles = Host::GetBaseBoolSettingValue("UI", "ShowGridCoverTitles", false);
updateGameGridActions(show_game_grid, show_grid_cover_titles);
m_game_list_widget = new GameListWidget(getContentParent());
m_game_list_widget->initialize();
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
m_ui.actionGridViewShowFullTitles->setChecked(m_game_list_widget->getShowGridFullCoverTitles());
m_ui.mainContainer->addWidget(m_game_list_widget);
updateEmulationActions(false, false, false);
@@ -543,6 +545,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
connect(m_ui.actionGridViewShowFullTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowFullCoverTitles);
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
if (isShowingGameList())
m_game_list_widget->gridZoomIn();
@@ -554,8 +557,10 @@ void MainWindow::connectSignals()
connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget, &GameListWidget::refreshGridCovers);
connect(m_game_list_widget, &GameListWidget::layoutChange, this, [this]() {
QSignalBlocker sb(m_ui.actionGridViewShowTitles);
QSignalBlocker sb2(m_ui.actionGridViewShowFullTitles);
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
updateGameGridActions(m_game_list_widget->isShowingGameGrid());
m_ui.actionGridViewShowFullTitles->setChecked(m_game_list_widget->getShowGridFullCoverTitles());
updateGameGridActions(m_game_list_widget->isShowingGameGrid(), m_game_list_widget->getShowGridCoverTitles());
});
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionViewStatusBarVerbose, "UI", "VerboseStatusBar", false);
@@ -3667,9 +3672,10 @@ void MainWindow::updateGameDependentActions()
m_ui.actionReloadPatches->setEnabled(s_vm_valid);
}
void MainWindow::updateGameGridActions(const bool show_game_grid)
void MainWindow::updateGameGridActions(const bool show_game_grid, const bool show_grid_cover_titles)
{
m_ui.actionGridViewShowTitles->setEnabled(show_game_grid);
m_ui.actionGridViewShowFullTitles->setEnabled(show_game_grid && show_grid_cover_titles);
m_ui.actionGridViewZoomIn->setEnabled(show_game_grid);
m_ui.actionGridViewZoomOut->setEnabled(show_game_grid);
m_ui.actionGridViewRefreshCovers->setEnabled(show_game_grid);
+1 -1
View File
@@ -257,7 +257,7 @@ private:
void updateEmulationActions(bool starting, bool running, bool stopping);
void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen);
void updateGameDependentActions();
void updateGameGridActions(const bool show_game_grid);
void updateGameGridActions(const bool show_game_grid, const bool show_grid_cover_titles);
void updateStatusBarWidgetVisibility();
void updateAdvancedSettingsVisibility();
void updateWindowTitle();
+15
View File
@@ -175,6 +175,7 @@
<addaction name="menuWindowSize"/>
<addaction name="separator"/>
<addaction name="actionGridViewShowTitles"/>
<addaction name="actionGridViewShowFullTitles"/>
<addaction name="actionGridViewZoomIn"/>
<addaction name="actionGridViewZoomOut"/>
<addaction name="actionGridViewRefreshCovers"/>
@@ -808,6 +809,20 @@
<string>Show Titl&amp;es (Grid View)</string>
</property>
</action>
<action name="actionGridViewShowFullTitles">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="price-tag-2-line"/>
</property>
<property name="text">
<string>Show F&amp;ull Titles (Grid View)</string>
</property>
</action>
<action name="actionGridViewZoomIn">
<property name="icon">
<iconset theme="zoom-in-line"/>
+1 -1
View File
@@ -1146,7 +1146,7 @@ void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
{
const QString message =
qApp->translate("QtHost", "RA: Logged in as %1 (%2 pts, softcore: %3 pts). %4 unread messages.")
qApp->translate("QtHost", "RA: Logged in as %1 (%2 pts, Casual: %3 pts). %4 unread messages.")
.arg(QString::fromUtf8(username))
.arg(points)
.arg(sc_points)
+466
View File
@@ -4,10 +4,12 @@
#include "QtUtils.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QLocale>
#include <QtCore/QtGlobal>
#include <QtCore/QMetaObject>
#include <QtCore/QStandardPaths>
#include <QtGui/QAction>
#include <QtGui/QDesktopServices>
#include <QtGui/QIcon>
@@ -15,6 +17,7 @@
#include <QtGui/QPainter>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QInputDialog>
@@ -27,21 +30,35 @@
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
#include <fmt/format.h>
#ifdef Q_OS_LINUX
#include <QtGui/private/qtx11extras_p.h>
#endif
#include <algorithm>
#include <array>
#include <cstdlib>
#include <map>
#include "common/CocoaTools.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "pcsx2/Config.h"
#include "QtHost.h"
#if defined(_WIN32)
#include "common/RedtapeWindows.h"
#include "common/RedtapeWilCom.h"
#include <Shlobj.h>
#include <shobjidl.h>
#include <comdef.h>
#else
#include <sys/stat.h>
#include <cerrno>
#include <cstring>
#endif
namespace QtUtils
@@ -549,4 +566,453 @@ namespace QtUtils
const QString flag_path = QStringLiteral("%1/icons/flags/%2.svg").arg(QtHost::GetResourcesBasePath()).arg(country_code.toLower());
return QIcon(flag_path);
}
bool IsRunningInFlatpak()
{
// Checks for the existence of the `.flatpak-info` file which seems to be always present inside flatpak sandboxes.
return FileSystem::FileExists("/.flatpak-info");
}
bool IsRunningInAppImage()
{
// The AppImage runtime sets APPIMAGE environment variable so we can check for that.
return std::getenv("APPIMAGE") != nullptr;
}
void CreateShortcut(QWidget* parent, const std::string& name, const std::string& game_path,
std::vector<std::string> passed_cli_args, const std::string& custom_args,
const std::string& icon_path, bool is_desktop, bool prompt_for_destination)
{
const auto tr_msg = [](const char* str) {
return QCoreApplication::translate("CreateShortcut", str);
};
#if defined(_WIN32)
if (name.empty())
{
Console.Error("Cannot create shortcuts without a name.");
return;
}
// Sanitize filename
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = game_path.empty() ? std::string() : Path::ToNativePath(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Get path to Desktop or per-user Start Menu\Programs directory
// https://superuser.com/questions/1489874/how-can-i-get-the-real-path-of-desktop-in-windows-explorer/1789849#1789849
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
std::string link_file;
if (wil::unique_cotaskmem_string directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
{
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory.get());
if (is_desktop)
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
else
{
const std::string pcsx2_start_menu_dir = Path::ToNativePath(fmt::format("{}/PCSX2", directory_utf8));
if (!FileSystem::EnsureDirectoryExists(pcsx2_start_menu_dir.c_str(), false))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", pcsx2_start_menu_dir, clean_name));
}
}
else
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), is_desktop ? tr_msg("'Desktop' directory not found") : tr_msg("User's 'Start Menu\\Programs' directory not found"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Check if the same shortcut already exists
if (prompt_for_destination && FileSystem::FileExists(link_file.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= EscapeShortcutCommandLine(&arg);
if (!clean_path.empty())
lossless &= EscapeShortcutCommandLine(&clean_path);
if (!lossless)
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string final_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
if (!custom_args.empty())
{
if (!final_args.empty())
final_args += ' ';
final_args += custom_args;
}
if (!clean_path.empty())
{
if (!final_args.empty())
final_args += ' ';
final_args += fmt::format("-- {}", clean_path);
}
Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args);
const auto str_error = [](HRESULT hr) -> std::string {
_com_error err(hr);
const TCHAR* errMsg = err.ErrorMessage();
return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr);
};
// Construct the shortcut
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
HRESULT res = CoInitialize(NULL);
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("CoInitialize failed (%1)").arg(QString::fromStdString(str_error(res))), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
wil::unique_couninitialize_call co_cleanup;
const auto report_error = [&](const QString& reason) {
Console.ErrorFmt("Failed to create shortcut: {}", reason.toStdString());
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
};
wil::com_ptr_nothrow<IShellLink> pShellLink = wil::CoCreateInstanceNoThrow<IShellLink>(CLSID_ShellLink);
wil::com_ptr_nothrow<IPersistFile> pPersistFile;
if (!pShellLink)
{
report_error(tr_msg("CoCreateInstance failed"));
return;
}
// Set path to the executable
const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath());
res = pShellLink->SetPath(target_file.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetPath failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the working directory
const std::wstring working_dir = StringUtil::UTF8StringToWideString(Path::GetDirectory(FileSystem::GetProgramPath()));
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetWorkingDirectory failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the description (shown as the shortcut's tooltip)
const std::wstring description = tr_msg("PlayStation 2 Emulator").toStdWString();
res = pShellLink->SetDescription(description.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetDescription failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the launch arguments
if (!final_args.empty())
{
const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args);
res = pShellLink->SetArguments(target_cli_args.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetArguments failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
}
// Set the icon
std::string final_icon_path;
if (!icon_path.empty())
{
final_icon_path = Path::ToNativePath(icon_path);
if (!FileSystem::FileExists(final_icon_path.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
else
{
final_icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
}
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(final_icon_path);
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
if (FAILED(res))
{
report_error(tr_msg("SetIconLocation failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Use the IPersistFile object to save the shell link
res = pShellLink.query_to(&pPersistFile);
if (FAILED(res))
{
report_error(tr_msg("QueryInterface failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Save shortcut link to disk
const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file);
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
if (FAILED(res))
{
report_error(tr_msg("Failed to save the shortcut (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
#else
if (name.empty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
const bool is_flatpak = IsRunningInFlatpak();
// Sanitize filename and game path
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = game_path.empty() ? std::string() : Path::Canonicalize(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the executable path
std::string executable_path = FileSystem::GetPackagePath();
if (executable_path.empty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the destination directory
const QString link_dir = QStandardPaths::writableLocation(is_desktop ? QStandardPaths::DesktopLocation : QStandardPaths::ApplicationsLocation);
if (link_dir.isEmpty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not determine the shortcut destination directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
const std::string link_path = fmt::format("{}/{}.desktop", link_dir.toStdString(), clean_name);
std::string icon_name;
if (!icon_path.empty())
{
if (!FileSystem::FileExists(icon_path.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
icon_name = icon_path;
}
else
{
if (is_flatpak) // Flatpak
{
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
// Copy PCSX2 icon
icon_name = "PCSX2";
const std::string icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).toStdString());
const std::string icon_path_dest = fmt::format("{}/{}.png", icon_dest, icon_name);
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path_dest.c_str(), true);
}
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= EscapeShortcutCommandLine(&arg);
if (!is_flatpak)
lossless &= EscapeShortcutCommandLine(&executable_path);
if (!clean_path.empty())
lossless &= EscapeShortcutCommandLine(&clean_path);
if (!lossless)
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
// Assembling the .desktop file
std::string final_args = executable_path;
if (!cmdline.empty())
final_args += fmt::format(" {}", cmdline);
if (!custom_args.empty())
final_args += fmt::format(" {}", custom_args);
if (!clean_path.empty())
final_args += fmt::format(" -- {}", clean_path);
std::string file_content =
"[Desktop Entry]\n"
"Version=1.0\n"
"Type=Application\n"
"Terminal=false\n"
"StartupWMClass=pcsx2-qt\n"
"Exec=" +
final_args + "\n" +
"Name=" +
name + "\n" +
"GenericName=PlayStation 2 Emulator\n"
"Icon=" +
icon_name + "\n" +
"Comment=Sony PlayStation 2 Emulator\n"
"Keywords=game;emulator;\n"
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);
QString final_path = QString::fromStdString(link_path);
if (prompt_for_destination)
{
const QString filter(tr_msg("Desktop Shortcut Files (*.desktop)"));
final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, tr_msg("Select Shortcut Save Destination"), final_path, filter));
if (final_path.isEmpty())
return;
}
else
{
const std::string link_dir(Path::GetDirectory(link_path));
if (!link_dir.empty() && !FileSystem::EnsureDirectoryExists(link_dir.c_str(), true))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not create the shortcut directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
// Write to .desktop file
if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file
Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno);
#endif
}
bool EscapeShortcutCommandLine(std::string* arg)
{
#ifdef _WIN32
if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos)
return true;
std::string temp;
temp.reserve(arg->length() + 10);
temp += '"';
for (auto it = arg->begin();; ++it)
{
int backslash_count = 0;
while (it != arg->end() && *it == '\\')
{
++it;
++backslash_count;
}
if (it == arg->end())
{
temp.append(backslash_count * 2, '\\');
break;
}
if (*it == '"')
{
temp.append(backslash_count * 2 + 1, '\\');
temp += '"';
}
else
{
temp.append(backslash_count, '\\');
temp += *it;
}
}
temp += '"';
*arg = std::move(temp);
return true;
#else
const char* carg = arg->c_str();
const char* cend = carg + arg->size();
const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
const char* next = carg + std::strcspn(carg, RESERVED_CHARS);
if (next == cend)
return true; // No escaping needed, don't modify
bool lossless = true;
std::string temp = "\"";
const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
while (true)
{
next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE);
temp.append(carg, next);
carg = next;
if (carg == cend)
break;
switch (*carg)
{
case '"':
case '`':
temp.push_back('\\');
temp.push_back(*carg);
break;
case '\\':
temp.append("\\\\\\\\");
break;
case '$':
temp.push_back('\\');
temp.push_back('\\');
temp.push_back(*carg);
break;
case '%':
temp.push_back('%');
temp.push_back(*carg);
break;
default:
temp.push_back(' ');
lossless = false;
break;
}
++carg;
}
temp.push_back('"');
*arg = std::move(temp);
return lossless;
#endif
}
} // namespace QtUtils

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