Compare commits

...
90 Commits
Author SHA1 Message Date
Connor McLaughlinandrefractionpcsx2 e8e0f355fc GS/WX: Fix default adapter causing unnecessary GS restarts 2022-03-09 09:14:36 +00:00
Mrlinkwiiandrefractionpcsx2 3202fd88d2 Gamedb: add VU clamping to 'DT Racer' and update MGS2 hack 2022-03-09 00:10:00 +00:00
RedDevilusandrefractionpcsx2 1c66c904ab GameDB: Katamari games
Katamari games have become sensitive again to this hack. It's forcefully disabled for this PR to fix performance and other breaking issues.
2022-03-09 00:08:21 +00:00
lightningterror 784e090081 GS-hw: Improve how we handle blending when output is Cd.
Disable hw blending on Cd output,
Disable rgb write on Cd output.
2022-03-08 23:46:27 +01:00
Connor McLaughlinandlightningterror 2d08d3dc94 GL/Context: Drop GL string logging back to devbuilds 2022-03-08 10:39:50 +01:00
Connor McLaughlinandlightningterror 237b9754c6 Qt: Log graphics driver info on startup 2022-03-08 10:39:50 +01:00
Connor McLaughlinandlightningterror 74bbccdf59 Wx: Log graphics driver info on startup 2022-03-08 10:39:50 +01:00
Connor McLaughlinandlightningterror 062d625901 Vulkan/Context: Allow non-debug device when validation layers unavailable 2022-03-08 10:39:50 +01:00
Connor McLaughlinandlightningterror 3f30c2bda2 VulkanHostDisplay: Improve Vulkan version reporting 2022-03-08 10:39:50 +01:00
PCSX2 Botandlightningterror 0702692746 PAD: Update to latest controller database. 2022-03-07 17:11:41 +01:00
dependabot[bot]andlightningterror 11a218b1aa Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 14:15:25 +01:00
Connor McLaughlinandlightningterror f4acff1321 Qt: Fix incorrect dropdown value in game properties 2022-03-07 14:14:49 +01:00
Connor McLaughlinandlightningterror a8ee746c39 Qt: Fix live changing game settings not applying 2022-03-07 14:14:49 +01:00
Connor McLaughlinandlightningterror ddd5fc7bf3 VMManager: Fix brief unpause before shutdown 2022-03-07 14:14:04 +01:00
lightningterror 22dfe05287 GS-hw: Optimize blending when ALPHA.A is equal to ALPHA.B.
Set ALPHA A B to 0 (Cs), and ALPHA.C to 0 (As).
2022-03-07 14:13:19 +01:00
refractionpcsx2 fa78e1295a GS: Handle Auto Flush across pages + scissor, add TEXFLUSH
Fixes Jak 3 shadows, partially fixes Beyond Good and Evil water, improves Auto Flush performance hugely (x3 in Jak 2 @ 3x Native)
2022-03-06 03:07:37 +00:00
refractionpcsx2 4a634ca154 GS: Fix compilation of GS SW Int Rasterizer 2022-03-05 16:55:54 +00:00
refractionpcsx2 bd1f5d0888 GS: Adjust Tekken 5 CRC hack for fire effect, fixes sun bleed.
Fixes #2766
2022-03-05 15:55:36 +00:00
Connor McLaughlinandrefractionpcsx2 2f028c5f40 Qt: Implement CTRL+C and confirm shutdown 2022-03-04 17:41:28 +00:00
Connor McLaughlinandrefractionpcsx2 56c8843406 Qt: Add log options to debug menu 2022-03-04 17:41:28 +00:00
Connor McLaughlinandrefractionpcsx2 37c4c596b5 Qt: Fix a few warnings on Linux 2022-03-04 17:41:28 +00:00
Connor McLaughlinandrefractionpcsx2 7b0bde4f1f Qt: Move section/key in setting binder
It's passed in by value, so elide the copy.
2022-03-04 17:41:28 +00:00
Connor McLaughlinandrefractionpcsx2 5c5c7d64a8 Qt: Remove version check macros
Not needed anymore since we require Qt6 on all platforms.
2022-03-04 17:41:28 +00:00
lightningterror 565189d08c GS-hw: Fix Wunused-variable warning in GSRendererHW. 2022-03-03 22:27:40 +01:00
lightningterror 682580e318 GS-hw: Fix Wsign-compare warnings in GSRendererNew. 2022-03-03 22:27:40 +01:00
lightningterror 74e3d6470a GS-hw: Fix Wunused-but-set-variable warnings in GSTextureCache. 2022-03-03 22:27:40 +01:00
refractionpcsx2 b4e6a715fc EE/JIT: Flush const on LDL/LDR instructions 2022-03-03 16:18:16 +00:00
Mrlinkwiiandrefractionpcsx2 85f0d44997 Gamedb: add patch for Cars 2022-03-03 11:29:52 +00:00
TellowKrinkleandrefractionpcsx2 294855700d GS:OGL: Don't memset C++ objects 2022-03-02 12:10:12 +00:00
TellowKrinkleandrefractionpcsx2 099ddfedc7 GS:Mac: Properly init vulkan on main thread 2022-03-02 12:02:15 +00:00
TellowKrinkleandrefractionpcsx2 d9e2bcef6d GS:Mac: Nicer message send 2022-03-02 12:02:15 +00:00
TellowKrinkleandrefractionpcsx2 7a628f1a12 WX: Make texture dump location configurable 2022-03-02 11:58:44 +00:00
Mrlinkwiiandrefractionpcsx2 75bc680934 GameDB: add 'VUSyncHack' to 'Tiger Woods PGA Tour 2002' and update documentation 2022-03-02 11:36:11 +00:00
Connor McLaughlinandrefractionpcsx2 0c98b5ff76 GS/TextureCache: Don't let hash cache memusage go negative 2022-03-01 17:34:59 +00:00
arcum42andGitHub 11918e5846 Disable warnings on several third party libraries. (#5519) 2022-02-28 21:14:52 -08:00
refractionpcsx2 2c33bf8c6f GUI/Qt: Update VU Sync option in Qt interface 2022-02-28 19:29:53 +00:00
refractionpcsx2 e833a67bb7 VU: Rework VUKickstart in to VUSync, swap behaviour 2022-02-28 19:29:53 +00:00
refractionpcsx2 6dc5087cbd VU: Run sync ahead on small blocks 2022-02-28 19:29:53 +00:00
PCSX2 Botandlightningterror eabda670ac PAD: Update to latest controller database. 2022-02-28 17:52:52 +01:00
Connor McLaughlinandrefractionpcsx2 845e7930d7 microVU: Move VU0 micro flag instance setup to program start
They're only used in micro mode, so no point updating them in a cop2
chain.
2022-02-28 15:07:15 +00:00
Connor McLaughlinandrefractionpcsx2 d20bfa240d EE: Add COP2 flag hack 2022-02-28 15:07:15 +00:00
Connor McLaughlinandrefractionpcsx2 ba27a46ac6 GS/TextureCache: Don't count replacements in hash cache budget
Large replacement textures were causing the hash cache to disable
itself. There's plenty of other massive VRAM sinks in PCSX2 that we need
to consider before we can say we're managing VRAM well anyway.
2022-02-28 09:38:12 +00:00
Connor McLaughlinandrefractionpcsx2 866e5bd929 GS/TextureReplacements: Disable generated mipmaps on compressed textures
They should be provided in the file.
2022-02-28 09:38:12 +00:00
TellowKrinkleandlightningterror 1beb5517a7 Pad: Fix connection of macOS GameController framework controllers 2022-02-28 09:51:11 +01:00
TheLastRarandlightningterror a12ad1f627 Config: Clang format Pcsx2Config 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror c18be9dde7 Dependencies: Remove LibXml 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 4eba400170 DEV9: Don't enforce MTA requirement
Also CoUninitialize() needs to be called for all successful calls to CoInitialize/Ex(), not just those that return S_OK
2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror dc376ab161 DEV9: Clang format net.cpp 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 5ff49a78eb Config: Add DEV9 options to global struct
Additionally switch to using std::string in various locations in DEV9
2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 6df50992c4 DEV9: Remove old settings/log directory functions 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 5d2bcfaa38 DEV9: Fix off by one error on LBA range check 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 990d7ad0f7 DEV9: Correct DEV9 include in Tap 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 510283cd4d DEV9: Rename DEV9's Config.h to DEV9Config.h 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror dcc183179a DEV9: Use fs namespace instead of ghc 2022-02-28 09:44:14 +01:00
TheLastRarandlightningterror 30bc9b04c5 DEV9: Use UTF8 strings on windows for AdapterEntry 2022-02-28 09:44:14 +01:00
dependabot[bot]andlightningterror 8aaeb1c7ce Bump actions/setup-node from 2 to 3
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 09:25:18 +01:00
AKuHAKandlightningterror 1984c68516 PCSX2: Add ability to combine CLI options --elf or --irx with --gameargs
Exception: if full path to host: contains spaces, then arguments will be ignored. To avoid this you can use relative paths when possible.
2022-02-28 09:20:01 +01:00
refractionpcsx2 c81c89d6d9 GUI/Qt: Fix startup project for debugger 2022-02-28 07:34:52 +00:00
refractionpcsx2 9ce2405f3f GUI/Qt: Fix loading ELF files from menu 2022-02-28 07:05:15 +00:00
refractionpcsx2 21fba45a3c GameDB: Added patch for PowerDrome US 2022-02-27 15:22:42 +00:00
refractionpcsx2 a0bbdcefb3 GS: Ignore 24bit on DATE and Handle Reversed Color and Z
Clamp Framebuffer width to 1-32
2022-02-27 15:22:42 +00:00
lightningterror 753da789a0 GS-hw: Adjust DATE selection.
Remove fbmask check from DATE,
Enable DATE_BARRIER with one barrier when alpha isn't written and there is no alpha test.
2022-02-26 17:48:27 +01:00
lightningterror cd5ce6e162 GH-workflows: Remove os mentions for auto pad db update.
Not needed anymore.
2022-02-24 14:52:41 +01:00
Connor McLaughlinandrefractionpcsx2 5a25cc171d GS: Add texture dumping and replacement system 2022-02-24 10:18:49 +00:00
Connor McLaughlinandrefractionpcsx2 32dc68f103 GS: Support BC1/BC2/BC3/BC7 compressed texture formats 2022-02-24 10:18:49 +00:00
Connor McLaughlinandrefractionpcsx2 69a500d657 GS: Add Swap() to GSTexture 2022-02-24 10:18:49 +00:00
Connor McLaughlinandrefractionpcsx2 ed9f34de5f Common/FileSystem: Add EnsureDirectoryExists() 2022-02-24 10:18:49 +00:00
iMineLinkandrefractionpcsx2 d6f1291c0b GS: skip no output draws. 2022-02-24 10:12:59 +00:00
iMineLinkandrefractionpcsx2 01079f5162 GS: upscale regardless of PCRTC enabling. 2022-02-24 10:12:59 +00:00
RedDevilusandrefractionpcsx2 9b0dc287c1 PCSX2-GUI: Rename Interlacing to Deinterlacing
So to sketch what is an annoying issue is that this for years has been wrongly presented to users.
None Interlacing = Interlacing but if used with a no-interlacing patch it will look like progressive but PCSX2 or really the internal PS2 side only detects interlacing mode. Which blows my mind that the user wouldn't be wrong to assume that None Interlacing = Interlacing.
Weave = Interlacing method (saw-tooth)
Bob = Likely the sharpest but can do vertical bouncing.
Blending = A bit blurry but the most consistent and has no bouncing.

I didn't change the code just the visual presentation to the users.

Example in gif format: https://upload.wikimedia.org/wikipedia/de/1/1e/Interlaced_Animation.gif
2022-02-24 00:38:01 +00:00
lightningterror bb92566781 GS-hw: Don't trigger no overlap check for DATE_BARRIER on d3d11.
d3d11 doesn't support DATE_BARRIER, it's better to let the Date one cases handle it as it will be more accurate.
2022-02-23 20:48:00 +01:00
Connor McLaughlinandrefractionpcsx2 17b1c664a8 GS: Add option to disable status indicators
Fast forward/pause icons.
2022-02-23 15:45:47 +00:00
Connor McLaughlinandrefractionpcsx2 2eaba084c7 GS/TextureCache: Fix target not getting invalidated in some cases
This would happen when there was a source from local memory with the
same pointer as the target.

Fixes sky getting messed up in the Jak 3 menu when the hash cache is
enabled.
2022-02-23 15:45:19 +00:00
arcum42@gmail.comandrefractionpcsx2 ff75ab73cc Qt: Fix qt compilation on linux. 2022-02-22 19:23:51 +00:00
RedDevilusandrefractionpcsx2 3c54593456 Qt: Add themes
Ranging from ColorBlind themes to flashy to pastel. Based on the Fusion factory. 3 Light and 1 Dark extra themes (2 light and 2 dark themes on master) with enough variability and choice.
It's just a placeholder for when other factories are implemented and perhaps even viable for future community-based static or dynamic ones.
2022-02-22 16:13:09 +00:00
RedDevilusandrefractionpcsx2 39dfb2e606 Qt: Resize default headersize BIOS
Qt: Resize default headersize BIOS

The first column was too small and visually makes it harder to see what BIOS you have.
2022-02-22 16:13:09 +00:00
PCSX2 Botandlightningterror 4e7ade8612 pad-windows/unix: Update to latest controller database. 2022-02-21 21:22:07 +01:00
seta-sanandGitHub 664013e7ad Qt: Fix type on graphics panel (#5553) 2022-02-21 19:10:14 +00:00
Connor McLaughlinandlightningterror 9d51c64d92 GS: Support mipmaps in hash cache 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror ee82c7834d GS: Auto-disable hash cache when it's too large 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror b389c6e103 GS: Only allocate valid page array when needed 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror b3a2d3c1e4 GS: Track hash cache memory usage 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror 5d33af13ca GS: Add full (hash cache) texture preloading option 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror 6b2a851dec GS/Vulkan: Fix uploading large (>64MB) textures 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror 3b3072801c GS/OpenGL: Fix uploading large (>16MB) textures 2022-02-21 16:50:57 +01:00
Connor McLaughlinandlightningterror 9ed33d4337 GS/HW: Fix mipmap level not updating on live config change 2022-02-21 16:50:57 +01:00
Mrlinkwiiandlightningterror 728b3eb5db Core: remove not needed logging 2022-02-21 10:46:29 +01:00
Connor McLaughlinandrefractionpcsx2 f73398ea84 GS/Vulkan: Fix warning when compiling RGBA8->RGB5A1 shader
Also gets rid of the program info log length check, since that's been
long removed in glslang.
2022-02-20 14:03:33 +00:00
lightningterror 2b646c2153 GS-hw: Disable dithering if blend mix is enabled.
It breaks stuff since we do a mix of hw/sw.
2022-02-20 02:31:03 +01:00
lightningterror f83692c0df GS-ogl: Remove GL_ARB_get_texture_sub_image.
Not used.
Also remove some not used commented out defines.
2022-02-19 18:08:05 +01:00
137 changed files with 4714 additions and 1972 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Validate GameDB
env:
+1 -1
View File
@@ -105,7 +105,7 @@ jobs:
# echo ${{ github.event.pull_request.title }}
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Checkout Submodules
if: steps.cache-submodules.outputs.cache-hit != 'true'
+1 -1
View File
@@ -86,7 +86,7 @@ jobs:
# echo ${{ github.event.pull_request.title }}
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Checkout Submodules
run: git submodule update --init --recursive -j $(getconf _NPROCESSORS_ONLN)
+2 -2
View File
@@ -9,9 +9,9 @@ jobs:
if: github.repository == 'PCSX2/pcsx2'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: 16
@@ -26,7 +26,7 @@ jobs:
if: github.repository == 'PCSX2/pcsx2'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# Docs - https://github.com/mathieudutour/github-tag-action
# Workflows cannot trigger other workflows implicitly
@@ -29,7 +29,6 @@ declare -a PCSX2_PACKAGES=(
"libudev-dev"
"libwxgtk3.0-gtk3-dev"
"libx11-xcb-dev"
"libxml2-dev"
"pkg-config"
"portaudio19-dev"
"zlib1g-dev"
@@ -32,7 +32,7 @@ allowed_game_fixes = [
"VIF1StallHack",
"GIFFIFOHack",
"GoemonTlbHack",
"VUKickstartHack",
"VUSyncHack",
"IbitHack",
"VUOverflowHack",
]
+4 -4
View File
@@ -9,7 +9,7 @@ jobs:
if: github.repository == 'PCSX2/pcsx2'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Get Latest DB and Prepare DB File
run: |
@@ -21,9 +21,9 @@ jobs:
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
title: "pad-windows/unix: Update to latest controller database"
commit-message: "pad-windows/unix: Update to latest controller database."
title: "PAD: Update to latest controller database"
commit-message: "PAD: Update to latest controller database."
committer: "PCSX2 Bot <PCSX2Bot@users.noreply.github.com>"
author: "PCSX2 Bot <PCSX2Bot@users.noreply.github.com>"
body: "Weekly automatic update of SDL Controller DB for Windows / macOS / Linux"
body: "Weekly automatic update of SDL Controller DB"
reviewers: lightningterror
+1 -1
View File
@@ -64,7 +64,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Checkout Submodules
run: git submodule update --init --recursive -j $env:NUMBER_OF_PROCESSORS
+2 -2
View File
@@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdparty", "3rdparty", "{78
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{2D6F0A62-A247-4CCF-947F-FCD54BE16103}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2-qt", "pcsx2-qt\pcsx2-qt.vcxproj", "{2A016F21-87AE-4154-8271-1F57E91408E9}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundTouch", "3rdparty\soundtouch\SoundTouch.vcxproj", "{E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "3rdparty\zlib\zlib.vcxproj", "{2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}"
@@ -46,8 +48,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glad", "3rdparty\glad\glad.
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "3rdparty\imgui\imgui.vcxproj", "{88FB34EC-845E-4F21-A552-F1573B9ED167}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2-qt", "pcsx2-qt\pcsx2-qt.vcxproj", "{2A016F21-87AE-4154-8271-1F57E91408E9}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simpleini", "3rdparty\simpleini\simpleini.vcxproj", "{1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cubeb", "3rdparty\cubeb\cubeb.vcxproj", "{BF74C473-DC04-44B3-92E8-4145F4E77342}"
Binary file not shown.
File diff suppressed because it is too large Load Diff
+14 -6
View File
@@ -390,7 +390,7 @@
030000006b140000080d000000000000,Nacon Revolution Unlimited Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000bd12000001c0000000000000,Nebular,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
03000000eb0300000000000000000000,NeGcon Adapter,a:a2,b:b13,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,lefttrigger:a4,leftx:a1,righttrigger:b11,start:b3,x:a3,y:b12,platform:Windows,
0300000092120000474e000000000000,Neo Geo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Windows,
0300000092120000474e000000000000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Windows,
0300000038070000efbe000000000000,NEO SE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000921200004b46000000000000,NES 2 port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Windows,
03000000000f00000100000000000000,NES Controller,a:b1,b:b0,back:b2,leftx:a0,lefty:a1,start:b3,platform:Windows,
@@ -540,6 +540,12 @@
030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000050b00005819000000000000,ROG Chakram Core,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b0000181a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b00001a1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
030000004f04000001d0000000000000,Rumble Force,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
030000008916000000fe000000000000,Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000c6240000045d000000000000,Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
@@ -806,7 +812,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X,
03000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c62400002b89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000632500007505000000020000,NEOGEO mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000632500007505000000020000,NeoGeo mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000921200004b46000003020000,NES 2-port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Mac OS X,
030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000d620000011a7000000020000,Nintendo Switch Core Plus Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -871,7 +877,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
030000006f0e00000302000025040000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006f0e00000702000003060000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000791d00000103000009010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000791d00000103000009010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,
050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,
030000005e0400008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
@@ -961,6 +967,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
05000000503200000210000000000000,Atari Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,
05000000503200000210000045010000,Atari Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,
05000000503200000210000046010000,Atari Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,
05000000503200000210000047010000,Atari VCS Modern Controller,a:b0,b:b1,x:b2,y:b3,back:b9,guide:b10,start:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b6,rightstick:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a5,righttrigger:-a4,platform:Linux,
03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,
03000000ef0500000300000000010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,
03000000c62400001b89000011010000,BDA MOGA XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@@ -982,6 +989,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000004f04000004b3000010010000,Dual Power 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,
030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000c11100000191000011010000,EasySMX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000242f00009100000000010000,EasySMX ESM-9101,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,
030000006e0500000320000010010000,Elecom U3613M,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,
030000006e0500000720000010010000,Elecom W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux,
03000000430b00000300000000010000,EMS Production PS2 Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
@@ -1117,7 +1125,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000250900006688000000010000,MP8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
030000006b140000010c000010010000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000004f1f00000800000011010000,Neogeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux,
03000000790000004518000010010000,Nexilux GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Linux,
@@ -1138,7 +1146,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
050000007e0500000720000001800000,Nintendo Switch Right Joy-Con,a:b1,b:b2,back:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,platform:Linux,
050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
05000000010000000100000003000000,Nintendo Wii Remote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000000d0500000308000010010000,Nostromo n45 Dual Analog,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,
@@ -1316,7 +1324,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006f0e00000302000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
030000006f0e00000702000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
05000000ac0500003232000001000000,VR Box Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000791d00000103000010010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000791d00000103000010010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
0000000058626f782033363020576900,Xbox 360 Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,
030000005e0400001907000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+1 -1
View File
@@ -90,7 +90,7 @@ void ps_filter_transparency()
// Need to be careful with precision here, it can break games like Spider-Man 3 and Dogs Life
void ps_convert_rgba8_16bits()
{
highp uvec4 i = uvec4(sample_c(v_tex) * vec4(255.5f, 255.5f, 255.5f, 255.5f));
uvec4 i = uvec4(sample_c(v_tex) * vec4(255.5f, 255.5f, 255.5f, 255.5f));
o_col0 = ((i.x & 0x00F8u) >> 3) | ((i.y & 0x00F8u) << 2) | ((i.z & 0x00f8u) << 7) | ((i.w & 0x80u) << 8);
}
+3 -2
View File
@@ -23,8 +23,6 @@ else()
make_imported_target_if_missing(ALSA::ALSA ALSA)
endif()
find_package(PCAP REQUIRED)
find_package(LibXml2 REQUIRED)
make_imported_target_if_missing(LibXml2::LibXml2 LibXml2)
find_package(Gettext) # translation tool
find_package(LibLZMA REQUIRED)
make_imported_target_if_missing(LibLZMA::LibLZMA LIBLZMA)
@@ -250,6 +248,7 @@ if(QT_BUILD)
endif()
add_subdirectory(3rdparty/libchdr/libchdr EXCLUDE_FROM_ALL)
target_compile_options(chdr-static PRIVATE "-w")
if(USE_NATIVE_TOOLS)
add_subdirectory(tools/bin2cpp EXCLUDE_FROM_ALL)
@@ -271,5 +270,7 @@ endif()
if(CUBEB_API)
add_subdirectory(3rdparty/cubeb EXCLUDE_FROM_ALL)
target_compile_options(cubeb PRIVATE "-w")
target_compile_options(speex PRIVATE "-w")
endif()
+9
View File
@@ -565,6 +565,15 @@ bool FileSystem::WriteFileToString(const char* filename, const std::string_view&
return true;
}
bool FileSystem::EnsureDirectoryExists(const char* path, bool recursive)
{
if (FileSystem::DirectoryExists(path))
return true;
// if it fails to create, we're not going to be able to use it anyway
return FileSystem::CreateDirectoryPath(path, recursive);
}
#ifdef _WIN32
static u32 TranslateWin32Attributes(u32 Win32Attributes)
+4
View File
@@ -150,6 +150,10 @@ namespace FileSystem
/// if they do not exist.
bool CreateDirectoryPath(const char* path, bool recursive);
/// Creates a directory if it doesn't already exist.
/// Returns false if it does not exist and creation failed.
bool EnsureDirectoryExists(const char* path, bool recursive);
/// Returns the path to the current executable.
std::string GetProgramPath();
+4 -4
View File
@@ -149,10 +149,10 @@ namespace GL
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
const char* gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
Console.WriteLn(Color_Magenta, "GL_VENDOR: %s", gl_vendor);
Console.WriteLn(Color_Magenta, "GL_RENDERER: %s", gl_renderer);
Console.WriteLn(Color_Magenta, "GL_VERSION: %s", gl_version);
Console.WriteLn(Color_Magenta, "GL_SHADING_LANGUAGE_VERSION: %s", gl_shading_language_version);
DevCon.WriteLn(Color_Magenta, "GL_VENDOR: %s", gl_vendor);
DevCon.WriteLn(Color_Magenta, "GL_RENDERER: %s", gl_renderer);
DevCon.WriteLn(Color_Magenta, "GL_VERSION: %s", gl_version);
DevCon.WriteLn(Color_Magenta, "GL_SHADING_LANGUAGE_VERSION: %s", gl_shading_language_version);
DisableBrokenExtensions(gl_vendor, gl_renderer);
+15 -37
View File
@@ -59,41 +59,6 @@ namespace Vulkan
Context::~Context() = default;
bool Context::CheckValidationLayerAvailablility()
{
u32 extension_count = 0;
VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
return false;
}
std::vector<VkExtensionProperties> extension_list(extension_count);
res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, extension_list.data());
pxAssert(res == VK_SUCCESS);
u32 layer_count = 0;
res = vkEnumerateInstanceLayerProperties(&layer_count, nullptr);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
return false;
}
std::vector<VkLayerProperties> layer_list(layer_count);
res = vkEnumerateInstanceLayerProperties(&layer_count, layer_list.data());
pxAssert(res == VK_SUCCESS);
// Check for both VK_EXT_debug_utils and VK_LAYER_LUNARG_standard_validation
return (std::find_if(extension_list.begin(), extension_list.end(),
[](const auto& it) { return strcmp(it.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0; }) !=
extension_list.end() &&
std::find_if(layer_list.begin(), layer_list.end(), [](const auto& it) {
return strcmp(it.layerName, "VK_LAYER_KHRONOS_validation") == 0;
}) != layer_list.end());
}
VkInstance Context::CreateVulkanInstance(
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer)
{
@@ -306,8 +271,20 @@ namespace Vulkan
VkInstance instance = CreateVulkanInstance(wi, enable_debug_utils, enable_validation_layer);
if (instance == VK_NULL_HANDLE)
{
Vulkan::UnloadVulkanLibrary();
return false;
if (enable_debug_utils || enable_validation_layer)
{
// Try again without the validation layer.
enable_debug_utils = false;
enable_validation_layer = false;
instance = CreateVulkanInstance(wi, enable_debug_utils, enable_validation_layer);
if (instance == VK_NULL_HANDLE)
{
Vulkan::UnloadVulkanLibrary();
return false;
}
Console.Error("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
}
}
if (!Vulkan::LoadVulkanInstanceFunctions(instance))
@@ -486,6 +463,7 @@ namespace Vulkan
m_device_features.largePoints = available_features.largePoints;
m_device_features.wideLines = available_features.wideLines;
m_device_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
m_device_features.textureCompressionBC = available_features.textureCompressionBC;
return true;
}
-3
View File
@@ -55,9 +55,6 @@ namespace Vulkan
~Context();
// Determines if the Vulkan validation layer is available on the system.
static bool CheckValidationLayerAvailablility();
// Helper method to create a Vulkan instance.
static VkInstance CreateVulkanInstance(
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer);
+2 -4
View File
@@ -107,13 +107,11 @@ namespace Vulkan::ShaderCompiler
glslang::GlslangToSpv(*intermediate, out_code, &logger);
// Write out messages
// Temporary: skip if it contains "Warning, version 450 is not yet complete; most version-specific
// features are present, but some are missing."
if (std::strlen(shader->getInfoLog()) > 108)
if (std::strlen(shader->getInfoLog()) > 0)
Console.Warning("Shader info log: %s", shader->getInfoLog());
if (std::strlen(shader->getInfoDebugLog()) > 0)
Console.Warning("Shader debug info log: %s", shader->getInfoDebugLog());
if (std::strlen(program->getInfoLog()) > 25)
if (std::strlen(program->getInfoLog()) > 0)
Console.Warning("Program info log: %s", program->getInfoLog());
if (std::strlen(program->getInfoDebugLog()) > 0)
Console.Warning("Program debug info log: %s", program->getInfoDebugLog());
+36 -6
View File
@@ -28,9 +28,39 @@
#if defined(__APPLE__)
#include <objc/message.h>
#include <dispatch/dispatch.h>
#ifdef __i386__
typedef float CGFloat;
#else
typedef double CGFloat;
#endif
template <typename Ret, typename Self, typename... Args>
Ret msgsend(Self self, const char* sel, Args... args)
{
void (*fn)(void) = objc_msgSend;
#ifdef __i386__
if (std::is_same<Ret, float>::value || std::is_same<Ret, double>::value || std::is_same<Ret, long double>::value)
fn = objc_msgSend_fpret;
#endif
#ifdef __x86_64__
if (std::is_same<Ret, long double>::value)
fn = objc_msgSend_fpret;
#endif
return reinterpret_cast<Ret(*)(Self, SEL, Args...)>(fn)(self, sel_getUid(sel), args...);
}
static bool CreateMetalLayer(WindowInfo* wi)
{
// if (![NSThread isMainThread])
if (!msgsend<BOOL, Class>(objc_getClass("NSThread"), "isMainThread"))
{
__block bool ret;
dispatch_sync(dispatch_get_main_queue(), ^{ ret = CreateMetalLayer(wi); });
return ret;
}
id view = reinterpret_cast<id>(wi->window_handle);
Class clsCAMetalLayer = objc_getClass("CAMetalLayer");
@@ -41,7 +71,7 @@ static bool CreateMetalLayer(WindowInfo* wi)
}
// [CAMetalLayer layer]
id layer = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(objc_getClass("CAMetalLayer"), sel_getUid("layer"));
id layer = msgsend<id, Class>(clsCAMetalLayer, "layer");
if (!layer)
{
Console.Error("Failed to create Metal layer.");
@@ -49,19 +79,19 @@ static bool CreateMetalLayer(WindowInfo* wi)
}
// [view setWantsLayer:YES]
reinterpret_cast<void (*)(id, SEL, BOOL)>(objc_msgSend)(view, sel_getUid("setWantsLayer:"), YES);
msgsend<void, id, BOOL>(view, "setWantsLayer:", YES);
// [view setLayer:layer]
reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend)(view, sel_getUid("setLayer:"), layer);
msgsend<void, id, id>(view, "setLayer:", layer);
// NSScreen* screen = [NSScreen mainScreen]
id screen = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(objc_getClass("NSScreen"), sel_getUid("mainScreen"));
id screen = msgsend<id, Class>(objc_getClass("NSScreen"), "mainScreen");
// CGFloat factor = [screen backingScaleFactor]
double factor = reinterpret_cast<double (*)(id, SEL)>(objc_msgSend)(screen, sel_getUid("backingScaleFactor"));
CGFloat factor = msgsend<CGFloat, id>(screen, "backingScaleFactor");
// layer.contentsScale = factor
reinterpret_cast<void (*)(id, SEL, double)>(objc_msgSend)(layer, sel_getUid("setContentsScale:"), factor);
msgsend<void, id, CGFloat>(layer, "setContentsScale:", factor);
// Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread.
wi->surface_handle = layer;
+1 -6
View File
@@ -49,12 +49,7 @@ DisplayWidget::~DisplayWidget() = default;
qreal DisplayWidget::devicePixelRatioFromScreen() const
{
QScreen* screen_for_ratio;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
screen_for_ratio = windowHandle()->screen();
#else
screen_for_ratio = screen();
#endif
const QScreen* screen_for_ratio = screen();
if (!screen_for_ratio)
screen_for_ratio = QGuiApplication::primaryScreen();
+34 -6
View File
@@ -16,6 +16,7 @@
#include "PrecompiledHeader.h"
#include <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>
#include "common/Assertions.h"
#include "common/Console.h"
@@ -137,12 +138,16 @@ void EmuThread::setVMPaused(bool paused)
VMManager::SetPaused(paused);
}
void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking /* = false */)
bool EmuThread::shutdownVM(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool blocking /* = false */)
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, allow_save_to_state),
Q_ARG(bool, blocking));
// only confirm on UI thread because we need to display a msgbox
if (allow_confirm && g_main_window && !g_main_window->confirmShutdown())
return false;
QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, false),
Q_ARG(bool, allow_save_to_state), Q_ARG(bool, blocking));
if (blocking)
{
@@ -151,16 +156,17 @@ void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
}
return;
return true;
}
const VMState state = VMManager::GetState();
if (state == VMState::Paused)
m_event_loop->quit();
else if (state != VMState::Running)
return;
return true;
VMManager::SetState(VMState::Stopping);
return true;
}
void EmuThread::loadState(const QString& filename)
@@ -285,6 +291,9 @@ void EmuThread::executeVM()
destroyVM();
m_event_loop->processEvents(QEventLoop::AllEvents);
return;
default:
continue;
}
}
}
@@ -365,6 +374,22 @@ void EmuThread::applySettings()
VMManager::ApplySettings();
}
void EmuThread::reloadGameSettings()
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, &EmuThread::reloadGameSettings, Qt::QueuedConnection);
return;
}
// this will skip applying settings when they're not active
if (VMManager::ReloadGameSettings())
{
// none of these settings below are per-game.. for now. but in case they are in the future.
checkForSettingChanges();
}
}
void EmuThread::checkForSettingChanges()
{
if (VMManager::HasValidVM())
@@ -607,6 +632,9 @@ HostDisplay* EmuThread::acquireHostDisplay(HostDisplay::RenderAPI api)
g_gs_window_info = s_host_display->GetWindowInfo();
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", HostDisplay::RenderAPIToString(s_host_display->GetRenderAPI()));
Console.Indent().WriteLn(s_host_display->GetDriverInfo());
return s_host_display.get();
}
@@ -738,7 +766,7 @@ ScopedVMPause::ScopedVMPause(bool was_paused)
ScopedVMPause::~ScopedVMPause()
{
if (m_was_paused)
if (!m_was_paused)
g_emu_thread->setVMPaused(false);
}
+2 -1
View File
@@ -57,7 +57,7 @@ public Q_SLOTS:
void startVM(std::shared_ptr<VMBootParameters> boot_params);
void resetVM();
void setVMPaused(bool paused);
void shutdownVM(bool allow_save_to_state = true, bool blocking = false);
bool shutdownVM(bool allow_confirm = true, bool allow_save_to_state = true, bool blocking = false);
void loadState(const QString& filename);
void loadStateFromSlot(qint32 slot);
void saveState(const QString& filename);
@@ -65,6 +65,7 @@ public Q_SLOTS:
void toggleFullscreen();
void setFullscreen(bool fullscreen);
void applySettings();
void reloadGameSettings();
void toggleSoftwareRendering();
void switchRenderer(GSRendererType renderer);
void changeDisc(const QString& path);
+1 -5
View File
@@ -17,6 +17,7 @@
#include <QtWidgets/QApplication>
#include <cstdlib>
#include <csignal>
#include "MainWindow.h"
#include "EmuThread.h"
@@ -184,13 +185,9 @@ static bool ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr<VMBo
int main(int argc, char* argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
#endif
QApplication app(argc, argv);
std::shared_ptr<VMBootParameters> autoboot;
@@ -216,7 +213,6 @@ int main(int argc, char* argv[])
const int result = app.exec();
EmuThread::stop();
QtHost::Shutdown();
return result;
}
+192 -6
View File
@@ -16,6 +16,7 @@
#include "PrecompiledHeader.h"
#include <QtCore/QDateTime>
#include <QtGui/QCloseEvent>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressBar>
@@ -39,6 +40,7 @@
#include "Settings/ControllerSettingsDialog.h"
#include "Settings/GameListSettingsWidget.h"
#include "Settings/InterfaceSettingsWidget.h"
#include "SettingWidgetBinder.h"
#include "svnrev.h"
static constexpr char DISC_IMAGE_FILTER[] =
@@ -170,6 +172,21 @@ void MainWindow::connectSignals()
});
connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget, &GameListWidget::refreshGridCovers);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableSystemConsole, "Logging", "EnableSystemConsole", false);
connect(m_ui.actionEnableSystemConsole, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged);
#ifndef PCSX2_DEVBUILD
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableVerboseLogging, "Logging", "EnableVerbose", false);
connect(m_ui.actionEnableVerboseLogging, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged);
#else
// Dev builds always have verbose logging.
m_ui.actionEnableVerboseLogging->setChecked(true);
m_ui.actionEnableVerboseLogging->setEnabled(false);
#endif
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableEEConsoleLogging, "Logging", "EnableEEConsole", false);
connect(m_ui.actionEnableEEConsoleLogging, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableIOPConsoleLogging, "Logging", "EnableIOPConsole", false);
connect(m_ui.actionEnableIOPConsoleLogging, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged);
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress);
connect(m_game_list_widget, &GameListWidget::refreshComplete, this, &MainWindow::onGameListRefreshComplete);
@@ -213,7 +230,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
void MainWindow::recreate()
{
if (m_vm_valid)
g_emu_thread->shutdownVM(true, true);
g_emu_thread->shutdownVM(false, true, true);
close();
g_main_window = nullptr;
@@ -235,6 +252,113 @@ void MainWindow::setStyleFromSettings()
qApp->setStyleSheet(QString());
qApp->setStyle(QStyleFactory::create("Fusion"));
}
else if (theme == "UntouchedLagoon")
{
// Custom pallete by RedDevilus, Tame (Light/Washed out) Green as main color and Grayish Blue as complimentary.
// Alternative white theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor black(25, 25, 25);
const QColor teal(0, 128, 128);
const QColor tameTeal(160, 190, 185);
const QColor grayBlue(160, 180, 190);
QPalette standardPalette;
standardPalette.setColor(QPalette::Window, tameTeal);
standardPalette.setColor(QPalette::WindowText, black);
standardPalette.setColor(QPalette::Base, grayBlue);
standardPalette.setColor(QPalette::AlternateBase, tameTeal);
standardPalette.setColor(QPalette::ToolTipBase, tameTeal);
standardPalette.setColor(QPalette::ToolTipText, grayBlue);
standardPalette.setColor(QPalette::Text, black);
standardPalette.setColor(QPalette::Button, tameTeal);
standardPalette.setColor(QPalette::ButtonText, Qt::white);
standardPalette.setColor(QPalette::Link, tameTeal);
standardPalette.setColor(QPalette::Highlight, teal);
standardPalette.setColor(QPalette::HighlightedText, Qt::white);
standardPalette.setColor(QPalette::Active, QPalette::Button, tameTeal.darker());
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, Qt::white);
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, Qt::white);
standardPalette.setColor(QPalette::Disabled, QPalette::Text, Qt::white);
standardPalette.setColor(QPalette::Disabled, QPalette::Light, tameTeal);
qApp->setPalette(standardPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "BabyPastel")
{
// Custom pallete by RedDevilus, Blue as main color and blue as complimentary.
// Alternative light theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor gray(150, 150, 150);
const QColor black(25, 25, 25);
const QColor pink(255, 174, 201);
const QColor brightPink(255, 230, 255);
const QColor congoPink(255, 127, 121);
const QColor blue(221, 225, 239);
QPalette standardPalette;
standardPalette.setColor(QPalette::Window, pink);
standardPalette.setColor(QPalette::WindowText, black);
standardPalette.setColor(QPalette::Base, brightPink);
standardPalette.setColor(QPalette::AlternateBase, blue);
standardPalette.setColor(QPalette::ToolTipBase, pink);
standardPalette.setColor(QPalette::ToolTipText, brightPink);
standardPalette.setColor(QPalette::Text, black);
standardPalette.setColor(QPalette::Button, pink);
standardPalette.setColor(QPalette::ButtonText, black);
standardPalette.setColor(QPalette::Link, congoPink);
standardPalette.setColor(QPalette::Highlight, congoPink);
standardPalette.setColor(QPalette::HighlightedText, black);
standardPalette.setColor(QPalette::Active, QPalette::Button, pink);
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, congoPink);
standardPalette.setColor(QPalette::Disabled, QPalette::Text, blue);
standardPalette.setColor(QPalette::Disabled, QPalette::Light, gray);
qApp->setPalette(standardPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "PCSX2Blue")
{
// Custom pallete by RedDevilus, White as main color and Blue as complimentary.
// Alternative light theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor black(25, 25, 25);
const QColor darkBlue(73, 97, 177);
const QColor blue(106, 156, 255);
const QColor lightBlue(130, 155, 241);
QPalette standardPalette;
standardPalette.setColor(QPalette::Window, lightBlue);
standardPalette.setColor(QPalette::WindowText, black);
standardPalette.setColor(QPalette::Base, darkBlue);
standardPalette.setColor(QPalette::AlternateBase, lightBlue);
standardPalette.setColor(QPalette::ToolTipBase, lightBlue);
standardPalette.setColor(QPalette::ToolTipText, Qt::white);
standardPalette.setColor(QPalette::Text, Qt::white);
standardPalette.setColor(QPalette::Button, blue);
standardPalette.setColor(QPalette::ButtonText, Qt::white);
standardPalette.setColor(QPalette::Link, blue);
standardPalette.setColor(QPalette::Highlight, Qt::white);
standardPalette.setColor(QPalette::HighlightedText, black);
standardPalette.setColor(QPalette::Active, QPalette::Button, blue.darker());
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkBlue);
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkBlue);
standardPalette.setColor(QPalette::Disabled, QPalette::Text, black);
standardPalette.setColor(QPalette::Disabled, QPalette::Light, darkBlue);
qApp->setPalette(standardPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "darkfusion")
{
// adapted from https://gist.github.com/QuantumCD/6245215
@@ -275,7 +399,6 @@ void MainWindow::setStyleFromSettings()
// adapted from https://gist.github.com/QuantumCD/6245215
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor lighterGray(75, 75, 75);
const QColor darkGray(53, 53, 53);
const QColor gray(128, 128, 128);
const QColor black(25, 25, 25);
@@ -306,6 +429,40 @@ void MainWindow::setStyleFromSettings()
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "ScarletDevilRed")
{
// Custom pallete by RedDevilus, Red as main color and Purple as complimentary.
// Alternative dark theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor darkRed(80, 45, 69);
const QColor purplishRed(120, 45, 69);
const QColor brightRed(200, 45, 69);
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, darkRed);
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, purplishRed);
darkPalette.setColor(QPalette::AlternateBase, darkRed);
darkPalette.setColor(QPalette::ToolTipBase, darkRed);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, darkRed);
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Link, darkRed);
darkPalette.setColor(QPalette::Highlight, brightRed);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Active, QPalette::Button, purplishRed.darker());
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, brightRed);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, brightRed);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, brightRed);
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkRed);
qApp->setPalette(darkPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else
{
qApp->setPalette(QApplication::style()->standardPalette());
@@ -319,7 +476,7 @@ void MainWindow::setIconThemeFromSettings()
const std::string theme(QtHost::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME));
QString icon_theme;
if (theme == "darkfusion" || theme == "darkfusionblue")
if (theme == "darkfusion" || theme == "darkfusionblue" || theme == "dualtoneOrangeBlue" || theme == "ScarletDevilRed")
icon_theme = QStringLiteral("white");
else
icon_theme = QStringLiteral("black");
@@ -484,6 +641,25 @@ void MainWindow::invalidateSaveStateCache() { m_save_states_invalidated = true;
void MainWindow::reportError(const QString& title, const QString& message) { QMessageBox::critical(this, title, message); }
bool MainWindow::confirmShutdown()
{
if (!m_vm_valid || !QtHost::GetBaseBoolSettingValue("UI", "ConfirmShutdown", true))
return true;
ScopedVMPause pauser(m_vm_paused);
return (QMessageBox::question(g_main_window, tr("Confirm Shutdown"),
tr("Are you sure you want to shut down the virtual machine?\n\nAll unsaved progress will be lost.")) == QMessageBox::Yes);
}
void MainWindow::requestExit()
{
if (!g_emu_thread->shutdownVM(true, true, false))
return;
close();
}
void Host::InvalidateSaveStateCache() { QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection); }
void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total)
@@ -536,7 +712,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
QAction* action = menu.addAction(tr("Properties..."));
action->setEnabled(!entry->serial.empty());
if (action->isEnabled())
connect(action, &QAction::triggered, [this, entry]() { SettingsDialog::openGamePropertiesDialog(entry, entry->crc); });
connect(action, &QAction::triggered, [entry]() { SettingsDialog::openGamePropertiesDialog(entry, entry->crc); });
action = menu.addAction(tr("Open Containing Directory..."));
connect(action, &QAction::triggered, [this, entry]() {
@@ -743,6 +919,11 @@ void MainWindow::onThemeChangedFromSettings()
g_main_window->doSettings();
}
void MainWindow::onLoggingOptionChanged()
{
QtHost::UpdateLogging();
}
void MainWindow::onVMStarting()
{
m_vm_valid = true;
@@ -805,7 +986,12 @@ void MainWindow::onGameChanged(const QString& path, const QString& serial, const
void MainWindow::closeEvent(QCloseEvent* event)
{
g_emu_thread->shutdownVM(true, true);
if (!g_emu_thread->shutdownVM(true, true, true))
{
event->ignore();
return;
}
saveStateToConfig();
QMainWindow::closeEvent(event);
}
@@ -1305,7 +1491,7 @@ void MainWindow::populateSaveStateMenu(QMenu* menu, const QString& serial, quint
timestamp = tr("Empty");
QString title(tr("Save Slot %1 (%2)").arg(i).arg(timestamp));
connect(menu->addAction(title), &QAction::triggered, [this, i]() { g_emu_thread->saveStateToSlot(i); });
connect(menu->addAction(title), &QAction::triggered, [i]() { g_emu_thread->saveStateToSlot(i); });
}
}
+4 -1
View File
@@ -54,6 +54,8 @@ public Q_SLOTS:
void refreshGameList(bool invalidate_cache);
void invalidateSaveStateCache();
void reportError(const QString& title, const QString& message);
bool confirmShutdown();
void requestExit();
private Q_SLOTS:
DisplayWidget* createDisplay(bool fullscreen, bool render_to_main);
@@ -92,6 +94,7 @@ private Q_SLOTS:
void onToolsOpenDataDirectoryTriggered();
void onThemeChanged();
void onThemeChangedFromSettings();
void onLoggingOptionChanged();
void onVMStarting();
void onVMStarted();
@@ -104,7 +107,7 @@ private Q_SLOTS:
void recreate();
protected:
void closeEvent(QCloseEvent* event);
void closeEvent(QCloseEvent* event) override;
private:
enum : s32
+170 -102
View File
@@ -45,7 +45,8 @@
<string>Change Disc</string>
</property>
<property name="icon">
<iconset theme="dvd-line"/>
<iconset theme="dvd-line">
<normaloff>.</normaloff>.</iconset>
</property>
<actiongroup name="actionGroupChangeDiscSubImages"/>
<addaction name="actionChangeDiscFromFile"/>
@@ -59,7 +60,8 @@
<string>Cheats</string>
</property>
<property name="icon">
<iconset theme="flask-line"/>
<iconset theme="flask-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
<widget class="QMenu" name="menuLoadState">
@@ -67,7 +69,8 @@
<string>Load State</string>
</property>
<property name="icon">
<iconset theme="folder-open-line"/>
<iconset theme="folder-open-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
<widget class="QMenu" name="menuSaveState">
@@ -75,7 +78,8 @@
<string>Save State</string>
</property>
<property name="icon">
<iconset theme="save-3-line"/>
<iconset theme="save-3-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
<addaction name="actionStartFile"/>
@@ -141,6 +145,11 @@
<addaction name="menuDebugSwitchRenderer"/>
<addaction name="separator"/>
<addaction name="actionReloadPatches"/>
<addaction name="separator"/>
<addaction name="actionEnableSystemConsole"/>
<addaction name="actionEnableVerboseLogging"/>
<addaction name="actionEnableEEConsoleLogging"/>
<addaction name="actionEnableIOPConsoleLogging"/>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
@@ -151,7 +160,8 @@
<string>&amp;Window Size</string>
</property>
<property name="icon">
<iconset theme="window-2-line"/>
<iconset theme="window-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
<addaction name="actionViewToolbar"/>
@@ -185,6 +195,9 @@
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="visible">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>Toolbar</string>
</property>
@@ -203,9 +216,6 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<property name="visible">
<bool>false</bool>
</property>
<addaction name="actionStartFile"/>
<addaction name="actionStartDisc"/>
<addaction name="actionStartBios"/>
@@ -227,143 +237,160 @@
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionStartFile">
<property name="icon">
<iconset theme="file-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Start &amp;File...</string>
</property>
<property name="icon">
<iconset theme="file-line"/>
</property>
</action>
<action name="actionStartDisc">
<property name="icon">
<iconset theme="disc-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Start &amp;Disc...</string>
</property>
<property name="icon">
<iconset theme="disc-line"/>
</property>
</action>
<action name="actionStartBios">
<property name="icon">
<iconset theme="hard-drive-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Start &amp;BIOS</string>
</property>
<property name="icon">
<iconset theme="hard-drive-2-line"/>
</property>
</action>
<action name="actionScanForNewGames">
<property name="icon">
<iconset theme="file-search-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Scan For New Games</string>
</property>
<property name="icon">
<iconset theme="file-search-line"/>
</property>
</action>
<action name="actionRescanAllGames">
<property name="icon">
<iconset theme="refresh-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Rescan All Games</string>
</property>
<property name="icon">
<iconset theme="refresh-line"/>
</property>
</action>
<action name="actionPowerOff">
<property name="icon">
<iconset theme="shut-down-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Shut &amp;Down</string>
</property>
<property name="icon">
<iconset theme="shut-down-line"/>
</property>
</action>
<action name="actionReset">
<property name="icon">
<iconset theme="restart-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Reset</string>
</property>
<property name="icon">
<iconset theme="restart-line"/>
</property>
</action>
<action name="actionPause">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="pause-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Pause</string>
</property>
<property name="icon">
<iconset theme="pause-line"/>
</property>
</action>
<action name="actionLoadState">
<property name="icon">
<iconset theme="folder-open-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Load State</string>
</property>
<property name="icon">
<iconset theme="folder-open-line"/>
</property>
</action>
<action name="actionSaveState">
<property name="icon">
<iconset theme="save-3-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Save State</string>
</property>
<property name="icon">
<iconset theme="save-3-line"/>
</property>
</action>
<action name="actionExit">
<property name="icon">
<iconset theme="door-open-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>E&amp;xit</string>
</property>
<property name="icon">
<iconset theme="door-open-line"/>
</property>
</action>
<action name="actionBIOSSettings">
<property name="icon">
<iconset theme="hard-drive-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;BIOS</string>
</property>
<property name="icon">
<iconset theme="hard-drive-2-line"/>
</property>
</action>
<action name="actionSystemSettings">
<property name="icon">
<iconset theme="artboard-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>System</string>
</property>
<property name="icon">
<iconset theme="artboard-2-line"/>
</property>
</action>
<action name="actionEmulationSettings">
<property name="icon">
<iconset theme="dashboard-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Emulation</string>
</property>
<property name="icon">
<iconset theme="dashboard-line"/>
</property>
</action>
<action name="actionControllerSettings">
<property name="icon">
<iconset theme="gamepad-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Controllers</string>
</property>
<property name="icon">
<iconset theme="gamepad-line"/>
</property>
</action>
<action name="actionHotkeySettings">
<property name="icon">
<iconset theme="keyboard-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Hotkeys</string>
</property>
<property name="icon">
<iconset theme="keyboard-line"/>
</property>
</action>
<action name="actionGraphicsSettings">
<property name="icon">
<iconset theme="brush-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Graphics</string>
</property>
<property name="icon">
<iconset theme="brush-line"/>
</property>
</action>
<action name="actionPostProcessingSettings">
<property name="text">
@@ -371,12 +398,13 @@
</property>
</action>
<action name="actionFullscreen">
<property name="icon">
<iconset theme="fullscreen-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Fullscreen</string>
</property>
<property name="icon">
<iconset theme="fullscreen-line"/>
</property>
</action>
<action name="actionResolution_Scale">
<property name="text">
@@ -400,7 +428,8 @@
</action>
<action name="actionCheckForUpdates">
<property name="icon">
<iconset theme="download-2-line"/>
<iconset theme="download-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Check for &amp;Updates...</string>
@@ -421,60 +450,67 @@
</property>
</action>
<action name="actionChangeDisc">
<property name="icon">
<iconset theme="dvd-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Change Disc...</string>
</property>
<property name="icon">
<iconset theme="dvd-line"/>
</property>
</action>
<action name="actionCheats">
<property name="icon">
<iconset theme="flask-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Cheats...</string>
</property>
<property name="icon">
<iconset theme="flask-line"/>
</property>
</action>
<action name="actionAudioSettings">
<property name="icon">
<iconset theme="volume-up-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Audio</string>
</property>
<property name="icon">
<iconset theme="volume-up-line"/>
</property>
</action>
<action name="actionGameListSettings">
<property name="icon">
<iconset theme="folder-settings-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Game List</string>
</property>
<property name="icon">
<iconset theme="folder-settings-line"/>
</property>
</action>
<action name="actionInterfaceSettings">
<property name="icon">
<iconset theme="settings-3-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Interface</string>
</property>
<property name="icon">
<iconset theme="settings-3-line"/>
</property>
</action>
<action name="actionAddGameDirectory">
<property name="icon">
<iconset theme="folder-add-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Add Game Directory...</string>
</property>
<property name="icon">
<iconset theme="folder-add-line"/>
</property>
</action>
<action name="actionSettings">
<property name="icon">
<iconset theme="settings-3-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Settings...</string>
</property>
<property name="icon">
<iconset theme="settings-3-line"/>
</property>
</action>
<action name="actionChangeDiscFromFile">
<property name="text">
@@ -502,20 +538,22 @@
</property>
</action>
<action name="actionScreenshot">
<property name="icon">
<iconset theme="screenshot-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Screenshot</string>
</property>
<property name="icon">
<iconset theme="screenshot-2-line"/>
</property>
</action>
<action name="actionMemoryCardSettings">
<property name="icon">
<iconset theme="sd-card-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Memory Cards</string>
</property>
<property name="icon">
<iconset theme="sd-card-line"/>
</property>
</action>
<action name="actionViewToolbar">
<property name="checkable">
@@ -551,42 +589,46 @@
</property>
</action>
<action name="actionViewGameList">
<property name="icon">
<iconset theme="list-check">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Game &amp;List</string>
</property>
<property name="icon">
<iconset theme="list-check"/>
</property>
</action>
<action name="actionViewSystemDisplay">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="tv-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>System &amp;Display</string>
</property>
<property name="icon">
<iconset theme="tv-2-line"/>
</property>
</action>
<action name="actionViewGameProperties">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="file-settings-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Game &amp;Properties</string>
</property>
<property name="icon">
<iconset theme="file-settings-line"/>
</property>
</action>
<action name="actionViewGameGrid">
<property name="icon">
<iconset theme="function-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Game &amp;Grid</string>
</property>
<property name="icon">
<iconset theme="function-line"/>
</property>
</action>
<action name="actionGridViewShowTitles">
<property name="checkable">
@@ -640,6 +682,32 @@
<string>Reload Cheats/Patches</string>
</property>
</action>
<action name="actionEnableSystemConsole">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable System Console</string>
</property>
</action>
<action name="actionEnableVerboseLogging">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable Verbose Logging</string>
</property>
</action>
<action name="actionEnableEEConsoleLogging">
<property name="text">
<string>Enable EE Console Logging</string>
</property>
</action>
<action name="actionEnableIOPConsoleLogging">
<property name="text">
<string>Enable IOP Console Logging</string>
</property>
</action>
</widget>
<resources>
<include location="resources/resources.qrc"/>
+92 -22
View File
@@ -15,6 +15,15 @@
#include "PrecompiledHeader.h"
#include <csignal>
#include <QtCore/QTimer>
#include <QtWidgets/QMessageBox>
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#endif
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/FileSystem.h"
@@ -26,9 +35,6 @@
#include "pcsx2/HostSettings.h"
#include "pcsx2/PAD/Host/PAD.h"
#include <QtCore/QTimer>
#include <QtWidgets/QMessageBox>
#include "EmuThread.h"
#include "GameList/GameListWidget.h"
#include "MainWindow.h"
@@ -42,11 +48,15 @@ static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
//////////////////////////////////////////////////////////////////////////
// Local function declarations
//////////////////////////////////////////////////////////////////////////
namespace QtHost {
static void InitializeWxRubbish();
static bool InitializeConfig();
static void HookSignals();
static bool SetCriticalFolders();
static void SetDefaultConfig();
static void QueueSettingsSave();
static void SaveSettings();
}
//////////////////////////////////////////////////////////////////////////
// Local variable declarations
@@ -72,12 +82,21 @@ bool QtHost::Initialize()
return false;
}
HookSignals();
return true;
}
void QtHost::Shutdown() {}
void QtHost::Shutdown()
{
EmuThread::stop();
if (g_main_window)
{
g_main_window->close();
delete g_main_window;
}
}
static bool SetCriticalFolders()
bool QtHost::SetCriticalFolders()
{
std::string program_path(FileSystem::GetProgramPath());
EmuFolders::AppRoot = wxDirName(wxFileName(StringUtil::UTF8StringToWxString(program_path)));
@@ -110,7 +129,7 @@ void QtHost::UpdateFolders()
EmuFolders::EnsureFoldersExist();
}
bool InitializeConfig()
bool QtHost::InitializeConfig()
{
if (!SetCriticalFolders())
return false;
@@ -138,7 +157,7 @@ bool InitializeConfig()
return true;
}
void SetDefaultConfig()
void QtHost::SetDefaultConfig()
{
EmuConfig = Pcsx2Config();
EmuFolders::SetDefaults();
@@ -247,7 +266,7 @@ void QtHost::RemoveBaseSettingValue(const char* section, const char* key)
QueueSettingsSave();
}
void SaveSettings()
void QtHost::SaveSettings()
{
pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread.");
@@ -261,7 +280,7 @@ void SaveSettings()
s_settings_save_timer.release();
}
void QueueSettingsSave()
void QtHost::QueueSettingsSave()
{
if (s_settings_save_timer)
return;
@@ -338,9 +357,38 @@ void PatchesVerboseReset()
// FIXME
}
static void SignalHandler(int signal)
{
// First try the normal (graceful) shutdown/exit.
static bool graceful_shutdown_attempted = false;
if (!graceful_shutdown_attempted && g_main_window)
{
std::fprintf(stderr, "Received CTRL+C, attempting graceful shutdown. Press CTRL+C again to force.\n");
graceful_shutdown_attempted = true;
// This could be a bit risky invoking from a signal handler... hopefully it's okay.
QMetaObject::invokeMethod(g_main_window, &MainWindow::requestExit, Qt::QueuedConnection);
return;
}
std::signal(signal, SIG_DFL);
// MacOS is missing std::quick_exit() despite it being C++11...
#ifndef __APPLE__
std::quick_exit(1);
#else
_Exit(1);
#endif
}
void QtHost::HookSignals()
{
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
}
// Replacement for Console so we actually get output to our console window on Windows.
#ifdef _WIN32
#include "common/RedtapeWindows.h"
static bool s_debugger_attached = false;
static bool s_console_handle_set = false;
@@ -433,24 +481,32 @@ static const IConsoleWriter ConsoleWriter_WinQt =
ConsoleWinQt_Newline,
ConsoleWinQt_SetTitle,
};
static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType)
{
Console.WriteLn("Handler %u", dwCtrlType);
if (dwCtrlType != CTRL_C_EVENT)
return FALSE;
SignalHandler(SIGTERM);
return TRUE;
}
#endif
void QtHost::UpdateLogging()
{
// TODO: Make this an actual option.
bool console_logging_enabled = false;
const bool system_console_enabled = QtHost::GetBaseBoolSettingValue("Logging", "EnableSystemConsole", false);
#if defined(_DEBUG) || defined(PCSX2_DEVBUILD)
console_logging_enabled = true;
#endif
const bool any_logging_sinks = system_console_enabled;
DevConWriterEnabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableVerbose", false);
SysConsole.eeConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableEEConsole", true);
SysConsole.iopConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableIOPConsole", true);
DevConWriterEnabled = console_logging_enabled;
SysConsole.eeConsole.Enabled = console_logging_enabled;
SysConsole.iopConsole.Enabled = console_logging_enabled;
if (console_logging_enabled)
{
#ifdef _WIN32
if (system_console_enabled)
{
s_debugger_attached = IsDebuggerPresent();
if (!s_console_handle_set)
{
@@ -471,17 +527,31 @@ void QtHost::UpdateLogging()
}
if (!s_console_handle && !s_debugger_attached)
{
Console_SetActiveHandler(ConsoleWriter_Null);
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
}
else
{
Console_SetActiveHandler(ConsoleWriter_WinQt);
SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
}
}
else
{
Console_SetActiveHandler(ConsoleWriter_Null);
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
}
#else
if (system_console_enabled)
{
Console_SetActiveHandler(ConsoleWriter_Stdout);
#endif
}
else
{
Console_SetActiveHandler(ConsoleWriter_Null);
}
#endif
}
#include <wx/module.h>
@@ -491,7 +561,7 @@ extern "C" HINSTANCE wxGetInstance();
extern void wxSetInstance(HINSTANCE hInst);
#endif
void InitializeWxRubbish()
void QtHost::InitializeWxRubbish()
{
wxLog::DoCreateOnDemand();
wxLog::GetActiveTarget();
-4
View File
@@ -20,11 +20,7 @@
#include <QtGui/QDesktopServices>
#include <QtGui/QKeyEvent>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtGui/QAction>
#else
#include <QtWidgets/QAction>
#endif
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
+29 -38
View File
@@ -14,16 +14,12 @@
*/
#pragma once
#include <QtCore/QtCore>
#include <optional>
#include <type_traits>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtCore/QtCore>
#include <QtGui/QAction>
#else
#include <QtWidgets/QAction>
#endif
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
@@ -33,7 +29,7 @@
#include "EmuThread.h"
#include "QtHost.h"
#include "SettingsDialog.h"
#include "Settings/SettingsDialog.h"
namespace SettingWidgetBinder
{
@@ -124,7 +120,7 @@ namespace SettingWidgetBinder
}
static std::optional<int> getNullableIntValue(const QComboBox* widget)
{
return isNullValue(widget) ? std::nullopt : std::optional<int>(widget->currentIndex() + 1);
return isNullValue(widget) ? std::nullopt : std::optional<int>(widget->currentIndex() - 1);
}
static void setNullableIntValue(QComboBox* widget, std::optional<int> value)
{
@@ -152,13 +148,8 @@ namespace SettingWidgetBinder
static QString getStringValue(const QComboBox* widget)
{
const QVariant currentData(widget->currentData());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (currentData.type() == QVariant::String)
return currentData.toString();
#else
if (currentData.metaType().id() == QMetaType::QString)
return currentData.toString();
#endif
return widget->currentText();
}
@@ -408,21 +399,21 @@ namespace SettingWidgetBinder
else
Accessor::setNullableBoolValue(widget, std::nullopt);
Accessor::connectValueChanged(widget, [sif, widget, section, key]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
if (std::optional<bool> new_value = Accessor::getNullableBoolValue(widget); new_value.has_value())
sif->SetBoolValue(section.c_str(), key.c_str(), new_value.value());
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
{
Accessor::setBoolValue(widget, value);
Accessor::connectValueChanged(widget, [widget, section, key]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const bool new_value = Accessor::getBoolValue(widget);
QtHost::SetBaseBoolSettingValue(section.c_str(), key.c_str(), new_value);
g_emu_thread->applySettings();
@@ -448,21 +439,21 @@ namespace SettingWidgetBinder
else
Accessor::setNullableIntValue(widget, std::nullopt);
Accessor::connectValueChanged(widget, [sif, widget, section, key]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), option_offset]() {
if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value())
sif->SetIntValue(section.c_str(), key.c_str(), new_value.value());
sif->SetIntValue(section.c_str(), key.c_str(), new_value.value() + option_offset);
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
{
Accessor::setIntValue(widget, static_cast<int>(value));
Accessor::connectValueChanged(widget, [widget, section, key, option_offset]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), option_offset]() {
const int new_value = Accessor::getIntValue(widget);
QtHost::SetBaseIntSettingValue(section.c_str(), key.c_str(), new_value + option_offset);
g_emu_thread->applySettings();
@@ -487,21 +478,21 @@ namespace SettingWidgetBinder
else
Accessor::setNullableFloatValue(widget, std::nullopt);
Accessor::connectValueChanged(widget, [sif, widget, section, key]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
if (std::optional<float> new_value = Accessor::getNullableFloatValue(widget); new_value.has_value())
sif->SetFloatValue(section.c_str(), key.c_str(), new_value.value());
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
{
Accessor::setFloatValue(widget, value);
Accessor::connectValueChanged(widget, [widget, section, key]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const float new_value = Accessor::getFloatValue(widget);
QtHost::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
g_emu_thread->applySettings();
@@ -527,21 +518,21 @@ namespace SettingWidgetBinder
else
Accessor::setNullableIntValue(widget, std::nullopt);
Accessor::connectValueChanged(widget, [sif, widget, section, key, range]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), range]() {
if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value())
sif->SetFloatValue(section.c_str(), key.c_str(), static_cast<float>(new_value.value()) / range);
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
{
Accessor::setIntValue(widget, static_cast<int>(value * range));
Accessor::connectValueChanged(widget, [widget, section, key, range]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), range]() {
const float new_value = (static_cast<float>(Accessor::getIntValue(widget)) / range);
QtHost::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
g_emu_thread->applySettings();
@@ -567,21 +558,21 @@ namespace SettingWidgetBinder
else
Accessor::setNullableStringValue(widget, std::nullopt);
Accessor::connectValueChanged(widget, [sif, widget, section, key]() {
Accessor::connectValueChanged(widget, [widget, sif, section = std::move(section), key = std::move(key)]() {
if (std::optional<QString> new_value = Accessor::getNullableStringValue(widget); new_value.has_value())
sif->SetStringValue(section.c_str(), key.c_str(), new_value->toUtf8().constData());
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
{
Accessor::setStringValue(widget, value);
Accessor::connectValueChanged(widget, [widget, section, key]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const QString new_value = Accessor::getStringValue(widget);
if (!new_value.isEmpty())
QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.toUtf8().constData());
@@ -621,7 +612,7 @@ namespace SettingWidgetBinder
Accessor::setNullableIntValue(widget, std::nullopt);
}
Accessor::connectValueChanged(widget, [sif, widget, section, key]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), to_string_function]() {
if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value())
{
const char* string_value = to_string_function(static_cast<DataType>(static_cast<UnderlyingType>(new_value.value())));
@@ -633,7 +624,7 @@ namespace SettingWidgetBinder
}
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
@@ -643,7 +634,7 @@ namespace SettingWidgetBinder
else
Accessor::setIntValue(widget, static_cast<int>(static_cast<UnderlyingType>(default_value)));
Accessor::connectValueChanged(widget, [widget, section, key, to_string_function]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), to_string_function]() {
const DataType value = static_cast<DataType>(static_cast<UnderlyingType>(Accessor::getIntValue(widget)));
const char* string_value = to_string_function(value);
QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), string_value);
@@ -691,21 +682,21 @@ namespace SettingWidgetBinder
}
Accessor::setNullableIntValue(widget, sif_int_value);
Accessor::connectValueChanged(widget, [sif, widget, section, key, enum_names]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), enum_names]() {
if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value())
sif->SetStringValue(section.c_str(), key.c_str(), enum_names[new_value.value()]);
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
{
Accessor::setIntValue(widget, static_cast<int>(enum_index));
Accessor::connectValueChanged(widget, [widget, section, key, enum_names]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), enum_names]() {
const UnderlyingType value = static_cast<UnderlyingType>(Accessor::getIntValue(widget));
QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), enum_names[value]);
g_emu_thread->applySettings();
@@ -753,14 +744,14 @@ namespace SettingWidgetBinder
}
Accessor::setNullableIntValue(widget, sif_int_value);
Accessor::connectValueChanged(widget, [sif, widget, section, key, enum_names]() {
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), enum_names]() {
if (std::optional<int> new_value = Accessor::getNullableIntValue(widget); new_value.has_value())
sif->SetStringValue(section.c_str(), key.c_str(), enum_names[new_value.value()]);
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->applySettings();
g_emu_thread->reloadGameSettings();
});
}
else
@@ -768,7 +759,7 @@ namespace SettingWidgetBinder
if (enum_index >= 0)
Accessor::setIntValue(widget, enum_index);
Accessor::connectValueChanged(widget, [widget, section, key, enum_values]() {
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), enum_values]() {
const int value = Accessor::getIntValue(widget);
QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), enum_values[value]);
g_emu_thread->applySettings();
+1 -1
View File
@@ -27,7 +27,7 @@
AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent)
: QWidget(parent)
{
SettingsInterface* sif = dialog->getSettingsInterface();
// SettingsInterface* sif = dialog->getSettingsInterface();
m_ui.setupUi(this);
}
+6
View File
@@ -108,6 +108,12 @@
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerMinimumSectionSize">
<number>250</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>250</number>
</attribute>
<column>
<property name="text">
<string>Filename</string>
+1 -1
View File
@@ -44,7 +44,7 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsDialog* dialog, QWidget* pa
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.VIF1StallHack, "EmuCore/Gamefixes", "VIF1StallHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.VuAddSubHack, "EmuCore/Gamefixes", "VuAddSubHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.IbitHack, "EmuCore/Gamefixes", "IbitHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.VUKickstartHack, "EmuCore/Gamefixes", "VUKickstartHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.VUSyncHack, "EmuCore/Gamefixes", "VUSyncHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.VUOverflowHack, "EmuCore/Gamefixes", "VUOverflowHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.XgKickHack, "EmuCore/Gamefixes", "XgKickHack", false);
}
+2 -2
View File
@@ -128,9 +128,9 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="VUKickstartHack">
<widget class="QCheckBox" name="VUSyncHack">
<property name="text">
<string>VU Kickstart Hack (Run Ahead)</string>
<string>VU Sync (Run Behind, M-Bit games)</string>
</property>
</widget>
</item>
+1 -1
View File
@@ -102,7 +102,7 @@ void GameListSettingsWidget::addPathToTable(const std::string& path, bool recurs
m_ui.searchDirectoryList->setCellWidget(row, 1, cb);
cb->setChecked(recursive);
connect(cb, &QCheckBox::stateChanged, [this, item](int state) {
connect(cb, &QCheckBox::stateChanged, [item](int state) {
const std::string path(item->text().toStdString());
if (state == Qt::Checked)
{
+14 -2
View File
@@ -106,6 +106,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowCPU, "EmuCore/GS", "OsdShowCPU", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowResolution, "EmuCore/GS", "OsdShowResolution", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowGSStats, "EmuCore/GS", "OsdShowGSStats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowIndicators, "EmuCore/GS", "OsdShowIndicators", true);
dialog->registerWidgetHelp(m_ui.osdShowMessages, tr("Show OSD Messages"), tr("Checked"),
tr("Shows on-screen-display messages when events occur such as save states being "
@@ -133,7 +134,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.accurateDATE, "EmuCore/GS", "accurate_date", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.conservativeBufferAllocation, "EmuCore/GS", "conservative_framebuffer", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuPaletteConversion, "EmuCore/GS", "paltex", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preloadTexture, "EmuCore/GS", "preload_texture", false);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.texturePreloading, "EmuCore/GS", "texture_preloading",
static_cast<int>(TexturePreloadingLevel::Off));
//////////////////////////////////////////////////////////////////////////
// HW Renderer Fixes
@@ -161,6 +163,16 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mergeSprite, "EmuCore/GS", "UserHacks_merge_pp_sprite", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.wildHack, "EmuCore/GS", "UserHacks_WildHack", false);
//////////////////////////////////////////////////////////////////////////
// Texture Replacements
//////////////////////////////////////////////////////////////////////////
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.dumpReplaceableTextures, "EmuCore/GS", "DumpReplaceableTextures", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.dumpReplaceableMipmaps, "EmuCore/GS", "DumpReplaceableMipmaps", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.dumpTexturesWithFMVActive, "EmuCore/GS", "DumpTexturesWithFMVActive", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.loadTextureReplacements, "EmuCore/GS", "LoadTextureReplacements", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.loadTextureReplacementsAsync, "EmuCore/GS", "LoadTextureReplacementsAsync", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.precacheTextureReplacements, "EmuCore/GS", "PrecacheTextureReplacements", false);
//////////////////////////////////////////////////////////////////////////
// Advanced Settings
//////////////////////////////////////////////////////////////////////////
@@ -337,7 +349,7 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
{
// software has no hacks tabs
m_ui.verticalLayout->insertWidget(1, m_ui.softwareRendererGroup);
m_ui.softwareRendererGroup->setCurrentIndex((current_tab >= 4) ? (current_tab - 2) : (current_tab >= 2 ? 1 : current_tab));
m_ui.softwareRendererGroup->setCurrentIndex((current_tab >= 5) ? (current_tab - 3) : (current_tab >= 2 ? 1 : current_tab));
}
m_software_renderer_visible = is_software;
+129 -34
View File
@@ -133,7 +133,7 @@
<item row="3" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Interlacing:</string>
<string>Deinterlacing:</string>
</property>
</widget>
</item>
@@ -560,12 +560,12 @@
</item>
</widget>
</item>
<item row="8" column="0" colspan="2">
<item row="9" column="0" colspan="2">
<layout class="QGridLayout" name="basicCheckboxGridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="accurateDATE">
<item row="1" column="0">
<widget class="QCheckBox" name="gpuPaletteConversion">
<property name="text">
<string>Accurate Destination Alpha Test</string>
<string>GPU Palette Conversion</string>
</property>
</widget>
</item>
@@ -576,21 +576,14 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="gpuPaletteConversion">
<item row="0" column="0">
<widget class="QCheckBox" name="accurateDATE">
<property name="text">
<string>GPU Palette Conversion</string>
<string>Accurate Destination Alpha Test</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="preloadTexture">
<property name="text">
<string>Preload Textures</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="enableHWFixes">
<property name="text">
<string>Enable Hardware Renderer Fixes</string>
@@ -599,6 +592,32 @@
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Texture Preloading:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QComboBox" name="texturePreloading">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Partial</string>
</property>
</item>
<item>
<property name="text">
<string>Full (Hash Cache)</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QGroupBox" name="hardwareFixesTab">
@@ -758,7 +777,7 @@
<widget class="QComboBox" name="roundSprite">
<item>
<property name="text">
<string>Off (Default</string>
<string>Off (Default)</string>
</property>
</item>
<item>
@@ -900,6 +919,13 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="osdShowIndicators">
<property name="text">
<string>Show Indicators</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -908,24 +934,93 @@
<attribute name="title">
<string>Advanced</string>
</attribute>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QCheckBox" name="useBlitSwapChain">
<property name="text">
<string>Use Blit Swap Chain</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="useDebugDevice">
<property name="text">
<string>Use Debug Device</string>
</property>
</widget>
</item>
</layout>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Debug Options</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QCheckBox" name="useBlitSwapChain">
<property name="text">
<string>Use Blit Swap Chain</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="useDebugDevice">
<property name="text">
<string>Use Debug Device</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Texture Replacement</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QCheckBox" name="dumpReplaceableTextures">
<property name="text">
<string>Dump Textures</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="dumpReplaceableMipmaps">
<property name="text">
<string>Dump Mipmaps</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="loadTextureReplacementsAsync">
<property name="text">
<string>Async Texture Loading</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="precacheTextureReplacements">
<property name="text">
<string>Precache Textures</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="loadTextureReplacements">
<property name="text">
<string>Load Textures</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="dumpTexturesWithFMVActive">
<property name="text">
<string>Dump FMV Textures</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
-1
View File
@@ -1,4 +1,3 @@
/* PCSX2 - PS2 Emulator for PCs
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
+13 -8
View File
@@ -20,12 +20,17 @@
#include "SettingWidgetBinder.h"
#include "SettingsDialog.h"
static const char* THEME_NAMES[] = {QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Fusion"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Gray)"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Blue)"), nullptr};
static const char* THEME_NAMES[] = {QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native [Light]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Fusion [Light]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Gray) [Dark]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Blue) [Dark]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Untouched Lagoon (Grayish Green/-Blue ) [Light]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Baby Pastel (Pink) [Light]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "PCSX2 (White/Blue) [Light]"),
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Scarlet Devil (Red/Purple) [Dark]"), nullptr};
static const char* THEME_VALUES[] = {"", "fusion", "darkfusion", "darkfusionblue", nullptr};
static const char* THEME_VALUES[] = {"", "fusion", "darkfusion", "darkfusionblue",
"UntouchedLagoon", "BabyPastel", "PCSX2Blue", "ScarletDevilRed", nullptr};
InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget* parent)
: QWidget(parent)
@@ -36,7 +41,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.inhibitScreensaver, "UI", "InhibitScreensaver", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "UI", "DiscordPresence", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmPowerOff, "UI", "ConfirmPowerOff", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.saveStateOnExit, "EmuCore", "AutoStateLoadSave", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnStart, "UI", "StartPaused", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false);
@@ -79,8 +84,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
}
dialog->registerWidgetHelp(
m_ui.confirmPowerOff, tr("Confirm Power Off"), tr("Checked"),
tr("Determines whether a prompt will be displayed to confirm shutting down the emulator/game "
m_ui.confirmShutdown, tr("Confirm Shutdown"), tr("Checked"),
tr("Determines whether a prompt will be displayed to confirm shutting down the virtual machine "
"when the hotkey is pressed."));
dialog->registerWidgetHelp(m_ui.saveStateOnExit, tr("Save State On Exit"), tr("Checked"),
tr("Automatically saves the emulator state when powering down or exiting. You can then "
+2 -2
View File
@@ -61,9 +61,9 @@
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="confirmPowerOff">
<widget class="QCheckBox" name="confirmShutdown">
<property name="text">
<string>Confirm Power Off</string>
<string>Confirm Shutdown</string>
</property>
</widget>
</item>
@@ -27,7 +27,7 @@
MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidget* parent)
: QWidget(parent)
{
SettingsInterface* sif = dialog->getSettingsInterface();
// SettingsInterface* sif = dialog->getSettingsInterface();
m_ui.setupUi(this);
}
+1 -1
View File
@@ -93,7 +93,7 @@ private Q_SLOTS:
void onRestoreDefaultsClicked();
protected:
void closeEvent(QCloseEvent*);
void closeEvent(QCloseEvent*) override;
private:
enum : u32
+1 -1
View File
@@ -37,7 +37,7 @@ SystemSettingsWidget::SystemSettingsWidget(SettingsDialog* dialog, QWidget* pare
m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeCycleSkipping, "EmuCore/Speedhacks", "EECycleSkip", 0);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeCycleSkipping, "EmuCore/Speedhacks", "EECycleSkip", DEFAULT_EE_CYCLE_SKIP);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.MTVU, "EmuCore/Speedhacks", "vuThread", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.instantVU1, "EmuCore/Speedhacks", "vu1Instant", true);
+6 -10
View File
@@ -376,6 +376,7 @@ set(pcsx2DEV9Headers
DEV9/ATA/ATA.h
DEV9/ATA/HddCreate.h
DEV9/DEV9.h
DEV9/DEV9Config.h
DEV9/InternalServers/DHCP_Server.cpp
DEV9/InternalServers/DNS_Logger.h
DEV9/InternalServers/DNS_Server.h
@@ -657,6 +658,8 @@ set(pcsx2GSSources
GS/Renderers/HW/GSRendererHW.cpp
GS/Renderers/HW/GSRendererNew.cpp
GS/Renderers/HW/GSTextureCache.cpp
GS/Renderers/HW/GSTextureReplacementLoaders.cpp
GS/Renderers/HW/GSTextureReplacements.cpp
GS/Renderers/SW/GSDrawScanline.cpp
GS/Renderers/SW/GSDrawScanlineCodeGenerator.cpp
GS/Renderers/SW/GSDrawScanlineCodeGenerator.all.cpp
@@ -721,6 +724,7 @@ set(pcsx2GSHeaders
GS/Renderers/HW/GSRendererHW.h
GS/Renderers/HW/GSRendererNew.h
GS/Renderers/HW/GSTextureCache.h
GS/Renderers/HW/GSTextureReplacements.h
GS/Renderers/HW/GSVertexHW.h
GS/Renderers/SW/GSDrawScanlineCodeGenerator.h
GS/Renderers/SW/GSDrawScanlineCodeGenerator.all.h
@@ -780,9 +784,6 @@ if(PCSX2_CORE)
list(APPEND pcsx2SPU2Headers
SPU2/WavFile.cpp
)
list(APPEND pcsx2DEV9Sources
DEV9/Linux/Config.cpp
)
endif()
elseif(WIN32)
list(APPEND pcsx2SPU2Sources
@@ -803,7 +804,6 @@ elseif(WIN32)
)
list(APPEND pcsx2DEV9Sources
DEV9/Win32/DEV9WinConfig.cpp
DEV9/Win32/pcap_io_win32.cpp
DEV9/Win32/tap-win32.cpp
)
@@ -874,10 +874,6 @@ else()
SPU2/wx/wxConfig.h
)
list(APPEND pcsx2DEV9Sources
DEV9/Linux/Config.cpp
)
list(APPEND pcsx2USBSources
USB/icon_buzz_24.cpp
USB/linux/config-gtk.cpp
@@ -1344,6 +1340,7 @@ set(pcsx2x86Sources
x86/iMMI.cpp
x86/iR3000A.cpp
x86/iR3000Atables.cpp
x86/iR5900Analysis.cpp
x86/iR5900Misc.cpp
x86/ir5900tables.cpp
x86/ix86-32/iCore-32.cpp
@@ -1375,6 +1372,7 @@ set(pcsx2x86Headers
x86/iR5900AritImm.h
x86/iR5900Branch.h
x86/iR5900.h
x86/iR5900Analysis.h
x86/iR5900Jump.h
x86/iR5900LoadStore.h
x86/iR5900Move.h
@@ -1566,7 +1564,6 @@ if(WIN32)
elseif(APPLE)
target_link_libraries(PCSX2_FLAGS INTERFACE
PCAP::PCAP
LibXml2::LibXml2
)
else()
if (NOT PCSX2_CORE)
@@ -1578,7 +1575,6 @@ else()
target_link_libraries(PCSX2_FLAGS INTERFACE
OpenGL::GL
PCAP::PCAP
LibXml2::LibXml2
)
endif()
+94 -4
View File
@@ -42,7 +42,7 @@ enum GamefixId
Fix_VIF1Stall,
Fix_VuAddSub,
Fix_Ibit,
Fix_VUKickstart,
Fix_VUSync,
Fix_VUOverflow,
Fix_XGKick,
@@ -179,6 +179,13 @@ enum class AccBlendLevel : u8
Ultra,
};
enum class TexturePreloadingLevel : u8
{
Off,
Partial,
Full,
};
// Template function for casting enumerations to their underlying type
template <typename Enumeration>
typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
@@ -421,7 +428,8 @@ struct Pcsx2Config
OsdShowFPS : 1,
OsdShowCPU : 1,
OsdShowResolution : 1,
OsdShowGSStats : 1;
OsdShowGSStats : 1,
OsdShowIndicators : 1;
bool
HWDisableReadbacks : 1,
@@ -451,7 +459,12 @@ struct Pcsx2Config
SaveFrame : 1,
SaveTexture : 1,
SaveDepth : 1,
PreloadTexture : 1;
DumpReplaceableTextures : 1,
DumpReplaceableMipmaps : 1,
DumpTexturesWithFMVActive : 1,
LoadTextureReplacements : 1,
LoadTextureReplacementsAsync : 1,
PrecacheTextureReplacements : 1;
};
};
@@ -490,6 +503,7 @@ struct Pcsx2Config
AccBlendLevel AccurateBlendingUnit{AccBlendLevel::Basic};
CRCHackLevel CRCHack{CRCHackLevel::Automatic};
BiFiltering TextureFiltering{BiFiltering::PS2};
TexturePreloadingLevel TexturePreloading{TexturePreloadingLevel::Off};
int Dithering{2};
int MaxAnisotropy{0};
int SWExtraThreads{2};
@@ -619,6 +633,80 @@ struct Pcsx2Config
}
};
struct DEV9Options
{
enum struct NetApi : int
{
Unset = 0,
PCAP_Bridged = 1,
PCAP_Switched = 2,
TAP = 3,
};
static const char* NetApiNames[];
bool EthEnable{false};
NetApi EthApi{NetApi::Unset};
std::string EthDevice;
bool EthLogDNS{false};
bool InterceptDHCP{false};
u8 PS2IP[4]{};
u8 Mask[4]{};
u8 Gateway[4]{};
u8 DNS1[4]{};
u8 DNS2[4]{};
bool AutoMask{true};
bool AutoGateway{true};
bool AutoDNS1{true};
bool AutoDNS2{true};
bool HddEnable{false};
std::string HddFile;
/* The PS2's HDD max size is 2TB
* which is 2^32 * 512 byte sectors
* Note that we don't yet support
* 48bit LBA, so our limit is lower */
uint HddSizeSectors{0};
DEV9Options();
void LoadSave(SettingsWrapper& wrap);
bool operator==(const DEV9Options& right) const
{
return OpEqu(EthEnable) &&
OpEqu(EthApi) &&
OpEqu(EthDevice) &&
OpEqu(EthLogDNS) &&
OpEqu(InterceptDHCP) &&
(*(int*)PS2IP == *(int*)right.PS2IP) &&
(*(int*)Gateway == *(int*)right.Gateway) &&
(*(int*)DNS1 == *(int*)right.DNS1) &&
(*(int*)DNS2 == *(int*)right.DNS2) &&
OpEqu(AutoMask) &&
OpEqu(AutoGateway) &&
OpEqu(AutoDNS1) &&
OpEqu(AutoDNS2) &&
OpEqu(HddEnable) &&
OpEqu(HddFile) &&
OpEqu(HddSizeSectors);
}
bool operator!=(const DEV9Options& right) const
{
return !this->operator==(right);
}
protected:
static void LoadIPHelper(u8* field, const std::string& setting);
static std::string SaveIPHelper(u8* field);
};
// ------------------------------------------------------------------------
// NOTE: The GUI's GameFixes panel is dependent on the order of bits in this structure.
struct GamefixOptions
@@ -638,7 +726,7 @@ struct Pcsx2Config
VIF1StallHack : 1, // Like above, processes FIFO data before the stall is allowed (to make sure data goes over).
VuAddSubHack : 1, // Tri-ace games, they use an encryption algorithm that requires VU ADDI opcode to be bit-accurate.
IbitHack : 1, // I bit hack. Needed to stop constant VU recompilation in some games
VUKickstartHack : 1, // Gives new VU programs a slight head start and runs VU's ahead of EE to avoid VU register reading/writing issues
VUSyncHack : 1, // Makes microVU run behind the EE to avoid VU register reading/writing sync issues. Useful for M-Bit games
VUOverflowHack : 1, // Tries to simulate overflow flag checks (not really possible on x86 without soft floats)
XgKickHack : 1; // Erementar Gerad, adds more delay to VU XGkick instructions. Corrects the color of some graphics, but breaks Tri-ace games and others.
BITFIELD_END
@@ -819,6 +907,7 @@ struct Pcsx2Config
DebugOptions Debugger;
FramerateOptions Framerate;
SPU2Options SPU2;
DEV9Options DEV9;
TraceLogFilters Trace;
@@ -878,6 +967,7 @@ namespace EmuFolders
extern wxDirName Cache;
extern wxDirName Covers;
extern wxDirName GameSettings;
extern wxDirName Textures;
// Assumes that AppRoot and DataRoot have been initialized.
void SetDefaults();
+2 -3
View File
@@ -20,7 +20,6 @@
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <ghc/filesystem.h>
#include <fstream>
#include "DEV9/SimpleQueue.h"
@@ -154,7 +153,7 @@ private:
public:
ATA();
int Open(ghc::filesystem::path hddPath);
int Open(fs::path hddPath);
void Close();
void ATA_HardReset();
@@ -172,7 +171,7 @@ public:
private:
//Info
void CreateHDDinfo(int sizeMb);
void CreateHDDinfo(u64 sizeSectors);
void CreateHDDinfoCsum();
//State
+3 -3
View File
@@ -44,11 +44,11 @@ void ATA::WritePaddedString(u8* data, int* index, std::string value, u32 len)
*index += len;
}
void ATA::CreateHDDinfo(int sizeMb)
void ATA::CreateHDDinfo(u64 sizeSectors)
{
const u16 sectorSize = 512;
DevCon.WriteLn("DEV9: HddSize : %i", config.HddSize);
const u64 nbSectors = ((u64)(sizeMb / sectorSize) * 1024 * 1024);
DevCon.WriteLn("DEV9: HddSize : %i", sizeSectors * sectorSize / (1024 * 1024));
const u64 nbSectors = sizeSectors;
DevCon.WriteLn("DEV9: nbSectors : %i", nbSectors);
memset(&identifyData, 0, sizeof(identifyData));
+6 -6
View File
@@ -26,25 +26,25 @@ ATA::ATA()
ResetEnd(true);
}
int ATA::Open(ghc::filesystem::path hddPath)
int ATA::Open(fs::path hddPath)
{
readBufferLen = 256 * 512;
readBuffer = new u8[readBufferLen];
CreateHDDinfo(config.HddSize);
CreateHDDinfo(EmuConfig.DEV9.HddSizeSectors);
//Open File
if (!ghc::filesystem::exists(hddPath))
if (!fs::exists(hddPath))
{
HddCreate hddCreator;
hddCreator.filePath = hddPath;
hddCreator.neededSize = config.HddSize;
hddCreator.neededSize = ((u64)EmuConfig.DEV9.HddSizeSectors) * 512;
hddCreator.Start();
if (hddCreator.errored)
return -1;
}
hddImage = ghc::filesystem::fstream(hddPath, std::ios::in | std::ios::out | std::ios::binary);
hddImage = fs::fstream(hddPath, std::ios::in | std::ios::out | std::ios::binary);
//Store HddImage size for later check
hddImage.seekg(0, std::ios::end);
@@ -396,7 +396,7 @@ bool ATA::HDD_CanAccess(int* sectors)
s64 posEnd;
s64 maxLBA;
maxLBA = std::min<s64>((s64)config.HddSize * 1024 * 1024 / 512, hddImageSize);
maxLBA = std::min<s64>(EmuConfig.DEV9.HddSizeSectors, hddImageSize / 512) - 1;
if ((regSelect & 0x40) == 0) //CHS mode
maxLBA = std::min<s64>(maxLBA, curCylinders * curHeads * curSectors);
+1 -1
View File
@@ -73,7 +73,7 @@ void ATA::HDD_IdentifyDevice()
DevCon.WriteLn("DEV9: HddidentifyDevice");
//IDE transfer start
CreateHDDinfo(config.HddSize);
CreateHDDinfo(EmuConfig.DEV9.HddSizeSectors);
pioDRQEndTransferFunc = nullptr;
DRQCmdPIODataToHost(identifyData, 256 * 2, 0, 256 * 2, true);
+35 -13
View File
@@ -32,8 +32,13 @@ void HddCreate::Start()
return;
}
#endif
int reqMiB = (neededSize + ((1024 * 1024) - 1)) / (1024 * 1024);
#ifndef PCSX2_CORE
//This creates a modeless dialog
progressDialog = new wxProgressDialog(_("Creating HDD file"), _("Creating HDD file"), neededSize, nullptr, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME);
progressDialog = new wxProgressDialog(_("Creating HDD file"), _("Creating HDD file"), reqMiB, nullptr, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME);
#endif
fileThread = std::thread(&HddCreate::WriteImage, this, filePath, neededSize);
@@ -45,9 +50,9 @@ void HddCreate::Start()
//Instead, loop here to update UI
wxString msg;
int currentSize;
while ((currentSize = written.load()) != neededSize && !errored.load())
while ((currentSize = written.load()) != reqMiB && !errored.load())
{
msg.Printf(_("%i / %i MiB"), written.load(), neededSize);
msg.Printf(_("%i / %i MiB"), written.load(), reqMiB);
#ifndef PCSX2_CORE
if (!progressDialog->Update(currentSize, msg))
@@ -78,18 +83,18 @@ void HddCreate::Start()
completedCV.notify_all();
}
void HddCreate::WriteImage(ghc::filesystem::path hddPath, int reqSizeMiB)
void HddCreate::WriteImage(fs::path hddPath, u64 reqSizeBytes)
{
constexpr int buffsize = 4 * 1024;
u8 buff[buffsize] = {0}; //4kb
if (ghc::filesystem::exists(hddPath))
if (fs::exists(hddPath))
{
SetError();
return;
}
std::fstream newImage = ghc::filesystem::fstream(hddPath, std::ios::out | std::ios::binary);
std::fstream newImage = fs::fstream(hddPath, std::ios::out | std::ios::binary);
if (newImage.fail())
{
@@ -98,14 +103,14 @@ void HddCreate::WriteImage(ghc::filesystem::path hddPath, int reqSizeMiB)
}
//Size file
newImage.seekp(((u64)reqSizeMiB) * 1024 * 1024 - 1, std::ios::beg);
newImage.seekp(reqSizeBytes - 1, std::ios::beg);
const char zero = 0;
newImage.write(&zero, 1);
if (newImage.fail())
{
newImage.close();
ghc::filesystem::remove(filePath);
fs::remove(filePath);
SetError();
return;
}
@@ -114,22 +119,39 @@ void HddCreate::WriteImage(ghc::filesystem::path hddPath, int reqSizeMiB)
newImage.seekp(0, std::ios::beg);
for (int iMiB = 0; iMiB < reqSizeMiB; iMiB++)
//Round up
const s32 reqMiB = (reqSizeBytes + ((1024 * 1024) - 1)) / (1024 * 1024);
for (s32 iMiB = 0; iMiB < reqMiB; iMiB++)
{
for (int i4kb = 0; i4kb < 256; i4kb++)
//Round down
const s32 req4Kib = std::min<s32>(1024, (reqSizeBytes / 1024) - (u64)iMiB * 1024) / 4;
for (s32 i4kb = 0; i4kb < req4Kib; i4kb++)
{
newImage.write((char*)buff, buffsize);
if (newImage.fail())
{
newImage.close();
ghc::filesystem::remove(filePath);
fs::remove(filePath);
SetError();
return;
}
}
if (req4Kib != 256)
{
const s32 remainingBytes = reqSizeBytes - (((u64)iMiB) * (1024 * 1024) + req4Kib * 4096);
newImage.write((char*)buff, remainingBytes);
if (newImage.fail())
{
newImage.close();
fs::remove(filePath);
SetError();
return;
}
}
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - lastUpdate).count() >= 100 || (iMiB + 1) == neededSize)
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - lastUpdate).count() >= 100 || (iMiB + 1) == reqMiB)
{
lastUpdate = now;
SetFileProgress(iMiB + 1);
@@ -137,7 +159,7 @@ void HddCreate::WriteImage(ghc::filesystem::path hddPath, int reqSizeMiB)
if (canceled.load())
{
newImage.close();
ghc::filesystem::remove(filePath);
fs::remove(filePath);
SetError();
return;
}
+3 -4
View File
@@ -24,13 +24,12 @@
#include <atomic>
#include <condition_variable>
#include <chrono>
#include "ghc/filesystem.h"
class HddCreate
{
public:
ghc::filesystem::path filePath;
int neededSize;
fs::path filePath;
u64 neededSize;
std::atomic_bool errored{false};
@@ -56,5 +55,5 @@ public:
private:
void SetFileProgress(int currentSize);
void SetError();
void WriteImage(ghc::filesystem::path hddPath, int reqSizeMB);
void WriteImage(fs::path hddPath, u64 reqSizeBytes);
};
+44 -45
View File
@@ -18,7 +18,6 @@
#include <stdio.h>
#include <vector>
#include <ghc/filesystem.h>
#include <wx/wx.h>
#include <wx/collpane.h>
@@ -27,6 +26,8 @@
#include <wx/spinctrl.h>
#include <wx/gbsizer.h>
#include "common/StringUtil.h"
#include "Config.h"
#include "DEV9.h"
#include "pcap_io.h"
@@ -80,7 +81,7 @@ class DEV9Dialog : public wxDialog
IPControl_SetValue(m_ip, ip);
m_auto->SetValue(is_auto);
}
void save(IP_Address& ip, int& is_auto)
void save(IP_Address& ip, bool& is_auto)
{
ip = IPControl_GetValue(m_ip);
is_auto = m_auto->GetValue();
@@ -89,7 +90,7 @@ class DEV9Dialog : public wxDialog
wxCheckBox* m_eth_enable;
wxChoice* m_eth_adapter_api;
wxChoice* m_eth_adapter;
std::vector<NetApi> m_api_list;
std::vector<Pcsx2Config::DEV9Options::NetApi> m_api_list;
std::vector<std::vector<AdapterEntry>> m_adapter_list;
wxCheckBox* m_intercept_dhcp;
wxTextCtrl* m_ps2_address;
@@ -142,8 +143,8 @@ public:
std::sort(list.begin(), list.end(), [](const AdapterEntry& a, AdapterEntry& b){ return a.name < b.name; });
wxArrayString adapter_api_name_list;
adapter_api_name_list.Add("");
for (const NetApi& type : m_api_list)
adapter_api_name_list.Add(NetApiToWxString(type));
for (const Pcsx2Config::DEV9Options::NetApi& type : m_api_list)
adapter_api_name_list.Add(Pcsx2Config::DEV9Options::NetApiNames[(int)type]);
auto* eth_adapter_api_label = new wxStaticText(this, wxID_ANY, _("Ethernet Device Type:"));
auto* eth_adapter_label = new wxStaticText(this, wxID_ANY, _("Ethernet Device:"));
@@ -217,9 +218,9 @@ public:
Bind(wxEVT_BUTTON, &DEV9Dialog::OnOK, this, wxID_OK);
}
void Load(const ConfigDEV9& config)
void Load(const Pcsx2Config::DEV9Options& config)
{
m_eth_enable->SetValue(config.ethEnable);
m_eth_enable->SetValue(config.EthEnable);
m_eth_adapter_api->SetSelection(0);
for (size_t i = 0; i < m_api_list.size(); i++)
{
@@ -233,7 +234,7 @@ public:
const auto& list = m_adapter_list[static_cast<u32>(config.EthApi)];
for (size_t i = 0; i < list.size(); i++)
{
if (list[i].guid == config.Eth)
if (list[i].guid == config.EthDevice)
{
m_eth_adapter->SetSelection(i + 1);
break;
@@ -241,47 +242,48 @@ public:
}
}
m_intercept_dhcp->SetValue(config.InterceptDHCP);
IPControl_SetValue(m_ps2_address, config.PS2IP);
m_subnet_mask .load(config.Mask, config.AutoMask);
m_gateway_address.load(config.Gateway, config.AutoGateway);
m_dns1_address .load(config.DNS1, config.AutoDNS1);
m_dns2_address .load(config.DNS2, config.AutoDNS2);
IPControl_SetValue(m_ps2_address, *(IP_Address*)config.PS2IP);
m_subnet_mask .load(*(IP_Address*)config.Mask, config.AutoMask);
m_gateway_address.load(*(IP_Address*)config.Gateway, config.AutoGateway);
m_dns1_address .load(*(IP_Address*)config.DNS1, config.AutoDNS1);
m_dns2_address .load(*(IP_Address*)config.DNS2, config.AutoDNS2);
m_hdd_enable->SetValue(config.hddEnable);
m_hdd_file->SetInitialDirectory(config.Hdd);
m_hdd_file->SetPath(config.Hdd);
m_hdd_size_spin->SetValue(config.HddSize / 1024);
m_hdd_size_slider->SetValue(config.HddSize / 1024);
m_hdd_enable->SetValue(config.HddEnable);
wxString wxHddFile = StringUtil::UTF8StringToWxString(config.HddFile);
m_hdd_file->SetInitialDirectory(wxHddFile);
m_hdd_file->SetPath(wxHddFile);
m_hdd_size_spin->SetValue((u64)config.HddSizeSectors * 512 / (1024 * 1024 * 1024));
m_hdd_size_slider->SetValue((u64)config.HddSizeSectors * 512 / (1024 * 1024 * 1024));
UpdateEnable();
}
void Save(ConfigDEV9& config)
void Save(Pcsx2Config::DEV9Options& config)
{
config.ethEnable = m_eth_enable->GetValue();
config.EthEnable = m_eth_enable->GetValue();
int api = m_eth_adapter_api->GetSelection();
int eth = m_eth_adapter->GetSelection();
if (api && eth)
{
const AdapterEntry& adapter = m_adapter_list[static_cast<u32>(m_api_list[api - 1])][eth - 1];
wxStrncpy(config.Eth, adapter.guid, std::size(config.Eth) - 1);
config.EthDevice = adapter.guid;
config.EthApi = adapter.type;
}
else
{
config.Eth[0] = 0;
config.EthApi = NetApi::Unset;
config.EthDevice = "";
config.EthApi = Pcsx2Config::DEV9Options::NetApi::Unset;
}
config.InterceptDHCP = m_intercept_dhcp->GetValue();
config.PS2IP = IPControl_GetValue(m_ps2_address);
m_subnet_mask .save(config.Mask, config.AutoMask);
m_gateway_address.save(config.Gateway, config.AutoGateway);
m_dns1_address .save(config.DNS1, config.AutoDNS1);
m_dns2_address .save(config.DNS2, config.AutoDNS2);
*(IP_Address*)&config.PS2IP = IPControl_GetValue(m_ps2_address);
m_subnet_mask .save(*(IP_Address*)config.Mask, config.AutoMask);
m_gateway_address.save(*(IP_Address*)config.Gateway, config.AutoGateway);
m_dns1_address .save(*(IP_Address*)config.DNS1, config.AutoDNS1);
m_dns2_address .save(*(IP_Address*)config.DNS2, config.AutoDNS2);
config.hddEnable = m_hdd_enable->GetValue();
wxStrncpy(config.Hdd, m_hdd_file->GetPath(), std::size(config.Hdd) - 1);
config.HddSize = m_hdd_size_spin->GetValue() * 1024;
config.HddEnable = m_hdd_enable->GetValue();
config.HddFile = StringUtil::wxStringToUTF8String(m_hdd_file->GetPath());
config.HddSizeSectors = (u64)m_hdd_size_spin->GetValue() * 1024 * 1024 * 1024 / 512;
}
void UpdateEnable()
@@ -316,11 +318,12 @@ public:
if (m_eth_adapter->GetCount())
current = m_eth_adapter->GetString(m_eth_adapter->GetSelection());
if (current.empty())
current = config.Eth;
current = StringUtil::UTF8StringToWxString(g_Conf->EmuOptions.DEV9.EthDevice);
for (size_t i = 0; i < list.size(); i++)
{
options.Add(list[i].name);
if (list[i].name == current)
wxString wxAdapterName = StringUtil::UTF8StringToWxString(list[i].name);
options.Add(wxAdapterName);
if (wxAdapterName == current)
selection = i + 1;
}
}
@@ -373,33 +376,29 @@ void DEV9configure()
ScopedCoreThreadPause paused_core;
DEV9Dialog dialog;
LoadConf();
dialog.Load(config);
dialog.Load(g_Conf->EmuOptions.DEV9);
if (dialog.ShowModal() == wxID_OK)
{
ConfigDEV9 oldConfig = config;
dialog.Save(config);
dialog.Save(g_Conf->EmuOptions.DEV9);
ghc::filesystem::path hddPath(config.Hdd);
fs::path hddPath(g_Conf->EmuOptions.DEV9.HddFile);
if (hddPath.is_relative())
{
//GHC uses UTF8 on all platforms
ghc::filesystem::path path(EmuFolders::Settings.ToString().wx_str());
fs::path path(EmuFolders::Settings.ToString().wx_str());
hddPath = path / hddPath;
}
if (config.hddEnable && !ghc::filesystem::exists(hddPath))
if (g_Conf->EmuOptions.DEV9.HddEnable && !fs::exists(hddPath))
{
HddCreate hddCreator;
hddCreator.filePath = hddPath;
hddCreator.neededSize = config.HddSize;
hddCreator.neededSize = ((u64)g_Conf->EmuOptions.DEV9.HddSizeSectors) * 512;
hddCreator.Start();
}
SaveConf();
ApplyConfigIfRunning(oldConfig);
AppSaveSettings();
}
paused_core.AllowResume();
+45 -81
View File
@@ -25,8 +25,6 @@
#include <err.h>
#endif
#include "ghc/filesystem.h"
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@@ -36,6 +34,7 @@
#include "DEV9.h"
#undef EXTERN
#include "Config.h"
#include "DEV9Config.h"
#include "smap.h"
@@ -91,11 +90,25 @@ int hEeprom;
int mapping;
#endif
std::string s_strIniPath = "inis";
std::string s_strLogPath = "logs";
bool isRunning = false;
fs::path GetHDDPath()
{
//GHC uses UTF8 on all platforms
fs::path hddPath(EmuConfig.DEV9.HddFile);
if (hddPath.empty())
EmuConfig.DEV9.HddEnable = false;
if (hddPath.is_relative())
{
fs::path path(EmuFolders::Settings.ToString().wx_str());
hddPath = path / hddPath;
}
return hddPath;
}
s32 DEV9init()
{
DevCon.WriteLn("DEV9: DEV9init");
@@ -186,34 +199,18 @@ void DEV9shutdown()
s32 DEV9open()
{
DevCon.WriteLn("DEV9: DEV9open");
LoadConf();
#ifdef _WIN32
//Convert to utf8
char mbHdd[sizeof(config.Hdd)] = {0};
WideCharToMultiByte(CP_UTF8, 0, config.Hdd, -1, mbHdd, sizeof(mbHdd) - 1, nullptr, nullptr);
DevCon.WriteLn("DEV9: open r+: %s", mbHdd);
#else
DevCon.WriteLn("DEV9: open r+: %s", config.Hdd);
#endif
LoadDnsHosts();
DevCon.WriteLn("DEV9: open r+: %s", EmuConfig.DEV9.HddFile.c_str());
ghc::filesystem::path hddPath(config.Hdd);
fs::path hddPath = GetHDDPath();
if (hddPath.empty())
config.hddEnable = false;
if (hddPath.is_relative())
{
ghc::filesystem::path path(EmuFolders::Settings.ToString().wx_str());
hddPath = path / hddPath;
}
if (config.hddEnable)
if (EmuConfig.DEV9.HddEnable)
{
if (dev9.ata->Open(hddPath) != 0)
config.hddEnable = false;
EmuConfig.DEV9.HddEnable = false;
}
if (config.ethEnable)
if (EmuConfig.DEV9.EthEnable)
InitNet();
isRunning = true;
@@ -313,7 +310,7 @@ void FIFOIntr()
u8 DEV9read8(u32 addr)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return 0;
u8 hard;
@@ -379,7 +376,7 @@ u8 DEV9read8(u32 addr)
u16 DEV9read16(u32 addr)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return 0;
u16 hard;
@@ -455,9 +452,9 @@ u16 DEV9read16(u32 addr)
case SPD_R_REV_3:
hard = 0;
if (config.hddEnable)
if (EmuConfig.DEV9.HddEnable)
hard |= SPD_CAPS_ATA;
if (config.ethEnable)
if (EmuConfig.DEV9.EthEnable)
hard |= SPD_CAPS_SMAP;
hard |= SPD_CAPS_FLASH;
//DevCon.WriteLn("DEV9: SPD_R_REV_3 16bit read %x", hard);
@@ -519,7 +516,7 @@ u16 DEV9read16(u32 addr)
u32 DEV9read32(u32 addr)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return 0;
u32 hard;
@@ -545,7 +542,7 @@ u32 DEV9read32(u32 addr)
void DEV9write8(u32 addr, u8 value)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return;
if (addr >= ATA_DEV9_HDD_BASE && addr < ATA_DEV9_HDD_END)
@@ -657,7 +654,7 @@ void DEV9write8(u32 addr, u8 value)
void DEV9write16(u32 addr, u16 value)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return;
if (addr >= ATA_DEV9_HDD_BASE && addr < ATA_DEV9_HDD_END)
@@ -967,7 +964,7 @@ void DEV9write16(u32 addr, u16 value)
void DEV9write32(u32 addr, u32 value)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return;
if (addr >= ATA_DEV9_HDD_BASE && addr < ATA_DEV9_HDD_END)
@@ -1003,7 +1000,7 @@ void DEV9write32(u32 addr, u32 value)
void DEV9readDMA8Mem(u32* pMem, int size)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return;
size >>= 1;
@@ -1029,7 +1026,7 @@ void DEV9readDMA8Mem(u32* pMem, int size)
void DEV9writeDMA8Mem(u32* pMem, int size)
{
if (!config.ethEnable & !config.hddEnable)
if (!EmuConfig.DEV9.EthEnable && !EmuConfig.DEV9.HddEnable)
return;
size >>= 1;
@@ -1059,69 +1056,36 @@ void DEV9async(u32 cycles)
dev9.ata->Async(cycles);
}
// extended funcs
void DEV9setSettingsDir(const char* dir)
{
// Grab the ini directory.
// TODO: Use
s_strIniPath = (dir == NULL) ? "inis" : dir;
}
void DEV9setLogDir(const char* dir)
{
// Get the path to the log directory.
s_strLogPath = (dir == NULL) ? "logs" : dir;
}
void ApplyConfigIfRunning(ConfigDEV9 oldConfig)
void DEV9CheckChanges(const Pcsx2Config& old_config)
{
if (!isRunning)
return;
//Eth
ReconfigureLiveNet(&oldConfig);
ReconfigureLiveNet(old_config);
//Hdd
//Hdd Validate Path
ghc::filesystem::path hddPath(config.Hdd);
if (hddPath.empty())
config.hddEnable = false;
if (hddPath.is_relative())
{
//GHC uses UTF8 on all platforms
ghc::filesystem::path path(EmuFolders::Settings.ToString().wx_str());
hddPath = path / hddPath;
}
fs::path hddPath = GetHDDPath();
//Hdd Compare with old config
if (config.hddEnable)
if (EmuConfig.DEV9.HddEnable)
{
if (oldConfig.hddEnable)
if (old_config.DEV9.HddEnable)
{
//ATA::Open/Close dosn't set any regs
//So we can close/open to apply settings
#ifdef _WIN32
if (wcscmp(config.Hdd, oldConfig.Hdd))
#else
if (strcmp(config.Hdd, oldConfig.Hdd))
#endif
if (EmuConfig.DEV9.HddFile != old_config.DEV9.HddFile ||
EmuConfig.DEV9.HddSizeSectors != old_config.DEV9.HddSizeSectors)
{
dev9.ata->Close();
if (dev9.ata->Open(hddPath) != 0)
config.hddEnable = false;
}
if (config.HddSize != oldConfig.HddSize)
{
dev9.ata->Close();
if (dev9.ata->Open(hddPath) != 0)
config.hddEnable = false;
EmuConfig.DEV9.HddEnable = false;
}
}
else if (dev9.ata->Open(hddPath) != 0)
EmuConfig.DEV9.HddEnable = false;
}
else if (oldConfig.hddEnable)
else if (old_config.DEV9.HddEnable)
dev9.ata->Close();
}
+1 -27
View File
@@ -65,29 +65,7 @@ struct ConfigHost
struct ConfigDEV9
{
char Eth[256];
NetApi EthApi;
bool InterceptDHCP;
PacketReader::IP::IP_Address PS2IP;
PacketReader::IP::IP_Address Mask;
PacketReader::IP::IP_Address Gateway;
PacketReader::IP::IP_Address DNS1;
PacketReader::IP::IP_Address DNS2;
int AutoMask;
int AutoGateway;
int AutoDNS1;
int AutoDNS2;
int EthLogDNS;
std::vector<ConfigHost> EthHosts;
#ifdef _WIN32
wchar_t Hdd[256];
#else
char Hdd[256];
#endif
int HddSize;
int hddEnable;
int ethEnable;
};
@@ -156,10 +134,6 @@ EXTERN dev9Struct dev9;
EXTERN int ThreadRun;
//Yes these are meant to be a lowercase extern
extern std::string s_strIniPath;
extern std::string s_strLogPath;
#define DEV9_R_REV 0x1f80146e
@@ -742,7 +716,7 @@ u32 DEV9read32(u32 addr);
void DEV9write8(u32 addr, u8 value);
void DEV9write16(u32 addr, u16 value);
void DEV9write32(u32 addr, u32 value);
void ApplyConfigIfRunning(ConfigDEV9 oldConfig);
void DEV9CheckChanges(const Pcsx2Config& old_config);
#ifdef _WIN32
#pragma warning(error : 4013)
+2 -2
View File
@@ -70,7 +70,7 @@ void LoadDnsHosts()
{
wxFileName iniPath = EmuFolders::Settings.Combine(wxString("DEV9Hosts.ini"));
config.EthHosts.clear();
//If no file exists, create one to provice an example config
//If no file exists, create one to provide an example config
if (!iniPath.FileExists())
{
//Load Default settings
@@ -120,7 +120,7 @@ void LoadDnsHosts()
else
ini.Entry(L"Enabled", entry.Enabled, false);
if (config.EthLogDNS && entry.Enabled)
if (EmuConfig.DEV9.EthLogDNS && entry.Enabled)
Console.WriteLn("DEV9: Host entry %i: url %s mapped to %s", i, entry.Url.c_str(), tmp.ToStdString().c_str());
config.EthHosts.push_back(entry);
@@ -13,8 +13,5 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
void SaveConf();
void LoadConf();
void SaveDnsHosts();
void LoadDnsHosts();
+10 -10
View File
@@ -52,30 +52,30 @@ namespace InternalServers
void DHCP_Server::Init(ifaddrs* adapter)
#endif
{
ps2IP = config.PS2IP;
ps2IP = *(IP_Address*)&EmuConfig.DEV9.PS2IP;
netmask = {0};
gateway = {0};
dns1 = {0};
dns2 = {0};
broadcastIP = {0};
if (config.AutoMask)
if (EmuConfig.DEV9.AutoMask)
AutoNetmask(adapter);
else
netmask = config.Mask;
netmask = *(IP_Address*)EmuConfig.DEV9.Mask;
if (config.AutoGateway)
if (EmuConfig.DEV9.AutoGateway)
AutoGateway(adapter);
else
gateway = config.Gateway;
gateway = *(IP_Address*)EmuConfig.DEV9.Gateway;
if (!config.AutoDNS1)
dns1 = config.DNS1;
if (!EmuConfig.DEV9.AutoDNS1)
dns1 = *(IP_Address*)EmuConfig.DEV9.DNS1;
if (!config.AutoDNS2)
dns2 = config.DNS2;
if (!EmuConfig.DEV9.AutoDNS2)
dns2 = *(IP_Address*)EmuConfig.DEV9.DNS2;
AutoDNS(adapter, config.AutoDNS1, config.AutoDNS2);
AutoDNS(adapter, EmuConfig.DEV9.AutoDNS1, EmuConfig.DEV9.AutoDNS2);
AutoBroadcast(ps2IP, netmask);
}
-240
View File
@@ -1,240 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include <stdlib.h>
#include <arpa/inet.h>
#include "DEV9/DEV9.h"
#include "DEV9/Config.h"
#include "Config.h"
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#if defined(__FreeBSD__)
#include <sys/socket.h>
#endif
void SaveConf()
{
xmlDocPtr doc = NULL; /* document pointer */
xmlNodePtr root_node = NULL;
char buff[256];
/*
* Creates a new document, a node and set it as a root node
*/
doc = xmlNewDoc(BAD_CAST "1.0");
root_node = xmlNewNode(NULL, BAD_CAST "dev9");
xmlDocSetRootElement(doc, root_node);
xmlNewChild(root_node, NULL, BAD_CAST "Eth",
BAD_CAST config.Eth);
sprintf(buff, "%d", (int)config.EthApi);
xmlNewChild(root_node, NULL, BAD_CAST "EthApi",
BAD_CAST buff);
sprintf(buff, "%d", config.InterceptDHCP);
xmlNewChild(root_node, NULL, BAD_CAST "InterceptDHCP",
BAD_CAST buff);
inet_ntop(AF_INET, &config.PS2IP, buff, 256);
xmlNewChild(root_node, NULL, BAD_CAST "PS2IP",
BAD_CAST buff);
inet_ntop(AF_INET, &config.Mask, buff, 256);
xmlNewChild(root_node, NULL, BAD_CAST "Subnet",
BAD_CAST buff);
sprintf(buff, "%d", config.AutoMask);
xmlNewChild(root_node, NULL, BAD_CAST "AutoSubnet",
BAD_CAST buff);
inet_ntop(AF_INET, &config.Gateway, buff, 256);
xmlNewChild(root_node, NULL, BAD_CAST "Gateway",
BAD_CAST buff);
sprintf(buff, "%d", config.AutoGateway);
xmlNewChild(root_node, NULL, BAD_CAST "AutoGateway",
BAD_CAST buff);
inet_ntop(AF_INET, &config.DNS1, buff, 256);
xmlNewChild(root_node, NULL, BAD_CAST "DNS1",
BAD_CAST buff);
sprintf(buff, "%d", config.AutoDNS1);
xmlNewChild(root_node, NULL, BAD_CAST "AutoDNS1",
BAD_CAST buff);
inet_ntop(AF_INET, &config.DNS2, buff, 256);
xmlNewChild(root_node, NULL, BAD_CAST "DNS2",
BAD_CAST buff);
sprintf(buff, "%d", config.AutoDNS2);
xmlNewChild(root_node, NULL, BAD_CAST "AutoDNS2",
BAD_CAST buff);
sprintf(buff, "%d", config.EthLogDNS);
xmlNewChild(root_node, NULL, BAD_CAST "EthLogDNS",
BAD_CAST buff);
xmlNewChild(root_node, NULL, BAD_CAST "Hdd",
BAD_CAST config.Hdd);
sprintf(buff, "%d", config.HddSize);
xmlNewChild(root_node, NULL, BAD_CAST "HddSize",
BAD_CAST buff);
sprintf(buff, "%d", config.ethEnable);
xmlNewChild(root_node, NULL, BAD_CAST "ethEnable",
BAD_CAST buff);
sprintf(buff, "%d", config.hddEnable);
xmlNewChild(root_node, NULL, BAD_CAST "hddEnable",
BAD_CAST buff);
/*
* Dumping document to stdio or file
*/
const std::string file(EmuFolders::Settings.Combine(wxString("DEV9.cfg")).GetFullPath());
Console.WriteLn("DEV9: CONF: %s", file.c_str());
xmlSaveFormatFileEnc(file.c_str(), doc, "UTF-8", 1);
// free(configFile);
/*free the document */
xmlFreeDoc(doc);
/*
*Free the global variables that may
*have been allocated by the parser.
*/
xmlCleanupParser();
SaveDnsHosts();
}
void LoadConf()
{
const std::string file(EmuFolders::Settings.Combine(wxString("DEV9.cfg")).GetFullPath());
if (-1 == access(file.c_str(), F_OK))
return;
memset(&config, 0, sizeof(config));
config.EthApi = NetApi::PCAP_Switched;
// Read the files
xmlDoc* doc = NULL;
xmlNode* cur_node = NULL;
doc = xmlReadFile(file.c_str(), NULL, 0);
if (doc == NULL)
{
Console.Error("Unable to parse configuration file! Suggest deleting it and starting over.");
}
for (cur_node = xmlDocGetRootElement(doc)->children; cur_node; cur_node = cur_node->next)
{
if (cur_node->type == XML_ELEMENT_NODE)
{
// printf("node type: Element, name: %s\n", cur_node->name);
if (0 == strcmp((const char*)cur_node->name, "Eth"))
{
strcpy(config.Eth, (const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "EthApi"))
{
config.EthApi = (NetApi)atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "InterceptDHCP"))
{
config.InterceptDHCP = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "PS2IP"))
{
inet_pton(AF_INET, (const char*)xmlNodeGetContent(cur_node), &config.PS2IP);
}
if (0 == strcmp((const char*)cur_node->name, "Subnet"))
{
inet_pton(AF_INET, (const char*)xmlNodeGetContent(cur_node), &config.Mask);
}
if (0 == strcmp((const char*)cur_node->name, "AutoSubnet"))
{
config.AutoMask = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "Gateway"))
{
inet_pton(AF_INET, (const char*)xmlNodeGetContent(cur_node), &config.Gateway);
}
if (0 == strcmp((const char*)cur_node->name, "AutoGateway"))
{
config.AutoGateway = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "DNS1"))
{
inet_pton(AF_INET, (const char*)xmlNodeGetContent(cur_node), &config.DNS1);
}
if (0 == strcmp((const char*)cur_node->name, "AutoDNS1"))
{
config.AutoDNS1 = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "DNS2"))
{
inet_pton(AF_INET, (const char*)xmlNodeGetContent(cur_node), &config.DNS2);
}
if (0 == strcmp((const char*)cur_node->name, "AutoDNS2"))
{
config.AutoDNS2 = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "EthLogDNS"))
{
config.EthLogDNS = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "Hdd"))
{
strcpy(config.Hdd, (const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "HddSize"))
{
config.HddSize = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "ethEnable"))
{
config.ethEnable = atoi((const char*)xmlNodeGetContent(cur_node));
}
if (0 == strcmp((const char*)cur_node->name, "hddEnable"))
{
config.hddEnable = atoi((const char*)xmlNodeGetContent(cur_node));
}
}
}
// free(configFile);
xmlFreeDoc(doc);
xmlCleanupParser();
LoadDnsHosts();
}
-136
View File
@@ -1,136 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include <stdlib.h>
#include <fstream>
#include "DEV9/DEV9.h"
#include "DEV9/Config.h"
#include "Config.h"
#include "ws2tcpip.h"
BOOL WritePrivateProfileInt(LPCWSTR lpAppName, LPCWSTR lpKeyName, int intvar, LPCWSTR lpFileName)
{
return WritePrivateProfileString(lpAppName, lpKeyName, std::to_wstring(intvar).c_str(), lpFileName);
}
bool FileExists(std::wstring szPath)
{
DWORD dwAttrib = GetFileAttributes(szPath.c_str());
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
void SaveConf()
{
const std::wstring file(EmuFolders::Settings.Combine(wxString("DEV9.cfg")).GetFullPath());
DeleteFile(file.c_str());
//Create file with UT16 BOM to allow PrivateProfile to save unicode data
int bom = 0xFEFF;
std::fstream nfile = std::fstream(file, std::ios::out | std::ios::binary);
nfile.write((char*)&bom, 2);
//Write header to avoid empty line
nfile.write((char*)L"[DEV9]", 14);
nfile.close();
wchar_t addrBuff[INET_ADDRSTRLEN] = {0};
wchar_t wEth[sizeof(config.Eth)] = {0};
mbstowcs(wEth, config.Eth, sizeof(config.Eth) - 1);
WritePrivateProfileString(L"DEV9", L"Eth", wEth, file.c_str());
WritePrivateProfileInt(L"DEV9", L"EthApi", (int)config.EthApi, file.c_str());
WritePrivateProfileInt(L"DEV9", L"InterceptDHCP", config.InterceptDHCP, file.c_str());
InetNtop(AF_INET, &config.PS2IP, addrBuff, INET_ADDRSTRLEN);
WritePrivateProfileString(L"DEV9", L"PS2IP", addrBuff, file.c_str());
InetNtop(AF_INET, &config.Mask, addrBuff, INET_ADDRSTRLEN);
WritePrivateProfileString(L"DEV9", L"Subnet", addrBuff, file.c_str());
WritePrivateProfileInt(L"DEV9", L"AutoSubnet", config.AutoMask, file.c_str());
InetNtop(AF_INET, &config.Gateway, addrBuff, INET_ADDRSTRLEN);
WritePrivateProfileString(L"DEV9", L"Gateway", addrBuff, file.c_str());
WritePrivateProfileInt(L"DEV9", L"AutoGateway", config.AutoGateway, file.c_str());
InetNtop(AF_INET, &config.DNS1, addrBuff, INET_ADDRSTRLEN);
WritePrivateProfileString(L"DEV9", L"DNS1", addrBuff, file.c_str());
WritePrivateProfileInt(L"DEV9", L"AutoDNS1", config.AutoDNS1, file.c_str());
InetNtop(AF_INET, &config.DNS2, addrBuff, INET_ADDRSTRLEN);
WritePrivateProfileString(L"DEV9", L"DNS2", addrBuff, file.c_str());
WritePrivateProfileInt(L"DEV9", L"AutoDNS2", config.AutoDNS2, file.c_str());
WritePrivateProfileInt(L"DEV9", L"EthLogDNS", config.EthLogDNS, file.c_str());
WritePrivateProfileString(L"DEV9", L"Hdd", config.Hdd, file.c_str());
WritePrivateProfileInt(L"DEV9", L"HddSize", config.HddSize, file.c_str());
WritePrivateProfileInt(L"DEV9", L"ethEnable", config.ethEnable, file.c_str());
WritePrivateProfileInt(L"DEV9", L"hddEnable", config.hddEnable, file.c_str());
SaveDnsHosts();
}
void LoadConf()
{
const std::wstring file(EmuFolders::Settings.Combine(wxString("DEV9.cfg")).GetFullPath());
if (FileExists(file.c_str()) == false)
{
LoadDnsHosts();
return;
}
wchar_t addrBuff[INET_ADDRSTRLEN] = {0};
wchar_t wEth[sizeof(config.Eth)] = {0};
mbstowcs(wEth, ETH_DEF, sizeof(config.Eth) - 1);
GetPrivateProfileString(L"DEV9", L"Eth", wEth, wEth, sizeof(config.Eth), file.c_str());
wcstombs(config.Eth, wEth, sizeof(config.Eth) - 1);
config.EthApi = (NetApi)GetPrivateProfileInt(L"DEV9", L"EthApi", (int)NetApi::TAP, file.c_str());
config.InterceptDHCP = GetPrivateProfileInt(L"DEV9", L"InterceptDHCP", config.InterceptDHCP, file.c_str());
GetPrivateProfileString(L"DEV9", L"PS2IP", L"0.0.0.0", addrBuff, INET_ADDRSTRLEN, file.c_str());
InetPton(AF_INET, addrBuff, &config.PS2IP);
GetPrivateProfileString(L"DEV9", L"Subnet", L"0.0.0.0", addrBuff, INET_ADDRSTRLEN, file.c_str());
InetPton(AF_INET, addrBuff, &config.Mask);
config.AutoMask = GetPrivateProfileInt(L"DEV9", L"AutoSubnet", config.AutoMask, file.c_str());
GetPrivateProfileString(L"DEV9", L"Gateway", L"0.0.0.0", addrBuff, INET_ADDRSTRLEN, file.c_str());
InetPton(AF_INET, addrBuff, &config.Gateway);
config.AutoGateway = GetPrivateProfileInt(L"DEV9", L"AutoGateway", config.AutoGateway, file.c_str());
GetPrivateProfileString(L"DEV9", L"DNS1", L"0.0.0.0", addrBuff, INET_ADDRSTRLEN, file.c_str());
InetPton(AF_INET, addrBuff, &config.DNS1);
config.AutoDNS1 = GetPrivateProfileInt(L"DEV9", L"AutoDNS1", config.AutoDNS1, file.c_str());
GetPrivateProfileString(L"DEV9", L"DNS2", L"0.0.0.0", addrBuff, INET_ADDRSTRLEN, file.c_str());
InetPton(AF_INET, addrBuff, &config.DNS2);
config.AutoDNS2 = GetPrivateProfileInt(L"DEV9", L"AutoDNS2", config.AutoDNS2, file.c_str());
config.EthLogDNS = GetPrivateProfileInt(L"DEV9", L"EthLogDNS", config.EthLogDNS, file.c_str());
GetPrivateProfileString(L"DEV9", L"Hdd", HDD_DEF, config.Hdd, sizeof(config.Hdd), file.c_str());
config.HddSize = GetPrivateProfileInt(L"DEV9", L"HddSize", config.HddSize, file.c_str());
config.ethEnable = GetPrivateProfileInt(L"DEV9", L"ethEnable", config.ethEnable, file.c_str());
config.hddEnable = GetPrivateProfileInt(L"DEV9", L"hddEnable", config.hddEnable, file.c_str());
LoadDnsHosts();
}
+18 -21
View File
@@ -16,6 +16,7 @@
#include "PrecompiledHeader.h"
#include "common/RedtapeWindows.h"
#include "common/StringUtil.h"
#include <stdio.h>
#include <windows.h>
@@ -28,7 +29,7 @@
#include <tchar.h>
#include "tap.h"
#include "..\dev9.h"
#include "DEV9/DEV9.h"
#include <string>
#include <wil/com.h>
@@ -186,9 +187,9 @@ std::vector<AdapterEntry> TAPAdapter::GetAdapters()
if (IsTAPDevice(enum_name))
{
AdapterEntry t;
t.type = NetApi::TAP;
t.name = std::wstring(name_data);
t.guid = std::wstring(enum_name);
t.type = Pcsx2Config::DEV9Options::NetApi::TAP;
t.name = StringUtil::WideStringToUTF8String(std::wstring(name_data));
t.guid = StringUtil::WideStringToUTF8String(std::wstring(enum_name));
tap_nic.push_back(t);
}
}
@@ -217,10 +218,8 @@ static int TAPSetStatus(HANDLE handle, int status)
&status, sizeof(status), &len, NULL);
}
//Open the TAP adapter and set the connection to enabled :)
HANDLE TAPOpen(const char* device_guid)
HANDLE TAPOpen(const std::string& device_guid)
{
char device_path[256];
struct
{
unsigned long major;
@@ -229,13 +228,10 @@ HANDLE TAPOpen(const char* device_guid)
} version;
LONG version_len;
sprintf_s(device_path, "%s%s%s",
USERMODEDEVICEDIR,
device_guid,
TAPSUFFIX);
std::string device_path = USERMODEDEVICEDIR + device_guid + TAPSUFFIX;
wil::unique_hfile handle(CreateFileA(
device_path,
device_path.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
@@ -281,7 +277,7 @@ PIP_ADAPTER_ADDRESSES FindAdapterViaIndex(PIP_ADAPTER_ADDRESSES adapterList, int
//IP_ADAPTER_ADDRESSES is a structure that contains ptrs to data in other regions
//of the buffer, se we need to return both so the caller can free the buffer
//after it's finished reading the needed data from IP_ADAPTER_ADDRESSES
bool TAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr<IP_ADAPTER_ADDRESSES[]>* buffer)
bool TAPGetWin32Adapter(const std::string& name, PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr<IP_ADAPTER_ADDRESSES[]>* buffer)
{
int neededSize = 256;
std::unique_ptr<IP_ADAPTER_ADDRESSES[]> AdapterInfo = std::make_unique<IP_ADAPTER_ADDRESSES[]>(neededSize);
@@ -321,7 +317,7 @@ bool TAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::un
do
{
if (0 == strcmp(pAdapterInfo->AdapterName, name))
if (0 == strcmp(pAdapterInfo->AdapterName, name.c_str()))
break;
pAdapterInfo = pAdapterInfo->Next;
@@ -421,7 +417,9 @@ bool TAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::un
//Step 2
//Init COM
const HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (cohr == RPC_E_CHANGED_MODE)
cohr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (!SUCCEEDED(cohr))
return false;
@@ -518,8 +516,7 @@ bool TAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::un
}
}
if (cohr == S_OK)
CoUninitialize();
CoUninitialize();
if (bridgeAdapter != nullptr)
{
@@ -534,9 +531,9 @@ bool TAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::un
TAPAdapter::TAPAdapter()
: NetAdapter()
{
if (config.ethEnable == 0)
if (!EmuConfig.DEV9.EthEnable)
return;
htap = TAPOpen(config.Eth);
htap = TAPOpen(EmuConfig.DEV9.EthDevice);
read.Offset = 0;
read.OffsetHigh = 0;
@@ -562,7 +559,7 @@ TAPAdapter::TAPAdapter()
IP_ADAPTER_ADDRESSES adapter;
std::unique_ptr<IP_ADAPTER_ADDRESSES[]> buffer;
if (TAPGetWin32Adapter(config.Eth, &adapter, &buffer))
if (TAPGetWin32Adapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
InitInternalServer(&adapter);
else
{
@@ -657,7 +654,7 @@ void TAPAdapter::reloadSettings()
{
IP_ADAPTER_ADDRESSES adapter;
std::unique_ptr<IP_ADAPTER_ADDRESSES[]> buffer;
if (TAPGetWin32Adapter(config.Eth, &adapter, &buffer))
if (TAPGetWin32Adapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
ReloadInternalServer(&adapter);
else
ReloadInternalServer(nullptr);
+21 -36
View File
@@ -71,15 +71,15 @@ NetAdapter* GetNetAdapter()
{
NetAdapter* na = nullptr;
switch (config.EthApi)
switch (EmuConfig.DEV9.EthApi)
{
#ifdef _WIN32
case NetApi::TAP:
case Pcsx2Config::DEV9Options::NetApi::TAP:
na = static_cast<NetAdapter*>(new TAPAdapter());
break;
#endif
case NetApi::PCAP_Bridged:
case NetApi::PCAP_Switched:
case Pcsx2Config::DEV9Options::NetApi::PCAP_Bridged:
case Pcsx2Config::DEV9Options::NetApi::PCAP_Switched:
na = static_cast<NetAdapter*>(new PCAPAdapter());
break;
default:
@@ -101,7 +101,7 @@ void InitNet()
if (!na)
{
Console.Error("DEV9: Failed to GetNetAdapter()");
config.ethEnable = false;
EmuConfig.DEV9.EthEnable = false;
return;
}
@@ -123,16 +123,16 @@ void InitNet()
#endif
}
void ReconfigureLiveNet(ConfigDEV9* oldConfig)
void ReconfigureLiveNet(const Pcsx2Config& old_config)
{
//Eth
if (config.ethEnable)
if (EmuConfig.DEV9.EthEnable)
{
if (oldConfig->ethEnable)
if (old_config.DEV9.EthEnable)
{
//Reload Net if adapter changed
if (strcmp(oldConfig->Eth, config.Eth) != 0 ||
oldConfig->EthApi != config.EthApi)
if (EmuConfig.DEV9.EthDevice != old_config.DEV9.EthDevice ||
EmuConfig.DEV9.EthApi != old_config.DEV9.EthApi)
{
TermNet();
InitNet();
@@ -144,7 +144,7 @@ void ReconfigureLiveNet(ConfigDEV9* oldConfig)
else
InitNet();
}
else if (oldConfig->ethEnable)
else if (old_config.DEV9.EthEnable)
TermNet();
}
@@ -163,21 +163,6 @@ void TermNet()
}
}
const wxChar* NetApiToWxString(NetApi api)
{
switch (api)
{
case NetApi::PCAP_Bridged:
return _("PCAP Bridged");
case NetApi::PCAP_Switched:
return _("PCAP Switched");
case NetApi::TAP:
return _("TAP");
default:
return _("UNK");
}
}
using namespace PacketReader;
using namespace PacketReader::IP;
using namespace PacketReader::IP::UDP;
@@ -224,7 +209,7 @@ NetAdapter::~NetAdapter()
void NetAdapter::InspectSend(NetPacket* pkt)
{
if (config.EthLogDNS)
if (EmuConfig.DEV9.EthLogDNS)
{
EthernetFrame frame(pkt);
if (frame.protocol == (u16)EtherType::IPv4)
@@ -240,7 +225,7 @@ void NetAdapter::InspectSend(NetPacket* pkt)
if (udppkt.destinationPort == 53)
{
Console.WriteLn("DEV9: DNS: Packet Sent To %i.%i.%i.%i",
ippkt.destinationIP.bytes[0], ippkt.destinationIP.bytes[1], ippkt.destinationIP.bytes[2], ippkt.destinationIP.bytes[3]);
ippkt.destinationIP.bytes[0], ippkt.destinationIP.bytes[1], ippkt.destinationIP.bytes[2], ippkt.destinationIP.bytes[3]);
dnsLogger.InspectSend(&udppkt);
}
}
@@ -249,7 +234,7 @@ void NetAdapter::InspectSend(NetPacket* pkt)
}
void NetAdapter::InspectRecv(NetPacket* pkt)
{
if (config.EthLogDNS)
if (EmuConfig.DEV9.EthLogDNS)
{
EthernetFrame frame(pkt);
if (frame.protocol == (u16)EtherType::IPv4)
@@ -265,7 +250,7 @@ void NetAdapter::InspectRecv(NetPacket* pkt)
if (udppkt.sourcePort == 53)
{
Console.WriteLn("DEV9: DNS: Packet Sent From %i.%i.%i.%i",
ippkt.sourceIP.bytes[0], ippkt.sourceIP.bytes[1], ippkt.sourceIP.bytes[2], ippkt.sourceIP.bytes[3]);
ippkt.sourceIP.bytes[0], ippkt.sourceIP.bytes[1], ippkt.sourceIP.bytes[2], ippkt.sourceIP.bytes[3]);
dnsLogger.InspectRecv(&udppkt);
}
}
@@ -313,9 +298,9 @@ void NetAdapter::InitInternalServer(ifaddrs* adapter)
if (adapter == nullptr)
Console.Error("DEV9: InitInternalServer() got nullptr for adapter");
if (config.InterceptDHCP)
if (EmuConfig.DEV9.InterceptDHCP)
dhcpServer.Init(adapter);
dnsServer.Init(adapter);
if (blocks())
@@ -334,9 +319,9 @@ void NetAdapter::ReloadInternalServer(ifaddrs* adapter)
if (adapter == nullptr)
Console.Error("DEV9: ReloadInternalServer() got nullptr for adapter");
if (config.InterceptDHCP)
if (EmuConfig.DEV9.InterceptDHCP)
dhcpServer.Init(adapter);
dnsServer.Init(adapter);
}
@@ -371,7 +356,7 @@ bool NetAdapter::InternalServerRecv(NetPacket* pkt)
InspectRecv(pkt);
return true;
}
return false;
}
@@ -391,7 +376,7 @@ bool NetAdapter::InternalServerSend(NetPacket* pkt)
if (udppkt.destinationPort == 67)
{
//Send DHCP
if (config.InterceptDHCP)
if (EmuConfig.DEV9.InterceptDHCP)
return dhcpServer.Send(&udppkt);
}
}
+3 -17
View File
@@ -58,24 +58,12 @@ extern mtfifo<NetPacket*> rx_fifo;
extern mtfifo<NetPacket*> tx_fifo;
*/
enum struct NetApi : int
{
Unset = 0,
PCAP_Bridged = 1,
PCAP_Switched = 2,
TAP = 3,
};
struct AdapterEntry
{
NetApi type;
#ifdef _WIN32
std::wstring name;
std::wstring guid;
#else
Pcsx2Config::DEV9Options::NetApi type;
//UTF8
std::string name;
std::string guid;
#endif
};
class NetAdapter
@@ -137,7 +125,5 @@ private:
void tx_put(NetPacket* ptr);
void InitNet();
void ReconfigureLiveNet(ConfigDEV9* oldConfig);
void ReconfigureLiveNet(const Pcsx2Config& old_config);
void TermNet();
const wxChar* NetApiToWxString(NetApi api);
+32 -34
View File
@@ -22,6 +22,7 @@
#include <iphlpapi.h>
//#include <ws2tcpip.h>
//#include <comdef.h>
#include "common/StringUtil.h"
#elif defined(__linux__)
#include <sys/ioctl.h>
#include <net/if.h>
@@ -59,7 +60,7 @@ mac_address host_mac;
//IP_ADAPTER_ADDRESSES is a structure that contains ptrs to data in other regions
//of the buffer, se we need to return both so the caller can free the buffer
//after it's finished reading the needed data from IP_ADAPTER_ADDRESSES
bool PCAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr<IP_ADAPTER_ADDRESSES[]>* buffer)
bool PCAPGetWin32Adapter(const std::string& name, PIP_ADAPTER_ADDRESSES adapter, std::unique_ptr<IP_ADAPTER_ADDRESSES[]>* buffer)
{
const int guidindex = strlen("\\Device\\NPF_");
@@ -98,7 +99,7 @@ bool PCAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::u
do
{
if (0 == strcmp(pAdapterInfo->AdapterName, &name[guidindex]))
if (0 == strcmp(pAdapterInfo->AdapterName, &name.c_str()[guidindex]))
{
*adapter = *pAdapterInfo;
buffer->swap(AdapterInfo);
@@ -111,7 +112,7 @@ bool PCAPGetWin32Adapter(const char* name, PIP_ADAPTER_ADDRESSES adapter, std::u
}
#elif defined(__POSIX__)
//getifaddrs is not POSIX, but is supported on MAC & Linux
bool PCAPGetIfAdapter(char* name, ifaddrs* adapter, ifaddrs** buffer)
bool PCAPGetIfAdapter(const std::string& name, ifaddrs* adapter, ifaddrs** buffer)
{
//Note, we don't support "any" adapter, but that also fails in pcap_io_init()
ifaddrs* adapterInfo;
@@ -125,7 +126,7 @@ bool PCAPGetIfAdapter(char* name, ifaddrs* adapter, ifaddrs** buffer)
do
{
if (pAdapter->ifa_addr != nullptr && pAdapter->ifa_addr->sa_family == AF_INET && strcmp(pAdapter->ifa_name, name) == 0)
if (pAdapter->ifa_addr != nullptr && pAdapter->ifa_addr->sa_family == AF_INET && strcmp(pAdapter->ifa_name, name.c_str()) == 0)
break;
pAdapter = pAdapter->ifa_next;
@@ -144,7 +145,7 @@ bool PCAPGetIfAdapter(char* name, ifaddrs* adapter, ifaddrs** buffer)
#endif
// Fetches the MAC address and prints it
int GetMACAddress(char* adapter, mac_address* addr)
int GetMACAddress(const std::string& adapter, mac_address* addr)
{
int retval = 0;
#ifdef _WIN32
@@ -160,7 +161,7 @@ int GetMACAddress(char* adapter, mac_address* addr)
#elif defined(__linux__)
struct ifreq ifr;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
strcpy(ifr.ifr_name, adapter);
strcpy(ifr.ifr_name, adapter.c_str());
if (0 == ioctl(fd, SIOCGIFHWADDR, &ifr))
{
retval = 1;
@@ -168,7 +169,7 @@ int GetMACAddress(char* adapter, mac_address* addr)
}
else
{
Console.Error("Could not get MAC address for adapter: %s", adapter);
Console.Error("Could not get MAC address for adapter: %s", adapter.c_str());
}
close(fd);
#else
@@ -177,18 +178,18 @@ int GetMACAddress(char* adapter, mac_address* addr)
return retval;
}
int pcap_io_init(char* adapter, bool switched, mac_address virtual_mac)
int pcap_io_init(const std::string& adapter, bool switched, mac_address virtual_mac)
{
struct bpf_program fp;
char filter[1024] = "ether broadcast or ether dst ";
int dlt;
char* dlt_name;
Console.WriteLn("DEV9: Opening adapter '%s'...", adapter);
Console.WriteLn("DEV9: Opening adapter '%s'...", adapter.c_str());
pcap_io_switched = switched;
/* Open the adapter */
if ((adhandle = pcap_open_live(adapter, // name of the device
if ((adhandle = pcap_open_live(adapter.c_str(), // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
switched ? 1 : 0,
@@ -197,7 +198,7 @@ int pcap_io_init(char* adapter, bool switched, mac_address virtual_mac)
)) == NULL)
{
Console.Error("DEV9: %s", errbuf);
Console.Error("DEV9: Unable to open the adapter. %s is not supported by pcap", adapter);
Console.Error("DEV9: Unable to open the adapter. %s is not supported by pcap", adapter.c_str());
return -1;
}
if (switched)
@@ -357,9 +358,8 @@ void pcap_io_close()
PCAPAdapter::PCAPAdapter()
: NetAdapter()
{
if (config.ethEnable == 0)
if (!EmuConfig.DEV9.EthEnable)
return;
#ifdef _WIN32
if (!load_pcap())
return;
@@ -368,7 +368,7 @@ PCAPAdapter::PCAPAdapter()
mac_address hostMAC;
mac_address newMAC;
GetMACAddress(config.Eth, &hostMAC);
GetMACAddress(EmuConfig.DEV9.EthDevice, &hostMAC);
memcpy(&newMAC, ps2MAC, 6);
//Lets take the hosts last 2 bytes to make it unique on Xlink
@@ -379,16 +379,16 @@ PCAPAdapter::PCAPAdapter()
host_mac = hostMAC;
ps2_mac = newMAC; //Needed outside of this class
if (pcap_io_init(config.Eth, config.EthApi == NetApi::PCAP_Switched, newMAC) == -1)
if (pcap_io_init(EmuConfig.DEV9.EthDevice, EmuConfig.DEV9.EthApi == Pcsx2Config::DEV9Options::NetApi::PCAP_Switched, newMAC) == -1)
{
Console.Error("DEV9: Can't open Device '%s'", config.Eth);
Console.Error("DEV9: Can't open Device '%s'", EmuConfig.DEV9.EthDevice.c_str());
return;
}
#ifdef _WIN32
IP_ADAPTER_ADDRESSES adapter;
std::unique_ptr<IP_ADAPTER_ADDRESSES[]> buffer;
if (PCAPGetWin32Adapter(config.Eth, &adapter, &buffer))
if (PCAPGetWin32Adapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
InitInternalServer(&adapter);
else
{
@@ -398,7 +398,7 @@ PCAPAdapter::PCAPAdapter()
#elif defined(__POSIX__)
ifaddrs adapter;
ifaddrs* buffer;
if (PCAPGetIfAdapter(config.Eth, &adapter, &buffer))
if (PCAPGetIfAdapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
{
InitInternalServer(&adapter);
freeifaddrs(buffer);
@@ -456,14 +456,14 @@ void PCAPAdapter::reloadSettings()
#ifdef _WIN32
IP_ADAPTER_ADDRESSES adapter;
std::unique_ptr<IP_ADAPTER_ADDRESSES[]> buffer;
if (PCAPGetWin32Adapter(config.Eth, &adapter, &buffer))
if (PCAPGetWin32Adapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
ReloadInternalServer(&adapter);
else
ReloadInternalServer(nullptr);
#elif defined(__POSIX__)
ifaddrs adapter;
ifaddrs* buffer;
if (PCAPGetIfAdapter(config.Eth, &adapter, &buffer))
if (PCAPGetIfAdapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
{
ReloadInternalServer(&adapter);
freeifaddrs(buffer);
@@ -499,32 +499,30 @@ std::vector<AdapterEntry> PCAPAdapter::GetAdapters()
while (d != NULL)
{
AdapterEntry entry;
entry.type = NetApi::PCAP_Switched;
entry.type = Pcsx2Config::DEV9Options::NetApi::PCAP_Switched;
#ifdef _WIN32
//guid
wchar_t wEth[sizeof(config.Eth)] = {0};
mbstowcs(wEth, d->name, sizeof(config.Eth) - 1);
entry.guid = std::wstring(wEth);
entry.guid = std::string(d->name);
IP_ADAPTER_ADDRESSES adapterInfo;
std::unique_ptr<IP_ADAPTER_ADDRESSES[]> buffer;
if (PCAPGetWin32Adapter(d->name, &adapterInfo, &buffer))
entry.name = std::wstring(adapterInfo.FriendlyName);
if (PCAPGetWin32Adapter(entry.guid, &adapterInfo, &buffer))
entry.name = StringUtil::WideStringToUTF8String(std::wstring(adapterInfo.FriendlyName));
else
{
//have to use description
//NPCAP 1.10 is using an version of pcap that dosn't
//allow us to set it to use UTF8
//see https://github.com/nmap/npcap/issues/276
//But use MultiByteToWideChar so we can later switch to UTF8
int len_desc = strlen(d->description) + 1;
int len_buf = MultiByteToWideChar(CP_ACP, 0, d->description, len_desc, nullptr, 0);
//We have to convert from ANSI to wstring, to then convert to UTF8
const int len_desc = strlen(d->description) + 1;
const int len_buf = MultiByteToWideChar(CP_ACP, 0, d->description, len_desc, nullptr, 0);
wchar_t* buf = new wchar_t[len_buf];
MultiByteToWideChar(CP_ACP, 0, d->description, len_desc, buf, len_buf);
entry.name = std::wstring(buf);
delete[] buf;
std::unique_ptr<wchar_t[]> buf = std::make_unique<wchar_t[]>(len_buf);
MultiByteToWideChar(CP_ACP, 0, d->description, len_desc, buf.get(), len_buf);
entry.name = StringUtil::WideStringToUTF8String(std::wstring(buf.get()));
}
#else
entry.name = std::string(d->name);
@@ -532,7 +530,7 @@ std::vector<AdapterEntry> PCAPAdapter::GetAdapters()
#endif
nic.push_back(entry);
entry.type = NetApi::PCAP_Bridged;
entry.type = Pcsx2Config::DEV9Options::NetApi::PCAP_Bridged;
nic.push_back(entry);
d = d->next;
}
+3 -3
View File
@@ -38,7 +38,7 @@ SERIAL-12345: # !required! Serial number for the game, this is how games are loo
- GIFFIFOHack
- GoemonTlbHack
- IbitHack
- VUKickstartHack
- VUSyncHack
- VUOverflowHack
# The value of the speedhacks is assumed to be an integer,
# but at the time of writing speedhacks are effectively booleans (0/1)
@@ -169,8 +169,8 @@ These values are case-sensitive so take care. If you incorrectly specify a Game
* `IbitHack`
* VU I bit Hack avoid constant recompilation in some games (Scarface The World Is Yours, Crash Tag Team Racing).
* `VUKickstartHack`
* Let the VU's both run ahead of the EE to fix some timing issues.
* `VUSyncHack`
* Make the VU's run behind/in sync with the EE to fix some timing issues.
* `VUOverflowHack`
* VU Overflow hack to check for possible float overflows (Superman Returns).
+11 -5
View File
@@ -601,16 +601,22 @@ static void DrawPerformanceOverlay()
}
}
const bool is_normal_speed = (EmuConfig.GS.LimitScalar == EmuConfig.Framerate.NominalScalar);
if (!is_normal_speed)
if (GSConfig.OsdShowIndicators)
{
const bool is_slowmo = (EmuConfig.GS.LimitScalar < EmuConfig.Framerate.NominalScalar);
DRAW_LINE(s_standard_font, is_slowmo ? ICON_FA_FORWARD : ICON_FA_FAST_FORWARD, IM_COL32(255, 255, 255, 255));
const bool is_normal_speed = (EmuConfig.GS.LimitScalar == EmuConfig.Framerate.NominalScalar);
if (!is_normal_speed)
{
const bool is_slowmo = (EmuConfig.GS.LimitScalar < EmuConfig.Framerate.NominalScalar);
DRAW_LINE(s_standard_font, is_slowmo ? ICON_FA_FORWARD : ICON_FA_FAST_FORWARD, IM_COL32(255, 255, 255, 255));
}
}
}
else
{
DRAW_LINE(s_standard_font, ICON_FA_PAUSE, IM_COL32(255, 255, 255, 255));
if (GSConfig.OsdShowIndicators)
{
DRAW_LINE(s_standard_font, ICON_FA_PAUSE, IM_COL32(255, 255, 255, 255));
}
}
#undef DRAW_LINE
+8 -5
View File
@@ -138,13 +138,15 @@ void VulkanHostDisplay::DestroyRenderSurface()
std::string VulkanHostDisplay::GetDriverInfo() const
{
std::string ret;
const u32 version = g_vulkan_context->GetDeviceProperties().apiVersion;
const u32 api_version = g_vulkan_context->GetDeviceProperties().apiVersion;
const u32 driver_version = g_vulkan_context->GetDeviceProperties().driverVersion;
if (g_vulkan_context->GetOptionalExtensions().vk_khr_driver_properties)
{
const VkPhysicalDeviceDriverProperties& props = g_vulkan_context->GetDeviceDriverProperties();
ret = StringUtil::StdStringFromFormat(
"Vulkan %u.%u.%u\nConformance Version %u.%u.%u.%u\n%s\n%s\n%s",
VK_API_VERSION_MAJOR(version), VK_API_VERSION_MINOR(version), VK_API_VERSION_PATCH(version),
"Driver %u.%u.%u\nVulkan %u.%u.%u\nConformance Version %u.%u.%u.%u\n%s\n%s\n%s",
VK_VERSION_MAJOR(driver_version), VK_VERSION_MINOR(driver_version), VK_VERSION_PATCH(driver_version),
VK_API_VERSION_MAJOR(api_version), VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version),
props.conformanceVersion.major, props.conformanceVersion.minor, props.conformanceVersion.subminor, props.conformanceVersion.patch,
props.driverInfo, props.driverName,
g_vulkan_context->GetDeviceProperties().deviceName);
@@ -152,8 +154,9 @@ std::string VulkanHostDisplay::GetDriverInfo() const
else
{
ret = StringUtil::StdStringFromFormat(
"Vulkan %u.%u.%u\n%s",
VK_API_VERSION_MAJOR(version), VK_API_VERSION_MINOR(version), VK_API_VERSION_PATCH(version),
"Driver %u.%u.%u\nVulkan %u.%u.%u\n%s",
VK_VERSION_MAJOR(driver_version), VK_VERSION_MINOR(driver_version), VK_VERSION_PATCH(driver_version),
VK_API_VERSION_MAJOR(api_version), VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version),
g_vulkan_context->GetDeviceProperties().deviceName);
}
+48 -12
View File
@@ -27,6 +27,7 @@
#include "Renderers/Null/GSDeviceNull.h"
#include "Renderers/OpenGL/GSDeviceOGL.h"
#include "Renderers/HW/GSRendererNew.h"
#include "Renderers/HW/GSTextureReplacements.h"
#include "GSLzma.h"
#include "common/pxStreams.h"
@@ -679,14 +680,29 @@ void GSgetStats(std::string& info)
}
else
{
info = format("%s HW | %d P | %d D | %d DC | %d RB | %d TC | %d TU",
api_name,
(int)pm.Get(GSPerfMon::Prim),
(int)pm.Get(GSPerfMon::Draw),
(int)std::ceil(pm.Get(GSPerfMon::DrawCalls)),
(int)std::ceil(pm.Get(GSPerfMon::Readbacks)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
if (GSConfig.TexturePreloading == TexturePreloadingLevel::Full)
{
info = format("%s HW | HC: %d MB | %d P | %d D | %d DC | %d RB | %d TC | %d TU",
api_name,
(int)std::ceil(static_cast<GSRendererHW*>(s_gs.get())->GetTextureCache()->GetHashCacheMemoryUsage() / 1048576.0f),
(int)pm.Get(GSPerfMon::Prim),
(int)pm.Get(GSPerfMon::Draw),
(int)std::ceil(pm.Get(GSPerfMon::DrawCalls)),
(int)std::ceil(pm.Get(GSPerfMon::Readbacks)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
}
else
{
info = format("%s HW | %d P | %d D | %d DC | %d RB | %d TC | %d TU",
api_name,
(int)pm.Get(GSPerfMon::Prim),
(int)pm.Get(GSPerfMon::Draw),
(int)std::ceil(pm.Get(GSPerfMon::DrawCalls)),
(int)std::ceil(pm.Get(GSPerfMon::Readbacks)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
}
}
}
@@ -798,6 +814,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
// reload texture cache when trilinear filtering or mipmap options change
if (GSConfig.HWMipmap != old_config.HWMipmap ||
GSConfig.TexturePreloading != old_config.TexturePreloading ||
GSConfig.UserHacks_TriFilter != old_config.UserHacks_TriFilter ||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion)
{
@@ -808,6 +825,17 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
// clear out the sampler cache when AF options change, since the anisotropy gets baked into them
if (GSConfig.MaxAnisotropy != old_config.MaxAnisotropy)
g_gs_device->ClearSamplerCache();
// texture dumping/replacement options
GSTextureReplacements::UpdateConfig(old_config);
// clear the hash texture cache since we might have replacements now
// also clear it when dumping changes, since we want to dump everything being used
if (GSConfig.LoadTextureReplacements != old_config.LoadTextureReplacements ||
GSConfig.DumpReplaceableTextures != old_config.DumpReplaceableTextures)
{
s_gs->PurgeTextureCache();
}
}
void GSSwitchRenderer(GSRendererType new_renderer)
@@ -1208,6 +1236,10 @@ void GSApp::Init()
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::PS2), "Trilinear", ""));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::Forced), "Trilinear", "Ultra/Slow"));
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Off), "None", "Default"));
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Partial), "Partial", ""));
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Full), "Full", "Hash Cache"));
m_gs_generic_list.push_back(GSSetting(-1, "Automatic", "Default"));
m_gs_generic_list.push_back(GSSetting(0, "Force-Disabled", ""));
m_gs_generic_list.push_back(GSSetting(1, "Force-Enabled", ""));
@@ -1284,6 +1316,9 @@ void GSApp::Init()
m_default_configuration["disable_shader_cache"] = "0";
m_default_configuration["dithering_ps2"] = "2";
m_default_configuration["dump"] = "0";
m_default_configuration["DumpReplaceableTextures"] = "0";
m_default_configuration["DumpReplaceableMipmaps"] = "0";
m_default_configuration["DumpTexturesWithFMVActive"] = "0";
m_default_configuration["extrathreads"] = "2";
m_default_configuration["extrathreads_height"] = "4";
m_default_configuration["filter"] = std::to_string(static_cast<s8>(BiFiltering::PS2));
@@ -1294,6 +1329,8 @@ void GSApp::Init()
m_default_configuration["interlace"] = "7";
m_default_configuration["conservative_framebuffer"] = "1";
m_default_configuration["linear_present"] = "1";
m_default_configuration["LoadTextureReplacements"] = "0";
m_default_configuration["LoadTextureReplacementsAsync"] = "1";
m_default_configuration["MaxAnisotropy"] = "0";
m_default_configuration["mipmap"] = "1";
m_default_configuration["mipmap_hw"] = std::to_string(static_cast<int>(HWMipmapLevel::Automatic));
@@ -1306,6 +1343,7 @@ void GSApp::Init()
m_default_configuration["OsdShowCPU"] = "0";
m_default_configuration["OsdShowResolution"] = "0";
m_default_configuration["OsdShowGSStats"] = "0";
m_default_configuration["OsdShowIndicators"] = "1";
m_default_configuration["OsdScale"] = "100";
m_default_configuration["override_geometry_shader"] = "-1";
m_default_configuration["override_GL_ARB_copy_image"] = "-1";
@@ -1318,13 +1356,10 @@ void GSApp::Init()
m_default_configuration["override_GL_ARB_sparse_texture"] = "-1";
m_default_configuration["override_GL_ARB_sparse_texture2"] = "-1";
m_default_configuration["override_GL_ARB_texture_barrier"] = "-1";
#ifdef GL_EXT_TEX_SUB_IMAGE
m_default_configuration["override_GL_ARB_get_texture_sub_image"] = "-1";
#endif
m_default_configuration["paltex"] = "0";
m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED);
m_default_configuration["PrecacheTextureReplacements"] = "0";
m_default_configuration["preload_frame_with_gs_data"] = "0";
m_default_configuration["preload_texture"] = "0";
m_default_configuration["Renderer"] = std::to_string(static_cast<int>(GSRendererType::Auto));
m_default_configuration["resx"] = "1024";
m_default_configuration["resy"] = "1024";
@@ -1342,6 +1377,7 @@ void GSApp::Init()
m_default_configuration["shaderfx_conf"] = "shaders/GS_FX_Settings.ini";
m_default_configuration["shaderfx_glsl"] = "shaders/GS.fx";
m_default_configuration["skip_duplicate_frames"] = "0";
m_default_configuration["texture_preloading"] = "0";
m_default_configuration["ThreadedPresentation"] = "0";
m_default_configuration["throttle_present_rate"] = "0";
m_default_configuration["TVShader"] = "0";
+1
View File
@@ -133,6 +133,7 @@ public:
std::vector<GSSetting> m_gs_dithering;
std::vector<GSSetting> m_gs_bifilter;
std::vector<GSSetting> m_gs_trifilter;
std::vector<GSSetting> m_gs_texture_preloading;
std::vector<GSSetting> m_gs_hack;
std::vector<GSSetting> m_gs_generic_list;
std::vector<GSSetting> m_gs_offset_hack;
+11
View File
@@ -110,14 +110,25 @@ static const GSVector2i default_rt_size(2048, 2048);
static const GSVector2i default_rt_size(0, 0);
#endif
extern Pcsx2Config::GSOptions GSConfig;
// Maximum texture size to skip preload/hash path.
// This is the width/height from the registers, i.e. not the power of 2.
__fi static bool CanCacheTextureSize(u32 tw, u32 th)
{
static constexpr u32 MAXIMUM_CACHE_SIZE = 10; // 1024
return (GSConfig.TexturePreloading == TexturePreloadingLevel::Full && tw <= MAXIMUM_CACHE_SIZE && th <= MAXIMUM_CACHE_SIZE);
}
__fi static bool CanPreloadTextureSize(u32 tw, u32 th)
{
static constexpr u32 MAXIMUM_SIZE_IN_ONE_DIRECTION = 10; // 1024
static constexpr u32 MAXIMUM_SIZE_IN_OTHER_DIRECTION = 8; // 256
static constexpr u32 MAXIMUM_SIZE_IN_BOTH_DIRECTIONS = 9; // 512
if (GSConfig.TexturePreloading < TexturePreloadingLevel::Partial)
return false;
// We use an area-based approach here. We want to hash long font maps,
// like 128x1024 (used in FFX), but skip 1024x512 textures (e.g. Xenosaga).
const u32 max_dimension = (tw > th) ? tw : th;
+174 -61
View File
@@ -209,7 +209,6 @@ void GSState::Reset()
m_vertex.tail = 0;
m_vertex.next = 0;
m_index.tail = 0;
m_scanmask_used = false;
}
@@ -895,10 +894,7 @@ void GSState::GIFRegHandlerTEX0(const GIFReg* RESTRICT r)
GL_REG("TEX0_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
GIFRegTEX0 TEX0 = r->TEX0;
bool MTBA_reload = false;
if ((m_env.CTXT[i].TEX0.TBP0 != TEX0.TBP0 || tex_flushed) && m_env.CTXT[i].TEX1.MTBA)
MTBA_reload = true;
const bool MTBAreload = ((tex_flushed && (i == m_env.PRIM.CTXT)) || (r->TEX0.TBP0 != m_env.CTXT[i].TEX0.TBP0)) ? true : false;
// Spec max is 10
//
@@ -916,20 +912,22 @@ void GSState::GIFRegHandlerTEX0(const GIFReg* RESTRICT r)
ApplyTEX0<i>(TEX0);
// When the texture cache reloads any MIPS need to update too, so let's do that here.
// This is essentially triggered by the texture page change, so if TEXFLUSH is called and a draw doesn't proceed it, or TBP changes.
// Textures must be of equal width/height and a minimum of 32x32
if (MTBA_reload && TEX0.TW == TEX0.TH && TEX0.TW >= 5)
if (MTBAreload && m_env.CTXT[i].TEX1.MTBA && m_env.CTXT[i].TEX0.TW == m_env.CTXT[i].TEX0.TH && m_env.CTXT[i].TEX0.TW >= 5)
{
// NOTE 1: TEX1.MXL must not be automatically set to 3 here.
// NOTE 2: Mipmap levels are tightly packed, if (tbw << 6) > (1 << tw) then the left-over space to the right is used. (common for PSM_PSMT4)
// NOTE 3: Non-rectangular textures are treated as rectangular when calculating the occupied space (height is extended, not sure about width)
u32 bp = TEX0.TBP0;
u32 bw = TEX0.TBW;
u32 w = 1u << TEX0.TW;
u32 h = 1u << TEX0.TH;
u32 minwidth = m_context->TEX1.MMIN >= 4 ? 8 : 1;
u32 bp = m_env.CTXT[i].TEX0.TBP0;
u32 bw = m_env.CTXT[i]. TEX0.TBW;
u32 w = 1u << m_env.CTXT[i].TEX0.TW;
u32 h = 1u << m_env.CTXT[i].TEX0.TH;
u32 minwidth = m_env.CTXT[i].TEX1.MMIN >= 4 ? 8 : 1;
const u32 bpp = GSLocalMemory::m_psm[TEX0.PSM].bpp;
const u32 bpp = GSLocalMemory::m_psm[m_env.CTXT[i].TEX0.PSM].bpp;
bp += (int)((w * h * ((float)bpp / 8))) >> 8;
@@ -944,7 +942,7 @@ void GSState::GIFRegHandlerTEX0(const GIFReg* RESTRICT r)
m_env.CTXT[i].MIPTBP1.TBW1 = bw;
bp += (int)((w * h * ((float)bpp / 8))) >> 8;
if (w > minwidth)
{
bw = std::max<u32>(bw >> 1, 1);
@@ -961,7 +959,7 @@ void GSState::GIFRegHandlerTEX0(const GIFReg* RESTRICT r)
{
bw = std::max<u32>(bw >> 1, 1);
}
m_env.CTXT[i].MIPTBP1.TBP3 = bp;
m_env.CTXT[i].MIPTBP1.TBW3 = bw;
}
@@ -1132,8 +1130,14 @@ void GSState::GIFRegHandlerFOGCOL(const GIFReg* RESTRICT r)
void GSState::GIFRegHandlerTEXFLUSH(const GIFReg* RESTRICT r)
{
GL_REG("TEXFLUSH = 0x%x_%x", r->U32[1], r->U32[0]);
GL_REG("TEXFLUSH = 0x%x_%x PRIM TME %x", r->U32[1], r->U32[0], PRIM->TME);
tex_flushed = true;
// Some games do a single sprite draw to itself, then flush the texture cache, then use that texture again.
// This won't get picked up by the new autoflush logic (which checks for page crossings for the PS2 Texture Cache flush)
// so we need to do it here.
if(m_userhacks_auto_flush)
Flush();
}
template <int i>
@@ -1230,18 +1234,28 @@ void GSState::GIFRegHandlerFRAME(const GIFReg* RESTRICT r)
{
GL_REG("FRAME_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
if (PRIM->CTXT == i && r->FRAME != m_env.CTXT[i].FRAME)
GIFRegFRAME NewFrame = r->FRAME;
// FBW is clamped between 1 and 32, however this is wrong, FBW of 0 *should* work and does on Dobiestation
// However there is some issues so even software mode is incorrect on PCSX2, but this works better..
NewFrame.FBW = std::clamp(NewFrame.FBW, 1U, 32U);
if (PRIM->CTXT == i && NewFrame != m_env.CTXT[i].FRAME)
Flush();
if ((m_env.CTXT[i].FRAME.U32[0] ^ r->FRAME.U32[0]) & 0x3f3f01ff) // FBP FBW PSM
if ((NewFrame.PSM & 0x30) == 0x30)
m_env.CTXT[i].ZBUF.PSM &= ~0x30;
else
m_env.CTXT[i].ZBUF.PSM |= 0x30;
if ((m_env.CTXT[i].FRAME.U32[0] ^ NewFrame.U32[0]) & 0x3f3f01ff) // FBP FBW PSM
{
m_env.CTXT[i].offset.fb = m_mem.GetOffset(r->FRAME.Block(), r->FRAME.FBW, r->FRAME.PSM);
m_env.CTXT[i].offset.zb = m_mem.GetOffset(m_env.CTXT[i].ZBUF.Block(), r->FRAME.FBW, m_env.CTXT[i].ZBUF.PSM);
m_env.CTXT[i].offset.fzb = m_mem.GetPixelOffset(r->FRAME, m_env.CTXT[i].ZBUF);
m_env.CTXT[i].offset.fzb4 = m_mem.GetPixelOffset4(r->FRAME, m_env.CTXT[i].ZBUF);
m_env.CTXT[i].offset.fb = m_mem.GetOffset(NewFrame.Block(), NewFrame.FBW, NewFrame.PSM);
m_env.CTXT[i].offset.zb = m_mem.GetOffset(m_env.CTXT[i].ZBUF.Block(), NewFrame.FBW, m_env.CTXT[i].ZBUF.PSM);
m_env.CTXT[i].offset.fzb = m_mem.GetPixelOffset(NewFrame, m_env.CTXT[i].ZBUF);
m_env.CTXT[i].offset.fzb4 = m_mem.GetPixelOffset4(NewFrame, m_env.CTXT[i].ZBUF);
}
m_env.CTXT[i].FRAME = (GSVector4i)r->FRAME;
m_env.CTXT[i].FRAME = (GSVector4i)NewFrame;
switch (m_env.CTXT[i].FRAME.PSM)
{
@@ -1273,11 +1287,16 @@ void GSState::GIFRegHandlerZBUF(const GIFReg* RESTRICT r)
GIFRegZBUF ZBUF = r->ZBUF;
// TODO: I tested this and I believe it is possible to set zbuf to a color format
// Powerdrome relies on this behavior to clear the z buffer
// the undocumented formats do have behavior (they mess with the swizzling)
// we don't emulate this yet (and maybe we wont need to)
ZBUF.PSM |= 0x30;
// We tested this on the PS2 and it seems to be that when the FRAME is a Z format,
// the Z buffer is forced to use color swizzling.
// Powerdrome relies on this behavior to clear the z buffer by drawing 32 pixel wide strips, skipping 32,
// causing the FRAME to do one strip and the Z to do the other 32 due to the block arrangement.
// Other games listed here also hit this Color/Z swap behaviour without masking Z so could be problematic:
// Black, Driver Parallel Lines, Driv3r, Dropship, DT Racer, Scarface, The Simpsons, THP8
if ((m_env.CTXT[i].FRAME.PSM & 0x30) == 0x30)
ZBUF.PSM &= ~0x30;
else
ZBUF.PSM |= 0x30;
if (PRIM->CTXT == i && ZBUF != m_env.CTXT[i].ZBUF)
Flush();
@@ -1491,7 +1510,6 @@ void GSState::FlushPrim()
g_perfmon.Put(GSPerfMon::Prim, m_index.tail / GSUtil::GetVertexCount(PRIM->PRIM));
m_index.tail = 0;
m_vertex.head = 0;
if (unused > 0)
@@ -2413,33 +2431,104 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
return overlap;
}
__forceinline void GSState::HandleAutoFlush()
{
// To briefly explain what's going on here, what we are checking for is draws over a texture when the source and destination are themselves.
// Because one page of the texture gets buffered in the Texture Cache (the PS2's one) if any of those pixels are overwritten, you still read the old data.
// So we need to calculate if a page boundary is being crossed for the format it is in and if the same part of the texture being written and read inside the draw.
if ((m_context->FRAME.Block() == m_context->TEX0.TBP0) && (m_context->TEX0.PSM == m_context->FRAME.PSM) && PRIM->TME && (m_context->FRAME.FBMSK != 0xFFFFFFFF))
{
const int page_mask_x = ~(GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.x - 1);
const int page_mask_y = ~(GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.y - 1);
const GSVector4i page_mask = { page_mask_x, page_mask_y, page_mask_x, page_mask_y };
size_t n = 1;
switch (GSUtil::GetPrimClass(PRIM->PRIM))
{
case GS_POINT_CLASS:
n = 1;
break;
case GS_LINE_CLASS:
case GS_SPRITE_CLASS:
n = 2;
break;
case GS_TRIANGLE_CLASS:
n = 3;
break;
}
GSVector4i tex_coord;
// Prepare the currently processed vertex.
if (PRIM->FST)
{
tex_coord.x = m_v.U >> 4;
tex_coord.y = m_v.V >> 4;
}
else
{
tex_coord.x = (int)((1 << m_context->TEX0.TW) * (m_v.ST.S / m_v.RGBAQ.Q));
tex_coord.y = (int)((1 << m_context->TEX0.TH) * (m_v.ST.T / m_v.RGBAQ.Q));
}
GSVector4i tex_rect = tex_coord.xyxy();
GSVector4i next_rect = tex_rect;
const int current_tex_end = (int)(m_index.tail - (m_index.tail % n)) - 1;
bool page_crossed = false;
// Check previous texture co-ordindates to see if we have changed page
for (int i = m_index.tail - 1; i >= current_tex_end; i--)
{
const GSVertex* v = &m_vertex.buff[m_index.buff[i]];
if (PRIM->FST)
{
tex_coord.x = v->U >> 4;
tex_coord.y = v->V >> 4;
}
else
{
tex_coord.x = (int)((1 << m_context->TEX0.TW) * (v->ST.S / v->RGBAQ.Q));
tex_coord.y = (int)((1 << m_context->TEX0.TH) * (v->ST.T / v->RGBAQ.Q));
}
next_rect.x = std::min(next_rect.x, tex_coord.x);
next_rect.z = std::max(next_rect.z, tex_coord.x);
next_rect.y = std::min(next_rect.y, tex_coord.y);
next_rect.w = std::max(next_rect.w, tex_coord.y);
const GSVector4i pages = next_rect & page_mask;
// We have changed page, so ignore the old textures co-ordinates.
if (!pages.xyxy().eq(pages.zwzw()))
{
page_crossed = true;
break;
}
tex_rect = next_rect;
}
tex_rect += GSVector4i(0, 0, 1, 1); // Intersect goes on space inside the rect
if(page_crossed)
{
// Update the vertex trace, scissor it (important for Jak 3!) and intersect with the current texture.
if((m_index.tail - 1) == current_tex_end)
m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail - m_vertex.head, m_index.tail, GSUtil::GetPrimClass(PRIM->PRIM));
GSVector4i area_out = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
if (!area_out.rintersect(tex_rect).rempty())
{
Flush();
}
}
}
}
template <u32 prim, bool auto_flush, bool index_swap>
__forceinline void GSState::VertexKick(u32 skip)
{
ASSERT(m_vertex.tail < m_vertex.maxcount + 3);
size_t head = m_vertex.head;
size_t tail = m_vertex.tail;
size_t next = m_vertex.next;
size_t xy_tail = m_vertex.xy_tail;
// callers should write XYZUVF to m_v.m[1] in one piece to have this load store-forwarded, either by the cpu or the compiler when this function is inlined
GSVector4i v0(m_v.m[0]);
GSVector4i v1(m_v.m[1]);
GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex.buff[tail];
tailptr[0] = v0;
tailptr[1] = v1;
const GSVector4i xy = v1.xxxx().u16to32().sub32(m_ofxy);
GSVector4i::storel(&m_vertex.xy[xy_tail & 3], xy.blend16<0xf0>(xy.sra32(4)).ps32());
m_vertex.tail = ++tail;
m_vertex.xy_tail = ++xy_tail;
size_t n = 0;
switch (prim)
@@ -2470,6 +2559,37 @@ __forceinline void GSState::VertexKick(u32 skip)
break;
}
if (m_context->FRAME.FBMSK != 0xFFFFFFFF)
m_mem.m_clut.Invalidate(m_context->FRAME.Block());
if (auto_flush && m_index.tail >= n)
HandleAutoFlush();
ASSERT(m_vertex.tail < m_vertex.maxcount + 3);
size_t head = m_vertex.head;
size_t tail = m_vertex.tail;
size_t next = m_vertex.next;
size_t xy_tail = m_vertex.xy_tail;
// callers should write XYZUVF to m_v.m[1] in one piece to have this load store-forwarded, either by the cpu or the compiler when this function is inlined
GSVector4i v0(m_v.m[0]);
GSVector4i v1(m_v.m[1]);
GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex.buff[tail];
tailptr[0] = v0;
tailptr[1] = v1;
const GSVector4i xy = v1.xxxx().u16to32().sub32(m_ofxy);
GSVector4i::storel(&m_vertex.xy[xy_tail & 3], xy.blend16<0xf0>(xy.sra32(4)).ps32());
m_vertex.tail = ++tail;
m_vertex.xy_tail = ++xy_tail;
size_t m = tail - head;
if (m < n)
@@ -2649,15 +2769,8 @@ __forceinline void GSState::VertexKick(u32 skip)
__assume(0);
}
if (m_context->FRAME.FBMSK != 0xFFFFFFFF)
{
m_mem.m_clut.Invalidate(m_context->FRAME.Block());
if (auto_flush && PRIM->TME && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && (m_context->TEX0.PSM == m_context->FRAME.PSM))
{
FlushPrim();
}
}
if (PRIM->TME)
tex_flushed = false;
}
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
+4 -3
View File
@@ -156,12 +156,12 @@ protected:
int m_skip;
int m_skip_offset;
bool m_userhacks_auto_flush;
bool tex_flushed;
GSVertex m_v;
float m_q;
GSVector4i m_scissor;
GSVector4i m_ofxy;
bool tex_flushed;
bool m_scanmask_used;
@@ -185,7 +185,8 @@ protected:
void UpdateVertexKick();
void GrowVertexBuffer();
void HandleAutoFlush();
template <u32 prim, bool auto_flush, bool index_swap>
void VertexKick(u32 skip);
@@ -217,7 +218,6 @@ protected:
bool IsOpaque();
bool IsMipMapDraw();
bool IsMipMapActive();
GIFRegTEX0 GetTex0Layer(u32 lod);
public:
GIFPath m_path[4];
@@ -303,4 +303,5 @@ public:
void SetFrameSkip(int skip);
PRIM_OVERLAP PrimitiveOverlap();
GIFRegTEX0 GetTex0Layer(u32 lod);
};
+2
View File
@@ -542,6 +542,8 @@ public:
bool point_expand : 1; ///< Supports point expansion in hardware without using geometry shaders.
bool line_expand : 1; ///< Supports line expansion in hardware without using geometry shaders.
bool prefer_new_textures : 1; ///< Allocate textures up to the pool size before reusing them, to avoid render pass restarts.
bool dxt_textures : 1; ///< Supports DXTn texture compression, i.e. S3TC and BC1-3.
bool bptc_textures : 1; ///< Supports BC6/7 texture compression.
FeatureSupport()
{
memset(this, 0, sizeof(*this));
+60 -2
View File
@@ -59,7 +59,7 @@ bool GSTexture::Save(const std::string& fn)
GSMap map;
if (!g_gs_device->DownloadTexture(this, GSVector4i(0, 0, m_size.x, m_size.y), map))
{
Console.Error("(GSTextureVK) DownloadTexture() failed.");
Console.Error("(GSTexture) DownloadTexture() failed.");
return false;
}
@@ -71,9 +71,67 @@ bool GSTexture::Save(const std::string& fn)
return success;
}
void GSTexture::Swap(GSTexture* tex)
{
std::swap(m_scale, tex->m_scale);
std::swap(m_size, tex->m_size);
std::swap(m_committed_size, tex->m_committed_size);
std::swap(m_mipmap_levels, tex->m_mipmap_levels);
std::swap(m_type, tex->m_type);
std::swap(m_format, tex->m_format);
std::swap(m_state, tex->m_state);
std::swap(m_sparse, tex->m_sparse);
std::swap(m_needs_mipmaps_generated, tex->m_needs_mipmaps_generated);
std::swap(last_frame_used, tex->last_frame_used);
std::swap(LikelyOffset, tex->LikelyOffset);
std::swap(OffsetHack_modx, tex->OffsetHack_modx);
std::swap(OffsetHack_mody, tex->OffsetHack_mody);
}
u32 GSTexture::GetCompressedBytesPerBlock() const
{
static constexpr u32 bytes_per_block[] = {
1, // Invalid
4, // Color/RGBA8
16, // FloatColor/RGBA32F
32, // DepthStencil
1, // UNorm8/R8
2, // UInt16/R16UI
4, // UInt32/R32UI
4, // Int32/R32I
8, // BC1 - 16 pixels in 64 bits
16, // BC2 - 16 pixels in 128 bits
16, // BC3 - 16 pixels in 128 bits
16, // BC4 - 16 pixels in 128 bits
};
return bytes_per_block[static_cast<u32>(m_format)];
}
u32 GSTexture::GetCompressedBlockSize() const
{
if (m_format >= Format::BC1 && m_format <= Format::BC7)
return 4;
else
return 1;
}
u32 GSTexture::CalcUploadRowLengthFromPitch(u32 pitch) const
{
const u32 block_size = GetCompressedBlockSize();
const u32 bytes_per_block = GetCompressedBytesPerBlock();
return ((pitch + (bytes_per_block - 1)) / bytes_per_block) * block_size;
}
u32 GSTexture::CalcUploadSize(u32 height, u32 pitch) const
{
const u32 block_size = GetCompressedBlockSize();
return pitch * ((static_cast<u32>(height) + (block_size - 1)) / block_size);
}
void GSTexture::GenerateMipmapsIfNeeded()
{
if (!m_needs_mipmaps_generated || m_mipmap_levels <= 1)
if (!m_needs_mipmaps_generated || m_mipmap_levels <= 1 || IsCompressedFormat())
return;
m_needs_mipmaps_generated = false;
+15 -1
View File
@@ -47,6 +47,10 @@ public:
UInt16, ///< UInt16 texture for reading back 16-bit depth
UInt32, ///< UInt32 texture for reading back 24 and 32-bit depth
Int32, ///< Int32 texture for date emulation
BC1, ///< BC1, aka DXT1 compressed texture for replacements
BC2, ///< BC2, aka DXT2/3 compressed texture for replacements
BC3, ///< BC3, aka DXT4/5 compressed texture for replacements
BC7, ///< BC7, aka BPTC compressed texture for replacements
};
enum class State : u8
@@ -86,6 +90,7 @@ public:
virtual void Unmap() = 0;
virtual void GenerateMipmap() {}
virtual bool Save(const std::string& fn);
virtual void Swap(GSTexture* tex);
virtual u32 GetID() { return 0; }
GSVector2 GetScale() const { return m_scale; }
@@ -99,6 +104,12 @@ public:
Type GetType() const { return m_type; }
Format GetFormat() const { return m_format; }
bool IsCompressedFormat() const { return IsCompressedFormat(m_format); }
u32 GetCompressedBytesPerBlock() const;
u32 GetCompressedBlockSize() const;
u32 CalcUploadRowLengthFromPitch(u32 pitch) const;
u32 CalcUploadSize(u32 height, u32 pitch) const;
bool IsRenderTargetOrDepthStencil() const
{
@@ -137,5 +148,8 @@ public:
float OffsetHack_mody;
// Typical size of a RGBA texture
virtual u32 GetMemUsage() { return m_size.x * m_size.y * 4; }
virtual u32 GetMemUsage() { return m_size.x * m_size.y * (m_format == Format::UNorm8 ? 1 : 4); }
// Helper routines for formats/types
static bool IsCompressedFormat(Format format) { return (format >= Format::BC1 && format <= Format::BC7); }
};
@@ -49,6 +49,9 @@ GSVertexTrace::GSVertexTrace(const GSState* state)
void GSVertexTrace::Update(const void* vertex, const u32* index, int v_count, int i_count, GS_PRIM_CLASS primclass)
{
if (i_count == 0)
return;
m_primclass = primclass;
u32 iip = m_state->PRIM->IIP;
+29 -2
View File
@@ -28,6 +28,15 @@
#include <VersionHelpers.h>
#include <d3dcompiler.h>
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
{
UINT support;
if (FAILED(dev->CheckFormatSupport(DXGI_FORMAT_BC1_UNORM, &support)))
return false;
return (support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
}
GSDevice11::GSDevice11()
{
memset(&m_state, 0, sizeof(m_state));
@@ -42,6 +51,8 @@ GSDevice11::GSDevice11()
m_features.point_expand = false;
m_features.line_expand = false;
m_features.prefer_new_textures = false;
m_features.dxt_textures = false;
m_features.bptc_textures = false;
}
bool GSDevice11::Create(HostDisplay* display)
@@ -113,6 +124,8 @@ bool GSDevice11::Create(HostDisplay* display)
m_features.broken_point_sampler = amd_vendor;
}
SetFeatures();
std::optional<std::string> shader = Host::ReadResourceFileToString("shaders/dx11/tfx.fx");
if (!shader.has_value())
return false;
@@ -324,6 +337,16 @@ bool GSDevice11::Create(HostDisplay* display)
return true;
}
void GSDevice11::SetFeatures()
{
// Check all three formats, since the feature means any can be used.
m_features.dxt_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC1_UNORM) &&
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC2_UNORM) &&
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC3_UNORM);
m_features.bptc_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC7_UNORM);
}
void GSDevice11::ResetAPIState()
{
// Clear out the GS, since the imgui draw doesn't get rid of it.
@@ -433,6 +456,10 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height
case GSTexture::Format::UInt16: dxformat = DXGI_FORMAT_R16_UINT; break;
case GSTexture::Format::UInt32: dxformat = DXGI_FORMAT_R32_UINT; break;
case GSTexture::Format::Int32: dxformat = DXGI_FORMAT_R32_SINT; break;
case GSTexture::Format::BC1: dxformat = DXGI_FORMAT_BC1_UNORM; break;
case GSTexture::Format::BC2: dxformat = DXGI_FORMAT_BC2_UNORM; break;
case GSTexture::Format::BC3: dxformat = DXGI_FORMAT_BC3_UNORM; break;
case GSTexture::Format::BC7: dxformat = DXGI_FORMAT_BC7_UNORM; break;
case GSTexture::Format::Invalid:
ASSERT(0);
dxformat = DXGI_FORMAT_UNKNOWN;
@@ -457,8 +484,8 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height
desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
break;
case GSTexture::Type::Texture:
desc.BindFlags = (levels > 1) ? (D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE) : D3D11_BIND_SHADER_RESOURCE;
desc.MiscFlags = (levels > 1) ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
desc.BindFlags = (levels > 1 && !GSTexture::IsCompressedFormat(format)) ? (D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE) : D3D11_BIND_SHADER_RESOURCE;
desc.MiscFlags = (levels > 1 && !GSTexture::IsCompressedFormat(format)) ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
break;
case GSTexture::Type::Offscreen:
desc.Usage = D3D11_USAGE_STAGING;
+2
View File
@@ -117,6 +117,8 @@ private:
float m_hack_topleft_offset;
int m_d3d_texsize;
void SetFeatures();
GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) final;
void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c) final;
+10
View File
@@ -188,6 +188,16 @@ void GSTexture11::GenerateMipmap()
GSDevice11::GetInstance()->GetD3DContext()->GenerateMips(operator ID3D11ShaderResourceView*());
}
void GSTexture11::Swap(GSTexture* tex)
{
GSTexture::Swap(tex);
std::swap(m_texture, static_cast<GSTexture11*>(tex)->m_texture);
std::swap(m_srv, static_cast<GSTexture11*>(tex)->m_srv);
std::swap(m_rtv, static_cast<GSTexture11*>(tex)->m_rtv);
std::swap(m_desc, static_cast<GSTexture11*>(tex)->m_desc);
std::swap(m_mapped_subresource, static_cast<GSTexture11*>(tex)->m_mapped_subresource);
}
GSTexture11::operator ID3D11Texture2D*()
{
return m_texture.get();
+1
View File
@@ -40,6 +40,7 @@ public:
void Unmap() override;
bool Save(const std::string& fn) override;
void GenerateMipmap() override;
void Swap(GSTexture* tex) override;
bool Equal(GSTexture11* tex);
operator ID3D11Texture2D*();
+1 -1
View File
@@ -351,7 +351,7 @@ bool GSC_Tekken5(const GSFrameInfo& fi, int& skip)
// Let's enable this hack for Aggressive only since it's an upscaling issue for both renders.
skip = 95;
}
else if (fi.TME && (fi.FBP == 0x02bc0 || fi.FBP == 0x02be0 || fi.FBP == 0x02d00 || fi.FBP == 0x03480 || fi.FBP == 0x034a0) && fi.FPSM == fi.TPSM && fi.TBP0 == 0x00000 && fi.TPSM == PSM_PSMCT32)
else if (fi.TZTST == 1 && fi.TME && (fi.FBP == 0x02bc0 || fi.FBP == 0x02be0 || fi.FBP == 0x02d00 || fi.FBP == 0x03480 || fi.FBP == 0x034a0) && fi.FPSM == fi.TPSM && fi.TBP0 == 0x00000 && fi.TPSM == PSM_PSMCT32)
{
// The moving display effect(flames) is not emulated properly in the entire screen so let's remove the effect in the stage: Burning Temple. Related to half screen bottom issue.
// Fixes black lines in the stage: Burning Temple - caused by upscaling. Note the black lines can also be fixed with Merge Sprite hack.
+75 -10
View File
@@ -15,7 +15,9 @@
#include "PrecompiledHeader.h"
#include "GSRendererHW.h"
#include "GSTextureReplacements.h"
#include "GS/GSGL.h"
#include "Host.h"
GSRendererHW::GSRendererHW()
: GSRenderer()
@@ -73,6 +75,7 @@ GSRendererHW::GSRendererHW()
}
m_dump_root = root_hw;
GSTextureReplacements::Initialize(m_tc);
}
void GSRendererHW::SetScaling()
@@ -188,6 +191,7 @@ GSRendererHW::~GSRendererHW()
void GSRendererHW::Destroy()
{
m_tc->RemoveAll();
GSTextureReplacements::Shutdown();
GSRenderer::Destroy();
}
@@ -204,8 +208,9 @@ void GSRendererHW::SetGameCRC(u32 crc, int options)
m_hacks.SetGameCRC(m_game);
// Code for Automatic Mipmapping. Relies on game CRCs.
m_mipmap = (GSConfig.HWMipmap >= HWMipmapLevel::Basic);
if (GSConfig.HWMipmap == HWMipmapLevel::Automatic)
m_hw_mipmap = GSConfig.HWMipmap;
m_mipmap = (m_hw_mipmap >= HWMipmapLevel::Basic);
if (m_hw_mipmap == HWMipmapLevel::Automatic)
{
switch (CRC::Lookup(crc).title)
{
@@ -258,6 +263,8 @@ void GSRendererHW::SetGameCRC(u32 crc, int options)
break;
}
}
GSTextureReplacements::GameChanged();
}
bool GSRendererHW::CanUpscale()
@@ -267,8 +274,7 @@ bool GSRendererHW::CanUpscale()
return false;
}
// upscale ratio depends on the display size, with no output it may not be set correctly (ps2 logo to game transition)
return GSConfig.UpscaleMultiplier != 1 && m_regs->PMODE.EN != 0;
return GSConfig.UpscaleMultiplier != 1;
}
int GSRendererHW::GetUpscaleMultiplier()
@@ -305,6 +311,9 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
m_reset = false;
}
if (GSConfig.LoadTextureReplacements)
GSTextureReplacements::ProcessAsyncLoadedTextures();
//Check if the frame buffer width or display width has changed
SetScaling();
@@ -312,6 +321,15 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
m_tc->IncAge();
if (m_tc->GetHashCacheMemoryUsage() > 1024 * 1024 * 1024)
{
Host::AddKeyedFormattedOSDMessage("HashCacheOverflow", 15.0f, "Hash cache has used %.2f MB of VRAM, disabling.",
static_cast<float>(m_tc->GetHashCacheMemoryUsage()) / 1048576.0f);
m_tc->RemoveAll();
g_gs_device->PurgePool();
GSConfig.TexturePreloading = TexturePreloadingLevel::Partial;
}
m_tc->PrintMemoryUsage();
g_gs_device->PrintMemoryUsage();
@@ -1242,6 +1260,21 @@ void GSRendererHW::Draw()
GSDrawingContext* context = m_context;
const GSLocalMemory::psm_t& tex_psm = GSLocalMemory::m_psm[m_context->TEX0.PSM];
if (!context->FRAME.FBW)
{
GL_CACHE("Skipping draw with FRAME.FBW = 0.");
return;
}
// When the format is 24bit (Z or C), DATE ceases to function.
// It was believed that in 24bit mode all pixels pass because alpha doesn't exist
// however after testing this on a PS2 it turns out nothing passes, it ignores the draw.
if ((m_context->FRAME.PSM & 0xF) == PSM_PSMCT24 && m_context->TEST.DATE)
{
GL_CACHE("DATE on a 24bit format, Frame PSM %x", m_context->FRAME.PSM);
return;
}
// Fix TEX0 size
if (PRIM->TME && !IsMipMapActive())
m_context->ComputeFixedTEX0(m_vt.m_min.t.xyxy(m_vt.m_max.t));
@@ -1280,6 +1313,12 @@ void GSRendererHW::Draw()
(context->FRAME.FBP == context->ZBUF.ZBP && !PRIM->TME && zm == 0 && fm == 0 && context->TEST.ZTE)
);
if (no_rt && no_ds)
{
GL_CACHE("Skipping draw with no color nor depth output.");
return;
}
const bool draw_sprite_tex = PRIM->TME && (m_vt.m_primclass == GS_SPRITE_CLASS);
const GSVector4 delta_p = m_vt.m_max.p - m_vt.m_min.p;
const bool single_page = (delta_p.x <= 64.0f) && (delta_p.y <= 64.0f);
@@ -1320,6 +1359,7 @@ void GSRendererHW::Draw()
if (PRIM->TME)
{
GIFRegCLAMP MIP_CLAMP = context->CLAMP;
GSVector2i hash_lod_range(0, 0);
m_lod = GSVector2i(0, 0);
// Code from the SW renderer
@@ -1382,6 +1422,10 @@ void GSRendererHW::Draw()
TEX0 = GetTex0Layer(m_lod.x);
// upload the full chain (with offset) for the hash cache, in case some other texture uses more levels
// for basic mipmapping, we can get away with just doing the base image, since all the mips get generated anyway.
hash_lod_range = GSVector2i(m_lod.x, (m_hw_mipmap == HWMipmapLevel::Full) ? mxl : m_lod.x);
MIP_CLAMP.MINU >>= m_lod.x;
MIP_CLAMP.MINV >>= m_lod.x;
MIP_CLAMP.MAXU >>= m_lod.x;
@@ -1405,8 +1449,8 @@ void GSRendererHW::Draw()
TextureMinMaxResult tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear());
m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, tmm.coverage) :
m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, m_hw_mipmap >= HWMipmapLevel::Basic ||
GSConfig.UserHacks_TriFilter == TriFiltering::Forced);
m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, (m_hw_mipmap >= HWMipmapLevel::Basic ||
GSConfig.UserHacks_TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr);
int tw = 1 << TEX0.TW;
int th = 1 << TEX0.TH;
@@ -1460,7 +1504,7 @@ void GSRendererHW::Draw()
}
// Round 2
if (IsMipMapActive() && m_hw_mipmap == HWMipmapLevel::Full && !tex_psm.depth)
if (IsMipMapActive() && m_hw_mipmap == HWMipmapLevel::Full && !tex_psm.depth && !m_src->m_from_hash_cache)
{
// Upload remaining texture layers
const GSVector4 tmin = m_vt.m_min.t;
@@ -1918,6 +1962,23 @@ void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds)
}
}
}
// Striped double clear done by Powerdrome and Snoopy Vs Red Baron, it will clear in 32 pixel stripes half done by the Z and half done by the FRAME
else if (rt && !ds && m_context->FRAME.FBP == m_context->ZBUF.ZBP && (m_context->FRAME.PSM & 0x30) != (m_context->ZBUF.PSM & 0x30)
&& (m_context->FRAME.PSM & 0xF) == (m_context->ZBUF.PSM & 0xF) && (u32)(GSVector4i(m_vt.m_max.p).z) == 0)
{
const GSVertex* v = &m_vertex.buff[0];
// Z and color must be constant and the same
if (m_vt.m_eq.rgba != 0xFFFF || !m_vt.m_eq.z || v[1].XYZ.Z != v[1].RGBAQ.U32[0])
return;
// If both buffers are side by side we can expect a fast clear in on-going
const u32 color = v[1].RGBAQ.U32[0];
const GSVector4i commitRect = ComputeBoundingBox(rt->GetScale(), rt->GetSize());
rt->CommitRegion(GSVector2i(commitRect.z, commitRect.w));
g_gs_device->ClearRenderTarget(rt, color);
}
}
// Note: hack is safe, but it could impact the perf a little (normally games do only a couple of clear by frame)
@@ -1925,8 +1986,12 @@ void GSRendererHW::OI_GsMemClear()
{
// Note gs mem clear must be tested before calling this function
// Striped double clear done by Powerdrome and Snoopy Vs Red Baron, it will clear in 32 pixel stripes half done by the Z and half done by the FRAME
const bool ZisFrame = m_context->FRAME.FBP == m_context->ZBUF.ZBP && (m_context->FRAME.PSM & 0x30) != (m_context->ZBUF.PSM & 0x30)
&& (m_context->FRAME.PSM & 0xF) == (m_context->ZBUF.PSM & 0xF) && (u32)(GSVector4i(m_vt.m_max.p).z) == 0;
// Limit it further to a full screen 0 write
if ((m_vertex.next == 2) && m_vt.m_min.c.eq(GSVector4i(0)))
if (((m_vertex.next == 2) || ZisFrame) && m_vt.m_min.c.eq(GSVector4i(0)))
{
const GSOffset& off = m_context->offset.fb;
const GSVector4i r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
@@ -1972,11 +2037,11 @@ void GSRendererHW::OI_GsMemClear()
; // Hack is used for FMV which are likely 24/32 bits. Let's keep the for reference
#if 0
// Based on WritePixel16
for(int y = r.top; y < r.bottom; y++)
for (int y = r.top; y < r.bottom; y++)
{
auto pa = off.assertSizesMatch(GSLocalMemory::swizzle16).paMulti(m_mem.m_vm16, 0, y);
for(int x = r.left; x < r.right; x++)
for (int x = r.left; x < r.right; x++)
{
*pa.value(x) = 0; // Here the constant color
}
+3
View File
@@ -17,6 +17,7 @@
#include "GSTextureCache.h"
#include "GS/Renderers/Common/GSFunctionMap.h"
#include "GS/Renderers/Common/GSRenderer.h"
#include "GS/GSState.h"
class GSRendererHW : public GSRenderer
@@ -164,6 +165,8 @@ public:
GSRendererHW();
virtual ~GSRendererHW() override;
__fi GSTextureCache* GetTextureCache() const { return m_tc; }
void Destroy() override;
void SetGameCRC(u32 crc, int options) override;
+44 -21
View File
@@ -528,7 +528,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
// PABE: Check condition early as an optimization.
const bool PABE = PRIM->ABE && m_env.PABE.PABE && (GetAlphaMinMax().max < 128);
// FBMASK: Color is not written, no need to do blending.
const int temp_fbmask = m_conf.ps.dfmt == 2 ? 0x00F8F8F8 : 0x00FFFFFF;
const u32 temp_fbmask = m_conf.ps.dfmt == 2 ? 0x00F8F8F8 : 0x00FFFFFF;
const bool FBMASK = (m_context->FRAME.FBMSK & temp_fbmask) == temp_fbmask;
// No blending or coverage anti-aliasing so early exit
@@ -632,6 +632,8 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
// Do not run BLEND MIX if sw blending is already present, it's less accurate
blend_mix &= !sw_blending;
sw_blending |= blend_mix;
// Disable dithering on blend mix.
m_conf.ps.dither &= !blend_mix;
[[fallthrough]];
case AccBlendLevel::Minimum:
break;
@@ -675,6 +677,8 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
// Do not run BLEND MIX if sw blending is already present, it's less accurate
blend_mix &= !sw_blending;
sw_blending |= blend_mix;
// Disable dithering on blend mix.
m_conf.ps.dither &= !blend_mix;
[[fallthrough]];
case AccBlendLevel::Minimum:
break;
@@ -769,9 +773,14 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
#endif
if (color_dest_blend)
{
// Blend output will be Cd, no need to set Af.
m_conf.blend = {blend_index, 0, ALPHA.C == 2, false, false};
// Blend output will be Cd, disable hw/sw blending.
m_conf.blend = {};
sw_blending = false; // DATE_PRIMID
// Output is Cd, set rgb write to 0.
m_conf.colormask.wr = 0;
m_conf.colormask.wg = 0;
m_conf.colormask.wb = 0;
}
else if (sw_blending)
{
@@ -780,16 +789,30 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
m_conf.ps.blend_c = ALPHA.C;
m_conf.ps.blend_d = ALPHA.D;
if (m_conf.ps.blend_a == m_conf.ps.blend_b)
{
// A == B
// (A - B) * C will be 0 so set A B to Cs, C to As
m_conf.ps.blend_a = 0;
m_conf.ps.blend_b = 0;
m_conf.ps.blend_c = 0;
}
else if (m_conf.ps.blend_c == 2)
{
// Require the fix alpha vlaue
m_conf.cb_ps.TA_MaxDepth_Af.a = static_cast<float>(ALPHA.FIX) / 128.0f;
}
if (accumulation_blend)
{
// Keep HW blending to do the addition/subtraction
m_conf.blend = {blend_index, 0, false, true, false};
if (ALPHA.A == 2)
if (m_conf.ps.blend_a == 2)
{
// The blend unit does a reverse subtraction so it means
// the shader must output a positive value.
// Replace 0 - Cs by Cs - 0
m_conf.ps.blend_a = ALPHA.B;
m_conf.ps.blend_a = m_conf.ps.blend_b;
m_conf.ps.blend_b = 2;
}
// Remove the addition/substraction from the SW blending
@@ -800,7 +823,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
}
else if (blend_mix)
{
m_conf.blend = {blend_index, ALPHA.FIX, ALPHA.C == 2, false, true};
m_conf.blend = {blend_index, ALPHA.FIX, m_conf.ps.blend_c == 2, false, true};
m_conf.ps.blend_mix = 1;
if (blend_mix1)
@@ -843,10 +866,6 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
else
m_conf.require_one_barrier |= !blend_non_recursive;
}
// Require the fix alpha vlaue
if (ALPHA.C == 2)
m_conf.cb_ps.TA_MaxDepth_Af.a = static_cast<float>(ALPHA.FIX) / 128.0f;
}
else
{
@@ -1300,13 +1319,12 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour
// DATE: selection of the algorithm. Must be done before blending because GL42 is not compatible with blending
if (DATE)
{
// It is way too complex to emulate texture shuffle with DATE. So just use
// the slow but accurate algo
const bool fbmask = (m_context->FRAME.FBMSK & 0x80000000);
const bool no_overlap = (m_prim_overlap == PRIM_OVERLAP_NO);
if (fbmask || no_overlap || m_texture_shuffle)
// It is way too complex to emulate texture shuffle with DATE, so use accurate path.
// No overlap should be triggered on gl/vk only as they support DATE_BARRIER.
const bool no_overlap = (g_gs_device->Features().texture_barrier) && (m_prim_overlap == PRIM_OVERLAP_NO);
if (no_overlap || m_texture_shuffle)
{
GL_PERF("DATE: Accurate with %s", m_texture_shuffle ? "texture shuffle" : no_overlap ? "no overlap" : "FBMASK");
GL_PERF("DATE: Accurate with %s", no_overlap ? "no overlap" : "texture shuffle");
if (g_gs_device->Features().texture_barrier)
{
m_conf.require_full_barrier = true;
@@ -1315,8 +1333,8 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour
}
else if (m_context->FBA.FBA)
{
DATE_one = !m_context->TEST.DATM;
GL_PERF("DATE: Fast with FBA, all pixels will be >= 128");
DATE_one = !m_context->TEST.DATM;
}
else if (m_conf.colormask.wa && !m_context->TEST.ATE)
{
@@ -1367,9 +1385,12 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour
}
else if (!m_conf.colormask.wa && !m_context->TEST.ATE)
{
// TODO: is it legal ? Likely but it need to be tested carefully
// DATE_BARRIER = true;
// m_conf.require_one_barrier = true; << replace it with a cheap barrier
GL_PERF("DATE: Accurate with no alpha write");
if (g_gs_device->Features().texture_barrier)
{
m_conf.require_one_barrier = true;
DATE_BARRIER = true;
}
}
// Will save my life !
@@ -1378,6 +1399,9 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour
ASSERT(!(DATE_PRIMID && DATE_BARRIER));
}
// Before emulateblending, dither will be used
m_conf.ps.dither = GSConfig.Dithering > 0 && m_conf.ps.dfmt == 2 && m_env.DTHE.DTHE;
// Blend
if (!IsOpaque() && rt)
@@ -1469,7 +1493,6 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour
}
m_conf.ps.fba = m_context->FBA.FBA;
m_conf.ps.dither = GSConfig.Dithering > 0 && m_conf.ps.dfmt == 2 && m_env.DTHE.DTHE;
if (m_conf.ps.dither)
{
+415 -174
View File
@@ -15,10 +15,13 @@
#include "PrecompiledHeader.h"
#include "GSTextureCache.h"
#include "GSTextureReplacements.h"
#include "GSRendererHW.h"
#include "GS/GSGL.h"
#include "GS/GSIntrin.h"
#include "GS/GSUtil.h"
#include "common/Align.h"
#include "common/HashCombine.h"
#define XXH_STATIC_LINKING_ONLY 1
#define XXH_INLINE_ALL 1
@@ -26,6 +29,7 @@
bool GSTextureCache::m_disable_partial_invalidation = false;
bool GSTextureCache::m_wrap_gs_mem = false;
u8* GSTextureCache::m_temp;
GSTextureCache::GSTextureCache(GSRenderer* r)
: m_renderer(r)
@@ -62,6 +66,8 @@ GSTextureCache::GSTextureCache(GSRenderer* r)
GSTextureCache::~GSTextureCache()
{
GSTextureReplacements::Shutdown();
RemoveAll();
m_surface_offset_cache.clear();
@@ -94,6 +100,11 @@ void GSTextureCache::RemoveAll()
m_dst[type].clear();
}
for (auto it : m_hash_cache)
g_gs_device->Recycle(it.second.texture);
m_hash_cache.clear();
m_hash_cache_memory_usage = 0;
m_palette_map.Clear();
}
@@ -155,7 +166,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0
TEX0.TBP0, psm_str(psm));
// Create a shared texture source
src = new Source(m_renderer, TEX0, TEXA, m_temp, true);
src = new Source(m_renderer, TEX0, TEXA, true);
src->m_texture = dst->m_texture;
src->m_shared_texture = true;
src->m_target = true; // So renderer can check if a conversion is required
@@ -179,7 +190,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0
else if (m_renderer->m_game.title == CRC::SVCChaos)
{
// SVCChaos black screen on main menu, regardless of depth enabled or disabled.
return LookupSource(TEX0, TEXA, r, false);
return LookupSource(TEX0, TEXA, r, nullptr);
}
else
{
@@ -202,7 +213,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0
return src;
}
GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool mipmap)
GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, const GSVector2i* lod)
{
GL_CACHE("TC: Lookup Source <%d,%d => %d,%d> (0x%x, %s, BW: %u)", r.x, r.y, r.z, r.w, TEX0.TBP0, psm_str(TEX0.PSM), TEX0.TBW);
@@ -405,7 +416,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
GL_CACHE("TC: src miss (0x%x, 0x%x, %s)", TEX0.TBP0, psm_s.pal > 0 ? TEX0.CBP : 0, psm_str(TEX0.PSM));
}
#endif
src = CreateSource(TEX0, TEXA, dst, half_right, x_offset, y_offset, mipmap);
src = CreateSource(TEX0, TEXA, dst, half_right, x_offset, y_offset, lod);
new_source = true;
}
else
@@ -746,15 +757,19 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
if (!s->m_target)
{
if (m_disable_partial_invalidation && s->m_repeating)
found |= b;
// No point keeping invalidated sources around when the hash cache is active,
// we can just re-hash and create a new source from the cached texture.
if (s->m_from_hash_cache || (m_disable_partial_invalidation && s->m_repeating))
{
m_src.RemoveAt(s);
}
else
{
u32* RESTRICT valid = s->m_valid;
u32* RESTRICT valid = s->m_valid.get();
if (!s->CanPreload())
if (valid && !s->CanPreload())
{
// Invalidate data of input texture
if (s->m_repeating)
@@ -772,8 +787,6 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
}
s->m_complete_layers = 0;
found |= b;
}
}
else
@@ -901,9 +914,9 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
// Called each time you want to read from the GS memory
void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r)
{
u32 bp = off.bp();
u32 psm = off.psm();
u32 bw = off.bw();
const u32 bp = off.bp();
const u32 psm = off.psm();
[[maybe_unused]] const u32 bw = off.bw();
GL_CACHE("TC: InvalidateLocalMem off(0x%x, %u, %s) r(%d, %d => %d, %d)",
bp,
@@ -1119,6 +1132,24 @@ void GSTextureCache::IncAge()
}
}
const u32 max_hash_cache_age = 30;
for (auto it = m_hash_cache.begin(); it != m_hash_cache.end();)
{
HashCacheEntry& e = it->second;
if (e.refcount == 0 && ++e.age > max_hash_cache_age)
{
if (!e.is_replacement)
m_hash_cache_memory_usage -= e.texture->GetMemUsage();
g_gs_device->Recycle(e.texture);
m_hash_cache.erase(it++);
}
else
{
++it;
}
}
m_src.m_used = false;
// Clearing of Rendertargets causes flickering in many scene transitions.
@@ -1162,10 +1193,10 @@ void GSTextureCache::IncAge()
}
//Fixme: Several issues in here. Not handling depth stencil, pitch conversion doesnt work.
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, bool half_right, int x_offset, int y_offset, bool mipmap)
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, bool half_right, int x_offset, int y_offset, const GSVector2i* lod)
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
Source* src = new Source(m_renderer, TEX0, TEXA, m_temp);
Source* src = new Source(m_renderer, TEX0, TEXA, false);
int tw = 1 << TEX0.TW;
int th = 1 << TEX0.TH;
@@ -1426,14 +1457,25 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
}
else
{
if (GSConfig.GPUPaletteConversion && psm.pal > 0)
// maintain the clut even when paltex is on for the dump/replacement texture lookup
bool paltex = (GSConfig.GPUPaletteConversion && psm.pal > 0);
const u32* clut = (psm.pal > 0) ? static_cast<const u32*>(m_renderer->m_mem.m_clut) : nullptr;
// try the hash cache
if ((src->m_from_hash_cache = LookupHashCache(TEX0, TEXA, paltex, clut, lod)) != nullptr)
{
src->m_texture = src->m_from_hash_cache->texture;
if (psm.pal > 0)
AttachPaletteToSource(src, psm.pal, paltex);
}
else if (paltex)
{
src->m_texture = g_gs_device->CreateTexture(tw, th, false, GSTexture::Format::UNorm8);
AttachPaletteToSource(src, psm.pal, true);
}
else
{
src->m_texture = g_gs_device->CreateTexture(tw, th, mipmap, GSTexture::Format::Color);
src->m_texture = g_gs_device->CreateTexture(tw, th, (lod != nullptr), GSTexture::Format::Color);
if (psm.pal > 0)
{
AttachPaletteToSource(src, psm.pal, false);
@@ -1448,11 +1490,124 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
return src;
}
// This really needs a better home...
extern bool FMVstarted;
GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod)
{
// don't bother hashing if we're not dumping or replacing.
const bool dump = GSConfig.DumpReplaceableTextures && (!FMVstarted || GSConfig.DumpTexturesWithFMVActive);
const bool replace = GSConfig.LoadTextureReplacements;
const bool can_cache = CanCacheTextureSize(TEX0.TW, TEX0.TH);
if (!dump && !replace && !can_cache)
return nullptr;
// need the hash either for replacing, dumping or caching.
// if dumping/replacing is on, we compute the clut hash regardless, since replacements aren't indexed
HashCacheKey key{HashCacheKey::Create(TEX0, TEXA, m_renderer, (dump || replace || !paltex) ? clut : nullptr, lod)};
// handle dumping first, this is mostly isolated.
if (dump)
{
// dump base level
GSTextureReplacements::DumpTexture(key, TEX0, TEXA, m_renderer->m_mem, 0);
// and the mips
if (lod && GSConfig.DumpReplaceableMipmaps)
{
const int basemip = lod->x;
const int nmips = lod->y - lod->x + 1;
for (int mip = 1; mip < nmips; mip++)
{
const GIFRegTEX0 MIP_TEX0{m_renderer->GetTex0Layer(basemip + mip)};
GSTextureReplacements::DumpTexture(key, MIP_TEX0, TEXA, m_renderer->m_mem, mip);
}
}
}
// check with the full key
auto it = m_hash_cache.find(key);
// if this fails, and paltex is on, try indexed texture
const bool needs_second_lookup = paltex && (dump || replace);
if (needs_second_lookup && it == m_hash_cache.end())
it = m_hash_cache.find(key.WithRemovedCLUTHash());
// did we find either a replacement, cached/indexed texture?
if (it != m_hash_cache.end())
{
// super easy, cache hit. remove paltex if it's a replacement texture.
HashCacheEntry* entry = &it->second;
paltex &= (entry->texture->GetFormat() == GSTexture::Format::UNorm8);
entry->refcount++;
return entry;
}
// cache miss.
// check for a replacement texture with the full clut key
if (replace)
{
bool replacement_texture_pending = false;
GSTexture* replacement_tex = GSTextureReplacements::LookupReplacementTexture(key, lod != nullptr, &replacement_texture_pending);
if (replacement_tex)
{
// found a replacement texture! insert it into the hash cache, and clear paltex (since it's not indexed)
paltex = false;
const HashCacheEntry entry{replacement_tex, 1u, 0u, true};
return &m_hash_cache.emplace(key, entry).first->second;
}
else if (replacement_texture_pending)
{
// we didn't have a texture immediately, but there is a replacement available (and being loaded).
// so clear paltex, since when it gets injected back, it's not going to be indexed
paltex = false;
}
}
// if this texture isn't cacheable, bail out now since we don't want to waste time preloading it
if (!can_cache)
return nullptr;
// expand/upload texture
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
GSTexture* tex = g_gs_device->CreateTexture(tw, th, paltex ? false : (lod != nullptr), paltex ? GSTexture::Format::UNorm8 : GSTexture::Format::Color);
if (!tex)
{
// out of video memory if we hit here
return nullptr;
}
// upload base level
PreloadTexture(TEX0, TEXA, m_renderer->m_mem, paltex, tex, 0);
// upload mips if present
if (lod)
{
const int basemip = lod->x;
const int nmips = lod->y - lod->x + 1;
for (int mip = 1; mip < nmips; mip++)
{
const GIFRegTEX0 MIP_TEX0{m_renderer->GetTex0Layer(basemip + mip)};
PreloadTexture(MIP_TEX0, TEXA, m_renderer->m_mem, paltex, tex, mip);
}
}
// remove the palette hash when using paltex/indexed
if (paltex)
key.RemoveCLUTHash();
// insert into the cache cache, and we're done
const HashCacheEntry entry{tex, 1u, 0u, false};
m_hash_cache_memory_usage += tex->GetMemUsage();
return &m_hash_cache.emplace(key, entry).first->second;
}
GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear)
{
ASSERT(type == RenderTarget || type == DepthStencil);
Target* t = new Target(m_renderer, TEX0, m_temp, m_can_convert_depth, type);
Target* t = new Target(m_renderer, TEX0, m_can_convert_depth, type);
// FIXME: initial data should be unswizzled from local mem in Update() if dirty
@@ -1605,11 +1760,11 @@ void GSTextureCache::PrintMemoryUsage()
// GSTextureCache::Surface
GSTextureCache::Surface::Surface(GSRenderer* r, u8* temp)
GSTextureCache::Surface::Surface(GSRenderer* r)
: m_renderer(r)
, m_texture(NULL)
, m_from_hash_cache(NULL)
, m_age(0)
, m_temp(temp)
, m_32_bits_fmt(false)
, m_shared_texture(false)
, m_end_block(0)
@@ -1621,7 +1776,7 @@ GSTextureCache::Surface::~Surface()
{
// Shared textures are pointers copy. Therefore no allocation
// to recycle.
if (!m_shared_texture && m_texture)
if (!m_shared_texture && !m_from_hash_cache && m_texture)
g_gs_device->Recycle(m_texture);
}
@@ -1647,8 +1802,8 @@ bool GSTextureCache::Surface::Overlaps(u32 bp, u32 bw, u32 psm, const GSVector4i
// GSTextureCache::Source
GSTextureCache::Source::Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, u8* temp, bool dummy_container)
: Surface(r, temp)
GSTextureCache::Source::Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container)
: Surface(r)
, m_palette_obj(nullptr)
, m_palette(nullptr)
, m_valid_rect(0, 0)
@@ -1674,8 +1829,6 @@ GSTextureCache::Source::Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFR
memset(m_layer_TEX0, 0, sizeof(m_layer_TEX0));
memset(m_layer_hash, 0, sizeof(m_layer_hash));
memset(m_valid, 0, sizeof(m_valid));
m_write.rect = (GSVector4i*)_aligned_malloc(3 * sizeof(GSVector4i), 32);
m_write.count = 0;
@@ -1699,7 +1852,7 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int level)
{
Surface::UpdateAge();
if (m_target || (m_complete_layers & (1u << level)))
if (m_target || m_from_hash_cache || (m_complete_layers & (1u << level)))
return;
if (CanPreload())
@@ -1721,6 +1874,9 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int level)
u32 blocks = 0;
if (!m_valid)
m_valid = std::make_unique<u32[]>(MAX_PAGES);
if (m_repeating)
{
for (int y = r.top; y < r.bottom; y += bs.y, bn.nextBlockY())
@@ -1906,70 +2062,10 @@ void GSTextureCache::Source::Flush(u32 count, int layer)
m_write.count -= count;
}
using BlockHashState = XXH3_state_t;
__fi static void BlockHashReset(BlockHashState& st)
{
XXH3_64bits_reset(&st);
}
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp)
{
XXH3_64bits_update(&st, bp, BLOCK_SIZE);
}
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp, u32 size)
{
XXH3_64bits_update(&st, bp, size);
}
__fi static GSTextureCache::Source::HashType FinishBlockHash(BlockHashState& st)
{
return XXH3_64bits_digest(&st);
}
void GSTextureCache::Source::PreloadLevel(int level)
{
// m_TEX0 is adjusted for mips (messy, should be changed).
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << m_TEX0.TW;
const int th = 1 << m_TEX0.TH;
// For textures which are smaller than the block size, we expand and then hash.
// This is because otherwise we get the padding bytes, which can be random junk.
if (tw < bs.x || th < bs.y)
{
PreloadSmallLevel(level);
return;
}
// From GSLocalMemory foreachBlock(), used for reading textures.
// We want to hash the exact same blocks here.
const GSVector4i rect(0, 0, tw, th);
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
const GSOffset& off = m_renderer->m_context->offset.tex;
GSLocalMemory& mem = m_renderer->m_mem;
HashType hash;
{
BlockHashState hash_st;
BlockHashReset(hash_st);
GSOffset::BNHelper bn = off.bnMulti(block_rect.left, block_rect.top);
const int right = block_rect.right >> off.blockShiftX();
const int bottom = block_rect.bottom >> off.blockShiftY();
const int xAdd = (1 << off.blockShiftX()) * (psm.bpp / 8);
for (; bn.blkY() < bottom; bn.nextBlockY())
{
for (int x = 0; bn.blkX() < right; bn.nextBlockX(), x += xAdd)
{
BlockHashAccumulate(hash_st, mem.BlockPtr(bn.value()));
}
}
hash = FinishBlockHash(hash_st);
}
const HashType hash = HashTexture(m_renderer, m_TEX0, m_TEXA);
// Layer is complete again, regardless of whether the hash matches or not (and we reupload).
const u8 layer_bit = static_cast<u8>(1) << level;
@@ -1982,91 +2078,8 @@ void GSTextureCache::Source::PreloadLevel(int level)
m_valid_hashes |= layer_bit;
m_layer_hash[level] = hash;
// Expand texture/apply palette.
const int read_width = std::max(tw, psm.bs.x);
u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
u32 row_size = static_cast<u32>(tw) * sizeof(u32);
GSLocalMemory::readTexture rtx = psm.rtx;
if (m_palette)
{
pitch >>= 2;
row_size >>= 2;
rtx = psm.rtxP;
}
// If we can stream it directly to GPU memory, do so, otherwise go through a temp buffer.
GSTexture::GSMap map;
if (rect.eq(block_rect) && m_texture->Map(map, &rect, level))
{
(m_renderer->m_mem.*rtx)(off, block_rect, map.bits, map.pitch, m_TEXA);
m_texture->Unmap();
}
else
{
u8* buff = m_temp;
(m_renderer->m_mem.*rtx)(off, block_rect, buff, pitch, m_TEXA);
m_texture->Update(rect, buff, pitch, level);
}
}
void GSTextureCache::Source::PreloadSmallLevel(int level)
{
// m_TEX0 is adjusted for mips (messy, should be changed).
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << m_TEX0.TW;
const int th = 1 << m_TEX0.TH;
const GSVector4i rect(0, 0, tw, th);
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
const GSOffset& off = m_renderer->m_context->offset.tex;
GSLocalMemory& mem = m_renderer->m_mem;
// Expand texture/apply palette.
u32 pitch = static_cast<u32>(block_rect.z) * sizeof(u32);
u32 row_size = static_cast<u32>(tw) * sizeof(u32);
GSLocalMemory::readTexture rtx = psm.rtx;
if (m_palette)
{
pitch >>= 2;
row_size >>= 2;
rtx = psm.rtxP;
}
// Use temp buffer for expanding, since we may not need to update.
u8* buff = m_temp;
(m_renderer->m_mem.*rtx)(off, block_rect, buff, pitch, m_TEXA);
// Hash the expanded texture.
HashType hash;
{
u8* ptr = buff;
BlockHashState state;
BlockHashReset(state);
if (pitch == row_size)
{
BlockHashAccumulate(state, ptr, pitch * static_cast<u32>(th));
}
else
{
for (int y = 0; y < th; y++, ptr += pitch)
BlockHashAccumulate(state, ptr, row_size);
}
hash = FinishBlockHash(state);
}
// Layer is complete again, regardless of whether the hash matches or not (and we reupload).
const u8 layer_bit = static_cast<u8>(1) << level;
m_complete_layers |= layer_bit;
// Check whether the hash matches. Black textures will be 0, so check the valid bit.
if ((m_valid_hashes & layer_bit) && m_layer_hash[level] == hash)
return;
m_valid_hashes |= layer_bit;
m_layer_hash[level] = hash;
// Upload to GPU.
m_texture->Update(rect, buff, pitch, level);
// And upload the texture.
PreloadTexture(m_TEX0, m_TEXA, m_renderer->m_mem, m_palette != nullptr, m_texture, level);
}
bool GSTextureCache::Source::ClutMatch(const PaletteKey& palette_key)
@@ -2076,8 +2089,8 @@ bool GSTextureCache::Source::ClutMatch(const PaletteKey& palette_key)
// GSTextureCache::Target
GSTextureCache::Target::Target(GSRenderer* r, const GIFRegTEX0& TEX0, u8* temp, const bool depth_supported, const int type)
: Surface(r, temp)
GSTextureCache::Target::Target(GSRenderer* r, const GIFRegTEX0& TEX0, const bool depth_supported, const int type)
: Surface(r)
, m_type(type)
, m_used(false)
, m_depth_supported(depth_supported)
@@ -2219,7 +2232,16 @@ void GSTextureCache::SourceMap::Add(Source* s, const GIFRegTEX0& TEX0, const GSO
void GSTextureCache::SourceMap::RemoveAll()
{
for (auto s : m_surfaces)
{
if (s->m_from_hash_cache)
{
pxAssert(s->m_from_hash_cache->refcount > 0);
if ((--s->m_from_hash_cache->refcount) == 0)
s->m_from_hash_cache->age = 0;
}
delete s;
}
m_surfaces.clear();
@@ -2250,6 +2272,13 @@ void GSTextureCache::SourceMap::RemoveAt(Source* s)
});
}
if (s->m_from_hash_cache)
{
pxAssert(s->m_from_hash_cache->refcount > 0);
if ((--s->m_from_hash_cache->refcount) == 0)
s->m_from_hash_cache->age = 0;
}
delete s;
}
@@ -2419,7 +2448,7 @@ GSTextureCache::SurfaceOffset GSTextureCache::ComputeSurfaceOffset(const Surface
const GSVector4i& r1 = so.b2a_offset;
const GSVector4i& r2 = b_rect;
const GSVector4i ri = r1.rintersect(r2);
[[maybe_unused]] const GSVector4i ri = r1.rintersect(r2);
assert(!so.is_valid || (r1.eq(ri) && r1.x >= 0 && r1.y >= 0 && r1.z > 0 && r1.w > 0));
// Clear cache if size too big.
@@ -2445,6 +2474,31 @@ GSTextureCache::SurfaceOffset GSTextureCache::ComputeSurfaceOffset(const Surface
return so;
}
void GSTextureCache::InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex)
{
auto it = m_hash_cache.find(key);
if (it == m_hash_cache.end())
{
// We must've got evicted before we finished loading. No matter, add it in there anyway;
// if it's not used again, it'll get tossed out later.
const HashCacheEntry entry{tex, 1u, 0u, true};
m_hash_cache.emplace(key, entry).first->second;
return;
}
// Reset age so we don't get thrown out too early.
it->second.age = 0;
// Update memory usage, swap the textures, and recycle the old one for reuse.
if (!it->second.is_replacement)
{
m_hash_cache_memory_usage -= it->second.texture->GetMemUsage();
it->second.is_replacement = true;
}
it->second.texture->Swap(tex);
g_gs_device->Recycle(tex);
}
// GSTextureCache::Palette
GSTextureCache::Palette::Palette(const GSRenderer* renderer, u16 pal, bool need_gs_texture)
@@ -2640,3 +2694,190 @@ bool GSTextureCache::SurfaceOffsetKeyEqual::operator()(const GSTextureCache::Sur
}
return true;
}
using BlockHashState = XXH3_state_t;
__fi static void BlockHashReset(BlockHashState& st)
{
XXH3_64bits_reset(&st);
}
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp)
{
XXH3_64bits_update(&st, bp, BLOCK_SIZE);
}
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp, u32 size)
{
XXH3_64bits_update(&st, bp, size);
}
__fi static GSTextureCache::HashType FinishBlockHash(BlockHashState& st)
{
return XXH3_64bits_digest(&st);
}
static void HashTextureLevel(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, BlockHashState& hash_st, u8* temp)
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
// From GSLocalMemory foreachBlock(), used for reading textures.
// We want to hash the exact same blocks here.
const GSVector4i rect(0, 0, tw, th);
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
GSLocalMemory& mem = renderer->m_mem;
GSOffset off = mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM);
// For textures which are smaller than the block size, we expand and then hash.
// This is because otherwise we get the padding bytes, which can be random junk.
if (tw < bs.x || th < bs.y)
{
// Expand texture indices. Align to 32 bytes for AVX2.
const u32 pitch = Common::AlignUpPow2(static_cast<u32>(block_rect.w), 32);
const u32 row_size = static_cast<u32>(tw);
const GSLocalMemory::readTexture rtx = psm.rtxP;
// Use temp buffer for expanding, since we may not need to update.
(renderer->m_mem.*rtx)(off, block_rect, temp, pitch, TEXA);
// Hash the expanded texture.
u8* ptr = temp;
if (pitch == row_size)
{
BlockHashAccumulate(hash_st, ptr, pitch * static_cast<u32>(th));
}
else
{
for (int y = 0; y < th; y++, ptr += pitch)
BlockHashAccumulate(hash_st, ptr, row_size);
}
}
else
{
BlockHashReset(hash_st);
GSOffset::BNHelper bn = off.bnMulti(block_rect.left, block_rect.top);
const int right = block_rect.right >> off.blockShiftX();
const int bottom = block_rect.bottom >> off.blockShiftY();
const int xAdd = (1 << off.blockShiftX()) * (psm.bpp / 8);
for (; bn.blkY() < bottom; bn.nextBlockY())
{
for (int x = 0; bn.blkX() < right; bn.nextBlockX(), x += xAdd)
{
BlockHashAccumulate(hash_st, mem.BlockPtr(bn.value()));
}
}
}
}
GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA)
{
BlockHashState hash_st;
BlockHashReset(hash_st);
HashTextureLevel(renderer, TEX0, TEXA, hash_st, m_temp);
return FinishBlockHash(hash_st);
}
void GSTextureCache::PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level)
{
// m_TEX0 is adjusted for mips (messy, should be changed).
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
// Expand texture/apply palette.
const GSVector4i rect(0, 0, tw, th);
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
const GSOffset off(mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM));
const int read_width = std::max(tw, psm.bs.x);
u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
u32 row_size = static_cast<u32>(tw) * sizeof(u32);
GSLocalMemory::readTexture rtx = psm.rtx;
if (paltex)
{
pitch >>= 2;
row_size >>= 2;
rtx = psm.rtxP;
}
// If we can stream it directly to GPU memory, do so, otherwise go through a temp buffer.
GSTexture::GSMap map;
if (rect.eq(block_rect) && tex->Map(map, &rect, level))
{
(mem.*rtx)(off, block_rect, map.bits, map.pitch, TEXA);
tex->Unmap();
}
else
{
// Align pitch to 32 bytes for AVX2 if we're going through the temp buffer path.
pitch = Common::AlignUpPow2(pitch, 32);
u8* buff = m_temp;
(mem.*rtx)(off, block_rect, buff, pitch, TEXA);
tex->Update(rect, buff, pitch, level);
}
}
GSTextureCache::HashCacheKey::HashCacheKey()
: TEX0Hash(0)
, CLUTHash(0)
{
TEX0.U64 = 0;
TEXA.U64 = 0;
}
GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut, const GSVector2i* lod)
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
HashCacheKey ret;
ret.TEX0.U64 = TEX0.U64 & 0x00000007FFF00000ULL;
ret.TEXA.U64 = (psm.pal == 0 && psm.fmt > 0) ? (TEXA.U64 & 0x000000FF000080FFULL) : 0;
ret.CLUTHash = clut ? GSTextureCache::PaletteKeyHash{}({clut, psm.pal}) : 0;
BlockHashState hash_st;
BlockHashReset(hash_st);
// base level is always hashed
HashTextureLevel(renderer, TEX0, TEXA, hash_st, m_temp);
if (lod)
{
// hash and combine full mipmaps when enabled
const int basemip = lod->x;
const int nmips = lod->y - lod->x + 1;
for (int i = 1; i < nmips; i++)
{
const GIFRegTEX0 MIP_TEX0{renderer->GetTex0Layer(basemip + i)};
HashTextureLevel(renderer, MIP_TEX0, TEXA, hash_st, m_temp);
}
}
ret.TEX0Hash = FinishBlockHash(hash_st);
return ret;
}
GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::WithRemovedCLUTHash() const
{
HashCacheKey ret{*this};
ret.CLUTHash = 0;
return ret;
}
void GSTextureCache::HashCacheKey::RemoveCLUTHash()
{
CLUTHash = 0;
}
u64 GSTextureCache::HashCacheKeyHash::operator()(const HashCacheKey& key) const
{
std::size_t h = 0;
HashCombine(h, key.TEX0Hash, key.CLUTHash, key.TEX0.U64, key.TEXA.U64);
return h;
}
+56 -14
View File
@@ -40,6 +40,40 @@ public:
return valid && overlap;
}
using HashType = u64;
struct HashCacheKey
{
HashType TEX0Hash, CLUTHash;
GIFRegTEX0 TEX0;
GIFRegTEXA TEXA;
HashCacheKey();
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut,
const GSVector2i* lod);
HashCacheKey WithRemovedCLUTHash() const;
void RemoveCLUTHash();
__fi bool operator==(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) == 0; }
__fi bool operator!=(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) != 0; }
__fi bool operator<(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) < 0; }
};
struct HashCacheKeyHash
{
u64 operator()(const HashCacheKey& key) const;
};
struct HashCacheEntry
{
GSTexture* texture;
u32 refcount;
u16 age;
bool is_replacement;
};
class Surface : public GSAlignedClass<32>
{
protected:
@@ -47,16 +81,16 @@ public:
public:
GSTexture* m_texture;
HashCacheEntry* m_from_hash_cache;
GIFRegTEX0 m_TEX0;
GIFRegTEXA m_TEXA;
int m_age;
u8* m_temp;
bool m_32_bits_fmt; // Allow to detect the casting of 32 bits as 16 bits texture
bool m_shared_texture;
u32 m_end_block; // Hint of the surface area.
public:
Surface(GSRenderer* r, u8* temp);
Surface(GSRenderer* r);
virtual ~Surface();
void UpdateAge();
@@ -111,10 +145,6 @@ public:
class Source : public Surface
{
public:
using HashType = u64;
private:
struct
{
GSVector4i* rect;
@@ -122,15 +152,14 @@ public:
} m_write;
void PreloadLevel(int level);
void PreloadSmallLevel(int level);
void Write(const GSVector4i& r, int layer);
void Flush(u32 count, int layer);
public:
std::shared_ptr<Palette> m_palette_obj;
std::unique_ptr<u32[]> m_valid;// each u32 bits map to the 32 blocks of that page
GSTexture* m_palette;
u32 m_valid[MAX_PAGES]; // each u32 bits map to the 32 blocks of that page
GSVector4i m_valid_rect;
u8 m_valid_hashes = 0;
u8 m_complete_layers = 0;
@@ -149,10 +178,10 @@ public:
GSOffset::PageLooper m_pages;
public:
Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, u8* temp, bool dummy_container = false);
Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container = false);
virtual ~Source();
__fi bool CanPreload() const { return (GSConfig.PreloadTexture && CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH)); }
__fi bool CanPreload() const { return CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH); }
void Update(const GSVector4i& rect, int layer = 0);
void UpdateLayer(const GIFRegTEX0& TEX0, const GSVector4i& rect, int layer = 0);
@@ -171,7 +200,7 @@ public:
bool m_dirty_alpha;
public:
Target(GSRenderer* r, const GIFRegTEX0& TEX0, u8* temp, const bool depth_supported, const int type);
Target(GSRenderer* r, const GIFRegTEX0& TEX0, const bool depth_supported, const int type);
void UpdateValidity(const GSVector4i& rect);
@@ -250,9 +279,11 @@ protected:
GSRenderer* m_renderer;
PaletteMap m_palette_map;
SourceMap m_src;
std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash> m_hash_cache;
u64 m_hash_cache_memory_usage = 0;
FastList<Target*> m_dst[2];
bool m_preload_frame;
u8* m_temp;
static u8* m_temp;
bool m_can_convert_depth;
bool m_cpu_fb_conversion;
static bool m_disable_partial_invalidation;
@@ -261,21 +292,29 @@ protected:
constexpr static size_t S_SURFACE_OFFSET_CACHE_MAX_SIZE = std::numeric_limits<u16>::max();
std::unordered_map<SurfaceOffsetKey, SurfaceOffset, SurfaceOffsetKeyHash, SurfaceOffsetKeyEqual> m_surface_offset_cache;
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, bool mipmap = false);
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, const GSVector2i* lod = nullptr);
Target* CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear);
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod);
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
static HashType HashTexture(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
// TODO: virtual void Write(Source* s, const GSVector4i& r) = 0;
// TODO: virtual void Write(Target* t, const GSVector4i& r) = 0;
public:
GSTextureCache(GSRenderer* r);
~GSTextureCache();
__fi u64 GetHashCacheMemoryUsage() const { return m_hash_cache_memory_usage; }
void Read(Target* t, const GSVector4i& r);
void Read(Source* t, const GSVector4i& r);
void RemoveAll();
void RemovePartial();
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool mipmap);
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, const GSVector2i* lod);
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette = false);
Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask = 0, const bool is_frame = false, const int real_h = 0);
@@ -302,4 +341,7 @@ public:
SurfaceOffset ComputeSurfaceOffset(const GSOffset& off, const GSVector4i& r, const Target* t);
SurfaceOffset ComputeSurfaceOffset(const uint32_t bp, const uint32_t bw, const uint32_t psm, const GSVector4i& r, const Target* t);
SurfaceOffset ComputeSurfaceOffset(const SurfaceOffsetKey& sok);
/// Injects a texture into the hash cache, by using GSTexture::Swap(), transitively applying to all sources. Ownership of tex is transferred.
void InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex);
};
@@ -0,0 +1,642 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "common/Align.h"
#include "common/FileSystem.h"
#include "common/StringUtil.h"
#include "common/ScopedGuard.h"
#include "GS/Renderers/HW/GSTextureReplacements.h"
#include <csetjmp>
#include <png.h>
struct LoaderDefinition
{
const char* extension;
GSTextureReplacements::ReplacementTextureLoader loader;
};
static bool PNGLoader(const std::string& filename, GSTextureReplacements::ReplacementTexture* tex, bool only_base_image);
static bool DDSLoader(const std::string& filename, GSTextureReplacements::ReplacementTexture* tex, bool only_base_image);
static constexpr LoaderDefinition s_loaders[] = {
{"png", PNGLoader},
{"dds", DDSLoader},
};
GSTextureReplacements::ReplacementTextureLoader GSTextureReplacements::GetLoader(const std::string_view& filename)
{
const std::string_view extension(FileSystem::GetExtension(filename));
if (extension.empty())
return nullptr;
for (const LoaderDefinition& defn : s_loaders)
{
if (StringUtil::Strncasecmp(extension.data(), defn.extension, extension.size()) == 0)
return defn.loader;
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper routines
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static u32 GetBlockCount(u32 extent, u32 block_size)
{
return std::max(Common::AlignUp(extent, block_size) / block_size, 1u);
}
static void CalcBlockMipmapSize(u32 block_size, u32 bytes_per_block, u32 base_width, u32 base_height, u32 mip, u32& width, u32& height, u32& pitch, u32& size)
{
width = std::max<u32>(base_width >> mip, 1u);
height = std::max<u32>(base_width >> mip, 1u);
const u32 blocks_wide = GetBlockCount(width, block_size);
const u32 blocks_high = GetBlockCount(height, block_size);
// Pitch can't be specified with each mip level, so we have to calculate it ourselves.
pitch = blocks_wide * bytes_per_block;
size = blocks_high * pitch;
}
static void ConvertTexture_X8B8G8R8(u32 width, u32 height, std::vector<u8>& data, u32& pitch)
{
for (u32 row = 0; row < height; row++)
{
u8* data_ptr = data.data() + row * pitch;
for (u32 x = 0; x < width; x++)
{
// Set alpha channel to full intensity.
data_ptr[3] = 0x80;
data_ptr += sizeof(u32);
}
}
}
static void ConvertTexture_A8R8G8B8(u32 width, u32 height, std::vector<u8>& data, u32& pitch)
{
for (u32 row = 0; row < height; row++)
{
u8* data_ptr = data.data() + row * pitch;
for (u32 x = 0; x < width; x++)
{
// Byte swap ABGR -> RGBA
u32 val;
std::memcpy(&val, data_ptr, sizeof(val));
val = ((val & 0xFF00FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000));
std::memcpy(data_ptr, &val, sizeof(u32));
data_ptr += sizeof(u32);
}
}
}
static void ConvertTexture_X8R8G8B8(u32 width, u32 height, std::vector<u8>& data, u32& pitch)
{
for (u32 row = 0; row < height; row++)
{
u8* data_ptr = data.data() + row * pitch;
for (u32 x = 0; x < width; x++)
{
// Byte swap XBGR -> RGBX, and set alpha to full intensity.
u32 val;
std::memcpy(&val, data_ptr, sizeof(val));
val = ((val & 0x0000FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000)) | 0xFF000000;
std::memcpy(data_ptr, &val, sizeof(u32));
data_ptr += sizeof(u32);
}
}
}
static void ConvertTexture_R8G8B8(u32 width, u32 height, std::vector<u8>& data, u32& pitch)
{
const u32 new_pitch = width * sizeof(u32);
std::vector<u8> new_data(new_pitch * height);
for (u32 row = 0; row < height; row++)
{
const u8* rgb_data_ptr = data.data() + row * pitch;
u8* data_ptr = new_data.data() + row * new_pitch;
for (u32 x = 0; x < width; x++)
{
// This is BGR in memory.
u32 val;
std::memcpy(&val, rgb_data_ptr, sizeof(val));
val = ((val & 0x0000FF00) | ((val >> 16) & 0xFF) | ((val << 16) & 0xFF0000)) | 0xFF000000;
std::memcpy(data_ptr, &val, sizeof(u32));
data_ptr += sizeof(u32);
rgb_data_ptr += 3;
}
}
data = std::move(new_data);
pitch = new_pitch;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PNG Handlers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool PNGLoader(const std::string& filename, GSTextureReplacements::ReplacementTexture* tex, bool only_base_image)
{
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr)
return false;
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
return false;
}
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
});
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "rb");
if (!fp)
return false;
if (setjmp(png_jmpbuf(png_ptr)))
return false;
png_init_io(png_ptr, fp.get());
png_read_info(png_ptr, info_ptr);
png_uint_32 width = 0;
png_uint_32 height = 0;
int bitDepth = 0;
int colorType = -1;
if (png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, nullptr, nullptr, nullptr) != 1 ||
width == 0 || height == 0)
{
return false;
}
const u32 pitch = width * sizeof(u32);
tex->width = width;
tex->height = height;
tex->format = GSTexture::Format::Color;
tex->pitch = pitch;
tex->data.resize(pitch * height);
const png_uint_32 row_bytes = png_get_rowbytes(png_ptr, info_ptr);
std::vector<u8> row_data(row_bytes);
for (u32 y = 0; y < height; y++)
{
png_read_row(png_ptr, static_cast<png_bytep>(row_data.data()), nullptr);
const u8* row_ptr = row_data.data();
u8* out_ptr = tex->data.data() + y * pitch;
if (colorType == PNG_COLOR_TYPE_RGB)
{
for (u32 x = 0; x < width; x++)
{
u32 pixel = static_cast<u32>(*(row_ptr)++);
pixel |= static_cast<u32>(*(row_ptr)++) << 8;
pixel |= static_cast<u32>(*(row_ptr)++) << 16;
pixel |= 0x80000000u; // make opaque
std::memcpy(out_ptr, &pixel, sizeof(pixel));
out_ptr += sizeof(pixel);
}
}
else if (colorType == PNG_COLOR_TYPE_RGBA)
{
std::memcpy(out_ptr, row_ptr, pitch);
}
}
return true;
}
bool GSTextureReplacements::SavePNGImage(const std::string& filename, u32 width, u32 height, const u8* buffer, u32 pitch)
{
const int compression = theApp.GetConfigI("png_compression_level");
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr)
return false;
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
png_destroy_write_struct(&png_ptr, nullptr);
return false;
}
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
png_destroy_write_struct(&png_ptr, &info_ptr);
});
if (setjmp(png_jmpbuf(png_ptr)))
return false;
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
if (!fp)
return false;
png_init_io(png_ptr, fp.get());
png_set_compression_level(png_ptr, compression);
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
png_set_swap(png_ptr);
for (u32 y = 0; y < height; ++y)
{
// cast is needed here for mac builder
png_write_row(png_ptr, (png_bytep)(buffer + y * pitch));
}
png_write_end(png_ptr, nullptr);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DDS Handler
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// From https://raw.githubusercontent.com/Microsoft/DirectXTex/master/DirectXTex/DDS.h
//
// This header defines constants and structures that are useful when parsing
// DDS files. DDS files were originally designed to use several structures
// and constants that are native to DirectDraw and are defined in ddraw.h,
// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar
// (compatible) constants and structures so that one can use DDS files
// without needing to include ddraw.h.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
#pragma pack(push, 1)
static constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS "
struct DDS_PIXELFORMAT
{
uint32_t dwSize;
uint32_t dwFlags;
uint32_t dwFourCC;
uint32_t dwRGBBitCount;
uint32_t dwRBitMask;
uint32_t dwGBitMask;
uint32_t dwBBitMask;
uint32_t dwABitMask;
};
#define DDS_FOURCC 0x00000004 // DDPF_FOURCC
#define DDS_RGB 0x00000040 // DDPF_RGB
#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS
#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
#define DDS_ALPHA 0x00000002 // DDPF_ALPHA
#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8
#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS
#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | ((uint32_t)(uint8_t)(ch2) << 16) | \
((uint32_t)(uint8_t)(ch3) << 24))
#endif /* defined(MAKEFOURCC) */
#define DDS_HEADER_FLAGS_TEXTURE \
0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT
#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH
#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH
#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE
// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION
enum DDS_RESOURCE_DIMENSION
{
DDS_DIMENSION_TEXTURE1D = 2,
DDS_DIMENSION_TEXTURE2D = 3,
DDS_DIMENSION_TEXTURE3D = 4,
};
struct DDS_HEADER
{
uint32_t dwSize;
uint32_t dwFlags;
uint32_t dwHeight;
uint32_t dwWidth;
uint32_t dwPitchOrLinearSize;
uint32_t dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwFlags
uint32_t dwMipMapCount;
uint32_t dwReserved1[11];
DDS_PIXELFORMAT ddspf;
uint32_t dwCaps;
uint32_t dwCaps2;
uint32_t dwCaps3;
uint32_t dwCaps4;
uint32_t dwReserved2;
};
struct DDS_HEADER_DXT10
{
uint32_t dxgiFormat;
uint32_t resourceDimension;
uint32_t miscFlag; // see DDS_RESOURCE_MISC_FLAG
uint32_t arraySize;
uint32_t miscFlags2; // see DDS_MISC_FLAGS2
};
#pragma pack(pop)
static_assert(sizeof(DDS_HEADER) == 124, "DDS Header size mismatch");
static_assert(sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch");
constexpr DDS_PIXELFORMAT DDSPF_A8R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000};
constexpr DDS_PIXELFORMAT DDSPF_X8R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
constexpr DDS_PIXELFORMAT DDSPF_A8B8G8R8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000};
constexpr DDS_PIXELFORMAT DDSPF_X8B8G8R8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000};
constexpr DDS_PIXELFORMAT DDSPF_R8G8B8 = {
sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000};
// End of Microsoft code from DDS.h.
static bool DDSPixelFormatMatches(const DDS_PIXELFORMAT& pf1, const DDS_PIXELFORMAT& pf2)
{
return std::tie(pf1.dwSize, pf1.dwFlags, pf1.dwFourCC, pf1.dwRGBBitCount, pf1.dwRBitMask,
pf1.dwGBitMask, pf1.dwGBitMask, pf1.dwBBitMask, pf1.dwABitMask) ==
std::tie(pf2.dwSize, pf2.dwFlags, pf2.dwFourCC, pf2.dwRGBBitCount, pf2.dwRBitMask,
pf2.dwGBitMask, pf2.dwGBitMask, pf2.dwBBitMask, pf2.dwABitMask);
}
struct DDSLoadInfo
{
u32 block_size = 1;
u32 bytes_per_block = 4;
u32 width = 0;
u32 height = 0;
u32 mip_count = 0;
GSTexture::Format format = GSTexture::Format::Color;
s64 base_image_offset = 0;
u32 base_image_size = 0;
u32 base_image_pitch = 0;
std::function<void(u32 width, u32 height, std::vector<u8>& data, u32& pitch)> conversion_function;
};
static bool ParseDDSHeader(std::FILE* fp, DDSLoadInfo* info)
{
u32 magic;
if (std::fread(&magic, sizeof(magic), 1, fp) != 1 || magic != DDS_MAGIC)
return false;
DDS_HEADER header;
u32 header_size = sizeof(header);
if (std::fread(&header, header_size, 1, fp) != 1 || header.dwSize < header_size)
return false;
// Required fields.
if ((header.dwFlags & DDS_HEADER_FLAGS_TEXTURE) != DDS_HEADER_FLAGS_TEXTURE)
return false;
// Image should be 2D.
if (header.dwFlags & DDS_HEADER_FLAGS_VOLUME)
return false;
// Presence of width/height fields is already tested by DDS_HEADER_FLAGS_TEXTURE.
info->width = header.dwWidth;
info->height = header.dwHeight;
if (info->width == 0 || info->height == 0)
return false;
// Check for mip levels.
if (header.dwFlags & DDS_HEADER_FLAGS_MIPMAP)
{
info->mip_count = header.dwMipMapCount;
if (header.dwMipMapCount != 0)
info->mip_count = header.dwMipMapCount;
else
info->mip_count = GSTextureReplacements::CalcMipmapLevelsForReplacement(info->width, info->height);
}
else
{
info->mip_count = 1;
}
// Handle fourcc formats vs uncompressed formats.
const bool has_fourcc = (header.ddspf.dwFlags & DDS_FOURCC) != 0;
if (has_fourcc)
{
// Handle DX10 extension header.
u32 dxt10_format = 0;
if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', '1', '0'))
{
DDS_HEADER_DXT10 dxt10_header;
if (std::fread(&dxt10_header, sizeof(dxt10_header), 1, fp) != 1)
return false;
// Can't handle array textures here. Doesn't make sense to use them, anyway.
if (dxt10_header.resourceDimension != DDS_DIMENSION_TEXTURE2D || dxt10_header.arraySize != 1)
return false;
header_size += sizeof(dxt10_header);
dxt10_format = dxt10_header.dxgiFormat;
}
const GSDevice::FeatureSupport features(g_gs_device->Features());
if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '1') || dxt10_format == 71)
{
info->format = GSTexture::Format::BC1;
info->block_size = 4;
info->bytes_per_block = 8;
if (!features.dxt_textures)
return false;
}
else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '2') || header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '3') || dxt10_format == 74)
{
info->format = GSTexture::Format::BC2;
info->block_size = 4;
info->bytes_per_block = 16;
if (!features.dxt_textures)
return false;
}
else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '4') || header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '5') || dxt10_format == 77)
{
info->format = GSTexture::Format::BC3;
info->block_size = 4;
info->bytes_per_block = 16;
if (!features.dxt_textures)
return false;
}
else if (dxt10_format == 98)
{
info->format = GSTexture::Format::BC7;
info->block_size = 4;
info->bytes_per_block = 16;
if (!features.bptc_textures)
return false;
}
else
{
// Leave all remaining formats to SOIL.
return false;
}
}
else
{
if (DDSPixelFormatMatches(header.ddspf, DDSPF_A8R8G8B8))
{
info->conversion_function = ConvertTexture_A8R8G8B8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_X8R8G8B8))
{
info->conversion_function = ConvertTexture_X8R8G8B8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_X8B8G8R8))
{
info->conversion_function = ConvertTexture_X8B8G8R8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_R8G8B8))
{
info->conversion_function = ConvertTexture_R8G8B8;
}
else if (DDSPixelFormatMatches(header.ddspf, DDSPF_A8B8G8R8))
{
// This format is already in RGBA order, so no conversion necessary.
}
else
{
return false;
}
// All these formats are RGBA, just with byte swapping.
info->format = GSTexture::Format::Color;
info->block_size = 1;
info->bytes_per_block = header.ddspf.dwRGBBitCount / 8;
}
// Mip levels smaller than the block size are padded to multiples of the block size.
const u32 blocks_wide = GetBlockCount(info->width, info->block_size);
const u32 blocks_high = GetBlockCount(info->height, info->block_size);
// Pitch can be specified in the header, otherwise we can derive it from the dimensions. For
// compressed formats, both DDS_HEADER_FLAGS_LINEARSIZE and DDS_HEADER_FLAGS_PITCH should be
// set. See https://msdn.microsoft.com/en-us/library/windows/desktop/bb943982(v=vs.85).aspx
if (header.dwFlags & DDS_HEADER_FLAGS_PITCH && header.dwFlags & DDS_HEADER_FLAGS_LINEARSIZE)
{
// Convert pitch (in bytes) to texels/row length.
if (header.dwPitchOrLinearSize < info->bytes_per_block)
{
// Likely a corrupted or invalid file.
return false;
}
info->base_image_pitch = header.dwPitchOrLinearSize;
info->base_image_size = info->base_image_pitch * blocks_high;
}
else
{
// Assume no padding between rows of blocks.
info->base_image_pitch = blocks_wide * info->bytes_per_block;
info->base_image_size = info->base_image_pitch * blocks_high;
}
// Check for truncated or corrupted files.
info->base_image_offset = sizeof(magic) + header_size;
if (info->base_image_offset >= FileSystem::FSize64(fp))
return false;
return true;
}
static bool ReadDDSMipLevel(std::FILE* fp, const std::string& filename, u32 mip_level, const DDSLoadInfo& info, u32 width, u32 height, std::vector<u8>& data, u32& pitch, u32 size)
{
// D3D11 cannot handle block compressed textures where the first mip level is
// not a multiple of the block size.
if (mip_level == 0 && info.block_size > 1 &&
((width % info.block_size) != 0 || (height % info.block_size) != 0))
{
Console.Error(
"Invalid dimensions for DDS texture %s. For compressed textures of this format, "
"the width/height of the first mip level must be a multiple of %u.",
filename.c_str(), info.block_size);
return false;
}
data.resize(size);
if (std::fread(data.data(), size, 1, fp) != 1)
return false;
// Apply conversion function for uncompressed textures.
if (info.conversion_function)
info.conversion_function(width, height, data, pitch);
return true;
}
bool DDSLoader(const std::string& filename, GSTextureReplacements::ReplacementTexture* tex, bool only_base_image)
{
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "rb");
if (!fp)
return false;
DDSLoadInfo info;
if (!ParseDDSHeader(fp.get(), &info))
return false;
// always load the base image
if (FileSystem::FSeek64(fp.get(), info.base_image_offset, SEEK_SET) != 0)
return false;
tex->format = info.format;
tex->width = info.width;
tex->height = info.height;
tex->pitch = info.base_image_pitch;
if (!ReadDDSMipLevel(fp.get(), filename, 0, info, tex->width, tex->height, tex->data, tex->pitch, info.base_image_size))
return false;
// Read in any remaining mip levels in the file.
if (!only_base_image)
{
for (u32 level = 1; level <= info.mip_count; level++)
{
GSTextureReplacements::ReplacementTexture::MipData md;
u32 mip_width, mip_height, mip_size;
CalcBlockMipmapSize(info.block_size, info.bytes_per_block, info.width, info.height, level, mip_width, mip_height, md.pitch, mip_size);
if (!ReadDDSMipLevel(fp.get(), filename, level, info, mip_width, mip_height, md.data, md.pitch, mip_size))
break;
tex->mips.push_back(std::move(md));
}
}
return true;
}
@@ -0,0 +1,684 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "common/HashCombine.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "common/ScopedGuard.h"
#include "Config.h"
#include "GS/GSLocalMemory.h"
#include "GS/Renderers/HW/GSTextureReplacements.h"
#ifndef PCSX2_CORE
#include "gui/AppCoreThread.h"
#else
#include "VMManager.h"
#endif
#include <cinttypes>
#include <cstring>
#include <functional>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <tuple>
#include <thread>
// this is a #define instead of a variable to avoid warnings from non-literal format strings
#define TEXTURE_FILENAME_FORMAT_STRING "%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-%08x"
#define TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME "replacements"
#define TEXTURE_DUMP_SUBDIRECTORY_NAME "dumps"
namespace
{
struct TextureName // 24 bytes
{
u64 TEX0Hash;
u64 CLUTHash;
union
{
struct
{
u32 TEX0_PSM : 6;
u32 TEX0_TW : 4;
u32 TEX0_TH : 4;
u32 TEX0_TCC : 1;
u32 TEXA_TA0 : 8;
u32 TEXA_AEM : 1;
u32 TEXA_TA1 : 8;
};
u32 bits;
};
u32 miplevel;
__fi u32 Width() const { return (1u << TEX0_TW); }
__fi u32 Height() const { return (1u << TEX0_TH); }
__fi bool HasPalette() const { return (GSLocalMemory::m_psm[TEX0_PSM].pal > 0); }
__fi GSVector2 ReplacementScale(const GSTextureReplacements::ReplacementTexture& rtex) const
{
return ReplacementScale(rtex.width, rtex.height);
}
__fi GSVector2 ReplacementScale(u32 rwidth, u32 rheight) const
{
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()), static_cast<float>(rheight) / static_cast<float>(Height()));
}
__fi bool operator==(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) == std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator!=(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) != std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator<(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) < std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
};
static_assert(sizeof(TextureName) == 24, "ReplacementTextureName is expected size");
} // namespace
namespace std
{
template <>
struct hash<TextureName>
{
std::size_t operator()(const TextureName& val) const
{
std::size_t h = 0;
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.bits, val.miplevel);
return h;
}
};
} // namespace std
namespace GSTextureReplacements
{
static TextureName CreateTextureName(const GSTextureCache::HashCacheKey& hash, u32 miplevel);
static GSTextureCache::HashCacheKey HashCacheKeyFromTextureName(const TextureName& tn);
static std::optional<TextureName> ParseReplacementName(const std::string& filename);
static std::string GetGameTextureDirectory();
static std::string GetDumpFilename(const TextureName& name, u32 level);
static std::string GetGameSerial();
static std::optional<ReplacementTexture> LoadReplacementTexture(const TextureName& name, const std::string& filename, bool only_base_image);
static void QueueAsyncReplacementTextureLoad(const TextureName& name, const std::string& filename, bool mipmap);
static void PrecacheReplacementTextures();
static void ClearReplacementTextures();
static void StartWorkerThread();
static void StopWorkerThread();
static void QueueWorkerThreadItem(std::function<void()> fn);
static void WorkerThreadEntryPoint();
static void SyncWorkerThread();
static void CancelPendingLoadsAndDumps();
static std::string s_current_serial;
/// Backreference to the texture cache so we can inject replacements.
static GSTextureCache* s_tc;
/// Textures that have been dumped, to save stat() calls.
static std::unordered_set<TextureName> s_dumped_textures;
/// Lookup map of texture names to replacements, if they exist.
static std::unordered_map<TextureName, std::string> s_replacement_texture_filenames;
/// Lookup map of texture names to replacement data which has been cached.
static std::unordered_map<TextureName, ReplacementTexture> s_replacement_texture_cache;
static std::mutex s_replacement_texture_cache_mutex;
/// List of textures that are pending asynchronous load.
static std::unordered_set<TextureName> s_pending_async_load_textures;
/// List of textures that we have asynchronously loaded and can now be injected back into the TC.
/// Second element is whether the texture should be created with mipmaps.
static std::vector<std::pair<TextureName, bool>> s_async_loaded_textures;
/// Loader/dumper thread.
static std::thread s_worker_thread;
static std::mutex s_worker_thread_mutex;
static std::condition_variable s_worker_thread_cv;
static std::queue<std::function<void()>> s_worker_thread_queue;
static bool s_worker_thread_running = false;
}; // namespace GSTextureReplacements
TextureName GSTextureReplacements::CreateTextureName(const GSTextureCache::HashCacheKey& hash, u32 miplevel)
{
TextureName name;
name.bits = 0;
name.TEX0_PSM = hash.TEX0.PSM;
name.TEX0_TW = hash.TEX0.TW;
name.TEX0_TH = hash.TEX0.TH;
name.TEX0_TCC = hash.TEX0.TCC;
name.TEXA_TA0 = hash.TEXA.TA0;
name.TEXA_AEM = hash.TEXA.AEM;
name.TEXA_TA1 = hash.TEXA.TA1;
name.TEX0Hash = hash.TEX0Hash;
name.CLUTHash = name.HasPalette() ? hash.CLUTHash : 0;
name.miplevel = miplevel;
return name;
}
GSTextureCache::HashCacheKey GSTextureReplacements::HashCacheKeyFromTextureName(const TextureName& tn)
{
GSTextureCache::HashCacheKey key = {};
key.TEX0.PSM = tn.TEX0_PSM;
key.TEX0.TW = tn.TEX0_TW;
key.TEX0.TH = tn.TEX0_TH;
key.TEX0.TCC = tn.TEX0_TCC;
key.TEXA.TA0 = tn.TEXA_TA0;
key.TEXA.AEM = tn.TEXA_AEM;
key.TEXA.TA1 = tn.TEXA_TA1;
key.TEX0Hash = tn.TEX0Hash;
key.CLUTHash = tn.HasPalette() ? tn.CLUTHash : 0;
return key;
}
std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std::string& filename)
{
TextureName ret;
ret.miplevel = 0;
// TODO(Stenzek): Make this better.
char extension_dot;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits, &extension_dot) != 4 || extension_dot != '.')
{
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) != 3 || extension_dot != '.')
return std::nullopt;
}
return ret;
}
std::string GSTextureReplacements::GetGameTextureDirectory()
{
return Path::CombineStdString(EmuFolders::Textures, s_current_serial);
}
std::string GSTextureReplacements::GetDumpFilename(const TextureName& name, u32 level)
{
std::string ret;
if (s_current_serial.empty())
return ret;
const std::string game_dir(GetGameTextureDirectory());
if (!FileSystem::DirectoryExists(game_dir.c_str()))
{
// create both dumps and replacements
if (!FileSystem::CreateDirectoryPath(game_dir.c_str(), false) ||
!FileSystem::EnsureDirectoryExists(Path::CombineStdString(game_dir, "dumps").c_str(), false) ||
!FileSystem::EnsureDirectoryExists(Path::CombineStdString(game_dir, "replacements").c_str(), false))
{
// if it fails to create, we're not going to be able to use it anyway
return ret;
}
}
const std::string game_subdir(Path::CombineStdString(game_dir, TEXTURE_DUMP_SUBDIRECTORY_NAME));
if (name.HasPalette())
{
const std::string filename(
(level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.CLUTHash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png", name.TEX0Hash, name.CLUTHash, name.bits));
ret = Path::CombineStdString(game_subdir, filename);
}
else
{
const std::string filename(
(level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits));
ret = Path::CombineStdString(game_subdir, filename);
}
return ret;
}
std::string GSTextureReplacements::GetGameSerial()
{
#ifndef PCSX2_CORE
return StringUtil::wxStringToUTF8String(GameInfo::gameSerial);
#else
return VMManager::GetGameSerial();
#endif
}
void GSTextureReplacements::Initialize(GSTextureCache* tc)
{
s_tc = tc;
s_current_serial = GetGameSerial();
if (GSConfig.DumpReplaceableTextures || GSConfig.LoadTextureReplacements)
StartWorkerThread();
ReloadReplacementMap();
}
void GSTextureReplacements::GameChanged()
{
if (!s_tc)
return;
std::string new_serial(GetGameSerial());
if (s_current_serial == new_serial)
return;
s_current_serial = std::move(new_serial);
ReloadReplacementMap();
ClearDumpedTextureList();
}
void GSTextureReplacements::ReloadReplacementMap()
{
SyncWorkerThread();
// clear out the caches
{
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
s_replacement_texture_cache.clear();
s_replacement_texture_filenames.clear();
s_pending_async_load_textures.clear();
s_async_loaded_textures.clear();
}
// can't replace bios textures.
if (s_current_serial.empty() || !GSConfig.LoadTextureReplacements)
return;
const std::string replacement_dir(Path::CombineStdString(GetGameTextureDirectory(), TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME));
FileSystem::FindResultsArray files;
if (!FileSystem::FindFiles(replacement_dir.c_str(), "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RECURSIVE, &files))
return;
std::string filename;
for (FILESYSTEM_FIND_DATA& fd : files)
{
// file format we can handle?
filename = FileSystem::GetFileNameFromPath(fd.FileName);
if (!GetLoader(filename))
continue;
// parse the name if it's valid
std::optional<TextureName> name = ParseReplacementName(filename);
if (!name.has_value())
continue;
DevCon.WriteLn("Found %ux%u replacement '%*s'", name->Width(), name->Height(), static_cast<int>(filename.size()), filename.data());
s_replacement_texture_filenames.emplace(std::move(name.value()), std::move(fd.FileName));
}
if (GSConfig.PrecacheTextureReplacements)
PrecacheReplacementTextures();
}
void GSTextureReplacements::UpdateConfig(Pcsx2Config::GSOptions& old_config)
{
// get rid of worker thread if it's no longer needed
if (s_worker_thread_running && !GSConfig.DumpReplaceableTextures && !GSConfig.LoadTextureReplacements)
StopWorkerThread();
if (!s_worker_thread_running && (GSConfig.DumpReplaceableTextures || GSConfig.LoadTextureReplacements))
StartWorkerThread();
if ((!GSConfig.DumpReplaceableTextures && old_config.DumpReplaceableTextures) ||
(!GSConfig.LoadTextureReplacements && old_config.LoadTextureReplacements))
{
CancelPendingLoadsAndDumps();
}
if (GSConfig.LoadTextureReplacements && !old_config.LoadTextureReplacements)
ReloadReplacementMap();
else if (!GSConfig.LoadTextureReplacements && old_config.LoadTextureReplacements)
ClearReplacementTextures();
if (!GSConfig.DumpReplaceableTextures && old_config.DumpReplaceableTextures)
ClearDumpedTextureList();
if (GSConfig.LoadTextureReplacements && GSConfig.PrecacheTextureReplacements && !old_config.PrecacheTextureReplacements)
PrecacheReplacementTextures();
}
void GSTextureReplacements::Shutdown()
{
StopWorkerThread();
std::string().swap(s_current_serial);
ClearReplacementTextures();
ClearDumpedTextureList();
s_tc = nullptr;
}
u32 GSTextureReplacements::CalcMipmapLevelsForReplacement(u32 width, u32 height)
{
return static_cast<u32>(std::log2(std::max(width, height))) + 1u;
}
GSTexture* GSTextureReplacements::LookupReplacementTexture(const GSTextureCache::HashCacheKey& hash, bool mipmap, bool* pending)
{
const TextureName name(CreateTextureName(hash, 0));
*pending = false;
// replacement for this name exists?
auto fnit = s_replacement_texture_filenames.find(name);
if (fnit == s_replacement_texture_filenames.end())
return nullptr;
// try the full cache first, to avoid reloading from disk
{
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
auto it = s_replacement_texture_cache.find(name);
if (it != s_replacement_texture_cache.end())
{
// replacement is cached, can immediately upload to host GPU
return CreateReplacementTexture(it->second, name.ReplacementScale(it->second), mipmap);
}
}
// load asynchronously?
if (GSConfig.LoadTextureReplacementsAsync)
{
// replacement will be injected into the TC later on
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
QueueAsyncReplacementTextureLoad(name, fnit->second, mipmap);
*pending = true;
return nullptr;
}
else
{
// synchronous load
std::optional<ReplacementTexture> replacement(LoadReplacementTexture(name, fnit->second, !mipmap));
if (!replacement.has_value())
return nullptr;
// insert into cache
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
const ReplacementTexture& rtex = s_replacement_texture_cache.emplace(name, std::move(replacement.value())).first->second;
// and upload to gpu
return CreateReplacementTexture(rtex, name.ReplacementScale(rtex), mipmap);
}
}
std::optional<GSTextureReplacements::ReplacementTexture> GSTextureReplacements::LoadReplacementTexture(const TextureName& name, const std::string& filename, bool only_base_image)
{
ReplacementTextureLoader loader = GetLoader(filename);
if (!loader)
return std::nullopt;
ReplacementTexture rtex;
if (!loader(filename.c_str(), &rtex, only_base_image))
return std::nullopt;
return rtex;
}
void GSTextureReplacements::QueueAsyncReplacementTextureLoad(const TextureName& name, const std::string& filename, bool mipmap)
{
// check the pending list, so we don't queue it up multiple times
if (s_pending_async_load_textures.find(name) != s_pending_async_load_textures.end())
return;
s_pending_async_load_textures.insert(name);
QueueWorkerThreadItem([name, filename, mipmap]() {
// actually load the file, this is what will take the time
std::optional<ReplacementTexture> replacement(LoadReplacementTexture(name, filename, !mipmap));
// check the pending set, there's a race here if we disable replacements while loading otherwise
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
if (s_pending_async_load_textures.find(name) == s_pending_async_load_textures.end())
return;
// insert into the cache and queue for later injection
if (replacement.has_value())
{
s_replacement_texture_cache.emplace(name, std::move(replacement.value()));
s_async_loaded_textures.emplace_back(name, mipmap);
}
else
{
// loading failed, so clear it from the pending list
s_pending_async_load_textures.erase(name);
}
});
}
void GSTextureReplacements::PrecacheReplacementTextures()
{
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
// predict whether the requests will come with mipmaps
// TODO: This will be wrong for hw mipmap games like Jak.
const bool mipmap = GSConfig.HWMipmap >= HWMipmapLevel::Basic ||
GSConfig.UserHacks_TriFilter == TriFiltering::Forced;
// pretty simple, just go through the filenames and if any aren't cached, cache them
for (const auto& it : s_replacement_texture_filenames)
{
if (s_replacement_texture_cache.find(it.first) != s_replacement_texture_cache.end())
continue;
// precaching always goes async.. for now
QueueAsyncReplacementTextureLoad(it.first, it.second, mipmap);
}
}
void GSTextureReplacements::ClearReplacementTextures()
{
s_replacement_texture_filenames.clear();
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
s_replacement_texture_cache.clear();
s_pending_async_load_textures.clear();
s_async_loaded_textures.clear();
}
GSTexture* GSTextureReplacements::CreateReplacementTexture(const ReplacementTexture& rtex, const GSVector2& scale, bool mipmap)
{
// can't use generated mipmaps with compressed formats, because they can't be rendered to
// in the future I guess we could decompress the dds and generate them... but there's no reason that modders can't generate mips in dds
if (mipmap && GSTexture::IsCompressedFormat(rtex.format) && rtex.mips.empty())
{
static bool log_once = false;
if (!log_once)
{
Console.Warning("Disabling mipmaps on one or more compressed replacement textures.");
log_once = true;
}
mipmap = false;
}
GSTexture* tex = g_gs_device->CreateTexture(rtex.width, rtex.height, mipmap, rtex.format);
if (!tex)
return nullptr;
// upload base level
tex->Update(GSVector4i(0, 0, rtex.width, rtex.height), rtex.data.data(), rtex.pitch);
// and the mips if they're present in the replacement texture
if (!rtex.mips.empty())
{
for (u32 i = 0; i < static_cast<u32>(rtex.mips.size()); i++)
{
const u32 mip = i + 1;
const u32 mipw = std::max<u32>(rtex.width >> mip, 1u);
const u32 miph = std::max<u32>(rtex.height >> mip, 1u);
tex->Update(GSVector4i(0, 0, mipw, miph), rtex.mips[i].data.data(), rtex.mips[i].pitch, mip);
}
}
tex->SetScale(scale);
return tex;
}
void GSTextureReplacements::ProcessAsyncLoadedTextures()
{
// this holds the lock while doing the upload, but it should be reasonably quick
std::unique_lock<std::mutex> lock(s_replacement_texture_cache_mutex);
for (const auto& [name, mipmap] : s_async_loaded_textures)
{
// no longer pending!
s_pending_async_load_textures.erase(name);
// we should be in the cache now, lock and loaded
auto it = s_replacement_texture_cache.find(name);
if (it == s_replacement_texture_cache.end())
continue;
// upload and inject into TC
GSTexture* tex = CreateReplacementTexture(it->second, name.ReplacementScale(it->second), mipmap);
if (tex)
s_tc->InjectHashCacheTexture(HashCacheKeyFromTextureName(name), tex);
}
s_async_loaded_textures.clear();
}
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level)
{
// check if it's been dumped already
const TextureName name(CreateTextureName(hash, level));
if (s_dumped_textures.find(name) != s_dumped_textures.end())
return;
s_dumped_textures.insert(name);
// already exists on disk?
std::string filename(GetDumpFilename(name, level));
if (filename.empty() || FileSystem::FileExists(filename.c_str()))
return;
const std::string_view title(FileSystem::GetFileTitleFromPath(filename));
DevCon.WriteLn("Dumping %ux%u texture '%*s'.", name.Width(), name.Height(), static_cast<int>(title.size()), title.data());
// compute width/height
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
const GSVector4i rect(0, 0, tw, th);
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
const int read_width = std::max(tw, psm.bs.x);
const int read_height = std::max(th, psm.bs.y);
const u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
// use per-texture buffer so we can compress the texture asynchronously and not block the GS thread
std::vector<u8> buffer(pitch * static_cast<u32>(read_height));
(mem.*psm.rtx)(mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM), block_rect, buffer.data(), pitch, TEXA);
// okay, now we can actually dump it
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer)]() {
if (!SavePNGImage(filename.c_str(), tw, th, buffer.data(), pitch))
Console.Error("Failed to dump texture to '%s'.", filename.c_str());
});
}
void GSTextureReplacements::ClearDumpedTextureList()
{
s_dumped_textures.clear();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Worker Thread
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GSTextureReplacements::StartWorkerThread()
{
std::unique_lock<std::mutex> lock(s_worker_thread_mutex);
if (s_worker_thread.joinable())
return;
s_worker_thread_running = true;
s_worker_thread = std::thread(WorkerThreadEntryPoint);
}
void GSTextureReplacements::StopWorkerThread()
{
{
std::unique_lock<std::mutex> lock(s_worker_thread_mutex);
if (!s_worker_thread.joinable())
return;
s_worker_thread_running = false;
s_worker_thread_cv.notify_one();
}
s_worker_thread.join();
// clear out workery-things too
CancelPendingLoadsAndDumps();
}
void GSTextureReplacements::QueueWorkerThreadItem(std::function<void()> fn)
{
pxAssert(s_worker_thread.joinable());
std::unique_lock<std::mutex> lock(s_worker_thread_mutex);
s_worker_thread_queue.push(std::move(fn));
s_worker_thread_cv.notify_one();
}
void GSTextureReplacements::WorkerThreadEntryPoint()
{
std::unique_lock<std::mutex> lock(s_worker_thread_mutex);
while (s_worker_thread_running)
{
if (s_worker_thread_queue.empty())
{
s_worker_thread_cv.wait(lock);
continue;
}
std::function<void()> fn = std::move(s_worker_thread_queue.front());
s_worker_thread_queue.pop();
lock.unlock();
fn();
lock.lock();
}
}
void GSTextureReplacements::SyncWorkerThread()
{
std::unique_lock<std::mutex> lock(s_worker_thread_mutex);
if (!s_worker_thread.joinable())
return;
// not the most efficient by far, but it only gets called on config changes, so whatever
for (;;)
{
if (s_worker_thread_queue.empty())
break;
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
lock.lock();
}
}
void GSTextureReplacements::CancelPendingLoadsAndDumps()
{
std::unique_lock<std::mutex> lock(s_worker_thread_mutex);
if (!s_worker_thread.joinable())
return;
while (!s_worker_thread_queue.empty())
s_worker_thread_queue.pop();
s_async_loaded_textures.clear();
s_pending_async_load_textures.clear();
}
@@ -0,0 +1,60 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "GS/Renderers/HW/GSTextureCache.h"
namespace GSTextureReplacements
{
struct ReplacementTexture
{
u32 width;
u32 height;
GSTexture::Format format;
u32 pitch;
std::vector<u8> data;
struct MipData
{
u32 pitch;
std::vector<u8> data;
};
std::vector<MipData> mips;
};
void Initialize(GSTextureCache* tc);
void GameChanged();
void ReloadReplacementMap();
void UpdateConfig(Pcsx2Config::GSOptions& old_config);
void Shutdown();
u32 CalcMipmapLevelsForReplacement(u32 width, u32 height);
GSTexture* LookupReplacementTexture(const GSTextureCache::HashCacheKey& hash, bool mipmap, bool* pending);
GSTexture* CreateReplacementTexture(const ReplacementTexture& rtex, const GSVector2& scale, bool mipmap);
void ProcessAsyncLoadedTextures();
void DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level);
void ClearDumpedTextureList();
/// Loader will take a filename and interpret the format (e.g. DDS, PNG, etc).
using ReplacementTextureLoader = bool (*)(const std::string& filename, GSTextureReplacements::ReplacementTexture* tex, bool only_base_image);
ReplacementTextureLoader GetLoader(const std::string_view& filename);
/// Saves an image buffer to a PNG file (for dumping).
bool SavePNGImage(const std::string& filename, u32 width, u32 height, const u8* buffer, u32 pitch);
} // namespace GSTextureReplacements
@@ -29,6 +29,12 @@ GSTextureNull::GSTextureNull(Type type, int w, int h, GSTexture::Format format)
m_desc.format = format;
}
void GSTextureNull::Swap(GSTexture* tex)
{
GSTexture::Swap(tex);
std::swap(m_desc, static_cast<GSTextureNull*>(tex)->m_desc);
}
void* GSTextureNull::GetNativeHandle() const
{
return nullptr;
+1
View File
@@ -37,5 +37,6 @@ public:
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override { return false; }
void Unmap() override {}
bool Save(const std::string& fn) override { return false; }
void Swap(GSTexture* tex) override;
void* GetNativeHandle() const override;
};
+7 -9
View File
@@ -83,6 +83,12 @@ namespace Emulate_DSA
glTexSubImage2D(GL_TEXTURE_2D, level, xoffset, yoffset, width, height, format, type, pixels);
}
void APIENTRY CompressedTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data)
{
BindTextureUnit(7, texture);
glCompressedTexSubImage2D(GL_TEXTURE_2D, level, xoffset, yoffset, width, height, format, imageSize, data);
}
void APIENTRY GetTexureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels)
{
BindTextureUnit(7, texture);
@@ -121,6 +127,7 @@ namespace Emulate_DSA
glCreateTextures = CreateTexture;
glTextureStorage2D = TextureStorage;
glTextureSubImage2D = TextureSubImage;
glCompressedTextureSubImage2D = CompressedTextureSubImage;
glGetTextureImage = GetTexureImage;
glTextureParameteri = TextureParameteri;
@@ -157,11 +164,6 @@ namespace GLLoader
bool found_compatible_GL_ARB_sparse_texture2 = false;
bool found_compatible_sparse_depth = false;
// Not yet used
#ifdef GL_EXT_TEX_SUB_IMAGE
bool found_GL_ARB_get_texture_sub_image = false;
#endif
static bool mandatory(const std::string& ext)
{
if (!GLExtension::Has(ext))
@@ -284,10 +286,6 @@ namespace GLLoader
// Mandatory for the advance HW renderer effect. Unfortunately Mesa LLVMPIPE/SWR renderers doesn't support this extension.
// Rendering might be corrupted but it could be good enough for test/virtual machine.
optional("GL_ARB_texture_barrier");
// Not yet used
#ifdef GL_EXT_TEX_SUB_IMAGE
found_GL_ARB_get_texture_sub_image = optional("GL_ARB_get_texture_sub_image");
#endif
}
if (vendor_id_amd)
+2 -12
View File
@@ -51,18 +51,6 @@ GSDeviceOGL::GSDeviceOGL()
, m_fbo_read(0)
, m_palette_ss(0)
{
memset(&m_merge_obj, 0, sizeof(m_merge_obj));
memset(&m_interlace, 0, sizeof(m_interlace));
memset(&m_convert, 0, sizeof(m_convert));
memset(&m_fxaa, 0, sizeof(m_fxaa));
#ifndef PCSX2_CORE
memset(&m_shaderfx, 0, sizeof(m_shaderfx));
#endif
memset(&m_date, 0, sizeof(m_date));
memset(&m_shadeboost, 0, sizeof(m_shadeboost));
memset(&m_om_dss, 0, sizeof(m_om_dss));
memset(&m_profiler, 0, sizeof(m_profiler));
// Reset the debug file
#ifdef ENABLE_OGL_DEBUG
m_debug_gl_file = fopen("GS_opengl_debug.txt", "w");
@@ -232,6 +220,8 @@ bool GSDeviceOGL::Create(HostDisplay* display)
m_features.texture_barrier = true;
m_features.provoking_vertex_last = true;
m_features.prefer_new_textures = false;
m_features.dxt_textures = GL_EXT_texture_compression_s3tc;
m_features.bptc_textures = GL_VERSION_4_2 || GL_ARB_texture_compression_bptc || GL_EXT_texture_compression_bptc;
GLint point_range[2] = {};
GLint line_range[2] = {};
+9 -9
View File
@@ -249,10 +249,10 @@ private:
{
std::string vs;
GL::Program ps[static_cast<int>(ShaderConvert::Count)]; // program object
GLuint ln; // sampler object
GLuint pt; // sampler object
GSDepthStencilOGL* dss;
GSDepthStencilOGL* dss_write;
GLuint ln = 0; // sampler object
GLuint pt = 0; // sampler object
GSDepthStencilOGL* dss = nullptr;
GSDepthStencilOGL* dss_write = nullptr;
} m_convert;
struct
@@ -269,8 +269,8 @@ private:
struct
{
GSDepthStencilOGL* dss;
GSTexture* t;
GSDepthStencilOGL* dss = nullptr;
GSTexture* t = nullptr;
} m_date;
struct
@@ -280,14 +280,14 @@ private:
struct
{
u16 last_query;
GLuint timer_query[1 << 16];
u16 last_query = 0;
GLuint timer_query[1 << 16] = {};
GLuint timer() { return timer_query[last_query]; }
} m_profiler;
GLuint m_ps_ss[1 << 8];
GSDepthStencilOGL* m_om_dss[1 << 5];
GSDepthStencilOGL* m_om_dss[1 << 5] = {};
std::unordered_map<ProgramSelector, GL::Program, ProgramSelectorHash> m_programs;
GL::ShaderCache m_shader_cache;
+93 -36
View File
@@ -240,6 +240,34 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
m_int_shift = 3; // 4 bytes for depth + 4 bytes for stencil by texels
break;
case Format::BC1:
gl_fmt = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
m_int_format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
m_int_type = GL_UNSIGNED_BYTE;
m_int_shift = 1;
break;
case Format::BC2:
gl_fmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
m_int_format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
m_int_type = GL_UNSIGNED_BYTE;
m_int_shift = 1;
break;
case Format::BC3:
gl_fmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
m_int_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
m_int_type = GL_UNSIGNED_BYTE;
m_int_shift = 1;
break;
case Format::BC7:
gl_fmt = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
m_int_format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
m_int_type = GL_UNSIGNED_BYTE;
m_int_shift = 1;
break;
case Format::Invalid:
m_int_format = 0;
m_int_type = 0;
@@ -286,6 +314,14 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
SetGpuPageSize(GSVector2i(127, 127));
break;
case GSTexture::Format::BC1:
case GSTexture::Format::BC2:
case GSTexture::Format::BC3:
case GSTexture::Format::BC7:
m_sparse = false;
SetGpuPageSize(GSVector2i(127, 127));
break;
case Format::Invalid:
ASSERT(0);
}
@@ -397,39 +433,46 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
GL_PUSH("Upload Texture %d", m_texture_id);
g_perfmon.Put(GSPerfMon::TextureUploads, 1);
// The easy solution without PBO
#if 0
// Likely a bad texture
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> m_int_shift);
glTextureSubImage2D(m_texture_id, GL_TEX_LEVEL_0, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type, data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); // Restore default behavior
#endif
// The complex solution with PBO
#if 1
char* src = (char*)data;
char* map = PboPool::Map(map_size);
// PERF: slow path of the texture upload. Dunno if we could do better maybe check if TC can keep row_byte == pitch
// Note: row_byte != pitch
for (int h = 0; h < r.height(); h++)
// Don't use PBOs for huge texture uploads, let the driver sort it out.
// Otherwise we'll just be syncing, or worse, crashing because the PBO routine above isn't great.
if (IsCompressedFormat())
{
memcpy(map, src, row_byte);
map += row_byte;
src += pitch;
const u32 row_length = CalcUploadRowLengthFromPitch(pitch);
const u32 upload_size = CalcUploadSize(r.height(), pitch);
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
glCompressedTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, upload_size, data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
else if (map_size >= PboPool::m_seg_size)
{
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> m_int_shift);
glTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type, data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); // Restore default behavior
}
else
{
// The complex solution with PBO
char* src = (char*)data;
char* map = PboPool::Map(map_size);
PboPool::Unmap();
// PERF: slow path of the texture upload. Dunno if we could do better maybe check if TC can keep row_byte == pitch
// Note: row_byte != pitch
for (int h = 0; h < r.height(); h++)
{
memcpy(map, src, row_byte);
map += row_byte;
src += pitch;
}
glTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type, (const void*)PboPool::Offset());
PboPool::Unmap();
// FIXME OGL4: investigate, only 1 unpack buffer always bound
PboPool::UnbindPbo();
glTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type, (const void*)PboPool::Offset());
PboPool::EndTransfer();
#endif
// FIXME OGL4: investigate, only 1 unpack buffer always bound
PboPool::UnbindPbo();
PboPool::EndTransfer();
}
m_needs_mipmaps_generated = true;
@@ -438,7 +481,7 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
{
if (layer >= m_mipmap_levels)
if (layer >= m_mipmap_levels || IsCompressedFormat())
return false;
GSVector4i r = _r ? *_r : GSVector4i(0, 0, m_size.x, m_size.y);
@@ -451,13 +494,15 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
if (m_type == Type::Texture || m_type == Type::RenderTarget)
{
const u32 map_size = r.height() * row_byte;
if (map_size > PboPool::m_seg_size)
return false;
GL_PUSH_("Upload Texture %d", m_texture_id); // POP is in Unmap
g_perfmon.Put(GSPerfMon::TextureUploads, 1);
m_clean = false;
u32 map_size = r.height() * row_byte;
m.bits = (u8*)PboPool::Map(map_size);
#ifdef ENABLE_OGL_DEBUG_MEM_BW
@@ -550,12 +595,7 @@ GSTexture::GSMap GSTextureOGL::Read(const GSVector4i& r, AlignedBuffer<u8, 32>&
// The fastest way will be to use a PBO to read the data asynchronously. Unfortunately GSdx
// architecture is waiting the data right now.
#if 0
// Maybe it is as good as the code below. I don't know
// With openGL 4.5 you can use glGetTextureSubImage
glGetTextureSubImage(m_texture_id, GL_TEX_LEVEL_0, r.x, r.y, 0, r.width(), r.height(), 1, m_int_format, m_int_type, m_size.x * m_size.y * 4, m.bits);
#else
// Bind the texture to the read framebuffer to avoid any disturbance
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_read);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture_id, 0);
@@ -566,7 +606,6 @@ GSTexture::GSMap GSTextureOGL::Read(const GSVector4i& r, AlignedBuffer<u8, 32>&
glReadPixels(r.x, r.y, r.width(), r.height(), m_int_format, m_int_type, m.bits);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
#endif
return m;
}
@@ -630,6 +669,24 @@ bool GSTextureOGL::Save(const std::string& fn)
return GSPng::Save(fmt, fn, image.get(), m_committed_size.x, m_committed_size.y, pitch, compression);
}
void GSTextureOGL::Swap(GSTexture* tex)
{
GSTexture::Swap(tex);
std::swap(m_texture_id, static_cast<GSTextureOGL*>(tex)->m_texture_id);
std::swap(m_fbo_read, static_cast<GSTextureOGL*>(tex)->m_fbo_read);
std::swap(m_clean, static_cast<GSTextureOGL*>(tex)->m_clean);
std::swap(m_r_x, static_cast<GSTextureOGL*>(tex)->m_r_x);
std::swap(m_r_x, static_cast<GSTextureOGL*>(tex)->m_r_y);
std::swap(m_r_w, static_cast<GSTextureOGL*>(tex)->m_r_w);
std::swap(m_r_h, static_cast<GSTextureOGL*>(tex)->m_r_h);
std::swap(m_layer, static_cast<GSTextureOGL*>(tex)->m_layer);
std::swap(m_int_format, static_cast<GSTextureOGL*>(tex)->m_int_format);
std::swap(m_int_type, static_cast<GSTextureOGL*>(tex)->m_int_type);
std::swap(m_int_shift, static_cast<GSTextureOGL*>(tex)->m_int_shift);
std::swap(m_mem_usage, static_cast<GSTextureOGL*>(tex)->m_mem_usage);
}
u32 GSTextureOGL::GetMemUsage()
{
return m_mem_usage;
+1
View File
@@ -68,6 +68,7 @@ public:
void Unmap() final;
void GenerateMipmap() final;
bool Save(const std::string& fn) final;
void Swap(GSTexture* tex) final;
GSMap Read(const GSVector4i& r, AlignedBuffer<u8, 32>& buffer);
bool IsDss() { return (m_type == Type::DepthStencil || m_type == Type::SparseDepthStencil); }
+2 -2
View File
@@ -455,7 +455,7 @@ void GSDrawScanline::DrawScanline(int pixels, int left, int top, const GSVertexS
skip = left & 7;
steps = pixels + skip - 8;
left -= skip;
test = GSVector8i::i8to32c(g_const->m_test_256b[skip]) | GSVector8i::i8to32c(g_const->m_test_256b[15 + (steps & (steps >> 31))]);
test = GSVector8i::i8to32(g_const->m_test_256b[skip]) | GSVector8i::i8to32(g_const->m_test_256b[15 + (steps & (steps >> 31))]);
}
else
{
@@ -1561,7 +1561,7 @@ void GSDrawScanline::DrawScanline(int pixels, int left, int top, const GSVertexS
if (!sel.notest)
{
test = GSVector8i::i8to32c(g_const->m_test_256b[15 + (steps & (steps >> 31))]);
test = GSVector8i::i8to32(g_const->m_test_256b[15 + (steps & (steps >> 31))]);
}
}
+9
View File
@@ -964,6 +964,15 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
u32 fm = context->FRAME.FBMSK;
u32 zm = context->ZBUF.ZMSK || context->TEST.ZTE == 0 ? 0xffffffff : 0;
// When the format is 24bit (Z or C), DATE ceases to function.
// It was believed that in 24bit mode all pixels pass because alpha doesn't exist
// however after testing this on a PS2 it turns out nothing passes, it ignores the draw.
if ((m_context->FRAME.PSM & 0xF) == PSM_PSMCT24 && m_context->TEST.DATE)
{
//DevCon.Warning("DATE on a 24bit format, Frame PSM %x", m_context->FRAME.PSM);
return false;
}
if (context->TEST.ZTE && context->TEST.ZTST == ZTST_NEVER)
{
fm = 0xffffffff;
+8
View File
@@ -94,3 +94,11 @@ bool GSTextureSW::Save(const std::string& fn)
int compression = theApp.GetConfigI("png_compression_level");
return GSPng::Save(fmt, fn, static_cast<u8*>(m_data), m_size.x, m_size.y, m_pitch, compression);
}
void GSTextureSW::Swap(GSTexture* tex)
{
GSTexture::Swap(tex);
std::swap(m_pitch, static_cast<GSTextureSW*>(tex)->m_pitch);
std::swap(m_data, static_cast<GSTextureSW*>(tex)->m_data);
// std::swap(m_mapped, static_cast<GSTextureSW*>(tex)->m_mapped);
}

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