mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-08-13 01:20:26 +02:00
Compare commits
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
+78
-366
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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>&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 &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 &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 &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>&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>&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 &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>&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>&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>&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>&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&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>&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>&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>&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>&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 &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>&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>&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>&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>&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 &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 &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 &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 &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
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
// SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
*
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ private Q_SLOTS:
|
||||
void onRestoreDefaultsClicked();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent*);
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
enum : u32
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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*();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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] = {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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))]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user