Compare commits
579 commits
pandory500
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
8c0b0ff0a1 | ||
|
7f67a10543 | ||
|
5a972be7ef | ||
|
d584162e06 | ||
|
3621529df9 | ||
|
6d977b4a12 | ||
|
5bd9437403 | ||
|
4ef54169af | ||
|
a981ea2ea3 | ||
|
e2b1c7c8b7 | ||
|
b636356f36 | ||
|
facc82cc01 | ||
|
7152ba7262 | ||
|
651645d2ac | ||
|
82e00dc564 | ||
|
6a075be663 | ||
|
08e1a9f56b | ||
|
cef17589d2 | ||
|
d9365a6df1 | ||
|
7920e86098 | ||
|
c3f881b7af | ||
|
b629c99dbf | ||
|
f6dcf6e834 | ||
|
75a59d100a | ||
|
737ec3e90b | ||
|
8ad0ef6901 | ||
|
4ec2d76bc9 | ||
|
45aae7b9da | ||
|
d58f826c8d | ||
|
04c6b1ac99 | ||
|
a9c1bc2ce7 | ||
|
55fdeb93ff | ||
|
707670cfcf | ||
|
4d94b7006a | ||
|
b1bc6caaf6 | ||
|
31c85ae0a5 | ||
|
7558544183 | ||
|
aa4ddbe9ae | ||
|
68acd3d96a | ||
|
77e3f1185f | ||
|
dae758e5f4 | ||
|
aec0606ba4 | ||
|
b76d519682 | ||
|
7b3596b906 | ||
|
6a93a18873 | ||
|
d6324d10a6 | ||
|
4b8ed69a37 | ||
|
69e22fb2f5 | ||
|
d0fced8ef1 | ||
|
4d7f00f968 | ||
|
3d59db02bc | ||
|
4d61896403 | ||
|
82ce83185e | ||
|
006d8ecb66 | ||
|
2353fdd414 | ||
|
933a51f2ff | ||
|
3d508ef282 | ||
|
c6628ee55c | ||
|
ecab503461 | ||
|
6b86cabd0c | ||
|
b2a759c711 | ||
|
652498e995 | ||
|
74a13b0ca4 | ||
|
bd08fdd566 | ||
|
07e0b963f9 | ||
|
211c23e004 | ||
|
4be1706876 | ||
|
931fdfbd55 | ||
|
4c315bacdf | ||
|
50d232d4aa | ||
|
1dea3f80eb | ||
|
d7323c88b8 | ||
|
1da6da446b | ||
|
19eeaef2ea | ||
|
d0ee5fc308 | ||
|
70c7bb3b0d | ||
|
cb9c6dc661 | ||
|
2910303d20 | ||
|
802bae6ee8 | ||
|
19205145e5 | ||
|
d891aaf9cd | ||
|
bfd11153db | ||
|
77825484a0 | ||
|
49f5da370a | ||
|
dfec953f1d | ||
|
cc6f9a73ca | ||
|
2530bc88af | ||
|
b87510115e | ||
|
632fa1c9d6 | ||
|
030a031943 | ||
|
3287c268ff | ||
|
d583da9d81 | ||
|
80f2d72817 | ||
|
5146a2a967 | ||
|
d4833b72b1 | ||
|
96cfdbaa96 | ||
|
dd032dc533 | ||
|
0c7b42b079 | ||
|
e0c0cb9a6d | ||
|
57b96250ef | ||
|
004d8fc20e | ||
|
cdd6f2f5c9 | ||
|
2036f1c36a | ||
|
4f2f1c4392 | ||
|
25ab1206b5 | ||
|
73932603e3 | ||
|
c5469c409a | ||
|
5a72e2d6d2 | ||
|
949dc97e3a | ||
|
56163469dc | ||
|
4dcf203b15 | ||
|
6d063ce3ea | ||
|
9fe618840a | ||
|
70999dc8f2 | ||
|
ede0f093e1 | ||
|
d0d9ce33a4 | ||
|
fad262e810 | ||
|
a41a1dcfff | ||
|
b3527fe1be | ||
|
157fabf91b | ||
|
48a1348352 | ||
|
ee6ffac28e | ||
|
9869267160 | ||
|
a4d667caf8 | ||
|
199f0dd704 | ||
|
e07495502e | ||
|
b01ec488d6 | ||
|
9745995e4b | ||
|
8ce639fddf | ||
|
ce83fec206 | ||
|
7092393ed7 | ||
|
8613c685eb | ||
|
391d45cc03 | ||
|
93c0ef68b6 | ||
|
8e43a9a204 | ||
|
bb3934f2fb | ||
|
07799ad8a3 | ||
|
a9d2ff29ea | ||
|
f3c2f22199 | ||
|
e0ada6e5ba | ||
|
aa5a3edda8 | ||
|
b71e44ca40 | ||
|
84d3bff507 | ||
|
23ce34ef78 | ||
|
87ddb3f3b1 | ||
|
42dd37352f | ||
|
99f3d21e31 | ||
|
6a844dd985 | ||
|
92c1eb4efa | ||
|
7b91e0d626 | ||
|
079452001e | ||
|
60ddac0048 | ||
|
e93f9f64cc | ||
|
0fb318c8e9 | ||
|
e2f6b36a19 | ||
|
fd55f1f4a8 | ||
|
53df5093d7 | ||
|
3611b4d94b | ||
|
533490848f | ||
|
618589abd8 | ||
|
39e2d2accd | ||
|
624d5a38e7 | ||
|
f7f05072fe | ||
|
5580d47d87 | ||
|
8ff38d0a9b | ||
|
5e813e6bd6 | ||
|
2a24c99441 | ||
|
b85b0476b9 | ||
|
54ad7d0bb8 | ||
|
2c795e701f | ||
|
1e6142d99b | ||
|
ac93419331 | ||
|
a196c5e7d4 | ||
|
88451f0476 | ||
|
4930da5568 | ||
|
6357b95ff5 | ||
|
f301035ba0 | ||
|
6f28c5d660 | ||
|
ba0ce34493 | ||
|
1dd98c5a55 | ||
|
f842f16fbe | ||
|
12a98baf59 | ||
|
6dbe49775c | ||
|
f1bc547e94 | ||
|
fc9a057083 | ||
|
a507563708 | ||
|
ae4f48ced3 | ||
|
2c751d39f8 | ||
|
028c7c3ea8 | ||
|
d60c9a015f | ||
|
f769b2c8a3 | ||
|
0ad2827e14 | ||
|
e26bf611e0 | ||
|
183d49329a | ||
|
f931f85d57 | ||
|
bdff93374b | ||
|
2ac14f555d | ||
|
52dae5c9a7 | ||
|
a3c2d058e0 | ||
|
51fd502d7d | ||
|
c362c159d7 | ||
|
31f28a3eaf | ||
|
a9987c6885 | ||
|
8d59e30b53 | ||
|
37de5b9d0d | ||
|
d00928e1db | ||
|
89effbf498 | ||
|
15b6a48070 | ||
|
833fc35462 | ||
|
3236f54693 | ||
|
358cfd4bc6 | ||
|
c53c33c0c2 | ||
|
a2129856a9 | ||
|
e4ea4831e9 | ||
|
7d43a4940d | ||
|
078018a943 | ||
|
82606b6eb2 | ||
|
4eecf5ab1d | ||
|
8ebc08185b | ||
|
6319dc2a90 | ||
|
3b22d1248e | ||
|
9767f43cbe | ||
|
3d949b080d | ||
|
9fdc7e2372 | ||
|
b38b46df0f | ||
|
2b0192d818 | ||
|
397745ce14 | ||
|
9c1c09ff5c | ||
|
ba4d1668ce | ||
|
8a4d84d82b | ||
|
018e4ee1d6 | ||
|
84f9c1694f | ||
|
af47ad035d | ||
|
f0ee3b8daa | ||
|
96be932003 | ||
|
ddafcce339 | ||
|
35fcec1e4b | ||
|
9ff5ba7c8d | ||
|
24409f6f94 | ||
|
f6ba4ee4de | ||
|
10bc6b4cd8 | ||
|
ced821169e | ||
|
9b7df47de3 | ||
|
19ee0f571c | ||
|
35a2f46ad0 | ||
|
8c821893f1 | ||
|
ef35cbbedb | ||
|
bb38210cfb | ||
|
34710ec750 | ||
|
a8b8580756 | ||
|
7fd7015987 | ||
|
a780d02c07 | ||
|
316bc03ac9 | ||
|
5a29ebfe61 | ||
|
bc4f6a8341 | ||
|
89e846dc10 | ||
|
18b62f3bda | ||
|
b43ded8e78 | ||
|
d280495e63 | ||
|
87d0461fe0 | ||
|
ae58fe3828 | ||
|
43cdb88ace | ||
|
c73e2351de | ||
|
03328638b1 | ||
|
28ed12aa93 | ||
|
b82a34539d | ||
|
88a50575c7 | ||
|
dbe395dd00 | ||
|
5bb1db557b | ||
|
34fbbf2c2a | ||
|
3e63fe8655 | ||
|
c7a3e7bc32 | ||
|
cd35252400 | ||
|
15df71c02a | ||
|
5711259b86 | ||
|
42164b37d6 | ||
|
10ccbfd68c | ||
|
64ee5675b8 | ||
|
d4703e9534 | ||
|
0cd02ab58e | ||
|
0d06af87b6 | ||
|
60a304f29b | ||
|
f21523ff74 | ||
|
e852771480 | ||
|
ba1688bd44 | ||
|
14c7eda7f9 | ||
|
5b14cb61a7 | ||
|
931ff4eb1e | ||
|
4e2759713f | ||
|
ae0c1e88c3 | ||
|
f9403efaf0 | ||
|
0e86ac6bf0 | ||
|
34189174d6 | ||
|
2c4fda0ecf | ||
|
b2f97d5934 | ||
|
06882d6e55 | ||
|
12de5bdead | ||
|
166ea2b2ba | ||
|
1cf5c1bb34 | ||
|
76f0c6cab4 | ||
|
f1a9e39ce9 | ||
|
69b43ab734 | ||
|
4d5a671b0d | ||
|
927fa6bfcb | ||
|
ca7480fa55 | ||
|
f94442d1b3 | ||
|
226d25721a | ||
|
7a2f30803d | ||
|
d07c3c5148 | ||
|
20c13f3b4c | ||
|
c4e2ad37ff | ||
|
4f43dac04d | ||
|
af7efe4b5d | ||
|
200575b2bc | ||
|
3aa0f5b543 | ||
|
4d95250052 | ||
|
0260aebc26 | ||
|
e63bb0459c | ||
|
1c49d5718c | ||
|
92ffef2626 | ||
|
9b411af1f5 | ||
|
6a2e5dd7f7 | ||
|
e39980fc73 | ||
|
bd760b9115 | ||
|
7c184a7e1c | ||
|
cd0b4fce48 | ||
|
521335cb2a | ||
|
49ac4c6774 | ||
|
23e2d0f797 | ||
|
008055d242 | ||
|
b85f7e28a9 | ||
|
76ad3dec4d | ||
|
bd931f9cbe | ||
|
3cef04f885 | ||
|
a2fe906534 | ||
|
52ad0d0335 | ||
|
db805cc4cc | ||
|
7bb7c2f28a | ||
|
74430ae9d7 | ||
|
8bdcd89b77 | ||
|
4d62b4c50d | ||
|
9389456e56 | ||
|
86f3c1ca9e | ||
|
0a4f1dc49b | ||
|
4bde384aaf | ||
|
cd46f0b4cb | ||
|
00c80cea6e | ||
|
4e0761b104 | ||
|
0668a60406 | ||
|
4380bf9787 | ||
|
cb835295c8 | ||
|
e79e0e21ad | ||
|
847a87f164 | ||
|
9844422fc8 | ||
|
fb4a1fb7dd | ||
|
aedd51f2f6 | ||
|
c0e5da02ff | ||
|
19e4de5088 | ||
|
2a4d21e53b | ||
|
526d3047c7 | ||
|
0b15d7d153 | ||
|
b8baff712b | ||
|
da801033f5 | ||
|
abdfe74c94 | ||
|
66fdb86eff | ||
|
aa411c2f09 | ||
|
5f3f2199c9 | ||
|
f0d3a8f88e | ||
|
8eefb9f935 | ||
|
dea038a91b | ||
|
fea88b62ec | ||
|
70edf4f234 | ||
|
1a19884769 | ||
|
80ae562b18 | ||
|
cf48532ef5 | ||
|
ee93e4a2ca | ||
|
b3a2b7a35c | ||
|
1a5d5452fe | ||
|
b3be6db3ae | ||
|
265a9021fd | ||
|
bb33a43d54 | ||
|
2f5f9df620 | ||
|
9e151d0794 | ||
|
e89396b652 | ||
|
1f53d8a9a2 | ||
|
64d92c9aa0 | ||
|
ade64171ce | ||
|
b8fa3a2071 | ||
|
d1fca2ac33 | ||
|
db421165c0 | ||
|
1d0114df4d | ||
|
5d8a0b3ac7 | ||
|
c92148ee2c | ||
|
9dcaf4e761 | ||
|
bc98cf6bcb | ||
|
86a287f512 | ||
|
6ec4d4f573 | ||
|
fde10ed4bf | ||
|
abbd1c83bd | ||
|
ed99e64b7c | ||
|
48d3efc473 | ||
|
65a787026d | ||
|
fdb1d69d11 | ||
|
6063e30fd4 | ||
|
ca4ee83038 | ||
|
038bc7fc49 | ||
|
43ae1e3b07 | ||
|
84d0236bf4 | ||
|
b5c4b2c3f7 | ||
|
3d14cd16eb | ||
|
cbbaf148f4 | ||
|
4c0077fd84 | ||
|
1fff976e48 | ||
|
c8d7226a5f | ||
|
c28dc9e4f2 | ||
|
3b004e7a4b | ||
|
d6a8bfdf3e | ||
|
ded18ff237 | ||
|
8baae83136 | ||
|
dd2b1ace88 | ||
|
51d5026792 | ||
|
c0ee711cb9 | ||
|
2bd2292bac | ||
|
20e703db83 | ||
|
41f4c08d17 | ||
|
daa1bc3c6e | ||
|
53c33f344c | ||
|
9fffa33eee | ||
|
d3cd065ccb | ||
|
cc616547bb | ||
|
38e5b33a53 | ||
|
b0dd2a52b1 | ||
|
42fcd399f4 | ||
|
f2cfbe1bcf | ||
|
91119c7052 | ||
|
4d465678cd | ||
|
0198f7c55d | ||
|
db245e1b34 | ||
|
01035f48a4 | ||
|
fae186bba6 | ||
|
308cbfd346 | ||
|
2173a8ffd4 | ||
|
80e429f2a1 | ||
|
e503242260 | ||
|
797db387e4 | ||
|
1bc6a1a23f | ||
|
f0361db13a | ||
|
211d30d589 | ||
|
58c4039d48 | ||
|
0d78d2e87e | ||
|
e8e646c568 | ||
|
165225dc18 | ||
|
0fd22ea3bb | ||
|
9f62a3f750 | ||
|
51456980db | ||
|
9b2fa46861 | ||
|
e104a28b71 | ||
|
4164f363da | ||
|
279b046b8f | ||
|
fc133f4994 | ||
|
b0da32f41f | ||
|
a416478780 | ||
|
810d8c0890 | ||
|
28edae016e | ||
|
05786f5719 | ||
|
685d2acffe | ||
|
46e704f879 | ||
|
d9f6bae1ff | ||
|
88b6442527 | ||
|
06a1f0b72c | ||
|
8b9836afd3 | ||
|
1b8b441cfd | ||
|
0013c6fede | ||
|
2b0bbb1e0c | ||
|
2291855a1f | ||
|
7b2657f3ff | ||
|
da013ee105 | ||
|
7d0f2e43b6 | ||
|
e5df318990 | ||
|
2ba63c65f2 | ||
|
272c162aae | ||
|
e7aec4dae1 | ||
|
d36728e532 | ||
|
decccf199a | ||
|
9742aaaffe | ||
|
ec7f163a32 | ||
|
772b3ff7b8 | ||
|
e433a8be4a | ||
|
fcb5042eaa | ||
|
86f98bf904 | ||
|
5929aaae85 | ||
|
017d0d4b17 | ||
|
34ff24a4f3 | ||
|
3e20a5802f | ||
|
cf8c5f3b6b | ||
|
638192b024 | ||
|
fa2b9f8fdd | ||
|
546f9d7743 | ||
|
3264209772 | ||
|
acf530e996 | ||
|
45bc4d8750 | ||
|
6e303e8f1d | ||
|
559cc60a66 | ||
|
d31ba393af | ||
|
1c58617392 | ||
|
ac3139b8ee | ||
|
87feeeb7e0 | ||
|
fb5ba16ef3 | ||
|
6d41f15f0d | ||
|
06ec41d1de | ||
|
3a705d9470 | ||
|
580c9a634b | ||
|
24da5a3ba2 | ||
|
15f01b13a2 | ||
|
964f606a9c | ||
|
dbd3045f87 | ||
|
9a515c851f | ||
|
c4ad32420a | ||
|
14e2e1ed62 | ||
|
c5d896a9d7 | ||
|
ae0305d974 | ||
|
946b16dbc5 | ||
|
a8295781bb | ||
|
ebf9de7864 | ||
|
1c81d47dd4 | ||
|
69b8fb9bc2 | ||
|
b610e2f314 | ||
|
4a2cd1bb7b | ||
|
7dc18a94af | ||
|
e64d1e94fe | ||
|
8fc01e37d9 | ||
|
6a8f65b566 | ||
|
c1529b2704 | ||
|
949f746f96 | ||
|
0b58f0917d | ||
|
a9b6421dfd | ||
|
90ac4d5127 | ||
|
d0d41262d1 | ||
|
455b58487d | ||
|
8fd0426c29 | ||
|
c2406aeb45 | ||
|
e7c0b41867 | ||
|
81f47caf2f | ||
|
755648c997 | ||
|
567ba61057 | ||
|
f83fb2325e | ||
|
602407fcf2 | ||
|
3aed81d51b | ||
|
50da8a91da | ||
|
8a90e94e74 | ||
|
1cb8bf38f9 | ||
|
1aab1c4b09 | ||
|
cdffce8ce0 | ||
|
2e171b22ec | ||
|
f8320e4764 | ||
|
2648cd0eee | ||
|
65b995ac6c | ||
|
01c3c3638f | ||
|
dee84e6810 | ||
|
966144fa64 | ||
|
3f2ef508c9 | ||
|
0f5fdf7840 | ||
|
3783afd855 | ||
|
b8353c6273 | ||
|
5c94b41dde | ||
|
c2c7933393 | ||
|
ad029e86d2 | ||
|
07e0cf9a22 | ||
|
4eec2e0444 | ||
|
cbab677075 | ||
|
be658f8f4e | ||
|
25a7334b5f | ||
|
e6a864ee04 | ||
|
b768210797 | ||
|
5c4e08fe19 | ||
|
646e3b269d | ||
|
00e691d633 | ||
|
a8493c0e19 | ||
|
f1f3e6fba2 |
393 changed files with 12877 additions and 6722 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -333,7 +333,7 @@ jobs:
|
|||
run: |
|
||||
sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
|
||||
sudo apt-get update -y -qq
|
||||
sudo apt-get install libsdl2-dev libgl1-mesa-dev libglu1-mesa-dev
|
||||
sudo apt-get install libsdl2-dev libgl1-mesa-dev libglu1-mesa-dev libsdl2-ttf-dev libfontconfig1-dev
|
||||
|
||||
- name: Install macOS dependencies
|
||||
if: runner.os == 'macOS'
|
||||
|
|
|
@ -158,4 +158,4 @@ libretro-build-tvos-arm64:
|
|||
- .core-defs
|
||||
- .cmake-defs
|
||||
variables:
|
||||
CORE_ARGS: -DIOS_PLATFORM=TVOS -DUSE_FFMPEG=NO -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/ios.cmake -DLIBRETRO=ON
|
||||
CORE_ARGS: -DIOS_PLATFORM=TVOS -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/ios.cmake -DLIBRETRO=ON
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -50,3 +50,6 @@
|
|||
[submodule "ext/naett"]
|
||||
path = ext/naett
|
||||
url = https://github.com/erkkah/naett.git
|
||||
[submodule "ext/libchdr"]
|
||||
path = ext/libchdr
|
||||
url = https://github.com/rtissera/libchdr.git
|
||||
|
|
|
@ -149,7 +149,7 @@ option(USING_EGL "Set to ON if target environment uses EGL" ${USING_EGL})
|
|||
option(USING_FBDEV "Set to ON if target environment uses fbdev (eg. Pandora)" ${USING_FBDEV})
|
||||
option(USING_GLES2 "Set to ON if target device uses OpenGL ES 2.0" ${USING_GLES2})
|
||||
option(USING_X11_VULKAN "Set to OFF if target environment doesn't use X11 for Vulkan" ON)
|
||||
option(USE_WAYLAND_WSI "Enable or disable Wayland WSI support for Vulkan" ${USE_WAYLAND_WSI})
|
||||
option(USE_WAYLAND_WSI "Enable or disable Wayland WSI support for Vulkan" ON)
|
||||
option(USE_VULKAN_DISPLAY_KHR "Enable or disable full screen display of Vulkan" ${USE_VULKAN_DISPLAY_KHR})
|
||||
# :: Frontends
|
||||
option(USING_QT_UI "Set to ON if you wish to use the Qt frontend wrapper" ${USING_QT_UI})
|
||||
|
@ -188,10 +188,9 @@ if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN)
|
|||
find_package(Wayland)
|
||||
if(NOT WAYLAND_FOUND)
|
||||
message(STATUS "Could not find Wayland libraries, disabling Wayland WSI support for Vulkan.")
|
||||
else()
|
||||
elseif(USE_WAYLAND_WSI)
|
||||
include_directories(${WAYLAND_INCLUDE_DIR})
|
||||
add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR)
|
||||
add_definitions(-DUSE_WAYLAND_WSI=ON)
|
||||
endif()
|
||||
|
||||
if(USE_VULKAN_DISPLAY_KHR)
|
||||
|
@ -221,7 +220,7 @@ else()
|
|||
set(CoreLinkType STATIC)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
if(NOT ANDROID AND NOT WIN32 AND (NOT APPLE OR IOS))
|
||||
set(HTTPS_NOT_AVAILABLE ON)
|
||||
endif()
|
||||
|
||||
|
@ -262,6 +261,14 @@ if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX)
|
|||
find_package(SDL2)
|
||||
find_package(SDL2_ttf)
|
||||
find_package(Fontconfig)
|
||||
|
||||
# TODO: this can be removed once CI supports newer SDL2_ttf
|
||||
if (NOT SDL2_ttf_FOUND)
|
||||
find_package(PkgConfig)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(SDL2_ttf_PKGCONFIG IMPORTED_TARGET SDL2_ttf)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MACOSX AND NOT IOS)
|
||||
|
@ -705,6 +712,8 @@ add_library(Common STATIC
|
|||
Common/GPU/Vulkan/VulkanDebug.h
|
||||
Common/GPU/Vulkan/VulkanContext.cpp
|
||||
Common/GPU/Vulkan/VulkanContext.h
|
||||
Common/GPU/Vulkan/VulkanDescSet.cpp
|
||||
Common/GPU/Vulkan/VulkanDescSet.h
|
||||
Common/GPU/Vulkan/VulkanFramebuffer.cpp
|
||||
Common/GPU/Vulkan/VulkanFramebuffer.h
|
||||
Common/GPU/Vulkan/VulkanImage.cpp
|
||||
|
@ -892,7 +901,11 @@ if(USE_FFMPEG)
|
|||
set(PLATFORM_ARCH "android/x86")
|
||||
endif()
|
||||
elseif(IOS)
|
||||
set(PLATFORM_ARCH "ios/universal")
|
||||
if(IOS_PLATFORM STREQUAL "TVOS")
|
||||
set(PLATFORM_ARCH "tvos/arm64")
|
||||
else()
|
||||
set(PLATFORM_ARCH "ios/universal")
|
||||
endif()
|
||||
elseif(MACOSX)
|
||||
set(PLATFORM_ARCH "macosx/universal")
|
||||
elseif(LINUX)
|
||||
|
@ -1336,14 +1349,21 @@ else()
|
|||
SDL/SDLVulkanGraphicsContext.cpp
|
||||
)
|
||||
endif()
|
||||
if(SDL2_ttf_FOUND)
|
||||
if(SDL2_ttf_FOUND OR
|
||||
(SDL2_ttf_PKGCONFIG_FOUND AND
|
||||
SDL2_ttf_PKGCONFIG_VERSION VERSION_GREATER_EQUAL "2.0.18"))
|
||||
add_definitions(-DUSE_SDL2_TTF)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} SDL2_ttf::SDL2_ttf)
|
||||
|
||||
if(FONTCONFIG_FOUND)
|
||||
add_definitions(-DUSE_SDL2_TTF_FONTCONFIG)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} Fontconfig::Fontconfig)
|
||||
endif()
|
||||
elseif(SDL2_ttf_PKGCONFIG_FOUND)
|
||||
message(WARNING "Found SDL2_ttf <2.0.18 - this is too old, falling back to atlas")
|
||||
endif()
|
||||
if(SDL2_ttf_FOUND)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} SDL2_ttf::SDL2_ttf)
|
||||
elseif(SDL2_ttf_PKGCONFIG_FOUND)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} PkgConfig::SDL2_ttf_PKGCONFIG)
|
||||
endif()
|
||||
if(APPLE)
|
||||
set(nativeExtra ${nativeExtra}
|
||||
|
@ -2245,6 +2265,8 @@ add_library(${CoreLibName} ${CoreLinkType}
|
|||
Core/Util/AudioFormat.h
|
||||
Core/Util/GameManager.cpp
|
||||
Core/Util/GameManager.h
|
||||
Core/Util/GameDB.cpp
|
||||
Core/Util/GameDB.h
|
||||
Core/Util/PortManager.cpp
|
||||
Core/Util/PortManager.h
|
||||
Core/Util/BlockAllocator.cpp
|
||||
|
@ -2303,11 +2325,16 @@ else()
|
|||
include_directories(ext/zstd/lib)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs}
|
||||
include_directories(ext/libchdr/include)
|
||||
|
||||
target_link_libraries(${CoreLibName} Common native chdr kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs}
|
||||
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
|
||||
|
||||
if(NOT HTTPS_NOT_AVAILABLE)
|
||||
target_link_libraries(${CoreLibName} naett)
|
||||
if(WIN32)
|
||||
target_link_libraries(${CoreLibName} winhttp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_compile_features(${CoreLibName} PUBLIC cxx_std_17)
|
||||
|
|
|
@ -4204,6 +4204,14 @@ void ARM64FloatEmitter::MOVI2FDUP(ARM64Reg Rd, float value, ARM64Reg scratch, bo
|
|||
if (negate) {
|
||||
FNEG(32, Rd, Rd);
|
||||
}
|
||||
} else if (TryAnyMOVI(32, Rd, ival)) {
|
||||
if (negate) {
|
||||
FNEG(32, Rd, Rd);
|
||||
}
|
||||
} else if (TryAnyMOVI(32, Rd, ival ^ 0x80000000)) {
|
||||
if (!negate) {
|
||||
FNEG(32, Rd, Rd);
|
||||
}
|
||||
} else {
|
||||
_assert_msg_(scratch != INVALID_REG, "Failed to find a way to generate FP immediate %f without scratch", value);
|
||||
if (negate) {
|
||||
|
@ -4214,6 +4222,96 @@ void ARM64FloatEmitter::MOVI2FDUP(ARM64Reg Rd, float value, ARM64Reg scratch, bo
|
|||
}
|
||||
}
|
||||
|
||||
bool ARM64FloatEmitter::TryMOVI(u8 size, ARM64Reg Rd, uint64_t elementValue) {
|
||||
if (size == 8) {
|
||||
// Can always do 8.
|
||||
MOVI(size, Rd, elementValue & 0xFF);
|
||||
return true;
|
||||
} else if (size == 16) {
|
||||
if ((elementValue & 0xFF00) == 0) {
|
||||
MOVI(size, Rd, elementValue & 0xFF, 0);
|
||||
return true;
|
||||
} else if ((elementValue & 0x00FF) == 0) {
|
||||
MOVI(size, Rd, (elementValue >> 8) & 0xFF, 8);
|
||||
return true;
|
||||
} else if ((elementValue & 0xFF00) == 0xFF00) {
|
||||
MVNI(size, Rd, ~elementValue & 0xFF, 0);
|
||||
return true;
|
||||
} else if ((elementValue & 0x00FF) == 0x00FF) {
|
||||
MVNI(size, Rd, (~elementValue >> 8) & 0xFF, 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (size == 32) {
|
||||
for (int shift = 0; shift < 32; shift += 8) {
|
||||
uint32_t mask = 0xFFFFFFFF &~ (0xFF << shift);
|
||||
if ((elementValue & mask) == 0) {
|
||||
MOVI(size, Rd, (elementValue >> shift) & 0xFF, shift);
|
||||
return true;
|
||||
} else if ((elementValue & mask) == mask) {
|
||||
MVNI(size, Rd, (~elementValue >> shift) & 0xFF, shift);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe an MSL shift will work?
|
||||
for (int shift = 8; shift <= 16; shift += 8) {
|
||||
uint32_t mask = 0xFFFFFFFF & ~(0xFF << shift);
|
||||
uint32_t ones = (1 << shift) - 1;
|
||||
uint32_t notOnes = 0xFFFFFF00 << shift;
|
||||
if ((elementValue & mask) == ones) {
|
||||
MOVI(size, Rd, (elementValue >> shift) & 0xFF, shift, true);
|
||||
return true;
|
||||
} else if ((elementValue & mask) == notOnes) {
|
||||
MVNI(size, Rd, (elementValue >> shift) & 0xFF, shift, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (size == 64) {
|
||||
uint8_t imm8 = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
uint8_t byte = (elementValue >> (i * 8)) & 0xFF;
|
||||
if (byte != 0 && byte != 0xFF)
|
||||
return false;
|
||||
|
||||
if (byte == 0xFF)
|
||||
imm8 |= 1 << i;
|
||||
}
|
||||
|
||||
// Didn't run into any partial bytes, so size 64 is doable.
|
||||
MOVI(size, Rd, imm8);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ARM64FloatEmitter::TryAnyMOVI(u8 size, ARM64Reg Rd, uint64_t elementValue) {
|
||||
// Try the original size first in case that's more optimal.
|
||||
if (TryMOVI(size, Rd, elementValue))
|
||||
return true;
|
||||
|
||||
uint64_t value = elementValue;
|
||||
if (size != 64) {
|
||||
uint64_t masked = elementValue & ((1 << size) - 1);
|
||||
for (int i = size; i < 64; ++i) {
|
||||
value |= masked << i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int attempt = 8; attempt <= 64; attempt += attempt) {
|
||||
// Original size was already attempted above.
|
||||
if (attempt != size) {
|
||||
if (TryMOVI(attempt, Rd, value))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ARM64XEmitter::SUBSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) {
|
||||
u32 val;
|
||||
bool shift;
|
||||
|
|
|
@ -925,6 +925,10 @@ public:
|
|||
void ORR(u8 size, ARM64Reg Rd, u8 imm8, u8 shift = 0);
|
||||
void BIC(u8 size, ARM64Reg Rd, u8 imm8, u8 shift = 0);
|
||||
|
||||
bool TryMOVI(u8 size, ARM64Reg Rd, uint64_t value);
|
||||
// Allow using a different size. Unclear if there's a penalty.
|
||||
bool TryAnyMOVI(u8 size, ARM64Reg Rd, uint64_t value);
|
||||
|
||||
// One source
|
||||
void FCVT(u8 size_to, u8 size_from, ARM64Reg Rd, ARM64Reg Rn);
|
||||
|
||||
|
|
|
@ -472,6 +472,7 @@
|
|||
<ClInclude Include="GPU\Vulkan\VulkanBarrier.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanContext.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanDebug.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanDescSet.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanFrameData.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanImage.h" />
|
||||
|
@ -900,6 +901,7 @@
|
|||
<ClCompile Include="GPU\Vulkan\VulkanBarrier.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanContext.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanDebug.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanDescSet.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanFramebuffer.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanFrameData.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanImage.cpp" />
|
||||
|
|
|
@ -515,6 +515,9 @@
|
|||
<ClInclude Include="Render\Text\draw_text_sdl.h">
|
||||
<Filter>Render\Text</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPU\Vulkan\VulkanDescSet.h">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
@ -966,6 +969,9 @@
|
|||
<ClCompile Include="Render\Text\draw_text_sdl.cpp">
|
||||
<Filter>Render\Text</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GPU\Vulkan\VulkanDescSet.cpp">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Crypto">
|
||||
|
@ -1073,6 +1079,9 @@
|
|||
<Filter Include="ext\naett">
|
||||
<UniqueIdentifier>{34f45db9-5c08-49cb-b349-b9e760ce3213}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ext\libchdr">
|
||||
<UniqueIdentifier>{b681797d-7747-487f-b448-5ef5b2d2805b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="..\ext\libpng17\CMakeLists.txt">
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
void clear() { size_ = 0; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
|
||||
const T *data() { return data_; }
|
||||
T *begin() { return data_; }
|
||||
T *end() { return data_ + size_; }
|
||||
const T *begin() const { return data_; }
|
||||
|
@ -117,6 +118,29 @@ public:
|
|||
IncreaseCapacityTo(newCapacity);
|
||||
}
|
||||
|
||||
void extend(const T *newData, size_t count) {
|
||||
IncreaseCapacityTo(size_ + count);
|
||||
memcpy(data_ + size_, newData, count * sizeof(T));
|
||||
size_ += count;
|
||||
}
|
||||
|
||||
T *extend_uninitialized(size_t count) {
|
||||
size_t sz = size_;
|
||||
if (size_ + count <= capacity_) {
|
||||
size_ += count;
|
||||
return &data_[sz];
|
||||
} else {
|
||||
size_t newCapacity = size_ + count * 2; // Leave some extra room when growing in all cases
|
||||
if (newCapacity < capacity_ * 2) {
|
||||
// Standard amortized O(1).
|
||||
newCapacity = capacity_ * 2;
|
||||
}
|
||||
IncreaseCapacityTo(newCapacity);
|
||||
size_ += count;
|
||||
return &data_[sz];
|
||||
}
|
||||
}
|
||||
|
||||
void LockCapacity() {
|
||||
#ifdef _DEBUG
|
||||
capacityLocked_ = true;
|
||||
|
|
|
@ -72,7 +72,7 @@ public:
|
|||
}
|
||||
|
||||
bool ContainsKey(const Key &key) const {
|
||||
// Slightly wasteful.
|
||||
// Slightly wasteful, though compiler might optimize it.
|
||||
Value value;
|
||||
return Get(key, &value);
|
||||
}
|
||||
|
@ -135,6 +135,7 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
// This will never crash if you call it without locking - but, the value might not be right.
|
||||
size_t size() const {
|
||||
return count_;
|
||||
}
|
||||
|
|
|
@ -617,6 +617,7 @@ void ConvertRGB565ToBGR565(u16 *dst, const u16 *src, u32 numPixels) {
|
|||
u32 i = 0;
|
||||
#endif
|
||||
|
||||
// TODO: Add a 64-bit loop too.
|
||||
const u32 *src32 = (const u32 *)src;
|
||||
u32 *dst32 = (u32 *)dst;
|
||||
for (; i < numPixels / 2; i++) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <inttypes.h>
|
||||
|
||||
// Hm, what's this for?
|
||||
#ifndef _MSC_VER
|
||||
#include <strings.h>
|
||||
#endif
|
||||
|
@ -19,17 +20,17 @@
|
|||
#include <vector>
|
||||
|
||||
#include "Common/Data/Format/IniFile.h"
|
||||
#include "Common/Data/Text/Parsers.h"
|
||||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/Data/Text/Parsers.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
#endif
|
||||
#include "Common/Log.h"
|
||||
#include "Common/Math/math_util.h"
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
|
||||
static bool ParseLineKey(const std::string &line, size_t &pos, std::string *keyOut) {
|
||||
// This unescapes # signs.
|
||||
// NOTE: These parse functions can make better use of the string_view - the pos argument should not be needed, for example.
|
||||
static bool ParseLineKey(std::string_view line, size_t &pos, std::string *keyOut) {
|
||||
std::string key = "";
|
||||
|
||||
while (pos < line.size()) {
|
||||
|
@ -44,7 +45,8 @@ static bool ParseLineKey(const std::string &line, size_t &pos, std::string *keyO
|
|||
}
|
||||
|
||||
// Escaped.
|
||||
key += line.substr(pos, next - pos - 1) + "#";
|
||||
key += line.substr(pos, next - pos - 1);
|
||||
key.push_back('#');
|
||||
pos = next + 1;
|
||||
} else if (line[next] == '=') {
|
||||
// Hurray, done.
|
||||
|
@ -60,11 +62,11 @@ static bool ParseLineKey(const std::string &line, size_t &pos, std::string *keyO
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool ParseLineValue(const std::string &line, size_t &pos, std::string *valueOut) {
|
||||
static bool ParseLineValue(std::string_view line, size_t &pos, std::string *valueOut) {
|
||||
std::string value = "";
|
||||
|
||||
std::string strippedLine = StripSpaces(line.substr(pos));
|
||||
if (strippedLine[0] == '"' && strippedLine[strippedLine.size()-1] == '"') {
|
||||
std::string_view strippedLine = StripSpaces(line.substr(pos));
|
||||
if (strippedLine.size() >= 2 && strippedLine[0] == '"' && strippedLine[strippedLine.size() - 1] == '"') {
|
||||
// Don't remove comment if is surrounded by " "
|
||||
value += line.substr(pos);
|
||||
pos = line.npos; // Won't enter the while below
|
||||
|
@ -84,7 +86,8 @@ static bool ParseLineValue(const std::string &line, size_t &pos, std::string *va
|
|||
break;
|
||||
} else {
|
||||
// Escaped.
|
||||
value += line.substr(pos, next - pos - 1) + "#";
|
||||
value += line.substr(pos, next - pos - 1);
|
||||
value.push_back('#');
|
||||
pos = next + 1;
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +99,7 @@ static bool ParseLineValue(const std::string &line, size_t &pos, std::string *va
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool ParseLineComment(const std::string& line, size_t &pos, std::string *commentOut) {
|
||||
static bool ParseLineComment(std::string_view line, size_t &pos, std::string *commentOut) {
|
||||
// Don't bother with anything if we don't need the comment data.
|
||||
if (commentOut) {
|
||||
// Include any whitespace/formatting in the comment.
|
||||
|
@ -117,8 +120,7 @@ static bool ParseLineComment(const std::string& line, size_t &pos, std::string *
|
|||
return true;
|
||||
}
|
||||
|
||||
// Ugh, this is ugly.
|
||||
static bool ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut, std::string* commentOut)
|
||||
static bool ParseLine(std::string_view line, std::string* keyOut, std::string* valueOut, std::string* commentOut)
|
||||
{
|
||||
// Rules:
|
||||
// 1. A line starting with ; is commented out.
|
||||
|
@ -142,7 +144,7 @@ static bool ParseLine(const std::string& line, std::string* keyOut, std::string*
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::string EscapeComments(const std::string &value) {
|
||||
static std::string EscapeHash(std::string_view value) {
|
||||
std::string result = "";
|
||||
|
||||
for (size_t pos = 0; pos < value.size(); ) {
|
||||
|
@ -151,7 +153,8 @@ static std::string EscapeComments(const std::string &value) {
|
|||
result += value.substr(pos);
|
||||
pos = value.npos;
|
||||
} else {
|
||||
result += value.substr(pos, next - pos) + "\\#";
|
||||
result += value.substr(pos, next - pos);
|
||||
result += "\\#";
|
||||
pos = next + 1;
|
||||
}
|
||||
}
|
||||
|
@ -159,34 +162,56 @@ static std::string EscapeComments(const std::string &value) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void ParsedIniLine::ParseFrom(std::string_view line) {
|
||||
line = StripSpaces(line);
|
||||
if (line.empty()) {
|
||||
key.clear();
|
||||
value.clear();
|
||||
comment.clear();
|
||||
} else if (line[0] == '#') {
|
||||
key.clear();
|
||||
value.clear();
|
||||
comment = line;
|
||||
} else {
|
||||
ParseLine(line, &key, &value, &comment);
|
||||
}
|
||||
}
|
||||
|
||||
void ParsedIniLine::Reconstruct(std::string *output) const {
|
||||
if (!key.empty()) {
|
||||
*output = EscapeHash(key) + " = " + EscapeHash(value) + comment;
|
||||
} else {
|
||||
*output = comment;
|
||||
}
|
||||
}
|
||||
|
||||
void Section::Clear() {
|
||||
lines.clear();
|
||||
lines_.clear();
|
||||
}
|
||||
|
||||
std::string* Section::GetLine(const char* key, std::string* valueOut, std::string* commentOut)
|
||||
{
|
||||
for (std::vector<std::string>::iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
std::string& line = *iter;
|
||||
std::string lineKey;
|
||||
ParseLine(line, &lineKey, valueOut, commentOut);
|
||||
if (!strcasecmp(lineKey.c_str(), key))
|
||||
return &line;
|
||||
bool Section::GetKeys(std::vector<std::string> &keys) const {
|
||||
keys.clear();
|
||||
for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {
|
||||
if (!liter->Key().empty())
|
||||
keys.push_back(std::string(liter->Key()));
|
||||
}
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string* Section::GetLine(const char* key, std::string* valueOut, std::string* commentOut) const
|
||||
{
|
||||
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
const std::string& line = *iter;
|
||||
std::string lineKey;
|
||||
ParseLine(line, &lineKey, valueOut, commentOut);
|
||||
if (!strcasecmp(lineKey.c_str(), key))
|
||||
ParsedIniLine *Section::GetLine(const char *key) {
|
||||
for (auto &line : lines_) {
|
||||
if (equalsNoCase(line.Key(), key))
|
||||
return &line;
|
||||
}
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ParsedIniLine *Section::GetLine(const char* key) const {
|
||||
for (auto &line : lines_) {
|
||||
if (equalsNoCase(line.Key(), key))
|
||||
return &line;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Section::Set(const char* key, uint32_t newValue) {
|
||||
|
@ -198,6 +223,7 @@ void Section::Set(const char* key, uint64_t newValue) {
|
|||
}
|
||||
|
||||
void Section::Set(const char* key, float newValue) {
|
||||
_dbg_assert_(!my_isnanorinf(newValue));
|
||||
Set(key, StringFromFormat("%f", newValue).c_str());
|
||||
}
|
||||
|
||||
|
@ -209,19 +235,13 @@ void Section::Set(const char* key, int newValue) {
|
|||
Set(key, StringFromInt(newValue).c_str());
|
||||
}
|
||||
|
||||
void Section::Set(const char* key, const char* newValue)
|
||||
{
|
||||
std::string value, commented;
|
||||
std::string* line = GetLine(key, &value, &commented);
|
||||
if (line)
|
||||
{
|
||||
// Change the value - keep the key and comment
|
||||
*line = StripSpaces(key) + " = " + EscapeComments(newValue) + commented;
|
||||
}
|
||||
else
|
||||
{
|
||||
void Section::Set(const char* key, const char* newValue) {
|
||||
ParsedIniLine *line = GetLine(key);
|
||||
if (line) {
|
||||
line->SetValue(newValue);
|
||||
} else {
|
||||
// The key did not already exist in this section - let's add it.
|
||||
lines.emplace_back(std::string(key) + " = " + EscapeComments(newValue));
|
||||
lines_.emplace_back(ParsedIniLine(key, newValue));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,16 +253,15 @@ void Section::Set(const char* key, const std::string& newValue, const std::strin
|
|||
Delete(key);
|
||||
}
|
||||
|
||||
bool Section::Get(const char* key, std::string* value, const char* defaultValue) const
|
||||
{
|
||||
const std::string* line = GetLine(key, value, 0);
|
||||
if (!line)
|
||||
{
|
||||
if (defaultValue)
|
||||
{
|
||||
bool Section::Get(const char* key, std::string* value, const char* defaultValue) const {
|
||||
const ParsedIniLine *line = GetLine(key);
|
||||
if (!line) {
|
||||
if (defaultValue) {
|
||||
*value = defaultValue;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
*value = line->Value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -287,7 +306,7 @@ void Section::Set(const char* key, const std::vector<std::string>& newValues)
|
|||
}
|
||||
|
||||
void Section::AddComment(const std::string &comment) {
|
||||
lines.emplace_back("# " + comment);
|
||||
lines_.emplace_back(ParsedIniLine::CommentOnly("# " + comment));
|
||||
}
|
||||
|
||||
bool Section::Get(const char* key, std::vector<std::string>& values) const
|
||||
|
@ -378,39 +397,29 @@ bool Section::Get(const char* key, double* value, double defaultValue) const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Section::Exists(const char *key) const
|
||||
{
|
||||
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
std::string lineKey;
|
||||
ParseLine(*iter, &lineKey, NULL, NULL);
|
||||
if (!strcasecmp(lineKey.c_str(), key))
|
||||
bool Section::Exists(const char *key) const {
|
||||
for (auto &line : lines_) {
|
||||
if (equalsNoCase(key, line.Key()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> Section::ToMap() const
|
||||
{
|
||||
std::map<std::string, std::string> Section::ToMap() const {
|
||||
std::map<std::string, std::string> outMap;
|
||||
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
std::string lineKey, lineValue;
|
||||
if (ParseLine(*iter, &lineKey, &lineValue, NULL)) {
|
||||
outMap[lineKey] = lineValue;
|
||||
for (auto &line : lines_) {
|
||||
if (!line.Key().empty()) {
|
||||
outMap[std::string(line.Key())] = line.Value();
|
||||
}
|
||||
}
|
||||
return outMap;
|
||||
}
|
||||
|
||||
bool Section::Delete(const char *key)
|
||||
{
|
||||
std::string* line = GetLine(key, 0, 0);
|
||||
for (std::vector<std::string>::iterator liter = lines.begin(); liter != lines.end(); ++liter)
|
||||
{
|
||||
if (line == &*liter)
|
||||
{
|
||||
lines.erase(liter);
|
||||
bool Section::Delete(const char *key) {
|
||||
ParsedIniLine *line = GetLine(key);
|
||||
for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {
|
||||
if (line == &*liter) {
|
||||
lines_.erase(liter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -423,14 +432,14 @@ const Section* IniFile::GetSection(const char* sectionName) const {
|
|||
for (const auto &iter : sections)
|
||||
if (!strcasecmp(iter->name().c_str(), sectionName))
|
||||
return iter.get();
|
||||
return nullptr ;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Section* IniFile::GetSection(const char* sectionName) {
|
||||
for (const auto &iter : sections)
|
||||
if (!strcasecmp(iter->name().c_str(), sectionName))
|
||||
return iter.get();
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Section* IniFile::GetOrCreateSection(const char* sectionName) {
|
||||
|
@ -463,27 +472,14 @@ bool IniFile::Exists(const char* sectionName, const char* key) const {
|
|||
return section->Exists(key);
|
||||
}
|
||||
|
||||
void IniFile::SetLines(const char* sectionName, const std::vector<std::string> &lines)
|
||||
{
|
||||
Section* section = GetOrCreateSection(sectionName);
|
||||
section->lines.clear();
|
||||
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
section->lines.push_back(*iter);
|
||||
}
|
||||
}
|
||||
|
||||
bool IniFile::DeleteKey(const char* sectionName, const char* key)
|
||||
{
|
||||
bool IniFile::DeleteKey(const char* sectionName, const char* key) {
|
||||
Section* section = GetSection(sectionName);
|
||||
if (!section)
|
||||
return false;
|
||||
std::string* line = section->GetLine(key, 0, 0);
|
||||
for (std::vector<std::string>::iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter)
|
||||
{
|
||||
if (line == &(*liter))
|
||||
{
|
||||
section->lines.erase(liter);
|
||||
ParsedIniLine *line = section->GetLine(key);
|
||||
for (auto liter = section->lines_.begin(); liter != section->lines_.end(); ++liter) {
|
||||
if (line == &(*liter)) {
|
||||
section->lines_.erase(liter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -491,55 +487,13 @@ bool IniFile::DeleteKey(const char* sectionName, const char* key)
|
|||
}
|
||||
|
||||
// Return a list of all keys in a section
|
||||
bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) const
|
||||
{
|
||||
const Section* section = GetSection(sectionName);
|
||||
bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) const {
|
||||
const Section *section = GetSection(sectionName);
|
||||
if (!section)
|
||||
return false;
|
||||
keys.clear();
|
||||
for (std::vector<std::string>::const_iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter)
|
||||
{
|
||||
std::string key;
|
||||
ParseLine(*liter, &key, 0, 0);
|
||||
if (!key.empty())
|
||||
keys.push_back(key);
|
||||
}
|
||||
return true;
|
||||
return section->GetKeys(keys);
|
||||
}
|
||||
|
||||
// Return a list of all lines in a section
|
||||
bool IniFile::GetLines(const char* sectionName, std::vector<std::string>& lines, const bool remove_comments) const
|
||||
{
|
||||
const Section* section = GetSection(sectionName);
|
||||
if (!section)
|
||||
return false;
|
||||
|
||||
lines.clear();
|
||||
for (std::vector<std::string>::const_iterator iter = section->lines.begin(); iter != section->lines.end(); ++iter)
|
||||
{
|
||||
std::string line = StripSpaces(*iter);
|
||||
|
||||
if (remove_comments)
|
||||
{
|
||||
int commentPos = (int)line.find('#');
|
||||
if (commentPos == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (commentPos != (int)std::string::npos)
|
||||
{
|
||||
line = StripSpaces(line.substr(0, commentPos));
|
||||
}
|
||||
}
|
||||
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void IniFile::SortSections()
|
||||
{
|
||||
std::sort(sections.begin(), sections.end());
|
||||
|
@ -613,7 +567,9 @@ bool IniFile::Load(std::istream &in) {
|
|||
if (sections.empty()) {
|
||||
sections.push_back(std::unique_ptr<Section>(new Section("")));
|
||||
}
|
||||
sections.back()->lines.push_back(line);
|
||||
ParsedIniLine parsedLine;
|
||||
parsedLine.ParseFrom(line);
|
||||
sections.back()->lines_.push_back(parsedLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -634,12 +590,13 @@ bool IniFile::Save(const Path &filename)
|
|||
fprintf(file, "\xEF\xBB\xBF");
|
||||
|
||||
for (const auto §ion : sections) {
|
||||
if (!section->name().empty() && (!section->lines.empty() || !section->comment.empty())) {
|
||||
if (!section->name().empty() && (!section->lines_.empty() || !section->comment.empty())) {
|
||||
fprintf(file, "[%s]%s\n", section->name().c_str(), section->comment.c_str());
|
||||
}
|
||||
|
||||
for (const std::string &s : section->lines) {
|
||||
fprintf(file, "%s\n", s.c_str());
|
||||
for (const auto &line : section->lines_) {
|
||||
std::string buffer;
|
||||
line.Reconstruct(&buffer);
|
||||
fprintf(file, "%s\n", buffer.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
|
@ -15,6 +16,39 @@
|
|||
|
||||
class VFSInterface;
|
||||
|
||||
class ParsedIniLine {
|
||||
public:
|
||||
ParsedIniLine() {}
|
||||
ParsedIniLine(std::string_view key, std::string_view value) {
|
||||
this->key = key;
|
||||
this->value = value;
|
||||
}
|
||||
ParsedIniLine(std::string_view key, std::string_view value, std::string_view comment) {
|
||||
this->key = key;
|
||||
this->value = value;
|
||||
this->comment = comment;
|
||||
}
|
||||
static ParsedIniLine CommentOnly(std::string_view comment) {
|
||||
return ParsedIniLine(std::string_view(), std::string_view(), comment);
|
||||
}
|
||||
|
||||
// Comments only come from "ParseFrom".
|
||||
void ParseFrom(std::string_view line);
|
||||
void Reconstruct(std::string *output) const;
|
||||
|
||||
// Having these as views allows a more efficient internal representation, like one joint string.
|
||||
std::string_view Key() const { return key; }
|
||||
std::string_view Value() const { return value; }
|
||||
std::string_view Comment() const { return comment; }
|
||||
|
||||
void SetValue(std::string_view newValue) { value = newValue; }
|
||||
|
||||
private:
|
||||
std::string key;
|
||||
std::string value;
|
||||
std::string comment;
|
||||
};
|
||||
|
||||
class Section {
|
||||
friend class IniFile;
|
||||
|
||||
|
@ -29,8 +63,8 @@ public:
|
|||
|
||||
std::map<std::string, std::string> ToMap() const;
|
||||
|
||||
std::string *GetLine(const char* key, std::string* valueOut, std::string* commentOut);
|
||||
const std::string *GetLine(const char* key, std::string* valueOut, std::string* commentOut) const;
|
||||
ParsedIniLine *GetLine(const char *key);
|
||||
const ParsedIniLine *GetLine(const char *key) const;
|
||||
|
||||
void Set(const char* key, const char* newValue);
|
||||
void Set(const char* key, const std::string& newValue, const std::string& defaultValue);
|
||||
|
@ -71,6 +105,9 @@ public:
|
|||
bool Get(const char* key, double* value, double defaultValue = false) const;
|
||||
bool Get(const char* key, std::vector<std::string>& values) const;
|
||||
|
||||
// Return a list of all keys in this section
|
||||
bool GetKeys(std::vector<std::string> &keys) const;
|
||||
|
||||
bool operator < (const Section& other) const {
|
||||
return name_ < other.name_;
|
||||
}
|
||||
|
@ -80,7 +117,7 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::string> lines;
|
||||
std::vector<ParsedIniLine> lines_;
|
||||
std::string name_;
|
||||
std::string comment;
|
||||
};
|
||||
|
@ -88,12 +125,10 @@ protected:
|
|||
class IniFile {
|
||||
public:
|
||||
bool Load(const Path &path);
|
||||
bool Load(const std::string &filename) { return Load(Path(filename)); }
|
||||
bool Load(std::istream &istream);
|
||||
bool LoadFromVFS(VFSInterface &vfs, const std::string &filename);
|
||||
|
||||
bool Save(const Path &path);
|
||||
bool Save(const std::string &filename) { return Save(Path(filename)); }
|
||||
|
||||
// Returns true if key exists in section
|
||||
bool Exists(const char* sectionName, const char* key) const;
|
||||
|
@ -138,9 +173,6 @@ public:
|
|||
|
||||
bool GetKeys(const char* sectionName, std::vector<std::string>& keys) const;
|
||||
|
||||
void SetLines(const char* sectionName, const std::vector<std::string> &lines);
|
||||
bool GetLines(const char* sectionName, std::vector<std::string>& lines, const bool remove_comments = true) const;
|
||||
|
||||
bool DeleteKey(const char* sectionName, const char* key);
|
||||
bool DeleteSection(const char* sectionName);
|
||||
|
||||
|
|
|
@ -116,8 +116,9 @@ public:
|
|||
std::string LanguageID();
|
||||
|
||||
std::shared_ptr<I18NCategory> GetCategory(I18NCat category);
|
||||
std::shared_ptr<I18NCategory> GetCategoryByName(const char *name);
|
||||
|
||||
// Translate the string, by looking up "key" in the file, and falling back to either def or key, in that order, if the lookup fails.
|
||||
// def can (and usually is) set to nullptr.
|
||||
const char *T(I18NCat category, const char *key, const char *def = nullptr) {
|
||||
if (category == I18NCat::NONE)
|
||||
return def ? def : key;
|
||||
|
|
|
@ -19,7 +19,7 @@ void NiceSizeFormat(uint64_t size, char *out, size_t bufSize) {
|
|||
if (s == 0)
|
||||
snprintf(out, bufSize, "%d B", (int)size);
|
||||
else
|
||||
snprintf(out, bufSize, "%3.1f %s", f, sizes[s]);
|
||||
snprintf(out, bufSize, "%3.2f %s", f, sizes[s]);
|
||||
}
|
||||
|
||||
std::string NiceSizeFormat(uint64_t size) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
#include "Common/File/AndroidContentURI.h"
|
||||
|
||||
bool AndroidContentURI::Parse(const std::string &path) {
|
||||
bool AndroidContentURI::Parse(std::string_view path) {
|
||||
const char *prefix = "content://";
|
||||
if (!startsWith(path, prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string components = path.substr(strlen(prefix));
|
||||
std::string_view components = path.substr(strlen(prefix));
|
||||
|
||||
std::vector<std::string> parts;
|
||||
std::vector<std::string_view> parts;
|
||||
SplitString(components, '/', parts);
|
||||
if (parts.size() == 3) {
|
||||
// Single file URI.
|
||||
|
@ -60,7 +60,7 @@ AndroidContentURI AndroidContentURI::WithRootFilePath(const std::string &filePat
|
|||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithComponent(const std::string &filePath) {
|
||||
AndroidContentURI AndroidContentURI::WithComponent(std::string_view filePath) {
|
||||
AndroidContentURI uri = *this;
|
||||
if (uri.file.empty()) {
|
||||
// Not sure what to do.
|
||||
|
@ -68,16 +68,17 @@ AndroidContentURI AndroidContentURI::WithComponent(const std::string &filePath)
|
|||
}
|
||||
if (uri.file.back() == ':') {
|
||||
// Special case handling for Document URIs: Treat the ':' as a directory separator too (but preserved in the filename).
|
||||
uri.file = uri.file + filePath;
|
||||
uri.file.append(filePath);
|
||||
} else {
|
||||
uri.file = uri.file + "/" + filePath;
|
||||
uri.file.push_back('/');
|
||||
uri.file.append(filePath);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithExtraExtension(const std::string &extension) {
|
||||
AndroidContentURI AndroidContentURI::WithExtraExtension(std::string_view extension) {
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.file + extension;
|
||||
uri.file.append(extension);
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,15 +23,15 @@ private:
|
|||
std::string file;
|
||||
public:
|
||||
AndroidContentURI() {}
|
||||
explicit AndroidContentURI(const std::string &path) {
|
||||
explicit AndroidContentURI(std::string_view path) {
|
||||
Parse(path);
|
||||
}
|
||||
|
||||
bool Parse(const std::string &path);
|
||||
bool Parse(std::string_view path);
|
||||
|
||||
AndroidContentURI WithRootFilePath(const std::string &filePath);
|
||||
AndroidContentURI WithComponent(const std::string &filePath);
|
||||
AndroidContentURI WithExtraExtension(const std::string &extension);
|
||||
AndroidContentURI WithComponent(std::string_view filePath);
|
||||
AndroidContentURI WithExtraExtension(std::string_view extension); // The ext string contains the dot.
|
||||
AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const;
|
||||
AndroidContentURI WithReplacedExtension(const std::string &newExtension) const;
|
||||
|
||||
|
|
|
@ -61,16 +61,16 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {
|
|||
_dbg_assert_(computeRecursiveDirectorySize);
|
||||
}
|
||||
|
||||
bool Android_IsContentUri(const std::string &filename) {
|
||||
bool Android_IsContentUri(std::string_view filename) {
|
||||
return startsWith(filename, "content://");
|
||||
}
|
||||
|
||||
int Android_OpenContentUriFd(const std::string &filename, Android_OpenContentUriMode mode) {
|
||||
int Android_OpenContentUriFd(std::string_view filename, Android_OpenContentUriMode mode) {
|
||||
if (!g_nativeActivity) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string fname = filename;
|
||||
std::string fname(filename);
|
||||
// PPSSPP adds an ending slash to directories before looking them up.
|
||||
// TODO: Fix that in the caller (or don't call this for directories).
|
||||
if (fname.back() == '/')
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "Common/File/DirListing.h"
|
||||
|
||||
|
@ -39,8 +40,8 @@ extern std::string g_externalDir;
|
|||
|
||||
void Android_StorageSetNativeActivity(jobject nativeActivity);
|
||||
|
||||
bool Android_IsContentUri(const std::string &uri);
|
||||
int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode);
|
||||
bool Android_IsContentUri(std::string_view uri);
|
||||
int Android_OpenContentUriFd(std::string_view uri, const Android_OpenContentUriMode mode);
|
||||
StorageError Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName);
|
||||
StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName);
|
||||
StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri);
|
||||
|
@ -63,8 +64,8 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj);
|
|||
|
||||
// Stub out the Android Storage wrappers, so that we can avoid ifdefs everywhere.
|
||||
|
||||
inline bool Android_IsContentUri(const std::string &uri) { return false; }
|
||||
inline int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode) { return -1; }
|
||||
inline bool Android_IsContentUri(std::string_view uri) { return false; }
|
||||
inline int Android_OpenContentUriFd(std::string_view uri, const Android_OpenContentUriMode mode) { return -1; }
|
||||
inline StorageError Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName) { return StorageError::UNKNOWN; }
|
||||
inline StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { return StorageError::UNKNOWN; }
|
||||
inline StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) { return StorageError::UNKNOWN; }
|
||||
|
|
|
@ -184,7 +184,7 @@ bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const ch
|
|||
std::string tmp;
|
||||
while (*filter) {
|
||||
if (*filter == ':') {
|
||||
filters.insert(std::move(tmp));
|
||||
filters.insert(tmp);
|
||||
tmp.clear();
|
||||
} else {
|
||||
tmp.push_back(*filter);
|
||||
|
@ -192,7 +192,7 @@ bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const ch
|
|||
filter++;
|
||||
}
|
||||
if (!tmp.empty())
|
||||
filters.insert(std::move(tmp));
|
||||
filters.insert(tmp);
|
||||
}
|
||||
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
|
|
|
@ -592,7 +592,7 @@ bool CreateFullPath(const Path &path) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> parts;
|
||||
std::vector<std::string_view> parts;
|
||||
SplitString(diff, '/', parts);
|
||||
|
||||
// Probably not necessary sanity check, ported from the old code.
|
||||
|
@ -602,7 +602,7 @@ bool CreateFullPath(const Path &path) {
|
|||
}
|
||||
|
||||
Path curPath = root;
|
||||
for (auto &part : parts) {
|
||||
for (auto part : parts) {
|
||||
curPath /= part;
|
||||
if (!File::Exists(curPath)) {
|
||||
File::CreateDir(curPath);
|
||||
|
@ -1181,6 +1181,7 @@ uint8_t *ReadLocalFile(const Path &filename, size_t *size) {
|
|||
return nullptr;
|
||||
}
|
||||
fseek(file, 0, SEEK_SET);
|
||||
// NOTE: If you find ~10 memory leaks from here, with very varying sizes, it might be the VFPU LUTs.
|
||||
uint8_t *contents = new uint8_t[f_size + 1];
|
||||
if (fread(contents, 1, f_size, file) != f_size) {
|
||||
delete[] contents;
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
Path::Path(const std::string &str) {
|
||||
Path::Path(std::string_view str) {
|
||||
Init(str);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ Path::Path(const std::wstring &str) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void Path::Init(const std::string &str) {
|
||||
void Path::Init(std::string_view str) {
|
||||
if (str.empty()) {
|
||||
type_ = PathType::UNDEFINED;
|
||||
path_.clear();
|
||||
|
@ -81,7 +81,7 @@ void Path::Init(const std::string &str) {
|
|||
|
||||
// We always use forward slashes internally, we convert to backslash only when
|
||||
// converted to a wstring.
|
||||
Path Path::operator /(const std::string &subdir) const {
|
||||
Path Path::operator /(std::string_view subdir) const {
|
||||
if (type_ == PathType::CONTENT_URI) {
|
||||
AndroidContentURI uri(path_);
|
||||
return Path(uri.WithComponent(subdir).ToString());
|
||||
|
@ -104,18 +104,18 @@ Path Path::operator /(const std::string &subdir) const {
|
|||
return Path(fullPath);
|
||||
}
|
||||
|
||||
void Path::operator /=(const std::string &subdir) {
|
||||
void Path::operator /=(std::string_view subdir) {
|
||||
*this = *this / subdir;
|
||||
}
|
||||
|
||||
Path Path::WithExtraExtension(const std::string &ext) const {
|
||||
Path Path::WithExtraExtension(std::string_view ext) const {
|
||||
if (type_ == PathType::CONTENT_URI) {
|
||||
AndroidContentURI uri(path_);
|
||||
return Path(uri.WithExtraExtension(ext).ToString());
|
||||
}
|
||||
|
||||
_dbg_assert_(!ext.empty() && ext[0] == '.');
|
||||
return Path(path_ + ext);
|
||||
return Path(path_ + std::string(ext));
|
||||
}
|
||||
|
||||
Path Path::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {
|
||||
|
@ -161,7 +161,7 @@ std::string Path::GetFilename() const {
|
|||
return path_;
|
||||
}
|
||||
|
||||
std::string GetExtFromString(const std::string &str) {
|
||||
std::string GetExtFromString(std::string_view str) {
|
||||
size_t pos = str.rfind(".");
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
|
@ -171,7 +171,7 @@ std::string GetExtFromString(const std::string &str) {
|
|||
// Don't want to detect "df/file" from "/as.df/file"
|
||||
return "";
|
||||
}
|
||||
std::string ext = str.substr(pos);
|
||||
std::string ext(str.substr(pos));
|
||||
for (size_t i = 0; i < ext.size(); i++) {
|
||||
ext[i] = tolower(ext[i]);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "ppsspp_config.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
|
@ -36,11 +37,11 @@ enum class PathType {
|
|||
|
||||
class Path {
|
||||
private:
|
||||
void Init(const std::string &str);
|
||||
void Init(std::string_view str);
|
||||
|
||||
public:
|
||||
Path() : type_(PathType::UNDEFINED) {}
|
||||
explicit Path(const std::string &str);
|
||||
explicit Path(std::string_view str);
|
||||
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
explicit Path(const std::wstring &str);
|
||||
|
@ -71,13 +72,13 @@ public:
|
|||
bool IsAbsolute() const;
|
||||
|
||||
// Returns a path extended with a subdirectory.
|
||||
Path operator /(const std::string &subdir) const;
|
||||
Path operator /(std::string_view subdir) const;
|
||||
|
||||
// Navigates down into a subdir.
|
||||
void operator /=(const std::string &subdir);
|
||||
void operator /=(std::string_view subdir);
|
||||
|
||||
// File extension manipulation.
|
||||
Path WithExtraExtension(const std::string &ext) const;
|
||||
Path WithExtraExtension(std::string_view ext) const;
|
||||
Path WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const;
|
||||
Path WithReplacedExtension(const std::string &newExtension) const;
|
||||
|
||||
|
@ -139,7 +140,7 @@ private:
|
|||
};
|
||||
|
||||
// Utility function for parsing out file extensions.
|
||||
std::string GetExtFromString(const std::string &str);
|
||||
std::string GetExtFromString(std::string_view str);
|
||||
|
||||
// Utility function for fixing the case of paths. Only present on Unix-like systems.
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *can
|
|||
return false;
|
||||
}
|
||||
|
||||
for (std::string item : items) {
|
||||
for (auto &item : items) {
|
||||
// Apply some workarounds.
|
||||
if (item.empty())
|
||||
continue;
|
||||
|
|
|
@ -106,7 +106,7 @@ public:
|
|||
void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
|
||||
void BindNativeTexture(int index, void *nativeTexture) override;
|
||||
void BindSamplerStates(int start, int count, SamplerState **states) override;
|
||||
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override;
|
||||
void BindVertexBuffer(Buffer *buffers, int offset) override;
|
||||
void BindIndexBuffer(Buffer *indexBuffer, int offset) override;
|
||||
void BindPipeline(Pipeline *pipeline) override;
|
||||
|
||||
|
@ -214,12 +214,12 @@ private:
|
|||
ID3D11GeometryShader *curGS_ = nullptr;
|
||||
D3D11_PRIMITIVE_TOPOLOGY curTopology_ = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
||||
|
||||
ID3D11Buffer *nextVertexBuffers_[4]{};
|
||||
int nextVertexBufferOffsets_[4]{};
|
||||
ID3D11Buffer *nextVertexBuffer_ = nullptr;
|
||||
UINT nextVertexBufferOffset_ = 0;
|
||||
|
||||
bool dirtyIndexBuffer_ = false;
|
||||
ID3D11Buffer *nextIndexBuffer_ = nullptr;
|
||||
int nextIndexBufferOffset_ = 0;
|
||||
UINT nextIndexBufferOffset_ = 0;
|
||||
|
||||
InvalidationCallback invalidationCallback_;
|
||||
int frameCount_ = FRAME_TIME_HISTORY_LENGTH;
|
||||
|
@ -725,7 +725,7 @@ public:
|
|||
D3D11InputLayout() {}
|
||||
InputLayoutDesc desc;
|
||||
std::vector<D3D11_INPUT_ELEMENT_DESC> elements;
|
||||
std::vector<int> strides;
|
||||
UINT stride; // type to match function parameter
|
||||
};
|
||||
|
||||
const char *semanticToD3D11(int semantic, UINT *index) {
|
||||
|
@ -752,15 +752,13 @@ InputLayout *D3D11DrawContext::CreateInputLayout(const InputLayoutDesc &desc) {
|
|||
D3D11_INPUT_ELEMENT_DESC el;
|
||||
el.AlignedByteOffset = desc.attributes[i].offset;
|
||||
el.Format = dataFormatToD3D11(desc.attributes[i].format);
|
||||
el.InstanceDataStepRate = desc.bindings[desc.attributes[i].binding].instanceRate ? 1 : 0;
|
||||
el.InputSlot = desc.attributes[i].binding;
|
||||
el.InstanceDataStepRate = 0;
|
||||
el.InputSlot = 0;
|
||||
el.SemanticName = semanticToD3D11(desc.attributes[i].location, &el.SemanticIndex);
|
||||
el.InputSlotClass = desc.bindings[desc.attributes[i].binding].instanceRate ? D3D11_INPUT_PER_INSTANCE_DATA : D3D11_INPUT_PER_VERTEX_DATA;
|
||||
el.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
|
||||
inputLayout->elements.push_back(el);
|
||||
}
|
||||
for (size_t i = 0; i < desc.bindings.size(); i++) {
|
||||
inputLayout->strides.push_back(desc.bindings[i].stride);
|
||||
}
|
||||
inputLayout->stride = desc.stride;
|
||||
return inputLayout;
|
||||
}
|
||||
|
||||
|
@ -1253,8 +1251,7 @@ void D3D11DrawContext::ApplyCurrentState() {
|
|||
}
|
||||
|
||||
if (curPipeline_->input != nullptr) {
|
||||
int numVBs = (int)curPipeline_->input->strides.size();
|
||||
context_->IASetVertexBuffers(0, numVBs, nextVertexBuffers_, (UINT *)curPipeline_->input->strides.data(), (UINT *)nextVertexBufferOffsets_);
|
||||
context_->IASetVertexBuffers(0, 1, &nextVertexBuffer_, &curPipeline_->input->stride, &nextVertexBufferOffset_);
|
||||
}
|
||||
if (dirtyIndexBuffer_) {
|
||||
context_->IASetIndexBuffer(nextIndexBuffer_, DXGI_FORMAT_R16_UINT, nextIndexBufferOffset_);
|
||||
|
@ -1323,14 +1320,11 @@ void D3D11DrawContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t
|
|||
context_->UpdateSubresource(buf->buf, 0, &box, data, 0, 0);
|
||||
}
|
||||
|
||||
void D3D11DrawContext::BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) {
|
||||
_assert_(start + count <= ARRAY_SIZE(nextVertexBuffers_));
|
||||
void D3D11DrawContext::BindVertexBuffer(Buffer *buffer, int offset) {
|
||||
// Lazy application
|
||||
for (int i = 0; i < count; i++) {
|
||||
D3D11Buffer *buf = (D3D11Buffer *)buffers[i];
|
||||
nextVertexBuffers_[start + i] = buf->buf;
|
||||
nextVertexBufferOffsets_[start + i] = offsets ? offsets[i] : 0;
|
||||
}
|
||||
D3D11Buffer *buf = (D3D11Buffer *)buffer;
|
||||
nextVertexBuffer_ = buf->buf;
|
||||
nextVertexBufferOffset_ = offset;
|
||||
}
|
||||
|
||||
void D3D11DrawContext::BindIndexBuffer(Buffer *indexBuffer, int offset) {
|
||||
|
@ -1354,10 +1348,10 @@ void D3D11DrawContext::DrawIndexed(int indexCount, int offset) {
|
|||
void D3D11DrawContext::DrawUP(const void *vdata, int vertexCount) {
|
||||
ApplyCurrentState();
|
||||
|
||||
int byteSize = vertexCount * curPipeline_->input->strides[0];
|
||||
int byteSize = vertexCount * curPipeline_->input->stride;
|
||||
|
||||
UpdateBuffer(upBuffer_, (const uint8_t *)vdata, 0, byteSize, Draw::UPDATE_DISCARD);
|
||||
BindVertexBuffers(0, 1, &upBuffer_, nullptr);
|
||||
BindVertexBuffer(upBuffer_, 0);
|
||||
int offset = 0;
|
||||
Draw(vertexCount, offset);
|
||||
}
|
||||
|
@ -1565,7 +1559,7 @@ void D3D11DrawContext::BeginFrame(DebugFlags debugFlags) {
|
|||
context_->IASetPrimitiveTopology(curTopology_);
|
||||
}
|
||||
if (curPipeline_ != nullptr) {
|
||||
context_->IASetVertexBuffers(0, 1, nextVertexBuffers_, (UINT *)curPipeline_->input->strides.data(), (UINT *)nextVertexBufferOffsets_);
|
||||
context_->IASetVertexBuffers(0, 1, &nextVertexBuffer_, &curPipeline_->input->stride, &nextVertexBufferOffset_);
|
||||
context_->IASetIndexBuffer(nextIndexBuffer_, DXGI_FORMAT_R16_UINT, nextIndexBufferOffset_);
|
||||
if (curPipeline_->dynamicUniforms) {
|
||||
context_->VSSetConstantBuffers(0, 1, &curPipeline_->dynamicUniforms);
|
||||
|
|
|
@ -231,14 +231,14 @@ public:
|
|||
decl_->Release();
|
||||
}
|
||||
}
|
||||
int GetStride(int binding) const { return stride_[binding]; }
|
||||
int GetStride() const { return stride_; }
|
||||
void Apply(LPDIRECT3DDEVICE9 device) {
|
||||
device->SetVertexDeclaration(decl_);
|
||||
}
|
||||
|
||||
private:
|
||||
LPDIRECT3DVERTEXDECLARATION9 decl_;
|
||||
int stride_[4];
|
||||
int stride_;
|
||||
};
|
||||
|
||||
class D3D9ShaderModule : public ShaderModule {
|
||||
|
@ -560,12 +560,9 @@ public:
|
|||
s->Apply(device_, start + i);
|
||||
}
|
||||
}
|
||||
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override {
|
||||
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
|
||||
for (int i = 0; i < count; i++) {
|
||||
curVBuffers_[i + start] = (D3D9Buffer *)buffers[i];
|
||||
curVBufferOffsets_[i + start] = offsets ? offsets[i] : 0;
|
||||
}
|
||||
void BindVertexBuffer(Buffer *vertexBuffer, int offset) override {
|
||||
curVBuffer_ = (D3D9Buffer *)vertexBuffer;
|
||||
curVBufferOffset_ = offset;
|
||||
}
|
||||
void BindIndexBuffer(Buffer *indexBuffer, int offset) override {
|
||||
curIBuffer_ = (D3D9Buffer *)indexBuffer;
|
||||
|
@ -645,8 +642,8 @@ private:
|
|||
|
||||
// Bound state
|
||||
AutoRef<D3D9Pipeline> curPipeline_;
|
||||
AutoRef<D3D9Buffer> curVBuffers_[4];
|
||||
int curVBufferOffsets_[4]{};
|
||||
AutoRef<D3D9Buffer> curVBuffer_;
|
||||
int curVBufferOffset_ = 0;
|
||||
AutoRef<D3D9Buffer> curIBuffer_;
|
||||
int curIBufferOffset_ = 0;
|
||||
AutoRef<Framebuffer> curRenderTarget_;
|
||||
|
@ -1028,7 +1025,7 @@ D3D9InputLayout::D3D9InputLayout(LPDIRECT3DDEVICE9 device, const InputLayoutDesc
|
|||
D3DVERTEXELEMENT9 *elements = new D3DVERTEXELEMENT9[desc.attributes.size() + 1];
|
||||
size_t i;
|
||||
for (i = 0; i < desc.attributes.size(); i++) {
|
||||
elements[i].Stream = desc.attributes[i].binding;
|
||||
elements[i].Stream = 0;
|
||||
elements[i].Offset = desc.attributes[i].offset;
|
||||
elements[i].Method = D3DDECLMETHOD_DEFAULT;
|
||||
SemanticToD3D9UsageAndIndex(desc.attributes[i].location, &elements[i].Usage, &elements[i].UsageIndex);
|
||||
|
@ -1038,9 +1035,7 @@ D3D9InputLayout::D3D9InputLayout(LPDIRECT3DDEVICE9 device, const InputLayoutDesc
|
|||
// Zero the last one.
|
||||
memcpy(&elements[i], &end, sizeof(elements[i]));
|
||||
|
||||
for (i = 0; i < desc.bindings.size(); i++) {
|
||||
stride_[i] = desc.bindings[i].stride;
|
||||
}
|
||||
stride_ = desc.stride;
|
||||
|
||||
HRESULT hr = device->CreateVertexDeclaration(elements, &decl_);
|
||||
if (FAILED(hr)) {
|
||||
|
@ -1174,7 +1169,7 @@ inline int D3DPrimCount(D3DPRIMITIVETYPE prim, int size) {
|
|||
}
|
||||
|
||||
void D3D9Context::Draw(int vertexCount, int offset) {
|
||||
device_->SetStreamSource(0, curVBuffers_[0]->vbuffer_, curVBufferOffsets_[0], curPipeline_->inputLayout->GetStride(0));
|
||||
device_->SetStreamSource(0, curVBuffer_->vbuffer_, curVBufferOffset_, curPipeline_->inputLayout->GetStride());
|
||||
curPipeline_->inputLayout->Apply(device_);
|
||||
curPipeline_->Apply(device_, stencilRef_, stencilWriteMask_, stencilCompareMask_);
|
||||
ApplyDynamicState();
|
||||
|
@ -1185,7 +1180,7 @@ void D3D9Context::DrawIndexed(int vertexCount, int offset) {
|
|||
curPipeline_->inputLayout->Apply(device_);
|
||||
curPipeline_->Apply(device_, stencilRef_, stencilWriteMask_, stencilCompareMask_);
|
||||
ApplyDynamicState();
|
||||
device_->SetStreamSource(0, curVBuffers_[0]->vbuffer_, curVBufferOffsets_[0], curPipeline_->inputLayout->GetStride(0));
|
||||
device_->SetStreamSource(0, curVBuffer_->vbuffer_, curVBufferOffset_, curPipeline_->inputLayout->GetStride());
|
||||
device_->SetIndices(curIBuffer_->ibuffer_);
|
||||
device_->DrawIndexedPrimitive(curPipeline_->prim, 0, 0, vertexCount, offset, D3DPrimCount(curPipeline_->prim, vertexCount));
|
||||
}
|
||||
|
@ -1195,7 +1190,7 @@ void D3D9Context::DrawUP(const void *vdata, int vertexCount) {
|
|||
curPipeline_->Apply(device_, stencilRef_, stencilWriteMask_, stencilCompareMask_);
|
||||
ApplyDynamicState();
|
||||
|
||||
device_->DrawPrimitiveUP(curPipeline_->prim, D3DPrimCount(curPipeline_->prim, vertexCount), vdata, curPipeline_->inputLayout->GetStride(0));
|
||||
device_->DrawPrimitiveUP(curPipeline_->prim, D3DPrimCount(curPipeline_->prim, vertexCount), vdata, curPipeline_->inputLayout->GetStride());
|
||||
}
|
||||
|
||||
static uint32_t SwapRB(uint32_t c) {
|
||||
|
|
|
@ -146,12 +146,14 @@ bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuin
|
|||
alignment = 16;
|
||||
break;
|
||||
|
||||
#ifdef GL_COMPRESSED_RGBA_ASTC_4x4_KHR
|
||||
case DataFormat::ASTC_4x4_UNORM_BLOCK:
|
||||
internalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
|
||||
format = GL_RGBA;
|
||||
type = GL_FLOAT;
|
||||
alignment = 16;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -592,7 +592,9 @@ bool CheckGLExtensions() {
|
|||
for (int i = 0; i < numCompressedFormats; i++) {
|
||||
switch (compressedFormats[i]) {
|
||||
case GL_COMPRESSED_RGB8_ETC2: gl_extensions.supportsETC2 = true; break;
|
||||
#ifdef GL_COMPRESSED_RGBA_ASTC_4x4_KHR
|
||||
case GL_COMPRESSED_RGBA_ASTC_4x4_KHR: gl_extensions.supportsASTC = true; break;
|
||||
#endif
|
||||
#ifndef USING_GLES2
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: gl_extensions.supportsBC123 = true; break;
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM: gl_extensions.supportsBC7 = true; break;
|
||||
|
|
|
@ -32,25 +32,25 @@ void GLDeleter::Perform(GLRenderManager *renderManager, bool skipGLCalls) {
|
|||
}
|
||||
pushBuffers.clear();
|
||||
for (auto shader : shaders) {
|
||||
if (skipGLCalls)
|
||||
if (skipGLCalls && shader)
|
||||
shader->shader = 0; // prevent the glDeleteShader
|
||||
delete shader;
|
||||
}
|
||||
shaders.clear();
|
||||
for (auto program : programs) {
|
||||
if (skipGLCalls)
|
||||
if (skipGLCalls && program)
|
||||
program->program = 0; // prevent the glDeleteProgram
|
||||
delete program;
|
||||
}
|
||||
programs.clear();
|
||||
for (auto buffer : buffers) {
|
||||
if (skipGLCalls)
|
||||
if (skipGLCalls && buffer)
|
||||
buffer->buffer_ = 0;
|
||||
delete buffer;
|
||||
}
|
||||
buffers.clear();
|
||||
for (auto texture : textures) {
|
||||
if (skipGLCalls)
|
||||
if (skipGLCalls && texture)
|
||||
texture->texture = 0;
|
||||
delete texture;
|
||||
}
|
||||
|
|
|
@ -334,10 +334,10 @@ void GLQueueRunner::RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLC
|
|||
step.create_shader.shader->desc.c_str(),
|
||||
infoLog.c_str(),
|
||||
LineNumberString(code).c_str());
|
||||
std::vector<std::string> lines;
|
||||
std::vector<std::string_view> lines;
|
||||
SplitString(errorString, '\n', lines);
|
||||
for (auto &line : lines) {
|
||||
ERROR_LOG(G3D, "%s", line.c_str());
|
||||
for (auto line : lines) {
|
||||
ERROR_LOG(G3D, "%.*s", (int)line.size(), line.data());
|
||||
}
|
||||
if (errorCallback_) {
|
||||
std::string desc = StringFromFormat("Shader compilation failed: %s", step.create_shader.stage == GL_VERTEX_SHADER ? "vertex" : "fragment");
|
||||
|
@ -1251,7 +1251,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
}
|
||||
for (size_t i = 0; i < layout->entries.size(); i++) {
|
||||
auto &entry = layout->entries[i];
|
||||
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.draw.vertexOffset + entry.offset));
|
||||
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, layout->stride, (const void *)(c.draw.vertexOffset + entry.offset));
|
||||
}
|
||||
if (c.draw.indexBuffer) {
|
||||
GLuint buf = c.draw.indexBuffer->buffer_;
|
||||
|
|
|
@ -190,10 +190,10 @@ public:
|
|||
int count;
|
||||
GLenum type;
|
||||
GLboolean normalized;
|
||||
int stride;
|
||||
intptr_t offset;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
int stride;
|
||||
int semanticsMask_ = 0;
|
||||
};
|
||||
|
||||
|
@ -331,11 +331,12 @@ public:
|
|||
return step.create_program.program;
|
||||
}
|
||||
|
||||
GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries) {
|
||||
GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries, int stride) {
|
||||
GLRInitStep &step = initSteps_.push_uninitialized();
|
||||
step.stepType = GLRInitStepType::CREATE_INPUT_LAYOUT;
|
||||
step.create_input_layout.inputLayout = new GLRInputLayout();
|
||||
step.create_input_layout.inputLayout->entries = entries;
|
||||
step.create_input_layout.inputLayout->stride = stride;
|
||||
for (auto &iter : step.create_input_layout.inputLayout->entries) {
|
||||
step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location;
|
||||
}
|
||||
|
@ -349,24 +350,31 @@ public:
|
|||
}
|
||||
|
||||
void DeleteShader(GLRShader *shader) {
|
||||
_dbg_assert_(shader != nullptr);
|
||||
deleter_.shaders.push_back(shader);
|
||||
}
|
||||
void DeleteProgram(GLRProgram *program) {
|
||||
_dbg_assert_(program != nullptr);
|
||||
deleter_.programs.push_back(program);
|
||||
}
|
||||
void DeleteBuffer(GLRBuffer *buffer) {
|
||||
_dbg_assert_(buffer != nullptr);
|
||||
deleter_.buffers.push_back(buffer);
|
||||
}
|
||||
void DeleteTexture(GLRTexture *texture) {
|
||||
_dbg_assert_(texture != nullptr);
|
||||
deleter_.textures.push_back(texture);
|
||||
}
|
||||
void DeleteInputLayout(GLRInputLayout *inputLayout) {
|
||||
_dbg_assert_(inputLayout != nullptr);
|
||||
deleter_.inputLayouts.push_back(inputLayout);
|
||||
}
|
||||
void DeleteFramebuffer(GLRFramebuffer *framebuffer) {
|
||||
_dbg_assert_(framebuffer != nullptr);
|
||||
deleter_.framebuffers.push_back(framebuffer);
|
||||
}
|
||||
void DeletePushBuffer(GLPushBuffer *pushbuffer) {
|
||||
_dbg_assert_(pushbuffer != nullptr);
|
||||
deleter_.pushBuffers.push_back(pushbuffer);
|
||||
}
|
||||
|
||||
|
|
|
@ -421,12 +421,9 @@ public:
|
|||
void BindNativeTexture(int sampler, void *nativeTexture) override;
|
||||
|
||||
void BindPipeline(Pipeline *pipeline) override;
|
||||
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override {
|
||||
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
|
||||
for (int i = 0; i < count; i++) {
|
||||
curVBuffers_[i + start] = (OpenGLBuffer *)buffers[i];
|
||||
curVBufferOffsets_[i + start] = offsets ? offsets[i] : 0;
|
||||
}
|
||||
void BindVertexBuffer(Buffer *buffer, int offset) override {
|
||||
curVBuffer_ = (OpenGLBuffer *)buffer;
|
||||
curVBufferOffset_ = offset;
|
||||
}
|
||||
void BindIndexBuffer(Buffer *indexBuffer, int offset) override {
|
||||
curIBuffer_ = (OpenGLBuffer *)indexBuffer;
|
||||
|
@ -505,9 +502,9 @@ private:
|
|||
const GLRTexture *boundTextures_[MAX_TEXTURE_SLOTS]{};
|
||||
|
||||
AutoRef<OpenGLPipeline> curPipeline_;
|
||||
AutoRef<OpenGLBuffer> curVBuffers_[4]{};
|
||||
int curVBufferOffsets_[4]{};
|
||||
AutoRef<OpenGLBuffer> curVBuffer_;
|
||||
AutoRef<OpenGLBuffer> curIBuffer_;
|
||||
int curVBufferOffset_ = 0;
|
||||
int curIBufferOffset_ = 0;
|
||||
AutoRef<Framebuffer> curRenderTarget_;
|
||||
|
||||
|
@ -934,7 +931,7 @@ void OpenGLTexture::UpdateTextureLevels(GLRenderManager *render, const uint8_t *
|
|||
OpenGLTexture::~OpenGLTexture() {
|
||||
if (tex_) {
|
||||
render_->DeleteTexture(tex_);
|
||||
tex_ = 0;
|
||||
tex_ = nullptr;
|
||||
generatedMips_ = false;
|
||||
}
|
||||
}
|
||||
|
@ -1370,20 +1367,20 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
|
|||
}
|
||||
|
||||
void OpenGLContext::Draw(int vertexCount, int offset) {
|
||||
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call Draw without a vertex buffer");
|
||||
_dbg_assert_msg_(curVBuffer_ != nullptr, "Can't call Draw without a vertex buffer");
|
||||
ApplySamplers();
|
||||
_assert_(curPipeline_->inputLayout);
|
||||
renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0], curPipeline_->prim, offset, vertexCount);
|
||||
renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, curVBuffer_->buffer_, curVBufferOffset_, curPipeline_->prim, offset, vertexCount);
|
||||
}
|
||||
|
||||
void OpenGLContext::DrawIndexed(int vertexCount, int offset) {
|
||||
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call DrawIndexed without a vertex buffer");
|
||||
_dbg_assert_msg_(curVBuffer_ != nullptr, "Can't call DrawIndexed without a vertex buffer");
|
||||
_dbg_assert_msg_(curIBuffer_ != nullptr, "Can't call DrawIndexed without an index buffer");
|
||||
ApplySamplers();
|
||||
_assert_(curPipeline_->inputLayout);
|
||||
renderManager_.DrawIndexed(
|
||||
curPipeline_->inputLayout->inputLayout_,
|
||||
curVBuffers_[0]->buffer_, curVBufferOffsets_[0],
|
||||
curVBuffer_->buffer_, curVBufferOffset_,
|
||||
curIBuffer_->buffer_, curIBufferOffset_ + offset * sizeof(uint32_t),
|
||||
curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT);
|
||||
}
|
||||
|
@ -1432,13 +1429,12 @@ OpenGLInputLayout::~OpenGLInputLayout() {
|
|||
void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) {
|
||||
// TODO: This is only accurate if there's only one stream. But whatever, for now we
|
||||
// never use multiple streams anyway.
|
||||
stride = desc.bindings.empty() ? 0 : (GLsizei)desc.bindings[0].stride;
|
||||
stride = desc.stride;
|
||||
|
||||
std::vector<GLRInputLayout::Entry> entries;
|
||||
for (auto &attr : desc.attributes) {
|
||||
GLRInputLayout::Entry entry;
|
||||
entry.location = attr.location;
|
||||
entry.stride = (GLsizei)desc.bindings[attr.binding].stride;
|
||||
entry.offset = attr.offset;
|
||||
switch (attr.format) {
|
||||
case DataFormat::R32G32_FLOAT:
|
||||
|
@ -1470,7 +1466,7 @@ void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) {
|
|||
entries.push_back(entry);
|
||||
}
|
||||
if (!entries.empty()) {
|
||||
inputLayout_ = render_->CreateInputLayout(entries);
|
||||
inputLayout_ = render_->CreateInputLayout(entries, stride);
|
||||
} else {
|
||||
inputLayout_ = nullptr;
|
||||
}
|
||||
|
|
|
@ -1666,80 +1666,101 @@ void VulkanDeleteList::Take(VulkanDeleteList &del) {
|
|||
}
|
||||
|
||||
void VulkanDeleteList::PerformDeletes(VulkanContext *vulkan, VmaAllocator allocator) {
|
||||
int deleteCount = 0;
|
||||
|
||||
for (auto &callback : callbacks_) {
|
||||
callback.func(vulkan, callback.userdata);
|
||||
deleteCount++;
|
||||
}
|
||||
callbacks_.clear();
|
||||
|
||||
VkDevice device = vulkan->GetDevice();
|
||||
for (auto &cmdPool : cmdPools_) {
|
||||
vkDestroyCommandPool(device, cmdPool, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
cmdPools_.clear();
|
||||
for (auto &descPool : descPools_) {
|
||||
vkDestroyDescriptorPool(device, descPool, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
descPools_.clear();
|
||||
for (auto &module : modules_) {
|
||||
vkDestroyShaderModule(device, module, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
modules_.clear();
|
||||
for (auto &buf : buffers_) {
|
||||
vkDestroyBuffer(device, buf, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
buffers_.clear();
|
||||
for (auto &buf : buffersWithAllocs_) {
|
||||
vmaDestroyBuffer(allocator, buf.buffer, buf.alloc);
|
||||
deleteCount++;
|
||||
}
|
||||
buffersWithAllocs_.clear();
|
||||
for (auto &bufView : bufferViews_) {
|
||||
vkDestroyBufferView(device, bufView, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
bufferViews_.clear();
|
||||
for (auto &imageWithAlloc : imagesWithAllocs_) {
|
||||
vmaDestroyImage(allocator, imageWithAlloc.image, imageWithAlloc.alloc);
|
||||
deleteCount++;
|
||||
}
|
||||
imagesWithAllocs_.clear();
|
||||
for (auto &imageView : imageViews_) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
imageViews_.clear();
|
||||
for (auto &mem : deviceMemory_) {
|
||||
vkFreeMemory(device, mem, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
deviceMemory_.clear();
|
||||
for (auto &sampler : samplers_) {
|
||||
vkDestroySampler(device, sampler, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
samplers_.clear();
|
||||
for (auto &pipeline : pipelines_) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
pipelines_.clear();
|
||||
for (auto &pcache : pipelineCaches_) {
|
||||
vkDestroyPipelineCache(device, pcache, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
pipelineCaches_.clear();
|
||||
for (auto &renderPass : renderPasses_) {
|
||||
vkDestroyRenderPass(device, renderPass, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
renderPasses_.clear();
|
||||
for (auto &framebuffer : framebuffers_) {
|
||||
vkDestroyFramebuffer(device, framebuffer, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
framebuffers_.clear();
|
||||
for (auto &pipeLayout : pipelineLayouts_) {
|
||||
vkDestroyPipelineLayout(device, pipeLayout, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
pipelineLayouts_.clear();
|
||||
for (auto &descSetLayout : descSetLayouts_) {
|
||||
vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
descSetLayouts_.clear();
|
||||
for (auto &queryPool : queryPools_) {
|
||||
vkDestroyQueryPool(device, queryPool, nullptr);
|
||||
deleteCount++;
|
||||
}
|
||||
queryPools_.clear();
|
||||
deleteCount_ = deleteCount;
|
||||
}
|
||||
|
||||
void VulkanContext::GetImageMemoryRequirements(VkImage image, VkMemoryRequirements *mem_reqs, bool *dedicatedAllocation) {
|
||||
|
|
|
@ -138,6 +138,10 @@ public:
|
|||
void Take(VulkanDeleteList &del);
|
||||
void PerformDeletes(VulkanContext *vulkan, VmaAllocator allocator);
|
||||
|
||||
int GetLastDeleteCount() const {
|
||||
return deleteCount_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<VkCommandPool> cmdPools_;
|
||||
std::vector<VkDescriptorPool> descPools_;
|
||||
|
@ -157,6 +161,7 @@ private:
|
|||
std::vector<VkDescriptorSetLayout> descSetLayouts_;
|
||||
std::vector<VkQueryPool> queryPools_;
|
||||
std::vector<Callback> callbacks_;
|
||||
int deleteCount_ = 0;
|
||||
};
|
||||
|
||||
// VulkanContext manages the device and swapchain, and deferred deletion of objects.
|
||||
|
@ -392,6 +397,10 @@ public:
|
|||
return availablePresentModes_;
|
||||
}
|
||||
|
||||
int GetLastDeleteCount() const {
|
||||
return frame_[curFrame_].deleteList.GetLastDeleteCount();
|
||||
}
|
||||
|
||||
private:
|
||||
bool ChooseQueue();
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <mutex>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||
#include "Common/GPU/Vulkan/VulkanDebug.h"
|
||||
|
||||
|
@ -90,6 +91,19 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
|
|||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
// Can be used to temporarily turn errors into info for easier debugging.
|
||||
switch (messageCode) {
|
||||
case 1544472022:
|
||||
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
messageSeverity = (VkDebugUtilsMessageSeverityFlagBitsEXT)((messageSeverity & ~VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
int count;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_errorCountMutex);
|
||||
|
@ -126,7 +140,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
|
|||
#ifdef _WIN32
|
||||
OutputDebugStringA(msg.c_str());
|
||||
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
if (options->breakOnError && IsDebuggerPresent()) {
|
||||
if (options->breakOnError && System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT)) {
|
||||
DebugBreak();
|
||||
}
|
||||
if (options->msgBoxOnError) {
|
||||
|
@ -134,7 +148,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
|
|||
}
|
||||
} else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
||||
// Don't break on perf warnings for now, even with a debugger. We log them at least.
|
||||
if (options->breakOnWarning && IsDebuggerPresent() && 0 == (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)) {
|
||||
if (options->breakOnWarning && System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT) && 0 == (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)) {
|
||||
DebugBreak();
|
||||
}
|
||||
}
|
||||
|
|
129
Common/GPU/Vulkan/VulkanDescSet.cpp
Normal file
129
Common/GPU/Vulkan/VulkanDescSet.cpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
#include "Common/GPU/Vulkan/VulkanDescSet.h"
|
||||
|
||||
VulkanDescSetPool::~VulkanDescSetPool() {
|
||||
_assert_msg_(descPool_ == VK_NULL_HANDLE, "VulkanDescSetPool %s never destroyed", tag_);
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::Create(VulkanContext *vulkan, const BindingType *bindingTypes, uint32_t bindingTypesCount, uint32_t descriptorCount) {
|
||||
_assert_msg_(descPool_ == VK_NULL_HANDLE, "VulkanDescSetPool::Create when already exists");
|
||||
|
||||
vulkan_ = vulkan;
|
||||
info_ = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
|
||||
info_.maxSets = descriptorCount;
|
||||
_dbg_assert_(sizes_.empty());
|
||||
|
||||
uint32_t storageImageCount = 0;
|
||||
uint32_t storageBufferCount = 0;
|
||||
uint32_t combinedImageSamplerCount = 0;
|
||||
uint32_t uniformBufferDynamicCount = 0;
|
||||
for (uint32_t i = 0; i < bindingTypesCount; i++) {
|
||||
switch (bindingTypes[i]) {
|
||||
case BindingType::COMBINED_IMAGE_SAMPLER: combinedImageSamplerCount++; break;
|
||||
case BindingType::UNIFORM_BUFFER_DYNAMIC_VERTEX:
|
||||
case BindingType::UNIFORM_BUFFER_DYNAMIC_ALL: uniformBufferDynamicCount++; break;
|
||||
case BindingType::STORAGE_BUFFER_VERTEX:
|
||||
case BindingType::STORAGE_BUFFER_COMPUTE: storageBufferCount++; break;
|
||||
case BindingType::STORAGE_IMAGE_COMPUTE: storageImageCount++; break;
|
||||
}
|
||||
}
|
||||
if (combinedImageSamplerCount) {
|
||||
sizes_.push_back(VkDescriptorPoolSize{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, combinedImageSamplerCount * descriptorCount });
|
||||
}
|
||||
if (uniformBufferDynamicCount) {
|
||||
sizes_.push_back(VkDescriptorPoolSize{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, uniformBufferDynamicCount * descriptorCount });
|
||||
}
|
||||
if (storageBufferCount) {
|
||||
sizes_.push_back(VkDescriptorPoolSize{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, storageBufferCount * descriptorCount });
|
||||
}
|
||||
if (storageImageCount) {
|
||||
sizes_.push_back(VkDescriptorPoolSize{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, storageImageCount * descriptorCount });
|
||||
}
|
||||
VkResult res = Recreate(false);
|
||||
_assert_msg_(res == VK_SUCCESS, "Could not create VulkanDescSetPool %s", tag_);
|
||||
}
|
||||
|
||||
bool VulkanDescSetPool::Allocate(VkDescriptorSet *descriptorSets, int count, const VkDescriptorSetLayout *layouts) {
|
||||
if (descPool_ == VK_NULL_HANDLE || usage_ + count >= info_.maxSets) {
|
||||
// Missing or out of space, need to recreate.
|
||||
VkResult res = Recreate(grow_);
|
||||
_assert_msg_(res == VK_SUCCESS, "Could not grow VulkanDescSetPool %s on usage %d", tag_, (int)usage_);
|
||||
}
|
||||
|
||||
VkDescriptorSetAllocateInfo descAlloc{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
|
||||
descAlloc.descriptorPool = descPool_;
|
||||
descAlloc.descriptorSetCount = count;
|
||||
descAlloc.pSetLayouts = layouts;
|
||||
VkResult result = vkAllocateDescriptorSets(vulkan_->GetDevice(), &descAlloc, descriptorSets);
|
||||
|
||||
if (result == VK_ERROR_FRAGMENTED_POOL || result < 0) {
|
||||
WARN_LOG(G3D, "Pool %s %s - recreating", tag_, result == VK_ERROR_FRAGMENTED_POOL ? "fragmented" : "full");
|
||||
// There seems to have been a spec revision. Here we should apparently recreate the descriptor pool,
|
||||
// so let's do that. See https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkAllocateDescriptorSets.html
|
||||
// Fragmentation shouldn't really happen though since we wipe the pool every frame.
|
||||
VkResult res = Recreate(false);
|
||||
_assert_msg_(res == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to recreate a descriptor pool. sz=%d res=%d", usage_, (int)res);
|
||||
|
||||
// Need to update this pointer since we have allocated a new one.
|
||||
descAlloc.descriptorPool = descPool_;
|
||||
result = vkAllocateDescriptorSets(vulkan_->GetDevice(), &descAlloc, descriptorSets);
|
||||
_assert_msg_(result == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to allocate after recreating a descriptor pool. res=%d", (int)result);
|
||||
}
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usage_ += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::Reset() {
|
||||
_assert_msg_(descPool_ != VK_NULL_HANDLE, "VulkanDescSetPool::Reset without valid pool");
|
||||
vkResetDescriptorPool(vulkan_->GetDevice(), descPool_, 0);
|
||||
|
||||
usage_ = 0;
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::Destroy() {
|
||||
if (descPool_ != VK_NULL_HANDLE) {
|
||||
vulkan_->Delete().QueueDeleteDescriptorPool(descPool_);
|
||||
usage_ = 0;
|
||||
}
|
||||
sizes_.clear();
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::DestroyImmediately() {
|
||||
if (descPool_ != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(vulkan_->GetDevice(), descPool_, nullptr);
|
||||
descPool_ = VK_NULL_HANDLE;
|
||||
usage_ = 0;
|
||||
}
|
||||
sizes_.clear();
|
||||
}
|
||||
|
||||
VkResult VulkanDescSetPool::Recreate(bool grow) {
|
||||
_assert_msg_(vulkan_ != nullptr, "VulkanDescSetPool::Recreate without VulkanContext");
|
||||
|
||||
uint32_t prevSize = info_.maxSets;
|
||||
if (grow) {
|
||||
info_.maxSets *= 2;
|
||||
for (auto &size : sizes_)
|
||||
size.descriptorCount *= 2;
|
||||
}
|
||||
|
||||
// Delete the pool if it already exists.
|
||||
if (descPool_ != VK_NULL_HANDLE) {
|
||||
INFO_LOG(G3D, "Reallocating %s desc pool from %d to %d", tag_, prevSize, info_.maxSets);
|
||||
vulkan_->Delete().QueueDeleteDescriptorPool(descPool_);
|
||||
usage_ = 0;
|
||||
}
|
||||
|
||||
info_.pPoolSizes = &sizes_[0];
|
||||
info_.poolSizeCount = (uint32_t)sizes_.size();
|
||||
|
||||
VkResult result = vkCreateDescriptorPool(vulkan_->GetDevice(), &info_, nullptr, &descPool_);
|
||||
if (result == VK_SUCCESS) {
|
||||
vulkan_->SetDebugName(descPool_, VK_OBJECT_TYPE_DESCRIPTOR_POOL, tag_);
|
||||
}
|
||||
return result;
|
||||
}
|
48
Common/GPU/Vulkan/VulkanDescSet.h
Normal file
48
Common/GPU/Vulkan/VulkanDescSet.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/Data/Collections/FastVec.h"
|
||||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
enum class BindingType {
|
||||
COMBINED_IMAGE_SAMPLER,
|
||||
UNIFORM_BUFFER_DYNAMIC_VERTEX,
|
||||
UNIFORM_BUFFER_DYNAMIC_ALL,
|
||||
STORAGE_BUFFER_VERTEX,
|
||||
STORAGE_BUFFER_COMPUTE,
|
||||
STORAGE_IMAGE_COMPUTE,
|
||||
};
|
||||
|
||||
// Only appropriate for use in a per-frame pool.
|
||||
class VulkanDescSetPool {
|
||||
public:
|
||||
VulkanDescSetPool(const char *tag, bool grow = true) : tag_(tag), grow_(grow) {}
|
||||
~VulkanDescSetPool();
|
||||
|
||||
void Create(VulkanContext *vulkan, const BindingType *bindingTypes, uint32_t bindingTypesCount, uint32_t descriptorCount);
|
||||
// Allocate a new set, which may resize and empty the current sets.
|
||||
// Use only for the current frame.
|
||||
bool Allocate(VkDescriptorSet *descriptorSets, int count, const VkDescriptorSetLayout *layouts);
|
||||
void Reset();
|
||||
|
||||
// This queues up destruction.
|
||||
void Destroy();
|
||||
// This actually destroys immediately.
|
||||
void DestroyImmediately();
|
||||
|
||||
bool IsDestroyed() const {
|
||||
return !descPool_;
|
||||
}
|
||||
|
||||
private:
|
||||
VkResult Recreate(bool grow);
|
||||
|
||||
const char *tag_;
|
||||
VulkanContext *vulkan_ = nullptr;
|
||||
VkDescriptorPool descPool_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPoolCreateInfo info_{};
|
||||
std::vector<VkDescriptorPoolSize> sizes_;
|
||||
uint32_t usage_ = 0;
|
||||
bool grow_;
|
||||
};
|
|
@ -90,6 +90,10 @@ void FrameData::AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared)
|
|||
WARN_LOG(G3D, "%s returned from AcquireNextImage - processing the frame, but not presenting", VulkanResultToString(res));
|
||||
skipSwap = true;
|
||||
break;
|
||||
case VK_ERROR_SURFACE_LOST_KHR:
|
||||
ERROR_LOG(G3D, "%s returned from AcquireNextImage - ignoring, but this better be during shutdown", VulkanResultToString(res));
|
||||
skipSwap = true;
|
||||
break;
|
||||
default:
|
||||
// Weird, shouldn't get any other values. Maybe lost device?
|
||||
_assert_msg_(false, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res));
|
||||
|
|
|
@ -28,6 +28,7 @@ struct QueueProfileContext {
|
|||
double cpuStartTime;
|
||||
double cpuEndTime;
|
||||
double descWriteTime;
|
||||
int descriptorsWritten;
|
||||
};
|
||||
|
||||
class VKRFramebuffer;
|
||||
|
|
|
@ -2,6 +2,27 @@
|
|||
#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
|
||||
#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
|
||||
|
||||
static const char *rpTypeDebugNames[] = {
|
||||
"RENDER",
|
||||
"RENDER_DEPTH",
|
||||
"MV_RENDER",
|
||||
"MV_RENDER_DEPTH",
|
||||
"MS_RENDER",
|
||||
"MS_RENDER_DEPTH",
|
||||
"MS_MV_RENDER",
|
||||
"MS_MV_RENDER_DEPTH",
|
||||
"BACKBUF",
|
||||
};
|
||||
|
||||
const char *GetRPTypeName(RenderPassType rpType) {
|
||||
uint32_t index = (uint32_t)rpType;
|
||||
if (index < ARRAY_SIZE(rpTypeDebugNames)) {
|
||||
return rpTypeDebugNames[index];
|
||||
} else {
|
||||
return "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
VkSampleCountFlagBits MultiSampleLevelToFlagBits(int count) {
|
||||
// TODO: Check hardware support here, or elsewhere?
|
||||
// Some hardware only supports 4x.
|
||||
|
@ -387,12 +408,25 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
}
|
||||
|
||||
if (isBackbuffer) {
|
||||
// We don't specify any explicit transitions for these, so let's use subpass dependencies.
|
||||
// This makes sure that writes to the depth image are done before we try to write to it again.
|
||||
// From Sascha's examples.
|
||||
deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
deps[numDeps].dstSubpass = 0;
|
||||
deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
deps[numDeps].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
deps[numDeps].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
numDeps++;
|
||||
// Dependencies for the color image.
|
||||
deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
deps[numDeps].dstSubpass = 0;
|
||||
deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
deps[numDeps].srcAccessMask = 0;
|
||||
deps[numDeps].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
numDeps++;
|
||||
}
|
||||
|
||||
|
@ -494,6 +528,10 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
res = vkCreateRenderPass(vulkan->GetDevice(), &rp, nullptr, &pass);
|
||||
}
|
||||
|
||||
if (pass) {
|
||||
vulkan->SetDebugName(pass, VK_OBJECT_TYPE_RENDER_PASS, GetRPTypeName(rpType));
|
||||
}
|
||||
|
||||
_assert_(res == VK_SUCCESS);
|
||||
_assert_(pass != VK_NULL_HANDLE);
|
||||
return pass;
|
||||
|
|
|
@ -157,3 +157,5 @@ private:
|
|||
VkSampleCountFlagBits sampleCounts[(size_t)RenderPassType::TYPE_COUNT];
|
||||
RPKey key_;
|
||||
};
|
||||
|
||||
const char *GetRPTypeName(RenderPassType rpType);
|
||||
|
|
|
@ -130,9 +130,12 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int depth, i
|
|||
|
||||
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view_);
|
||||
if (res != VK_SUCCESS) {
|
||||
ERROR_LOG(G3D, "vkCreateImageView failed: %s", VulkanResultToString(res));
|
||||
// This leaks the image.
|
||||
ERROR_LOG(G3D, "vkCreateImageView failed: %s. Destroying image.", VulkanResultToString(res));
|
||||
_assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
|
||||
vmaDestroyImage(vulkan_->Allocator(), image_, allocation_);
|
||||
view_ = VK_NULL_HANDLE;
|
||||
image_ = VK_NULL_HANDLE;
|
||||
allocation_ = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
vulkan_->SetDebugName(view_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
|
||||
|
@ -141,6 +144,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int depth, i
|
|||
if (view_info.viewType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &arrayView_);
|
||||
// Assume that if the above view creation succeeded, so will this.
|
||||
_assert_(res == VK_SUCCESS);
|
||||
vulkan_->SetDebugName(arrayView_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
|
||||
}
|
||||
|
|
|
@ -314,7 +314,7 @@ static void VulkanFreeLibrary(VulkanLibraryHandle &h) {
|
|||
}
|
||||
|
||||
void VulkanSetAvailable(bool available) {
|
||||
INFO_LOG(G3D, "Forcing Vulkan availability to true");
|
||||
INFO_LOG(G3D, "Setting Vulkan availability to true");
|
||||
g_vulkanAvailabilityChecked = true;
|
||||
g_vulkanMayBeAvailable = available;
|
||||
}
|
||||
|
|
|
@ -35,223 +35,6 @@ using namespace PPSSPP_VK;
|
|||
// Always keep around push buffers at least this long (seconds).
|
||||
static const double PUSH_GARBAGE_COLLECTION_DELAY = 10.0;
|
||||
|
||||
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, const char *name, size_t size, VkBufferUsageFlags usage)
|
||||
: vulkan_(vulkan), name_(name), size_(size), usage_(usage) {
|
||||
RegisterGPUMemoryManager(this);
|
||||
bool res = AddBuffer();
|
||||
_assert_(res);
|
||||
}
|
||||
|
||||
VulkanPushBuffer::~VulkanPushBuffer() {
|
||||
UnregisterGPUMemoryManager(this);
|
||||
_dbg_assert_(!writePtr_);
|
||||
_assert_(buffers_.empty());
|
||||
}
|
||||
|
||||
bool VulkanPushBuffer::AddBuffer() {
|
||||
BufInfo info;
|
||||
VkDevice device = vulkan_->GetDevice();
|
||||
|
||||
VkBufferCreateInfo b{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||
b.size = size_;
|
||||
b.flags = 0;
|
||||
b.usage = usage_;
|
||||
b.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
b.queueFamilyIndexCount = 0;
|
||||
b.pQueueFamilyIndices = nullptr;
|
||||
|
||||
VmaAllocationCreateInfo allocCreateInfo{};
|
||||
allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
VmaAllocationInfo allocInfo{};
|
||||
|
||||
VkResult res = vmaCreateBuffer(vulkan_->Allocator(), &b, &allocCreateInfo, &info.buffer, &info.allocation, &allocInfo);
|
||||
if (VK_SUCCESS != res) {
|
||||
_assert_msg_(false, "vkCreateBuffer failed! result=%d", (int)res);
|
||||
return false;
|
||||
}
|
||||
|
||||
vulkan_->SetDebugName(info.buffer, VK_OBJECT_TYPE_BUFFER, name_);
|
||||
|
||||
buffers_.push_back(info);
|
||||
buf_ = buffers_.size() - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::Destroy(VulkanContext *vulkan) {
|
||||
_dbg_assert_(!writePtr_);
|
||||
for (BufInfo &info : buffers_) {
|
||||
vulkan->Delete().QueueDeleteBufferAllocation(info.buffer, info.allocation);
|
||||
}
|
||||
buffers_.clear();
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::NextBuffer(size_t minSize) {
|
||||
// First, unmap the current memory.
|
||||
Unmap();
|
||||
|
||||
buf_++;
|
||||
if (buf_ >= buffers_.size() || minSize > size_) {
|
||||
// Before creating the buffer, adjust to the new size_ if necessary.
|
||||
while (size_ < minSize) {
|
||||
size_ <<= 1;
|
||||
}
|
||||
|
||||
bool res = AddBuffer();
|
||||
_assert_(res);
|
||||
if (!res) {
|
||||
// Let's try not to crash at least?
|
||||
buf_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, move to the next buffer and map it.
|
||||
offset_ = 0;
|
||||
Map();
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::Defragment(VulkanContext *vulkan) {
|
||||
if (buffers_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, we have more than one. Destroy them all and start over with a larger one.
|
||||
size_t newSize = size_ * buffers_.size();
|
||||
Destroy(vulkan);
|
||||
|
||||
size_ = newSize;
|
||||
bool res = AddBuffer();
|
||||
_assert_(res);
|
||||
}
|
||||
|
||||
size_t VulkanPushBuffer::GetTotalSize() const {
|
||||
size_t sum = 0;
|
||||
if (buffers_.size() > 1)
|
||||
sum += size_ * (buffers_.size() - 1);
|
||||
sum += offset_;
|
||||
return sum;
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::GetDebugString(char *buffer, size_t bufSize) const {
|
||||
size_t sum = 0;
|
||||
if (buffers_.size() > 1)
|
||||
sum += size_ * (buffers_.size() - 1);
|
||||
sum += offset_;
|
||||
size_t capacity = size_ * buffers_.size();
|
||||
snprintf(buffer, bufSize, "Push %s: %s / %s", name_, NiceSizeFormat(sum).c_str(), NiceSizeFormat(capacity).c_str());
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::Map() {
|
||||
_dbg_assert_(!writePtr_);
|
||||
VkResult res = vmaMapMemory(vulkan_->Allocator(), buffers_[buf_].allocation, (void **)(&writePtr_));
|
||||
_dbg_assert_(writePtr_);
|
||||
_assert_(VK_SUCCESS == res);
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::Unmap() {
|
||||
_dbg_assert_msg_(writePtr_ != nullptr, "VulkanPushBuffer::Unmap: writePtr_ null here means we have a bug (map/unmap mismatch)");
|
||||
if (!writePtr_)
|
||||
return;
|
||||
|
||||
vmaUnmapMemory(vulkan_->Allocator(), buffers_[buf_].allocation);
|
||||
writePtr_ = nullptr;
|
||||
}
|
||||
|
||||
VulkanDescSetPool::~VulkanDescSetPool() {
|
||||
_assert_msg_(descPool_ == VK_NULL_HANDLE, "VulkanDescSetPool %s never destroyed", tag_);
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::Create(VulkanContext *vulkan, const VkDescriptorPoolCreateInfo &info, const std::vector<VkDescriptorPoolSize> &sizes) {
|
||||
_assert_msg_(descPool_ == VK_NULL_HANDLE, "VulkanDescSetPool::Create when already exists");
|
||||
|
||||
vulkan_ = vulkan;
|
||||
info_ = info;
|
||||
sizes_ = sizes;
|
||||
|
||||
VkResult res = Recreate(false);
|
||||
_assert_msg_(res == VK_SUCCESS, "Could not create VulkanDescSetPool %s", tag_);
|
||||
}
|
||||
|
||||
VkDescriptorSet VulkanDescSetPool::Allocate(int n, const VkDescriptorSetLayout *layouts, const char *tag) {
|
||||
if (descPool_ == VK_NULL_HANDLE || usage_ + n >= info_.maxSets) {
|
||||
// Missing or out of space, need to recreate.
|
||||
VkResult res = Recreate(grow_);
|
||||
_assert_msg_(res == VK_SUCCESS, "Could not grow VulkanDescSetPool %s on usage %d", tag_, (int)usage_);
|
||||
}
|
||||
|
||||
VkDescriptorSet desc;
|
||||
VkDescriptorSetAllocateInfo descAlloc{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
|
||||
descAlloc.descriptorPool = descPool_;
|
||||
descAlloc.descriptorSetCount = n;
|
||||
descAlloc.pSetLayouts = layouts;
|
||||
VkResult result = vkAllocateDescriptorSets(vulkan_->GetDevice(), &descAlloc, &desc);
|
||||
|
||||
if (result == VK_ERROR_FRAGMENTED_POOL || result < 0) {
|
||||
// There seems to have been a spec revision. Here we should apparently recreate the descriptor pool,
|
||||
// so let's do that. See https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkAllocateDescriptorSets.html
|
||||
// Fragmentation shouldn't really happen though since we wipe the pool every frame.
|
||||
VkResult res = Recreate(false);
|
||||
_assert_msg_(res == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to recreate a descriptor pool. sz=%d res=%d", usage_, (int)res);
|
||||
|
||||
// Need to update this pointer since we have allocated a new one.
|
||||
descAlloc.descriptorPool = descPool_;
|
||||
result = vkAllocateDescriptorSets(vulkan_->GetDevice(), &descAlloc, &desc);
|
||||
_assert_msg_(result == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to allocate after recreating a descriptor pool. res=%d", (int)result);
|
||||
}
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
vulkan_->SetDebugName(desc, VK_OBJECT_TYPE_DESCRIPTOR_SET, tag);
|
||||
return desc;
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::Reset() {
|
||||
_assert_msg_(descPool_ != VK_NULL_HANDLE, "VulkanDescSetPool::Reset without valid pool");
|
||||
vkResetDescriptorPool(vulkan_->GetDevice(), descPool_, 0);
|
||||
|
||||
clear_();
|
||||
usage_ = 0;
|
||||
}
|
||||
|
||||
void VulkanDescSetPool::Destroy() {
|
||||
_assert_msg_(vulkan_ != nullptr, "VulkanDescSetPool::Destroy without VulkanContext");
|
||||
|
||||
if (descPool_ != VK_NULL_HANDLE) {
|
||||
vulkan_->Delete().QueueDeleteDescriptorPool(descPool_);
|
||||
clear_();
|
||||
usage_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
VkResult VulkanDescSetPool::Recreate(bool grow) {
|
||||
_assert_msg_(vulkan_ != nullptr, "VulkanDescSetPool::Recreate without VulkanContext");
|
||||
|
||||
uint32_t prevSize = info_.maxSets;
|
||||
if (grow) {
|
||||
info_.maxSets *= 2;
|
||||
for (auto &size : sizes_)
|
||||
size.descriptorCount *= 2;
|
||||
}
|
||||
|
||||
// Delete the pool if it already exists.
|
||||
if (descPool_ != VK_NULL_HANDLE) {
|
||||
DEBUG_LOG(G3D, "Reallocating %s desc pool from %d to %d", tag_, prevSize, info_.maxSets);
|
||||
vulkan_->Delete().QueueDeleteDescriptorPool(descPool_);
|
||||
clear_();
|
||||
usage_ = 0;
|
||||
}
|
||||
|
||||
info_.pPoolSizes = &sizes_[0];
|
||||
info_.poolSizeCount = (uint32_t)sizes_.size();
|
||||
|
||||
VkResult result = vkCreateDescriptorPool(vulkan_->GetDevice(), &info_, nullptr, &descPool_);
|
||||
if (result == VK_SUCCESS) {
|
||||
vulkan_->SetDebugName(descPool_, VK_OBJECT_TYPE_DESCRIPTOR_POOL, tag_);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage)
|
||||
: vulkan_(vulkan), name_(name), originalBlockSize_(originalBlockSize), usage_(usage) {
|
||||
RegisterGPUMemoryManager(this);
|
||||
|
@ -291,7 +74,7 @@ VulkanPushPool::Block VulkanPushPool::CreateBlock(size_t size) {
|
|||
_assert_(result == VK_SUCCESS);
|
||||
|
||||
result = vmaMapMemory(vulkan_->Allocator(), block.allocation, (void **)(&block.writePtr));
|
||||
_assert_msg_(result == VK_SUCCESS, "VulkanPushPool: Failed to map memory (result = %08x)", result);
|
||||
_assert_msg_(result == VK_SUCCESS, "VulkanPushPool: Failed to map memory (result = %s)", VulkanResultToString(result));
|
||||
|
||||
_assert_msg_(block.writePtr != nullptr, "VulkanPushPool: Failed to map memory on block of size %d", (int)block.size);
|
||||
return block;
|
||||
|
|
|
@ -16,88 +16,6 @@ VK_DEFINE_HANDLE(VmaAllocation);
|
|||
//
|
||||
// Vulkan memory management utils.
|
||||
|
||||
// VulkanPushBuffer
|
||||
// Simple incrementing allocator.
|
||||
// Use these to push vertex, index and uniform data. Generally you'll have two or three of these
|
||||
// and alternate on each frame. Make sure not to reset until the fence from the last time you used it
|
||||
// has completed.
|
||||
// NOTE: This has now been replaced with VulkanPushPool for all uses except the vertex cache.
|
||||
class VulkanPushBuffer : public GPUMemoryManager {
|
||||
struct BufInfo {
|
||||
VkBuffer buffer;
|
||||
VmaAllocation allocation;
|
||||
};
|
||||
|
||||
public:
|
||||
// NOTE: If you create a push buffer with PushBufferType::GPU_ONLY,
|
||||
// then you can't use any of the push functions as pointers will not be reachable from the CPU.
|
||||
// You must in this case use Allocate() only, and pass the returned offset and the VkBuffer to Vulkan APIs.
|
||||
VulkanPushBuffer(VulkanContext *vulkan, const char *name, size_t size, VkBufferUsageFlags usage);
|
||||
~VulkanPushBuffer();
|
||||
|
||||
void Destroy(VulkanContext *vulkan);
|
||||
|
||||
void Reset() { offset_ = 0; }
|
||||
|
||||
void GetDebugString(char *buffer, size_t bufSize) const override;
|
||||
const char *Name() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
// Needs context in case of defragment.
|
||||
void Begin(VulkanContext *vulkan) {
|
||||
buf_ = 0;
|
||||
offset_ = 0;
|
||||
// Note: we must defrag because some buffers may be smaller than size_.
|
||||
Defragment(vulkan);
|
||||
Map();
|
||||
}
|
||||
|
||||
void BeginNoReset() { Map(); }
|
||||
void End() { Unmap(); }
|
||||
|
||||
void Map();
|
||||
void Unmap();
|
||||
|
||||
// When using the returned memory, make sure to bind the returned vkbuf.
|
||||
uint8_t *Allocate(VkDeviceSize numBytes, VkDeviceSize alignment, VkBuffer *vkbuf, uint32_t *bindOffset) {
|
||||
size_t offset = (offset_ + alignment - 1) & ~(alignment - 1);
|
||||
if (offset + numBytes > size_) {
|
||||
NextBuffer(numBytes);
|
||||
offset = offset_;
|
||||
}
|
||||
offset_ = offset + numBytes;
|
||||
*bindOffset = (uint32_t)offset;
|
||||
*vkbuf = buffers_[buf_].buffer;
|
||||
return writePtr_ + offset;
|
||||
}
|
||||
|
||||
VkDeviceSize Push(const void *data, VkDeviceSize numBytes, int alignment, VkBuffer *vkbuf) {
|
||||
uint32_t bindOffset;
|
||||
uint8_t *ptr = Allocate(numBytes, alignment, vkbuf, &bindOffset);
|
||||
memcpy(ptr, data, numBytes);
|
||||
return bindOffset;
|
||||
}
|
||||
|
||||
size_t GetOffset() const { return offset_; }
|
||||
size_t GetTotalSize() const;
|
||||
|
||||
private:
|
||||
bool AddBuffer();
|
||||
void NextBuffer(size_t minSize);
|
||||
void Defragment(VulkanContext *vulkan);
|
||||
|
||||
VulkanContext *vulkan_;
|
||||
|
||||
std::vector<BufInfo> buffers_;
|
||||
size_t buf_ = 0;
|
||||
size_t offset_ = 0;
|
||||
size_t size_ = 0;
|
||||
uint8_t *writePtr_ = nullptr;
|
||||
VkBufferUsageFlags usage_;
|
||||
const char *name_;
|
||||
};
|
||||
|
||||
// Simple memory pushbuffer pool that can share blocks between the "frames", to reduce the impact of push memory spikes -
|
||||
// a later frame can gobble up redundant buffers from an earlier frame even if they don't share frame index.
|
||||
// NOT thread safe! Can only be used from one thread (our main thread).
|
||||
|
@ -176,33 +94,3 @@ private:
|
|||
int curBlockIndex_ = -1;
|
||||
const char *name_;
|
||||
};
|
||||
|
||||
// Only appropriate for use in a per-frame pool.
|
||||
class VulkanDescSetPool {
|
||||
public:
|
||||
VulkanDescSetPool(const char *tag, bool grow) : tag_(tag), grow_(grow) {}
|
||||
~VulkanDescSetPool();
|
||||
|
||||
// Must call this before use: defines how to clear cache of ANY returned values from Allocate().
|
||||
void Setup(const std::function<void()> &clear) {
|
||||
clear_ = clear;
|
||||
}
|
||||
void Create(VulkanContext *vulkan, const VkDescriptorPoolCreateInfo &info, const std::vector<VkDescriptorPoolSize> &sizes);
|
||||
// Allocate a new set, which may resize and empty the current sets.
|
||||
// Use only for the current frame, unless in a cache cleared by clear_.
|
||||
VkDescriptorSet Allocate(int n, const VkDescriptorSetLayout *layouts, const char *tag);
|
||||
void Reset();
|
||||
void Destroy();
|
||||
|
||||
private:
|
||||
VkResult Recreate(bool grow);
|
||||
|
||||
const char *tag_;
|
||||
VulkanContext *vulkan_ = nullptr;
|
||||
VkDescriptorPool descPool_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPoolCreateInfo info_{};
|
||||
std::vector<VkDescriptorPoolSize> sizes_;
|
||||
std::function<void()> clear_;
|
||||
uint32_t usage_ = 0;
|
||||
bool grow_;
|
||||
};
|
||||
|
|
|
@ -129,7 +129,6 @@ bool VulkanQueueRunner::CreateSwapchain(VkCommandBuffer cmdInit) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool VulkanQueueRunner::InitBackbufferFramebuffers(int width, int height) {
|
||||
VkResult res;
|
||||
// We share the same depth buffer but have multiple color buffers, see the loop below.
|
||||
|
@ -173,7 +172,7 @@ bool VulkanQueueRunner::InitDepthStencilBuffer(VkCommandBuffer cmd) {
|
|||
image_info.queueFamilyIndexCount = 0;
|
||||
image_info.pQueueFamilyIndices = nullptr;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
image_info.flags = 0;
|
||||
|
||||
depth_.format = depth_format;
|
||||
|
@ -338,7 +337,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) {
|
|||
}
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps) {
|
||||
void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, int curFrame, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps) {
|
||||
QueueProfileContext *profile = frameData.profile.enabled ? &frameData.profile : nullptr;
|
||||
|
||||
if (profile)
|
||||
|
@ -394,7 +393,7 @@ void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frame
|
|||
vkCmdBeginDebugUtilsLabelEXT(cmd, &labelInfo);
|
||||
}
|
||||
}
|
||||
PerformRenderPass(step, cmd);
|
||||
PerformRenderPass(step, cmd, curFrame);
|
||||
break;
|
||||
case VKRStepType::COPY:
|
||||
PerformCopy(step, cmd);
|
||||
|
@ -414,7 +413,7 @@ void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frame
|
|||
|
||||
if (profile && profile->timestampsEnabled && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) {
|
||||
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size());
|
||||
profile->timestampDescriptions.push_back(StepToString(step));
|
||||
profile->timestampDescriptions.push_back(StepToString(vulkan_, step));
|
||||
}
|
||||
|
||||
if (emitLabels) {
|
||||
|
@ -674,36 +673,16 @@ const char *AspectToString(VkImageAspectFlags aspect) {
|
|||
}
|
||||
}
|
||||
|
||||
static const char *rpTypeDebugNames[] = {
|
||||
"RENDER",
|
||||
"RENDER_DEPTH",
|
||||
"RENDER_INPUT",
|
||||
"RENDER_DEPTH_INPUT",
|
||||
"MV_RENDER",
|
||||
"MV_RENDER_DEPTH",
|
||||
"MV_RENDER_INPUT",
|
||||
"MV_RENDER_DEPTH_INPUT",
|
||||
"MS_RENDER",
|
||||
"MS_RENDER_DEPTH",
|
||||
"MS_RENDER_INPUT",
|
||||
"MS_RENDER_DEPTH_INPUT",
|
||||
"MS_MV_RENDER",
|
||||
"MS_MV_RENDER_DEPTH",
|
||||
"MS_MV_RENDER_INPUT",
|
||||
"MS_MV_RENDER_DEPTH_INPUT",
|
||||
"BACKBUF",
|
||||
};
|
||||
|
||||
std::string VulkanQueueRunner::StepToString(const VKRStep &step) const {
|
||||
std::string VulkanQueueRunner::StepToString(VulkanContext *vulkan, const VKRStep &step) {
|
||||
char buffer[256];
|
||||
switch (step.stepType) {
|
||||
case VKRStepType::RENDER:
|
||||
{
|
||||
int w = step.render.framebuffer ? step.render.framebuffer->width : vulkan_->GetBackbufferWidth();
|
||||
int h = step.render.framebuffer ? step.render.framebuffer->height : vulkan_->GetBackbufferHeight();
|
||||
int w = step.render.framebuffer ? step.render.framebuffer->width : vulkan->GetBackbufferWidth();
|
||||
int h = step.render.framebuffer ? step.render.framebuffer->height : vulkan->GetBackbufferHeight();
|
||||
int actual_w = step.render.renderArea.extent.width;
|
||||
int actual_h = step.render.renderArea.extent.height;
|
||||
const char *renderCmd = rpTypeDebugNames[(size_t)step.render.renderPassType];
|
||||
const char *renderCmd = GetRPTypeName(step.render.renderPassType);
|
||||
snprintf(buffer, sizeof(buffer), "%s %s %s (draws: %d, %dx%d/%dx%d)", renderCmd, step.tag, step.render.framebuffer ? step.render.framebuffer->Tag() : "", step.render.numDraws, actual_w, actual_h, w, h);
|
||||
break;
|
||||
}
|
||||
|
@ -896,9 +875,6 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) {
|
|||
case VKRRenderCommand::BIND_GRAPHICS_PIPELINE:
|
||||
INFO_LOG(G3D, " BindGraphicsPipeline(%x)", (int)(intptr_t)cmd.graphics_pipeline.pipeline);
|
||||
break;
|
||||
case VKRRenderCommand::BIND_COMPUTE_PIPELINE:
|
||||
INFO_LOG(G3D, " BindComputePipeline(%x)", (int)(intptr_t)cmd.compute_pipeline.pipeline);
|
||||
break;
|
||||
case VKRRenderCommand::BLEND:
|
||||
INFO_LOG(G3D, " BlendColor(%08x)", cmd.blendColor.color);
|
||||
break;
|
||||
|
@ -938,19 +914,19 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) {
|
|||
}
|
||||
|
||||
void VulkanQueueRunner::LogCopy(const VKRStep &step) {
|
||||
INFO_LOG(G3D, "%s", StepToString(step).c_str());
|
||||
INFO_LOG(G3D, "%s", StepToString(vulkan_, step).c_str());
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::LogBlit(const VKRStep &step) {
|
||||
INFO_LOG(G3D, "%s", StepToString(step).c_str());
|
||||
INFO_LOG(G3D, "%s", StepToString(vulkan_, step).c_str());
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::LogReadback(const VKRStep &step) {
|
||||
INFO_LOG(G3D, "%s", StepToString(step).c_str());
|
||||
INFO_LOG(G3D, "%s", StepToString(vulkan_, step).c_str());
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::LogReadbackImage(const VKRStep &step) {
|
||||
INFO_LOG(G3D, "%s", StepToString(step).c_str());
|
||||
INFO_LOG(G3D, "%s", StepToString(vulkan_, step).c_str());
|
||||
}
|
||||
|
||||
void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, VkImageLayout depthStencilLayout, int numLayers, VulkanBarrier *recordBarrier) {
|
||||
|
@ -1123,7 +1099,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou
|
|||
}
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer cmd) {
|
||||
void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer cmd, int curFrame) {
|
||||
for (size_t i = 0; i < step.preTransitions.size(); i++) {
|
||||
const TransitionRequest &iter = step.preTransitions[i];
|
||||
if (iter.aspect == VK_IMAGE_ASPECT_COLOR_BIT && iter.fb->color.layout != iter.targetLayout) {
|
||||
|
@ -1219,6 +1195,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
|
|||
// The stencil ones are very commonly mostly redundant so let's eliminate them where possible.
|
||||
// Might also want to consider scissor and viewport.
|
||||
VkPipeline lastPipeline = VK_NULL_HANDLE;
|
||||
FastVec<PendingDescSet> *descSets = nullptr;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
|
||||
bool pipelineOK = false;
|
||||
|
@ -1261,7 +1238,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
|
|||
|
||||
if (pipeline != VK_NULL_HANDLE) {
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
pipelineLayout = c.pipeline.pipelineLayout;
|
||||
descSets = &c.graphics_pipeline.pipelineLayout->frameData[curFrame].descSets_;
|
||||
pipelineLayout = c.graphics_pipeline.pipelineLayout->pipelineLayout;
|
||||
_dbg_assert_(pipelineLayout != VK_NULL_HANDLE);
|
||||
lastGraphicsPipeline = graphicsPipeline;
|
||||
pipelineOK = true;
|
||||
} else {
|
||||
|
@ -1276,20 +1255,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
|
|||
break;
|
||||
}
|
||||
|
||||
case VKRRenderCommand::BIND_COMPUTE_PIPELINE:
|
||||
{
|
||||
VKRComputePipeline *computePipeline = c.compute_pipeline.pipeline;
|
||||
if (computePipeline != lastComputePipeline) {
|
||||
VkPipeline pipeline = computePipeline->pipeline->BlockUntilReady();
|
||||
if (pipeline != VK_NULL_HANDLE) {
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
pipelineLayout = c.pipeline.pipelineLayout;
|
||||
lastComputePipeline = computePipeline;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case VKRRenderCommand::VIEWPORT:
|
||||
if (fb != nullptr) {
|
||||
vkCmdSetViewport(cmd, 0, 1, &c.viewport.vp);
|
||||
|
@ -1356,7 +1321,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
|
|||
|
||||
case VKRRenderCommand::DRAW_INDEXED:
|
||||
if (pipelineOK) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.drawIndexed.ds, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
|
||||
VkDescriptorSet set = (*descSets)[c.drawIndexed.descSetIndex].set;
|
||||
_dbg_assert_(set != VK_NULL_HANDLE);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
|
||||
vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, VK_INDEX_TYPE_UINT16);
|
||||
VkDeviceSize voffset = c.drawIndexed.voffset;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
|
||||
|
@ -1366,7 +1333,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
|
|||
|
||||
case VKRRenderCommand::DRAW:
|
||||
if (pipelineOK) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.draw.ds, c.draw.numUboOffsets, c.draw.uboOffsets);
|
||||
VkDescriptorSet set = (*descSets)[c.drawIndexed.descSetIndex].set;
|
||||
_dbg_assert_(set != VK_NULL_HANDLE);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, c.draw.numUboOffsets, c.draw.uboOffsets);
|
||||
if (c.draw.vbuffer) {
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &c.draw.vbuffer, &c.draw.voffset);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class VKRFramebuffer;
|
|||
struct VKRGraphicsPipeline;
|
||||
struct VKRComputePipeline;
|
||||
struct VKRImage;
|
||||
struct VKRPipelineLayout;
|
||||
struct FrameData;
|
||||
|
||||
enum {
|
||||
|
@ -30,7 +31,6 @@ enum {
|
|||
enum class VKRRenderCommand : uint8_t {
|
||||
REMOVED,
|
||||
BIND_GRAPHICS_PIPELINE, // async
|
||||
BIND_COMPUTE_PIPELINE, // async
|
||||
STENCIL,
|
||||
BLEND,
|
||||
VIEWPORT,
|
||||
|
@ -56,20 +56,12 @@ ENUM_CLASS_BITOPS(PipelineFlags);
|
|||
struct VkRenderData {
|
||||
VKRRenderCommand cmd;
|
||||
union {
|
||||
struct {
|
||||
VkPipeline pipeline;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
} pipeline;
|
||||
struct {
|
||||
VKRGraphicsPipeline *pipeline;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VKRPipelineLayout *pipelineLayout;
|
||||
} graphics_pipeline;
|
||||
struct {
|
||||
VKRComputePipeline *pipeline;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
} compute_pipeline;
|
||||
struct {
|
||||
VkDescriptorSet ds;
|
||||
uint32_t descSetIndex;
|
||||
int numUboOffsets;
|
||||
uint32_t uboOffsets[3];
|
||||
VkBuffer vbuffer;
|
||||
|
@ -78,7 +70,7 @@ struct VkRenderData {
|
|||
uint32_t offset;
|
||||
} draw;
|
||||
struct {
|
||||
VkDescriptorSet ds;
|
||||
uint32_t descSetIndex;
|
||||
uint32_t uboOffsets[3];
|
||||
uint16_t numUboOffsets;
|
||||
uint16_t instances;
|
||||
|
@ -118,9 +110,7 @@ struct VkRenderData {
|
|||
const char *annotation;
|
||||
} debugAnnotation;
|
||||
struct {
|
||||
int setNumber;
|
||||
VkDescriptorSet set;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
int setIndex;
|
||||
} bindDescSet;
|
||||
};
|
||||
};
|
||||
|
@ -230,10 +220,10 @@ public:
|
|||
}
|
||||
|
||||
void PreprocessSteps(std::vector<VKRStep *> &steps);
|
||||
void RunSteps(std::vector<VKRStep *> &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps = false);
|
||||
void RunSteps(std::vector<VKRStep *> &steps, int curFrame, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps = false);
|
||||
void LogSteps(const std::vector<VKRStep *> &steps, bool verbose);
|
||||
|
||||
std::string StepToString(const VKRStep &step) const;
|
||||
static std::string StepToString(VulkanContext *vulkan, const VKRStep &step);
|
||||
|
||||
void CreateDeviceObjects();
|
||||
void DestroyDeviceObjects();
|
||||
|
@ -290,7 +280,7 @@ private:
|
|||
bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering.
|
||||
|
||||
VKRRenderPass *PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd);
|
||||
void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd);
|
||||
void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd, int curFrame);
|
||||
void PerformCopy(const VKRStep &pass, VkCommandBuffer cmd);
|
||||
void PerformBlit(const VKRStep &pass, VkCommandBuffer cmd);
|
||||
void PerformReadback(const VKRStep &pass, VkCommandBuffer cmd, FrameData &frameData);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
|
||||
|
||||
#include "Common/LogReporting.h"
|
||||
#include "Common/Thread/ThreadUtil.h"
|
||||
#include "Common/VR/PPSSPPVR.h"
|
||||
|
||||
|
@ -29,6 +30,10 @@ using namespace PPSSPP_VK;
|
|||
|
||||
// renderPass is an example of the "compatibility class" or RenderPassType type.
|
||||
bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType, VkSampleCountFlagBits sampleCount, double scheduleTime, int countToCompile) {
|
||||
// Good torture test to test the shutdown-while-precompiling-shaders issue on PC where it's normally
|
||||
// hard to catch because shaders compile so fast.
|
||||
// sleep_ms(200);
|
||||
|
||||
bool multisample = RenderPassTypeHasMultisample(rpType);
|
||||
if (multisample) {
|
||||
if (sampleCount_ != VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM) {
|
||||
|
@ -111,7 +116,7 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
|
|||
pipe.pDynamicState = &desc->ds;
|
||||
pipe.pInputAssemblyState = &inputAssembly;
|
||||
pipe.pMultisampleState = &ms;
|
||||
pipe.layout = desc->pipelineLayout;
|
||||
pipe.layout = desc->pipelineLayout->pipelineLayout;
|
||||
pipe.basePipelineHandle = VK_NULL_HANDLE;
|
||||
pipe.basePipelineIndex = 0;
|
||||
pipe.subpass = 0;
|
||||
|
@ -187,7 +192,7 @@ void VKRGraphicsPipeline::DestroyVariantsInstant(VkDevice device) {
|
|||
|
||||
VKRGraphicsPipeline::~VKRGraphicsPipeline() {
|
||||
// This is called from the callbacked queued in QueueForDeletion.
|
||||
// Here we are free to directly delete stuff, don't need to queue.
|
||||
// When we reach here, we should already be empty, so let's assert on that.
|
||||
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
|
||||
_assert_(!pipeline[i]);
|
||||
}
|
||||
|
@ -195,6 +200,14 @@ VKRGraphicsPipeline::~VKRGraphicsPipeline() {
|
|||
desc->Release();
|
||||
}
|
||||
|
||||
void VKRGraphicsPipeline::BlockUntilCompiled() {
|
||||
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
|
||||
if (pipeline[i]) {
|
||||
pipeline[i]->BlockUntilReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VKRGraphicsPipeline::QueueForDeletion(VulkanContext *vulkan) {
|
||||
// Can't destroy variants here, the pipeline still lives for a while.
|
||||
vulkan->Delete().QueueCallback([](VulkanContext *vulkan, void *p) {
|
||||
|
@ -252,6 +265,7 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread,
|
|||
initTimeMs_("initTimeMs"),
|
||||
totalGPUTimeMs_("totalGPUTimeMs"),
|
||||
renderCPUTimeMs_("renderCPUTimeMs"),
|
||||
descUpdateTimeMs_("descUpdateCPUTimeMs"),
|
||||
useRenderThread_(useThread),
|
||||
frameTimeHistory_(frameTimeHistory)
|
||||
{
|
||||
|
@ -275,7 +289,6 @@ bool VulkanRenderManager::CreateBackbuffers() {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
VkCommandBuffer cmdInit = GetInitCmd();
|
||||
|
||||
if (!queueRunner_.CreateSwapchain(cmdInit)) {
|
||||
|
@ -297,6 +310,11 @@ bool VulkanRenderManager::CreateBackbuffers() {
|
|||
|
||||
outOfDateFrames_ = 0;
|
||||
|
||||
for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
|
||||
auto &frameData = frameData_[i];
|
||||
frameData.readyForFence = true; // Just in case.
|
||||
}
|
||||
|
||||
// Start the thread(s).
|
||||
if (HasBackbuffers()) {
|
||||
run_ = true; // For controlling the compiler thread's exit
|
||||
|
@ -395,6 +413,8 @@ VulkanRenderManager::~VulkanRenderManager() {
|
|||
|
||||
vulkan_->WaitUntilQueueIdle();
|
||||
|
||||
_dbg_assert_(pipelineLayouts_.empty());
|
||||
|
||||
VkDevice device = vulkan_->GetDevice();
|
||||
frameDataShared_.Destroy(vulkan_);
|
||||
for (int i = 0; i < inflightFramesAtStart_; i++) {
|
||||
|
@ -495,7 +515,7 @@ void VulkanRenderManager::CompileThreadFunc() {
|
|||
Task *task = new CreateMultiPipelinesTask(vulkan_, entries);
|
||||
g_threadManager.EnqueueTask(task);
|
||||
}
|
||||
|
||||
|
||||
queueRunner_.NotifyCompileDone();
|
||||
}
|
||||
}
|
||||
|
@ -628,6 +648,8 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
|
|||
|
||||
PollPresentTiming();
|
||||
|
||||
ResetDescriptorLists(curFrame);
|
||||
|
||||
int validBits = vulkan_->GetQueueFamilyProperties(vulkan_->GetGraphicsQueueFamilyIndex()).timestampValidBits;
|
||||
|
||||
FrameTimeData &frameTimeData = frameTimeHistory_.Add(frameId);
|
||||
|
@ -662,6 +684,13 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
|
|||
renderCPUTimeMs_.Update((frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0);
|
||||
renderCPUTimeMs_.Format(line, sizeof(line));
|
||||
str << line;
|
||||
descUpdateTimeMs_.Update(frameData.profile.descWriteTime * 1000.0);
|
||||
descUpdateTimeMs_.Format(line, sizeof(line));
|
||||
str << line;
|
||||
snprintf(line, sizeof(line), "Descriptors written: %d\n", frameData.profile.descriptorsWritten);
|
||||
str << line;
|
||||
snprintf(line, sizeof(line), "Resource deletions: %d\n", vulkan_->GetLastDeleteCount());
|
||||
str << line;
|
||||
for (int i = 0; i < numQueries - 1; i++) {
|
||||
uint64_t diff = (queryResults[i + 1] - queryResults[i]) & timestampDiffMask;
|
||||
double milliseconds = (double)diff * timestampConversionFactor;
|
||||
|
@ -687,10 +716,17 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
|
|||
renderCPUTimeMs_.Update((frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0);
|
||||
renderCPUTimeMs_.Format(line, sizeof(line));
|
||||
str << line;
|
||||
descUpdateTimeMs_.Update(frameData.profile.descWriteTime * 1000.0);
|
||||
descUpdateTimeMs_.Format(line, sizeof(line));
|
||||
str << line;
|
||||
snprintf(line, sizeof(line), "Descriptors written: %d\n", frameData.profile.descriptorsWritten);
|
||||
str << line;
|
||||
frameData.profile.profileSummary = str.str();
|
||||
}
|
||||
}
|
||||
|
||||
frameData.profile.descriptorsWritten = 0;
|
||||
|
||||
// Must be after the fence - this performs deletes.
|
||||
VLOG("PUSH: BeginFrame %d", curFrame);
|
||||
|
||||
|
@ -714,6 +750,21 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() {
|
|||
return frameData_[curFrame].GetInitCmd(vulkan_);
|
||||
}
|
||||
|
||||
void VulkanRenderManager::ReportBadStateForDraw() {
|
||||
const char *cause1 = "";
|
||||
char cause2[256];
|
||||
cause2[0] = '\0';
|
||||
if (!curRenderStep_) {
|
||||
cause1 = "No current render step";
|
||||
}
|
||||
if (curRenderStep_ && curRenderStep_->stepType != VKRStepType::RENDER) {
|
||||
cause1 = "Not a render step: ";
|
||||
std::string str = VulkanQueueRunner::StepToString(vulkan_, *curRenderStep_);
|
||||
truncate_cpy(cause2, str.c_str());
|
||||
}
|
||||
ERROR_LOG_REPORT_ONCE(baddraw, G3D, "Can't draw: %s%s. Step count: %d", cause1, cause2, (int)steps_.size());
|
||||
}
|
||||
|
||||
VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, bool cacheLoad, const char *tag) {
|
||||
if (!desc->vertexShader || !desc->fragmentShader) {
|
||||
ERROR_LOG(G3D, "Can't create graphics pipeline with missing vs/ps: %p %p", desc->vertexShader, desc->fragmentShader);
|
||||
|
@ -1430,6 +1481,11 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
|
|||
}
|
||||
frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_);
|
||||
|
||||
// Flush descriptors.
|
||||
double descStart = time_now_d();
|
||||
FlushDescriptors(task.frame);
|
||||
frameData.profile.descWriteTime = time_now_d() - descStart;
|
||||
|
||||
if (!frameData.hasMainCommands) {
|
||||
// Effectively resets both main and present command buffers, since they both live in this pool.
|
||||
// We always record main commands first, so we don't need to reset the present command buffer separately.
|
||||
|
@ -1451,11 +1507,11 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
|
|||
int passes = GetVRPassesCount();
|
||||
for (int i = 0; i < passes; i++) {
|
||||
PreVRFrameRender(i);
|
||||
queueRunner_.RunSteps(task.steps, frameData, frameDataShared_, i < passes - 1);
|
||||
queueRunner_.RunSteps(task.steps, task.frame, frameData, frameDataShared_, i < passes - 1);
|
||||
PostVRFrameRender();
|
||||
}
|
||||
} else {
|
||||
queueRunner_.RunSteps(task.steps, frameData, frameDataShared_);
|
||||
queueRunner_.RunSteps(task.steps, task.frame, frameData, frameDataShared_);
|
||||
}
|
||||
|
||||
switch (task.runType) {
|
||||
|
@ -1486,6 +1542,8 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
|
|||
|
||||
// Called from main thread.
|
||||
void VulkanRenderManager::FlushSync() {
|
||||
_dbg_assert_(!curRenderStep_);
|
||||
|
||||
if (invalidationCallback_) {
|
||||
invalidationCallback_(InvalidationCallbackFlags::COMMAND_BUFFER_STATE);
|
||||
}
|
||||
|
@ -1529,3 +1587,238 @@ void VulkanRenderManager::ResetStats() {
|
|||
totalGPUTimeMs_.Reset();
|
||||
renderCPUTimeMs_.Reset();
|
||||
}
|
||||
|
||||
VKRPipelineLayout *VulkanRenderManager::CreatePipelineLayout(BindingType *bindingTypes, size_t bindingTypesCount, bool geoShadersEnabled, const char *tag) {
|
||||
VKRPipelineLayout *layout = new VKRPipelineLayout();
|
||||
layout->tag = tag;
|
||||
layout->bindingTypesCount = (uint32_t)bindingTypesCount;
|
||||
|
||||
_dbg_assert_(bindingTypesCount <= ARRAY_SIZE(layout->bindingTypes));
|
||||
memcpy(layout->bindingTypes, bindingTypes, sizeof(BindingType) * bindingTypesCount);
|
||||
|
||||
VkDescriptorSetLayoutBinding bindings[VKRPipelineLayout::MAX_DESC_SET_BINDINGS];
|
||||
for (int i = 0; i < bindingTypesCount; i++) {
|
||||
bindings[i].binding = i;
|
||||
bindings[i].descriptorCount = 1;
|
||||
bindings[i].pImmutableSamplers = nullptr;
|
||||
|
||||
switch (bindingTypes[i]) {
|
||||
case BindingType::COMBINED_IMAGE_SAMPLER:
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
break;
|
||||
case BindingType::UNIFORM_BUFFER_DYNAMIC_VERTEX:
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
break;
|
||||
case BindingType::UNIFORM_BUFFER_DYNAMIC_ALL:
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
if (geoShadersEnabled) {
|
||||
bindings[i].stageFlags |= VK_SHADER_STAGE_GEOMETRY_BIT;
|
||||
}
|
||||
break;
|
||||
case BindingType::STORAGE_BUFFER_VERTEX:
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
break;
|
||||
case BindingType::STORAGE_BUFFER_COMPUTE:
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
break;
|
||||
case BindingType::STORAGE_IMAGE_COMPUTE:
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
break;
|
||||
default:
|
||||
_dbg_assert_(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
dsl.bindingCount = (uint32_t)bindingTypesCount;
|
||||
dsl.pBindings = bindings;
|
||||
VkResult res = vkCreateDescriptorSetLayout(vulkan_->GetDevice(), &dsl, nullptr, &layout->descriptorSetLayout);
|
||||
_assert_(VK_SUCCESS == res && layout->descriptorSetLayout);
|
||||
|
||||
VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
|
||||
VkDescriptorSetLayout setLayouts[1] = { layout->descriptorSetLayout };
|
||||
pl.setLayoutCount = ARRAY_SIZE(setLayouts);
|
||||
pl.pSetLayouts = setLayouts;
|
||||
res = vkCreatePipelineLayout(vulkan_->GetDevice(), &pl, nullptr, &layout->pipelineLayout);
|
||||
_assert_(VK_SUCCESS == res && layout->pipelineLayout);
|
||||
|
||||
vulkan_->SetDebugName(layout->descriptorSetLayout, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, tag);
|
||||
vulkan_->SetDebugName(layout->pipelineLayout, VK_OBJECT_TYPE_PIPELINE_LAYOUT, tag);
|
||||
|
||||
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
|
||||
// Some games go beyond 1024 and end up having to resize like GTA, but most stay below so we start there.
|
||||
layout->frameData[i].pool.Create(vulkan_, bindingTypes, (uint32_t)bindingTypesCount, 1024);
|
||||
}
|
||||
|
||||
pipelineLayouts_.push_back(layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
void VulkanRenderManager::DestroyPipelineLayout(VKRPipelineLayout *layout) {
|
||||
for (auto iter = pipelineLayouts_.begin(); iter != pipelineLayouts_.end(); iter++) {
|
||||
if (*iter == layout) {
|
||||
pipelineLayouts_.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
vulkan_->Delete().QueueCallback([](VulkanContext *vulkan, void *userdata) {
|
||||
VKRPipelineLayout *layout = (VKRPipelineLayout *)userdata;
|
||||
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
|
||||
layout->frameData[i].pool.DestroyImmediately();
|
||||
}
|
||||
vkDestroyPipelineLayout(vulkan->GetDevice(), layout->pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(vulkan->GetDevice(), layout->descriptorSetLayout, nullptr);
|
||||
|
||||
delete layout;
|
||||
}, layout);
|
||||
}
|
||||
|
||||
void VulkanRenderManager::FlushDescriptors(int frame) {
|
||||
for (auto iter : pipelineLayouts_) {
|
||||
iter->FlushDescSets(vulkan_, frame, &frameData_[frame].profile);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderManager::ResetDescriptorLists(int frame) {
|
||||
for (auto iter : pipelineLayouts_) {
|
||||
VKRPipelineLayout::FrameData &data = iter->frameData[frame];
|
||||
|
||||
data.flushedDescriptors_ = 0;
|
||||
data.descSets_.clear();
|
||||
data.descData_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
VKRPipelineLayout::~VKRPipelineLayout() {
|
||||
_assert_(frameData[0].pool.IsDestroyed());
|
||||
}
|
||||
|
||||
void VKRPipelineLayout::FlushDescSets(VulkanContext *vulkan, int frame, QueueProfileContext *profile) {
|
||||
_dbg_assert_(frame < VulkanContext::MAX_INFLIGHT_FRAMES);
|
||||
|
||||
FrameData &data = frameData[frame];
|
||||
|
||||
VulkanDescSetPool &pool = data.pool;
|
||||
FastVec<PackedDescriptor> &descData = data.descData_;
|
||||
FastVec<PendingDescSet> &descSets = data.descSets_;
|
||||
|
||||
pool.Reset();
|
||||
|
||||
VkDescriptorSet setCache[8];
|
||||
VkDescriptorSetLayout layoutsForAlloc[ARRAY_SIZE(setCache)];
|
||||
for (int i = 0; i < ARRAY_SIZE(setCache); i++) {
|
||||
layoutsForAlloc[i] = descriptorSetLayout;
|
||||
}
|
||||
int setsUsed = ARRAY_SIZE(setCache); // To allocate immediately.
|
||||
|
||||
// This will write all descriptors.
|
||||
// Initially, we just do a simple look-back comparing to the previous descriptor to avoid sequential dupes.
|
||||
|
||||
// Initially, let's do naive single desc set writes.
|
||||
VkWriteDescriptorSet writes[MAX_DESC_SET_BINDINGS];
|
||||
VkDescriptorImageInfo imageInfo[MAX_DESC_SET_BINDINGS]; // just picked a practical number
|
||||
VkDescriptorBufferInfo bufferInfo[MAX_DESC_SET_BINDINGS];
|
||||
|
||||
size_t start = data.flushedDescriptors_;
|
||||
int writeCount = 0;
|
||||
|
||||
for (size_t index = start; index < descSets.size(); index++) {
|
||||
auto &d = descSets[index];
|
||||
|
||||
// This is where we look up to see if we already have an identical descriptor previously in the array.
|
||||
// We could do a simple custom hash map here that doesn't handle collisions, since those won't matter.
|
||||
// Instead, for now we just check history one item backwards. Good enough, it seems.
|
||||
if (index > start + 1) {
|
||||
if (descSets[index - 1].count == d.count) {
|
||||
if (!memcmp(descData.data() + d.offset, descData.data() + descSets[index - 1].offset, d.count * sizeof(PackedDescriptor))) {
|
||||
d.set = descSets[index - 1].set;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setsUsed < ARRAY_SIZE(setCache)) {
|
||||
d.set = setCache[setsUsed++];
|
||||
} else {
|
||||
// Allocate in small batches.
|
||||
bool success = pool.Allocate(setCache, ARRAY_SIZE(setCache), layoutsForAlloc);
|
||||
_dbg_assert_(success);
|
||||
d.set = setCache[0];
|
||||
setsUsed = 1;
|
||||
}
|
||||
|
||||
// TODO: Build up bigger batches of writes.
|
||||
const PackedDescriptor *data = descData.begin() + d.offset;
|
||||
int numWrites = 0;
|
||||
int numBuffers = 0;
|
||||
int numImages = 0;
|
||||
for (int i = 0; i < d.count; i++) {
|
||||
if (!data[i].image.view) { // This automatically also checks for an null buffer due to the union.
|
||||
continue;
|
||||
}
|
||||
switch (this->bindingTypes[i]) {
|
||||
case BindingType::COMBINED_IMAGE_SAMPLER:
|
||||
_dbg_assert_(data[i].image.sampler != VK_NULL_HANDLE);
|
||||
imageInfo[numImages].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
imageInfo[numImages].imageView = data[i].image.view;
|
||||
imageInfo[numImages].sampler = data[i].image.sampler;
|
||||
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
writes[numWrites].pImageInfo = &imageInfo[numImages];
|
||||
writes[numWrites].pBufferInfo = nullptr;
|
||||
numImages++;
|
||||
break;
|
||||
case BindingType::STORAGE_IMAGE_COMPUTE:
|
||||
imageInfo[numImages].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
imageInfo[numImages].imageView = data[i].image.view;
|
||||
imageInfo[numImages].sampler = VK_NULL_HANDLE;
|
||||
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
writes[numWrites].pImageInfo = &imageInfo[numImages];
|
||||
writes[numWrites].pBufferInfo = nullptr;
|
||||
numImages++;
|
||||
break;
|
||||
case BindingType::STORAGE_BUFFER_VERTEX:
|
||||
case BindingType::STORAGE_BUFFER_COMPUTE:
|
||||
bufferInfo[numBuffers].buffer = data[i].buffer.buffer;
|
||||
bufferInfo[numBuffers].offset = data[i].buffer.offset;
|
||||
bufferInfo[numBuffers].range = data[i].buffer.range;
|
||||
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
writes[numWrites].pBufferInfo = &bufferInfo[numBuffers];
|
||||
writes[numWrites].pImageInfo = nullptr;
|
||||
numBuffers++;
|
||||
break;
|
||||
case BindingType::UNIFORM_BUFFER_DYNAMIC_ALL:
|
||||
case BindingType::UNIFORM_BUFFER_DYNAMIC_VERTEX:
|
||||
bufferInfo[numBuffers].buffer = data[i].buffer.buffer;
|
||||
bufferInfo[numBuffers].offset = 0;
|
||||
bufferInfo[numBuffers].range = data[i].buffer.range;
|
||||
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
writes[numWrites].pBufferInfo = &bufferInfo[numBuffers];
|
||||
writes[numWrites].pImageInfo = nullptr;
|
||||
numBuffers++;
|
||||
break;
|
||||
}
|
||||
writes[numWrites].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[numWrites].pNext = nullptr;
|
||||
writes[numWrites].descriptorCount = 1;
|
||||
writes[numWrites].dstArrayElement = 0;
|
||||
writes[numWrites].dstBinding = i;
|
||||
writes[numWrites].dstSet = d.set;
|
||||
writes[numWrites].pTexelBufferView = nullptr;
|
||||
numWrites++;
|
||||
}
|
||||
|
||||
vkUpdateDescriptorSets(vulkan->GetDevice(), numWrites, writes, 0, nullptr);
|
||||
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
data.flushedDescriptors_ = (int)descSets.size();
|
||||
profile->descriptorsWritten += writeCount;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "Common/GPU/MiscTypes.h"
|
||||
#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
|
||||
#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
|
||||
#include "Common/GPU/Vulkan/VulkanDescSet.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
|
||||
// Forward declaration
|
||||
|
@ -106,7 +107,7 @@ public:
|
|||
VkPipelineVertexInputStateCreateInfo vis{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
|
||||
VkPipelineViewportStateCreateInfo views{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
|
||||
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VKRPipelineLayout *pipelineLayout = nullptr;
|
||||
|
||||
// Does not include the render pass type, it's passed in separately since the
|
||||
// desc is persistent.
|
||||
|
@ -131,6 +132,10 @@ struct VKRGraphicsPipeline {
|
|||
// This deletes the whole VKRGraphicsPipeline, you must remove your last pointer to it when doing this.
|
||||
void QueueForDeletion(VulkanContext *vulkan);
|
||||
|
||||
// This blocks until any background compiles are finished.
|
||||
// Used during game shutdown before we clear out shaders that these compiles depend on.
|
||||
void BlockUntilCompiled();
|
||||
|
||||
u32 GetVariantsBitmask() const;
|
||||
|
||||
void LogCreationFailure() const;
|
||||
|
@ -180,6 +185,56 @@ struct CompileQueueEntry {
|
|||
VkSampleCountFlagBits sampleCount;
|
||||
};
|
||||
|
||||
// Pending descriptor sets.
|
||||
// TODO: Sort these by VKRPipelineLayout to avoid storing it for each element.
|
||||
struct PendingDescSet {
|
||||
int offset; // probably enough with a u16.
|
||||
u8 count;
|
||||
VkDescriptorSet set;
|
||||
};
|
||||
|
||||
struct PackedDescriptor {
|
||||
union {
|
||||
struct {
|
||||
VkImageView view;
|
||||
VkSampler sampler;
|
||||
} image;
|
||||
struct {
|
||||
VkBuffer buffer;
|
||||
uint32_t offset;
|
||||
uint32_t range;
|
||||
} buffer;
|
||||
};
|
||||
};
|
||||
|
||||
// Note that we only support a single descriptor set due to compatibility with some ancient devices.
|
||||
// We should probably eventually give that up.
|
||||
struct VKRPipelineLayout {
|
||||
~VKRPipelineLayout();
|
||||
enum { MAX_DESC_SET_BINDINGS = 10 };
|
||||
BindingType bindingTypes[MAX_DESC_SET_BINDINGS];
|
||||
|
||||
uint32_t bindingTypesCount = 0;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; // only support 1 for now.
|
||||
int pushConstSize = 0;
|
||||
const char *tag = nullptr;
|
||||
|
||||
struct FrameData {
|
||||
FrameData() : pool("GameDescPool", true) {}
|
||||
VulkanDescSetPool pool;
|
||||
FastVec<PackedDescriptor> descData_;
|
||||
FastVec<PendingDescSet> descSets_;
|
||||
// TODO: We should be able to get away with a single descData_/descSets_ and then send it along,
|
||||
// but it's easier to just segregate by frame id.
|
||||
int flushedDescriptors_ = 0;
|
||||
};
|
||||
|
||||
FrameData frameData[VulkanContext::MAX_INFLIGHT_FRAMES];
|
||||
|
||||
void FlushDescSets(VulkanContext *vulkan, int frame, QueueProfileContext *profile);
|
||||
};
|
||||
|
||||
class VulkanRenderManager {
|
||||
public:
|
||||
VulkanRenderManager(VulkanContext *vulkan, bool useThread, HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory);
|
||||
|
@ -234,14 +289,25 @@ public:
|
|||
VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, bool cacheLoad, const char *tag);
|
||||
VKRComputePipeline *CreateComputePipeline(VKRComputePipelineDesc *desc);
|
||||
|
||||
VKRPipelineLayout *CreatePipelineLayout(BindingType *bindingTypes, size_t bindingCount, bool geoShadersEnabled, const char *tag);
|
||||
void DestroyPipelineLayout(VKRPipelineLayout *pipelineLayout);
|
||||
|
||||
void ReportBadStateForDraw();
|
||||
|
||||
void NudgeCompilerThread() {
|
||||
compileMutex_.lock();
|
||||
compileCond_.notify_one();
|
||||
compileMutex_.unlock();
|
||||
}
|
||||
|
||||
void BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
|
||||
_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && pipeline != nullptr);
|
||||
// This is the first call in a draw operation. Instead of asserting like we used to, you can now check the
|
||||
// return value and skip the draw if we're in a bad state. In that case, call ReportBadState.
|
||||
// The old assert wasn't very helpful in figuring out what caused it anyway...
|
||||
bool BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VKRPipelineLayout *pipelineLayout) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && pipeline != nullptr);
|
||||
if (!curRenderStep_ || curRenderStep_->stepType != VKRStepType::RENDER) {
|
||||
return false;
|
||||
}
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::BIND_GRAPHICS_PIPELINE;
|
||||
pipelinesToCheck_.push_back(pipeline);
|
||||
|
@ -252,16 +318,8 @@ public:
|
|||
// DebugBreak();
|
||||
// }
|
||||
curPipelineFlags_ |= flags;
|
||||
}
|
||||
|
||||
void BindPipeline(VKRComputePipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
_dbg_assert_(pipeline != nullptr);
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::BIND_COMPUTE_PIPELINE;
|
||||
data.compute_pipeline.pipeline = pipeline;
|
||||
data.compute_pipeline.pipelineLayout = pipelineLayout;
|
||||
curPipelineFlags_ |= flags;
|
||||
curPipelineLayout_ = pipelineLayout;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetViewport(const VkViewport &vp) {
|
||||
|
@ -380,13 +438,36 @@ public:
|
|||
curRenderStep_->render.stencilStore = VKRRenderPassStoreAction::DONT_CARE;
|
||||
}
|
||||
|
||||
void Draw(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0) {
|
||||
// Descriptors will match the current pipeline layout, set by the last call to BindPipeline.
|
||||
// Count is the count of void*s. Two are needed for COMBINED_IMAGE_SAMPLER, everything else is a single one.
|
||||
// The goal is to keep this function very small and fast, and do the expensive work on the render thread or
|
||||
// another thread.
|
||||
PackedDescriptor *PushDescriptorSet(int count, int *descSetIndex) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
|
||||
int curFrame = vulkan_->GetCurFrame();
|
||||
|
||||
VKRPipelineLayout::FrameData &data = curPipelineLayout_->frameData[curFrame];
|
||||
|
||||
size_t offset = data.descData_.size();
|
||||
PackedDescriptor *retval = data.descData_.extend_uninitialized(count);
|
||||
|
||||
int setIndex = (int)data.descSets_.size();
|
||||
PendingDescSet &descSet = data.descSets_.push_uninitialized();
|
||||
descSet.offset = (uint32_t)offset;
|
||||
descSet.count = count;
|
||||
// descSet.set = VK_NULL_HANDLE; // to be filled in
|
||||
*descSetIndex = setIndex;
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Draw(int descSetIndex, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::DRAW;
|
||||
data.draw.count = count;
|
||||
data.draw.offset = offset;
|
||||
data.draw.ds = descSet;
|
||||
data.draw.descSetIndex = descSetIndex;
|
||||
data.draw.vbuffer = vbuffer;
|
||||
data.draw.voffset = voffset;
|
||||
data.draw.numUboOffsets = numUboOffsets;
|
||||
|
@ -396,13 +477,13 @@ public:
|
|||
curRenderStep_->render.numDraws++;
|
||||
}
|
||||
|
||||
void DrawIndexed(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances) {
|
||||
void DrawIndexed(int descSetIndex, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::DRAW_INDEXED;
|
||||
data.drawIndexed.count = count;
|
||||
data.drawIndexed.instances = numInstances;
|
||||
data.drawIndexed.ds = descSet;
|
||||
data.drawIndexed.descSetIndex = descSetIndex;
|
||||
data.drawIndexed.vbuffer = vbuffer;
|
||||
data.drawIndexed.voffset = voffset;
|
||||
data.drawIndexed.ibuffer = ibuffer;
|
||||
|
@ -473,6 +554,9 @@ private:
|
|||
void PresentWaitThreadFunc();
|
||||
void PollPresentTiming();
|
||||
|
||||
void ResetDescriptorLists(int frame);
|
||||
void FlushDescriptors(int frame);
|
||||
|
||||
FrameDataShared frameDataShared_;
|
||||
|
||||
FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES];
|
||||
|
@ -540,9 +624,13 @@ private:
|
|||
SimpleStat initTimeMs_;
|
||||
SimpleStat totalGPUTimeMs_;
|
||||
SimpleStat renderCPUTimeMs_;
|
||||
SimpleStat descUpdateTimeMs_;
|
||||
|
||||
std::function<void(InvalidationCallbackFlags)> invalidationCallback_;
|
||||
|
||||
uint64_t frameIdGen_ = FRAME_TIME_HISTORY_LENGTH;
|
||||
HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory_;
|
||||
|
||||
VKRPipelineLayout *curPipelineLayout_ = nullptr;
|
||||
std::vector<VKRPipelineLayout *> pipelineLayouts_;
|
||||
};
|
||||
|
|
|
@ -20,27 +20,20 @@
|
|||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/System/Display.h"
|
||||
#include "Common/Math/lin/matrix4x4.h"
|
||||
#include "Common/Data/Convert/SmallDataConvert.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||
#include "Common/GPU/Vulkan/VulkanImage.h"
|
||||
#include "Common/GPU/Vulkan/VulkanMemory.h"
|
||||
#include "Common/GPU/Vulkan/VulkanLoader.h"
|
||||
#include "Common/Thread/Promise.h"
|
||||
|
||||
#include "Common/GPU/Vulkan/VulkanLoader.h"
|
||||
|
||||
// We support a frame-global descriptor set, which can be optionally used by other code,
|
||||
// but is not directly used by thin3d. It has to be defined here though, be in set 0
|
||||
// and specified in every pipeline layout, otherwise it can't sit undisturbed when other
|
||||
// descriptor sets are bound on top.
|
||||
|
||||
// For descriptor set 1, we use a simple descriptor set for all thin3d rendering: 1 UBO binding point, 3 combined texture/samples.
|
||||
// For descriptor set 0 (the only one), we use a simple descriptor set for all thin3d rendering: 1 UBO binding point, 3 combined texture/samples.
|
||||
//
|
||||
// binding 0 - uniform buffer
|
||||
// binding 1 - texture/sampler
|
||||
|
@ -254,7 +247,7 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con
|
|||
|
||||
class VKInputLayout : public InputLayout {
|
||||
public:
|
||||
std::vector<VkVertexInputBindingDescription> bindings;
|
||||
VkVertexInputBindingDescription binding;
|
||||
std::vector<VkVertexInputAttributeDescription> attributes;
|
||||
VkPipelineVertexInputStateCreateInfo visc;
|
||||
};
|
||||
|
@ -297,7 +290,7 @@ public:
|
|||
|
||||
std::vector<VKShaderModule *> deps;
|
||||
|
||||
int stride[4]{};
|
||||
int stride = 0;
|
||||
int dynamicUniformSize = 0;
|
||||
|
||||
bool usesStencil = false;
|
||||
|
@ -453,13 +446,9 @@ public:
|
|||
curPipeline_ = (VKPipeline *)pipeline;
|
||||
}
|
||||
|
||||
// TODO: Make VKBuffers proper buffers, and do a proper binding model. This is just silly.
|
||||
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override {
|
||||
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
|
||||
for (int i = 0; i < count; i++) {
|
||||
curVBuffers_[i + start] = (VKBuffer *)buffers[i];
|
||||
curVBufferOffsets_[i + start] = offsets ? offsets[i] : 0;
|
||||
}
|
||||
void BindVertexBuffer(Buffer *vertexBuffer, int offset) override {
|
||||
curVBuffer_ = (VKBuffer *)vertexBuffer;
|
||||
curVBufferOffset_ = offset;
|
||||
}
|
||||
void BindIndexBuffer(Buffer *indexBuffer, int offset) override {
|
||||
curIBuffer_ = (VKBuffer *)indexBuffer;
|
||||
|
@ -511,7 +500,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
VkDescriptorSet GetOrCreateDescriptorSet(VkBuffer buffer);
|
||||
void BindDescriptors(VkBuffer buffer, PackedDescriptor descriptors[4]);
|
||||
|
||||
std::vector<std::string> GetFeatureList() const override;
|
||||
std::vector<std::string> GetExtensionList(bool device, bool enabledOnly) const override;
|
||||
|
@ -542,13 +531,12 @@ private:
|
|||
VulkanTexture *nullTexture_ = nullptr;
|
||||
|
||||
AutoRef<VKPipeline> curPipeline_;
|
||||
AutoRef<VKBuffer> curVBuffers_[4];
|
||||
int curVBufferOffsets_[4]{};
|
||||
AutoRef<VKBuffer> curVBuffer_;
|
||||
int curVBufferOffset_ = 0;
|
||||
AutoRef<VKBuffer> curIBuffer_;
|
||||
int curIBufferOffset_ = 0;
|
||||
|
||||
VkDescriptorSetLayout descriptorSetLayout_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
|
||||
VKRPipelineLayout *pipelineLayout_ = nullptr;
|
||||
VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
|
||||
AutoRef<VKFramebuffer> curFramebuffer_;
|
||||
|
||||
|
@ -564,19 +552,6 @@ private:
|
|||
|
||||
VulkanPushPool *push_ = nullptr;
|
||||
|
||||
struct FrameData {
|
||||
FrameData() : descriptorPool("VKContext", false) {
|
||||
descriptorPool.Setup([this] { descSets_.clear(); });
|
||||
}
|
||||
// Per-frame descriptor set cache. As it's per frame and reset every frame, we don't need to
|
||||
// worry about invalidating descriptors pointing to deleted textures.
|
||||
// However! ARM is not a fan of doing it this way.
|
||||
std::map<DescriptorSetKey, VkDescriptorSet> descSets_;
|
||||
VulkanDescSetPool descriptorPool;
|
||||
};
|
||||
|
||||
FrameData frame_[VulkanContext::MAX_INFLIGHT_FRAMES];
|
||||
|
||||
DeviceCaps caps_{};
|
||||
|
||||
uint8_t stencilRef_ = 0;
|
||||
|
@ -874,8 +849,11 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread)
|
|||
caps_.tesselationShaderSupported = vulkan->GetDeviceFeatures().enabled.standard.tessellationShader != 0;
|
||||
caps_.dualSourceBlend = vulkan->GetDeviceFeatures().enabled.standard.dualSrcBlend != 0;
|
||||
caps_.depthClampSupported = vulkan->GetDeviceFeatures().enabled.standard.depthClamp != 0;
|
||||
|
||||
// Comment out these two to test geometry shader culling on any geometry shader-supporting hardware.
|
||||
caps_.clipDistanceSupported = vulkan->GetDeviceFeatures().enabled.standard.shaderClipDistance != 0;
|
||||
caps_.cullDistanceSupported = vulkan->GetDeviceFeatures().enabled.standard.shaderCullDistance != 0;
|
||||
|
||||
caps_.framebufferBlitSupported = true;
|
||||
caps_.framebufferCopySupported = true;
|
||||
caps_.framebufferDepthBlitSupported = vulkan->GetDeviceInfo().canBlitToPreferredDepthStencilFormat;
|
||||
|
@ -1038,64 +1016,22 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread)
|
|||
caps_.deviceID = deviceProps.deviceID;
|
||||
device_ = vulkan->GetDevice();
|
||||
|
||||
std::vector<VkDescriptorPoolSize> dpTypes;
|
||||
dpTypes.resize(2);
|
||||
dpTypes[0].descriptorCount = 200;
|
||||
dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
dpTypes[1].descriptorCount = 200 * MAX_BOUND_TEXTURES;
|
||||
dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
|
||||
VkDescriptorPoolCreateInfo dp{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
|
||||
// Don't want to mess around with individually freeing these, let's go dynamic each frame.
|
||||
dp.flags = 0;
|
||||
// 200 textures per frame was not enough for the UI.
|
||||
dp.maxSets = 4096;
|
||||
|
||||
VkBufferUsageFlags usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
push_ = new VulkanPushPool(vulkan_, "pushBuffer", 4 * 1024 * 1024, usage);
|
||||
|
||||
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
|
||||
frame_[i].descriptorPool.Create(vulkan_, dp, dpTypes);
|
||||
}
|
||||
|
||||
// binding 0 - uniform data
|
||||
// binding 1 - combined sampler/image 0
|
||||
// binding 2 - combined sampler/image 1
|
||||
VkDescriptorSetLayoutBinding bindings[MAX_BOUND_TEXTURES + 1];
|
||||
bindings[0].descriptorCount = 1;
|
||||
bindings[0].pImmutableSamplers = nullptr;
|
||||
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[0].binding = 0;
|
||||
// ...etc
|
||||
BindingType bindings[MAX_BOUND_TEXTURES + 1];
|
||||
bindings[0] = BindingType::UNIFORM_BUFFER_DYNAMIC_ALL;
|
||||
for (int i = 0; i < MAX_BOUND_TEXTURES; ++i) {
|
||||
bindings[i + 1].descriptorCount = 1;
|
||||
bindings[i + 1].pImmutableSamplers = nullptr;
|
||||
bindings[i + 1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
bindings[i + 1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[i + 1].binding = i + 1;
|
||||
bindings[1 + i] = BindingType::COMBINED_IMAGE_SAMPLER;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
dsl.bindingCount = ARRAY_SIZE(bindings);
|
||||
dsl.pBindings = bindings;
|
||||
VkResult res = vkCreateDescriptorSetLayout(device_, &dsl, nullptr, &descriptorSetLayout_);
|
||||
_assert_(VK_SUCCESS == res);
|
||||
|
||||
vulkan_->SetDebugName(descriptorSetLayout_, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, "thin3d_d_layout");
|
||||
|
||||
VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
|
||||
pl.pPushConstantRanges = nullptr;
|
||||
pl.pushConstantRangeCount = 0;
|
||||
VkDescriptorSetLayout setLayouts[1] = { descriptorSetLayout_ };
|
||||
pl.setLayoutCount = ARRAY_SIZE(setLayouts);
|
||||
pl.pSetLayouts = setLayouts;
|
||||
res = vkCreatePipelineLayout(device_, &pl, nullptr, &pipelineLayout_);
|
||||
_assert_(VK_SUCCESS == res);
|
||||
|
||||
vulkan_->SetDebugName(pipelineLayout_, VK_OBJECT_TYPE_PIPELINE_LAYOUT, "thin3d_p_layout");
|
||||
pipelineLayout_ = renderManager_.CreatePipelineLayout(bindings, ARRAY_SIZE(bindings), caps_.geometryShaderSupported, "thin3d_layout");
|
||||
|
||||
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
|
||||
res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
|
||||
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
|
||||
_assert_(VK_SUCCESS == res);
|
||||
}
|
||||
|
||||
|
@ -1103,25 +1039,15 @@ VKContext::~VKContext() {
|
|||
DestroyPresets();
|
||||
|
||||
delete nullTexture_;
|
||||
// This also destroys all descriptor sets.
|
||||
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
|
||||
frame_[i].descriptorPool.Destroy();
|
||||
}
|
||||
push_->Destroy();
|
||||
delete push_;
|
||||
vulkan_->Delete().QueueDeleteDescriptorSetLayout(descriptorSetLayout_);
|
||||
vulkan_->Delete().QueueDeletePipelineLayout(pipelineLayout_);
|
||||
renderManager_.DestroyPipelineLayout(pipelineLayout_);
|
||||
vulkan_->Delete().QueueDeletePipelineCache(pipelineCache_);
|
||||
}
|
||||
|
||||
void VKContext::BeginFrame(DebugFlags debugFlags) {
|
||||
renderManager_.BeginFrame(debugFlags & DebugFlags::PROFILE_TIMESTAMPS, debugFlags & DebugFlags::PROFILE_SCOPES);
|
||||
|
||||
FrameData &frame = frame_[vulkan_->GetCurFrame()];
|
||||
|
||||
push_->BeginFrame();
|
||||
|
||||
frame.descriptorPool.Reset();
|
||||
}
|
||||
|
||||
void VKContext::EndFrame() {
|
||||
|
@ -1159,81 +1085,30 @@ void VKContext::WipeQueue() {
|
|||
renderManager_.Wipe();
|
||||
}
|
||||
|
||||
VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) {
|
||||
DescriptorSetKey key{};
|
||||
|
||||
FrameData *frame = &frame_[vulkan_->GetCurFrame()];
|
||||
void VKContext::BindDescriptors(VkBuffer buf, PackedDescriptor descriptors[4]) {
|
||||
descriptors[0].buffer.buffer = buf;
|
||||
descriptors[0].buffer.offset = 0; // dynamic
|
||||
descriptors[0].buffer.range = curPipeline_->GetUBOSize();
|
||||
|
||||
int numDescs = 1;
|
||||
for (int i = 0; i < MAX_BOUND_TEXTURES; ++i) {
|
||||
VkImageView view;
|
||||
VkSampler sampler;
|
||||
if (boundTextures_[i]) {
|
||||
key.imageViews_[i] = (boundTextureFlags_[i] & TextureBindFlags::VULKAN_BIND_ARRAY) ? boundTextures_[i]->GetImageArrayView() : boundTextures_[i]->GetImageView();
|
||||
view = (boundTextureFlags_[i] & TextureBindFlags::VULKAN_BIND_ARRAY) ? boundTextures_[i]->GetImageArrayView() : boundTextures_[i]->GetImageView();
|
||||
} else {
|
||||
key.imageViews_[i] = boundImageView_[i];
|
||||
view = boundImageView_[i];
|
||||
}
|
||||
key.samplers_[i] = boundSamplers_[i];
|
||||
}
|
||||
key.buffer_ = buf;
|
||||
sampler = boundSamplers_[i] ? boundSamplers_[i]->GetSampler() : VK_NULL_HANDLE;
|
||||
|
||||
auto iter = frame->descSets_.find(key);
|
||||
if (iter != frame->descSets_.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
VkDescriptorSet descSet = frame->descriptorPool.Allocate(1, &descriptorSetLayout_, "thin3d_descset");
|
||||
if (descSet == VK_NULL_HANDLE) {
|
||||
ERROR_LOG(G3D, "GetOrCreateDescriptorSet failed");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
vulkan_->SetDebugName(descSet, VK_OBJECT_TYPE_DESCRIPTOR_SET, "(thin3d desc set)");
|
||||
|
||||
VkDescriptorBufferInfo bufferDesc;
|
||||
bufferDesc.buffer = buf;
|
||||
bufferDesc.offset = 0;
|
||||
bufferDesc.range = curPipeline_->GetUBOSize();
|
||||
|
||||
VkDescriptorImageInfo imageDesc[MAX_BOUND_TEXTURES]{};
|
||||
VkWriteDescriptorSet writes[1 + MAX_BOUND_TEXTURES]{};
|
||||
|
||||
// If handles are NULL for whatever buggy reason, it's best to leave the descriptors
|
||||
// unwritten instead of trying to write a zero, which is not legal.
|
||||
|
||||
int numWrites = 0;
|
||||
if (buf) {
|
||||
writes[numWrites].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[numWrites].dstSet = descSet;
|
||||
writes[numWrites].dstArrayElement = 0;
|
||||
writes[numWrites].dstBinding = 0;
|
||||
writes[numWrites].pBufferInfo = &bufferDesc;
|
||||
writes[numWrites].pImageInfo = nullptr;
|
||||
writes[numWrites].pTexelBufferView = nullptr;
|
||||
writes[numWrites].descriptorCount = 1;
|
||||
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
numWrites++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_BOUND_TEXTURES; ++i) {
|
||||
if (key.imageViews_[i] && key.samplers_[i] && key.samplers_[i]->GetSampler()) {
|
||||
imageDesc[i].imageView = key.imageViews_[i];
|
||||
imageDesc[i].sampler = key.samplers_[i]->GetSampler();
|
||||
imageDesc[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
writes[numWrites].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[numWrites].dstSet = descSet;
|
||||
writes[numWrites].dstArrayElement = 0;
|
||||
writes[numWrites].dstBinding = i + 1;
|
||||
writes[numWrites].pBufferInfo = nullptr;
|
||||
writes[numWrites].pImageInfo = &imageDesc[i];
|
||||
writes[numWrites].pTexelBufferView = nullptr;
|
||||
writes[numWrites].descriptorCount = 1;
|
||||
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
numWrites++;
|
||||
if (view && sampler) {
|
||||
descriptors[i + 1].image.view = view;
|
||||
descriptors[i + 1].image.sampler = sampler;
|
||||
} else {
|
||||
descriptors[i + 1].image.view = VK_NULL_HANDLE;
|
||||
descriptors[i + 1].image.sampler = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
vkUpdateDescriptorSets(device_, numWrites, writes, 0, nullptr);
|
||||
|
||||
frame->descSets_[key] = descSet;
|
||||
return descSet;
|
||||
}
|
||||
|
||||
Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char *tag) {
|
||||
|
@ -1270,13 +1145,11 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
|
|||
}
|
||||
}
|
||||
|
||||
_dbg_assert_(input && input->bindings.size() == 1);
|
||||
_dbg_assert_(input);
|
||||
_dbg_assert_((int)input->attributes.size() == (int)input->visc.vertexAttributeDescriptionCount);
|
||||
|
||||
for (int i = 0; i < (int)input->bindings.size(); i++) {
|
||||
pipeline->stride[i] = input->bindings[i].stride;
|
||||
}
|
||||
gDesc.ibd = input->bindings[0];
|
||||
pipeline->stride = input->binding.stride;
|
||||
gDesc.ibd = input->binding;
|
||||
for (size_t i = 0; i < input->attributes.size(); i++) {
|
||||
gDesc.attrs[i] = input->attributes[i];
|
||||
}
|
||||
|
@ -1358,23 +1231,20 @@ InputLayout *VKContext::CreateInputLayout(const InputLayoutDesc &desc) {
|
|||
VKInputLayout *vl = new VKInputLayout();
|
||||
vl->visc = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
|
||||
vl->visc.flags = 0;
|
||||
vl->visc.vertexBindingDescriptionCount = 1;
|
||||
vl->visc.vertexAttributeDescriptionCount = (uint32_t)desc.attributes.size();
|
||||
vl->visc.vertexBindingDescriptionCount = (uint32_t)desc.bindings.size();
|
||||
vl->bindings.resize(vl->visc.vertexBindingDescriptionCount);
|
||||
vl->attributes.resize(vl->visc.vertexAttributeDescriptionCount);
|
||||
vl->visc.pVertexBindingDescriptions = vl->bindings.data();
|
||||
vl->visc.pVertexBindingDescriptions = &vl->binding;
|
||||
vl->visc.pVertexAttributeDescriptions = vl->attributes.data();
|
||||
for (size_t i = 0; i < desc.attributes.size(); i++) {
|
||||
vl->attributes[i].binding = (uint32_t)desc.attributes[i].binding;
|
||||
vl->attributes[i].binding = 0;
|
||||
vl->attributes[i].format = DataFormatToVulkan(desc.attributes[i].format);
|
||||
vl->attributes[i].location = desc.attributes[i].location;
|
||||
vl->attributes[i].offset = desc.attributes[i].offset;
|
||||
}
|
||||
for (size_t i = 0; i < desc.bindings.size(); i++) {
|
||||
vl->bindings[i].inputRate = desc.bindings[i].instanceRate ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
vl->bindings[i].binding = (uint32_t)i;
|
||||
vl->bindings[i].stride = desc.bindings[i].stride;
|
||||
}
|
||||
vl->binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
vl->binding.binding = 0;
|
||||
vl->binding.stride = desc.stride;
|
||||
return vl;
|
||||
}
|
||||
|
||||
|
@ -1483,7 +1353,7 @@ void VKContext::BindTextures(int start, int count, Texture **textures, TextureBi
|
|||
boundTextures_[i] = static_cast<VKTexture *>(textures[i - start]);
|
||||
boundTextureFlags_[i] = flags;
|
||||
if (boundTextures_[i]) {
|
||||
// If a texture is bound, we set these up in GetOrCreateDescriptorSet too.
|
||||
// If a texture is bound, we set these up in BindDescriptors too.
|
||||
// But we might need to set the view here anyway so it can be queried using GetNativeObject.
|
||||
if (flags & TextureBindFlags::VULKAN_BIND_ARRAY) {
|
||||
boundImageView_[i] = boundTextures_[i]->GetImageArrayView();
|
||||
|
@ -1529,42 +1399,36 @@ void VKContext::ApplyDynamicState() {
|
|||
}
|
||||
|
||||
void VKContext::Draw(int vertexCount, int offset) {
|
||||
VKBuffer *vbuf = curVBuffers_[0];
|
||||
VKBuffer *vbuf = curVBuffer_;
|
||||
|
||||
VkBuffer vulkanVbuf;
|
||||
VkBuffer vulkanUBObuf;
|
||||
uint32_t ubo_offset = (uint32_t)curPipeline_->PushUBO(push_, vulkan_, &vulkanUBObuf);
|
||||
size_t vbBindOffset = push_->Push(vbuf->GetData(), vbuf->GetSize(), 4, &vulkanVbuf);
|
||||
|
||||
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
|
||||
if (descSet == VK_NULL_HANDLE) {
|
||||
ERROR_LOG(G3D, "GetOrCreateDescriptorSet failed, skipping %s", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
BindCurrentPipeline();
|
||||
ApplyDynamicState();
|
||||
renderManager_.Draw(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vertexCount, offset);
|
||||
int descSetIndex;
|
||||
PackedDescriptor *descriptors = renderManager_.PushDescriptorSet(4, &descSetIndex);
|
||||
BindDescriptors(vulkanUBObuf, descriptors);
|
||||
renderManager_.Draw(descSetIndex, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffset_, vertexCount, offset);
|
||||
}
|
||||
|
||||
void VKContext::DrawIndexed(int vertexCount, int offset) {
|
||||
VKBuffer *ibuf = curIBuffer_;
|
||||
VKBuffer *vbuf = curVBuffers_[0];
|
||||
VKBuffer *vbuf = curVBuffer_;
|
||||
|
||||
VkBuffer vulkanVbuf, vulkanIbuf, vulkanUBObuf;
|
||||
uint32_t ubo_offset = (uint32_t)curPipeline_->PushUBO(push_, vulkan_, &vulkanUBObuf);
|
||||
size_t vbBindOffset = push_->Push(vbuf->GetData(), vbuf->GetSize(), 4, &vulkanVbuf);
|
||||
size_t ibBindOffset = push_->Push(ibuf->GetData(), ibuf->GetSize(), 4, &vulkanIbuf);
|
||||
|
||||
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
|
||||
if (descSet == VK_NULL_HANDLE) {
|
||||
ERROR_LOG(G3D, "GetOrCreateDescriptorSet failed, skipping %s", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
BindCurrentPipeline();
|
||||
ApplyDynamicState();
|
||||
renderManager_.DrawIndexed(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1);
|
||||
int descSetIndex;
|
||||
PackedDescriptor *descriptors = renderManager_.PushDescriptorSet(4, &descSetIndex);
|
||||
BindDescriptors(vulkanUBObuf, descriptors);
|
||||
renderManager_.DrawIndexed(descSetIndex, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffset_, vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1);
|
||||
}
|
||||
|
||||
void VKContext::DrawUP(const void *vdata, int vertexCount) {
|
||||
|
@ -1574,7 +1438,7 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) {
|
|||
}
|
||||
|
||||
VkBuffer vulkanVbuf, vulkanUBObuf;
|
||||
size_t dataSize = vertexCount * curPipeline_->stride[0];
|
||||
size_t dataSize = vertexCount * curPipeline_->stride;
|
||||
uint32_t vbBindOffset;
|
||||
uint8_t *dataPtr = push_->Allocate(dataSize, 4, &vulkanVbuf, &vbBindOffset);
|
||||
_assert_(dataPtr != nullptr);
|
||||
|
@ -1582,15 +1446,12 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) {
|
|||
|
||||
uint32_t ubo_offset = (uint32_t)curPipeline_->PushUBO(push_, vulkan_, &vulkanUBObuf);
|
||||
|
||||
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
|
||||
if (descSet == VK_NULL_HANDLE) {
|
||||
ERROR_LOG(G3D, "GetOrCreateDescriptorSet failed, skipping %s", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
BindCurrentPipeline();
|
||||
ApplyDynamicState();
|
||||
renderManager_.Draw(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vertexCount);
|
||||
int descSetIndex;
|
||||
PackedDescriptor *descriptors = renderManager_.PushDescriptorSet(4, &descSetIndex);
|
||||
BindDescriptors(vulkanUBObuf, descriptors);
|
||||
renderManager_.Draw(descSetIndex, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffset_, vertexCount);
|
||||
}
|
||||
|
||||
void VKContext::BindCurrentPipeline() {
|
||||
|
|
|
@ -475,20 +475,14 @@ protected:
|
|||
DataFormat format_ = DataFormat::UNDEFINED;
|
||||
};
|
||||
|
||||
struct BindingDesc {
|
||||
int stride;
|
||||
bool instanceRate;
|
||||
};
|
||||
|
||||
struct AttributeDesc {
|
||||
int binding;
|
||||
int location; // corresponds to semantic
|
||||
DataFormat format;
|
||||
int offset;
|
||||
};
|
||||
|
||||
struct InputLayoutDesc {
|
||||
std::vector<BindingDesc> bindings;
|
||||
int stride;
|
||||
std::vector<AttributeDesc> attributes;
|
||||
};
|
||||
|
||||
|
@ -787,7 +781,7 @@ public:
|
|||
|
||||
virtual void BindSamplerStates(int start, int count, SamplerState **state) = 0;
|
||||
virtual void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags = TextureBindFlags::NONE) = 0;
|
||||
virtual void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) = 0;
|
||||
virtual void BindVertexBuffer(Buffer *vertexBuffer, int offset) = 0;
|
||||
virtual void BindIndexBuffer(Buffer *indexBuffer, int offset) = 0;
|
||||
|
||||
// Sometimes it's necessary to bind a texture not created by thin3d, and use with a thin3d pipeline.
|
||||
|
|
|
@ -37,6 +37,7 @@ const char *GetDeviceName(int deviceId) {
|
|||
std::vector<InputMapping> dpadKeys;
|
||||
std::vector<InputMapping> confirmKeys;
|
||||
std::vector<InputMapping> cancelKeys;
|
||||
std::vector<InputMapping> infoKeys;
|
||||
std::vector<InputMapping> tabLeftKeys;
|
||||
std::vector<InputMapping> tabRightKeys;
|
||||
static std::unordered_map<InputDeviceID, int> uiFlipAnalogY;
|
||||
|
@ -69,6 +70,10 @@ void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::ve
|
|||
tabRightKeys = tabRight;
|
||||
}
|
||||
|
||||
void SetInfoKeys(const std::vector<InputMapping> &info) {
|
||||
infoKeys = info;
|
||||
}
|
||||
|
||||
void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId) {
|
||||
uiFlipAnalogY = flipYByDeviceId;
|
||||
}
|
||||
|
@ -81,11 +86,12 @@ int GetAnalogYDirection(InputDeviceID deviceId) {
|
|||
}
|
||||
|
||||
// NOTE: Changing the format of FromConfigString/ToConfigString breaks controls.ini backwards compatibility.
|
||||
InputMapping InputMapping::FromConfigString(const std::string &str) {
|
||||
std::vector<std::string> parts;
|
||||
InputMapping InputMapping::FromConfigString(const std::string_view str) {
|
||||
std::vector<std::string_view> parts;
|
||||
SplitString(str, '-', parts);
|
||||
InputDeviceID deviceId = (InputDeviceID)(atoi(parts[0].c_str()));
|
||||
InputKeyCode keyCode = (InputKeyCode)atoi(parts[1].c_str());
|
||||
// We only convert to std::string here to add null terminators for atoi.
|
||||
InputDeviceID deviceId = (InputDeviceID)(atoi(std::string(parts[0]).c_str()));
|
||||
InputKeyCode keyCode = (InputKeyCode)atoi(std::string(parts[1]).c_str());
|
||||
|
||||
InputMapping mapping;
|
||||
mapping.deviceId = deviceId;
|
||||
|
|
|
@ -31,7 +31,7 @@ enum InputDeviceID {
|
|||
DEVICE_ID_XINPUT_1 = 21,
|
||||
DEVICE_ID_XINPUT_2 = 22,
|
||||
DEVICE_ID_XINPUT_3 = 23,
|
||||
DEVICE_ID_ACCELEROMETER = 30,
|
||||
DEVICE_ID_ACCELEROMETER = 30, // no longer used
|
||||
DEVICE_ID_XR_HMD = 39,
|
||||
DEVICE_ID_XR_CONTROLLER_LEFT = 40,
|
||||
DEVICE_ID_XR_CONTROLLER_RIGHT = 41,
|
||||
|
@ -79,7 +79,7 @@ public:
|
|||
_dbg_assert_(direction != 0);
|
||||
}
|
||||
|
||||
static InputMapping FromConfigString(const std::string &str);
|
||||
static InputMapping FromConfigString(std::string_view str);
|
||||
std::string ToConfigString() const;
|
||||
|
||||
InputDeviceID deviceId;
|
||||
|
@ -191,12 +191,14 @@ struct AxisInput {
|
|||
extern std::vector<InputMapping> dpadKeys;
|
||||
extern std::vector<InputMapping> confirmKeys;
|
||||
extern std::vector<InputMapping> cancelKeys;
|
||||
extern std::vector<InputMapping> infoKeys;
|
||||
extern std::vector<InputMapping> tabLeftKeys;
|
||||
extern std::vector<InputMapping> tabRightKeys;
|
||||
void SetDPadKeys(const std::vector<InputMapping> &leftKey, const std::vector<InputMapping> &rightKey,
|
||||
const std::vector<InputMapping> &upKey, const std::vector<InputMapping> &downKey);
|
||||
void SetConfirmCancelKeys(const std::vector<InputMapping> &confirm, const std::vector<InputMapping> &cancel);
|
||||
void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::vector<InputMapping> &tabRight);
|
||||
void SetInfoKeys(const std::vector<InputMapping> &info);
|
||||
|
||||
// 0 means unknown (attempt autodetect), -1 means flip, 1 means original direction.
|
||||
void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId);
|
||||
|
|
|
@ -305,7 +305,7 @@ enum InputAxis {
|
|||
JOYSTICK_AXIS_MOUSE_REL_X = 26,
|
||||
JOYSTICK_AXIS_MOUSE_REL_Y = 27,
|
||||
|
||||
// Mobile device accelerometer/gyro
|
||||
// Mobile device accelerometer/gyro. NOTE: These are no longer passed around internally, only used for the plugin API.
|
||||
JOYSTICK_AXIS_ACCELEROMETER_X = 40,
|
||||
JOYSTICK_AXIS_ACCELEROMETER_Y = 41,
|
||||
JOYSTICK_AXIS_ACCELEROMETER_Z = 42,
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "StringUtils.h"
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
#include "Common/Thread/ThreadUtil.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
#include <android/log.h>
|
||||
|
@ -38,10 +39,12 @@ static bool hitAnyAsserts = false;
|
|||
|
||||
std::mutex g_extraAssertInfoMutex;
|
||||
std::string g_extraAssertInfo = "menu";
|
||||
double g_assertInfoTime = 0.0;
|
||||
|
||||
void SetExtraAssertInfo(const char *info) {
|
||||
std::lock_guard<std::mutex> guard(g_extraAssertInfoMutex);
|
||||
g_extraAssertInfo = info ? info : "menu";
|
||||
g_assertInfoTime = time_now_d();
|
||||
}
|
||||
|
||||
bool HandleAssert(const char *function, const char *file, int line, const char *expression, const char* format, ...) {
|
||||
|
@ -57,7 +60,8 @@ bool HandleAssert(const char *function, const char *file, int line, const char *
|
|||
char formatted[LOG_BUF_SIZE + 128];
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_extraAssertInfoMutex);
|
||||
snprintf(formatted, sizeof(formatted), "(%s:%s:%d): [%s] (%s) %s", file, function, line, expression, g_extraAssertInfo.c_str(), text);
|
||||
double delta = time_now_d() - g_assertInfoTime;
|
||||
snprintf(formatted, sizeof(formatted), "(%s:%s:%d): [%s] (%s, %0.1fs) %s", file, function, line, expression, g_extraAssertInfo.c_str(), delta, text);
|
||||
}
|
||||
|
||||
// Normal logging (will also log to Android log)
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Common/Net/URL.h"
|
||||
|
||||
#include "Common/File/FileDescriptor.h"
|
||||
#include "Common/SysError.h"
|
||||
#include "Common/Thread/ThreadUtil.h"
|
||||
#include "Common/Data/Encoding/Compression.h"
|
||||
#include "Common/Net/NetBuffer.h"
|
||||
|
@ -97,7 +98,7 @@ static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) {
|
|||
switch (info->ai_family) {
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
inet_ntop(info->ai_family, info->ai_addr, addrbuf, bufsize);
|
||||
inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize);
|
||||
break;
|
||||
default:
|
||||
snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family);
|
||||
|
@ -131,11 +132,22 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
|
|||
// Start trying to connect (async with timeout.)
|
||||
errno = 0;
|
||||
if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) {
|
||||
if (errno != 0 && errno != EINPROGRESS) {
|
||||
char addrStr[128];
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
int errorCode = WSAGetLastError();
|
||||
std::string errorString = GetStringErrorMsg(errorCode);
|
||||
bool unreachable = errorCode == WSAENETUNREACH;
|
||||
bool inProgress = errorCode == WSAEINPROGRESS || errorCode == WSAEWOULDBLOCK;
|
||||
#else
|
||||
int errorCode = errno;
|
||||
std::string errorString = strerror(errno);
|
||||
bool unreachable = errorCode == ENETUNREACH;
|
||||
bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK;
|
||||
#endif
|
||||
if (!inProgress) {
|
||||
char addrStr[128]{};
|
||||
FormatAddr(addrStr, sizeof(addrStr), possible);
|
||||
if (errno != ENETUNREACH) {
|
||||
ERROR_LOG(HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errno, strerror(errno));
|
||||
if (!unreachable) {
|
||||
ERROR_LOG(HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str());
|
||||
} else {
|
||||
INFO_LOG(HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr);
|
||||
}
|
||||
|
@ -207,9 +219,9 @@ namespace http {
|
|||
|
||||
// TODO: do something sane here
|
||||
constexpr const char *DEFAULT_USERAGENT = "PPSSPP";
|
||||
constexpr const char *HTTP_VERSION = "1.1";
|
||||
|
||||
Client::Client() {
|
||||
httpVersion_ = "1.1";
|
||||
userAgent_ = DEFAULT_USERAGENT;
|
||||
}
|
||||
|
||||
|
@ -341,7 +353,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, co
|
|||
"\r\n";
|
||||
|
||||
buffer.Printf(tpl,
|
||||
method, req.resource.c_str(), httpVersion_,
|
||||
method, req.resource.c_str(), HTTP_VERSION,
|
||||
host_.c_str(),
|
||||
userAgent_.c_str(),
|
||||
req.acceptMime,
|
||||
|
|
|
@ -86,7 +86,6 @@ public:
|
|||
|
||||
protected:
|
||||
std::string userAgent_;
|
||||
const char *httpVersion_;
|
||||
double dataTimeout_ = 900.0;
|
||||
};
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ bool RequestHeader::GetParamValue(const char *param_name, std::string *value) co
|
|||
if (!params)
|
||||
return false;
|
||||
std::string p(params);
|
||||
std::vector<std::string> v;
|
||||
std::vector<std::string_view> v;
|
||||
SplitString(p, '&', v);
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
std::vector<std::string> parts;
|
||||
std::vector<std::string_view> parts;
|
||||
SplitString(v[i], '=', parts);
|
||||
DEBUG_LOG(IO, "Param: %s Value: %s", parts[0].c_str(), parts[1].c_str());
|
||||
DEBUG_LOG(IO, "Param: %.*s Value: %.*s", (int)parts[0].size(), parts[0].data(), (int)parts[1].size(), parts[1].data());
|
||||
if (parts[0] == param_name) {
|
||||
*value = parts[1];
|
||||
return true;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
#include "ext/naett/naett.h"
|
||||
|
@ -109,7 +110,7 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo **
|
|||
|
||||
if (result != 0) {
|
||||
#ifdef _WIN32
|
||||
error = gai_strerrorA(result);
|
||||
error = ConvertWStringToUTF8(gai_strerror(result));
|
||||
#else
|
||||
error = gai_strerror(result);
|
||||
#endif
|
||||
|
|
|
@ -115,13 +115,13 @@ const char HEX2DEC[256] =
|
|||
/* F */ N1,N1,N1,N1, N1,N1,N1,N1, N1,N1,N1,N1, N1,N1,N1,N1
|
||||
};
|
||||
|
||||
std::string UriDecode(const std::string & sSrc)
|
||||
std::string UriDecode(std::string_view sSrc)
|
||||
{
|
||||
// Note from RFC1630: "Sequences which start with a percent sign
|
||||
// but are not followed by two hexadecimal characters (0-9, A-F) are reserved
|
||||
// for future extension"
|
||||
|
||||
const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
|
||||
const unsigned char * pSrc = (const unsigned char *)sSrc.data();
|
||||
const size_t SRC_LEN = sSrc.length();
|
||||
const unsigned char * const SRC_END = pSrc + SRC_LEN;
|
||||
const unsigned char * const SRC_LAST_DEC = SRC_END - 2; // last decodable '%'
|
||||
|
@ -129,14 +129,10 @@ std::string UriDecode(const std::string & sSrc)
|
|||
char * const pStart = new char[SRC_LEN]; // Output will be shorter.
|
||||
char * pEnd = pStart;
|
||||
|
||||
while (pSrc < SRC_LAST_DEC)
|
||||
{
|
||||
if (*pSrc == '%')
|
||||
{
|
||||
while (pSrc < SRC_LAST_DEC) {
|
||||
if (*pSrc == '%') {
|
||||
char dec1, dec2;
|
||||
if (N1 != (dec1 = HEX2DEC[*(pSrc + 1)])
|
||||
&& N1 != (dec2 = HEX2DEC[*(pSrc + 2)]))
|
||||
{
|
||||
if (N1 != (dec1 = HEX2DEC[*(pSrc + 1)]) && N1 != (dec2 = HEX2DEC[*(pSrc + 2)])) {
|
||||
*pEnd++ = (dec1 << 4) + dec2;
|
||||
pSrc += 3;
|
||||
continue;
|
||||
|
@ -156,8 +152,7 @@ std::string UriDecode(const std::string & sSrc)
|
|||
}
|
||||
|
||||
// Only alphanum and underscore is safe.
|
||||
const char SAFE[256] =
|
||||
{
|
||||
static const char SAFE[256] = {
|
||||
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
||||
/* 0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
|
||||
/* 1 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
|
||||
|
@ -180,21 +175,18 @@ const char SAFE[256] =
|
|||
/* F */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
|
||||
};
|
||||
|
||||
std::string UriEncode(const std::string & sSrc)
|
||||
{
|
||||
std::string UriEncode(std::string_view sSrc) {
|
||||
const char DEC2HEX[16 + 1] = "0123456789ABCDEF";
|
||||
const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
|
||||
const unsigned char * pSrc = (const unsigned char *)sSrc.data();
|
||||
const size_t SRC_LEN = sSrc.length();
|
||||
unsigned char * const pStart = new unsigned char[SRC_LEN * 3];
|
||||
unsigned char * pEnd = pStart;
|
||||
const unsigned char * const SRC_END = pSrc + SRC_LEN;
|
||||
|
||||
for (; pSrc < SRC_END; ++pSrc)
|
||||
{
|
||||
if (SAFE[*pSrc])
|
||||
for (; pSrc < SRC_END; ++pSrc) {
|
||||
if (SAFE[*pSrc]) {
|
||||
*pEnd++ = *pSrc;
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// escape this char
|
||||
*pEnd++ = '%';
|
||||
*pEnd++ = DEC2HEX[*pSrc >> 4];
|
||||
|
|
|
@ -203,5 +203,5 @@ private:
|
|||
};
|
||||
|
||||
|
||||
std::string UriDecode(const std::string & sSrc);
|
||||
std::string UriEncode(const std::string & sSrc);
|
||||
std::string UriDecode(std::string_view sSrc);
|
||||
std::string UriEncode(std::string_view sSrc);
|
||||
|
|
|
@ -39,13 +39,11 @@ void DrawBuffer::Init(Draw::DrawContext *t3d, Draw::Pipeline *pipeline) {
|
|||
Draw::InputLayout *DrawBuffer::CreateInputLayout(Draw::DrawContext *t3d) {
|
||||
using namespace Draw;
|
||||
InputLayoutDesc desc = {
|
||||
sizeof(Vertex),
|
||||
{
|
||||
{ sizeof(Vertex), false },
|
||||
},
|
||||
{
|
||||
{ 0, SEM_POSITION, DataFormat::R32G32B32_FLOAT, 0 },
|
||||
{ 0, SEM_TEXCOORD0, DataFormat::R32G32_FLOAT, 12 },
|
||||
{ 0, SEM_COLOR0, DataFormat::R8G8B8A8_UNORM, 20 },
|
||||
{ SEM_POSITION, DataFormat::R32G32B32_FLOAT, 0 },
|
||||
{ SEM_TEXCOORD0, DataFormat::R32G32_FLOAT, 12 },
|
||||
{ SEM_COLOR0, DataFormat::R8G8B8A8_UNORM, 20 },
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -106,7 +104,7 @@ void DrawBuffer::V(float x, float y, float z, uint32_t color, float u, float v)
|
|||
|
||||
void DrawBuffer::Rect(float x, float y, float w, float h, uint32_t color, int align) {
|
||||
DoAlign(align, &x, &y, &w, &h);
|
||||
RectVGradient(x, y, w, h, color, color);
|
||||
RectVGradient(x, y, x + w, y + h, color, color);
|
||||
}
|
||||
|
||||
void DrawBuffer::hLine(float x1, float y, float x2, uint32_t color) {
|
||||
|
@ -121,13 +119,13 @@ void DrawBuffer::vLine(float x, float y1, float y2, uint32_t color) {
|
|||
Rect(x, y1, g_display.pixel_in_dps_x, y2 - y1, color);
|
||||
}
|
||||
|
||||
void DrawBuffer::RectVGradient(float x, float y, float w, float h, uint32_t colorTop, uint32_t colorBottom) {
|
||||
V(x, y, 0, colorTop, 0, 0);
|
||||
V(x + w, y, 0, colorTop, 1, 0);
|
||||
V(x + w, y + h, 0, colorBottom, 1, 1);
|
||||
V(x, y, 0, colorTop, 0, 0);
|
||||
V(x + w, y + h, 0, colorBottom, 1, 1);
|
||||
V(x, y + h, 0, colorBottom, 0, 1);
|
||||
void DrawBuffer::RectVGradient(float x1, float y1, float x2, float y2, uint32_t colorTop, uint32_t colorBottom) {
|
||||
V(x1, y1, 0, colorTop, 0, 0);
|
||||
V(x2, y1, 0, colorTop, 1, 0);
|
||||
V(x2, y2, 0, colorBottom, 1, 1);
|
||||
V(x1, y1, 0, colorTop, 0, 0);
|
||||
V(x2, y2, 0, colorBottom, 1, 1);
|
||||
V(x1, y2, 0, colorBottom, 0, 1);
|
||||
}
|
||||
|
||||
void DrawBuffer::RectOutline(float x, float y, float w, float h, uint32_t color, int align) {
|
||||
|
@ -142,7 +140,7 @@ void DrawBuffer::MultiVGradient(float x, float y, float w, float h, const Gradie
|
|||
for (int i = 0; i < numStops - 1; i++) {
|
||||
float t0 = stops[i].t, t1 = stops[i+1].t;
|
||||
uint32_t c0 = stops[i].color, c1 = stops[i+1].color;
|
||||
RectVGradient(x, y + h * t0, w, h * (t1 - t0), c0, c1);
|
||||
RectVGradient(x, y + h * t0, x + w, y + h * (t1 - t0), c0, c1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,9 +83,10 @@ public:
|
|||
|
||||
void RectOutline(float x, float y, float w, float h, uint32_t color, int align = ALIGN_TOPLEFT);
|
||||
|
||||
void RectVGradient(float x, float y, float w, float h, uint32_t colorTop, uint32_t colorBottom);
|
||||
// NOTE: This one takes x2/y2 instead of w/h, better for gap-free graphics.
|
||||
void RectVGradient(float x1, float y1, float x2, float y2, uint32_t colorTop, uint32_t colorBottom);
|
||||
void RectVDarkFaded(float x, float y, float w, float h, uint32_t colorTop) {
|
||||
RectVGradient(x, y, w, h, colorTop, darkenColor(colorTop));
|
||||
RectVGradient(x, y, x + w, y + h, colorTop, darkenColor(colorTop));
|
||||
}
|
||||
|
||||
void MultiVGradient(float x, float y, float w, float h, const GradientStop *stops, int numStops);
|
||||
|
|
|
@ -186,8 +186,15 @@ void ManagedTexture::DeviceLost() {
|
|||
|
||||
void ManagedTexture::DeviceRestored(Draw::DrawContext *draw) {
|
||||
INFO_LOG(G3D, "ManagedTexture::DeviceRestored(%s)", filename_.c_str());
|
||||
_assert_(!texture_);
|
||||
|
||||
draw_ = draw;
|
||||
|
||||
_dbg_assert_(!texture_);
|
||||
if (texture_) {
|
||||
ERROR_LOG(G3D, "ManagedTexture: Unexpected - texture already present: %s", filename_.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Vulkan: Can't load textures before the first frame has started.
|
||||
// Should probably try to lift that restriction again someday..
|
||||
loadPending_ = true;
|
||||
|
|
|
@ -378,12 +378,14 @@ void TextDrawerSDL::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStrin
|
|||
font = fallbackFonts_[0];
|
||||
}
|
||||
|
||||
#if SDL_TTF_VERSION_ATLEAST(2, 20, 0)
|
||||
if (align & ALIGN_HCENTER)
|
||||
TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_CENTER);
|
||||
else if (align & ALIGN_RIGHT)
|
||||
TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_RIGHT);
|
||||
else
|
||||
TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_LEFT);
|
||||
#endif
|
||||
|
||||
SDL_Color fgColor = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
SDL_Surface *text = TTF_RenderUTF8_Blended_Wrapped(font, processedStr.c_str(), fgColor, 0);
|
||||
|
|
|
@ -239,18 +239,15 @@ std::string StringFromFormat(const char* format, ...)
|
|||
return temp;
|
||||
}
|
||||
|
||||
std::string StringFromInt(int value)
|
||||
{
|
||||
std::string StringFromInt(int value) {
|
||||
char temp[16];
|
||||
snprintf(temp, sizeof(temp), "%d", value);
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Turns " hej " into "hej". Also handles tabs.
|
||||
std::string StripSpaces(const std::string &str)
|
||||
{
|
||||
std::string StripSpaces(const std::string &str) {
|
||||
const size_t s = str.find_first_not_of(" \t\r\n");
|
||||
|
||||
if (str.npos != s)
|
||||
return str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1);
|
||||
else
|
||||
|
@ -268,8 +265,26 @@ std::string StripQuotes(const std::string& s)
|
|||
return s;
|
||||
}
|
||||
|
||||
void SplitString(const std::string& str, const char delim, std::vector<std::string>& output)
|
||||
{
|
||||
// Turns " hej " into "hej". Also handles tabs.
|
||||
std::string_view StripSpaces(std::string_view str) {
|
||||
const size_t s = str.find_first_not_of(" \t\r\n");
|
||||
if (str.npos != s)
|
||||
return str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
// "\"hello\"" is turned to "hello"
|
||||
// This one assumes that the string has already been space stripped in both
|
||||
// ends, as done by StripSpaces above, for example.
|
||||
std::string_view StripQuotes(std::string_view s) {
|
||||
if (s.size() && '\"' == s[0] && '\"' == *s.rbegin())
|
||||
return s.substr(1, s.size() - 2);
|
||||
else
|
||||
return s;
|
||||
}
|
||||
|
||||
void SplitString(std::string_view str, const char delim, std::vector<std::string_view> &output) {
|
||||
size_t next = 0;
|
||||
for (size_t pos = 0, len = str.length(); pos < len; ++pos) {
|
||||
if (str[pos] == delim) {
|
||||
|
@ -286,6 +301,23 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
|
|||
}
|
||||
}
|
||||
|
||||
void SplitString(std::string_view str, const char delim, std::vector<std::string> &output) {
|
||||
size_t next = 0;
|
||||
for (size_t pos = 0, len = str.length(); pos < len; ++pos) {
|
||||
if (str[pos] == delim) {
|
||||
output.emplace_back(str.substr(next, pos - next));
|
||||
// Skip the delimiter itself.
|
||||
next = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (next == 0) {
|
||||
output.emplace_back(str);
|
||||
} else if (next < str.length()) {
|
||||
output.emplace_back(str.substr(next));
|
||||
}
|
||||
}
|
||||
|
||||
static std::string ApplyHtmlEscapes(std::string str) {
|
||||
struct Repl {
|
||||
const char *a;
|
||||
|
@ -305,8 +337,7 @@ static std::string ApplyHtmlEscapes(std::string str) {
|
|||
}
|
||||
|
||||
// Meant for HTML listings and similar, so supports some HTML escapes.
|
||||
void GetQuotedStrings(const std::string& str, std::vector<std::string>& output)
|
||||
{
|
||||
void GetQuotedStrings(const std::string& str, std::vector<std::string> &output) {
|
||||
size_t next = 0;
|
||||
bool even = 0;
|
||||
for (size_t pos = 0, len = str.length(); pos < len; ++pos) {
|
||||
|
@ -325,15 +356,13 @@ void GetQuotedStrings(const std::string& str, std::vector<std::string>& output)
|
|||
}
|
||||
}
|
||||
|
||||
std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest)
|
||||
{
|
||||
std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) {
|
||||
size_t pos = 0;
|
||||
|
||||
if (src == dest)
|
||||
return result;
|
||||
|
||||
while (1)
|
||||
{
|
||||
while (true) {
|
||||
pos = result.find(src, pos);
|
||||
if (pos == result.npos)
|
||||
break;
|
||||
|
@ -369,7 +398,7 @@ std::string UnescapeMenuString(const char *input, char *shortcutChar) {
|
|||
return output;
|
||||
}
|
||||
|
||||
std::string ApplySafeSubstitutions(const char *format, const std::string &string1, const std::string &string2, const std::string &string3) {
|
||||
std::string ApplySafeSubstitutions(const char *format, std::string_view string1, std::string_view string2, std::string_view string3, std::string_view string4) {
|
||||
size_t formatLen = strlen(format);
|
||||
std::string output;
|
||||
output.reserve(formatLen + 20);
|
||||
|
@ -392,6 +421,40 @@ std::string ApplySafeSubstitutions(const char *format, const std::string &string
|
|||
case '3':
|
||||
output += string3; i++;
|
||||
break;
|
||||
case '4':
|
||||
output += string4; i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string ApplySafeSubstitutions(const char *format, int i1, int i2, int i3, int i4) {
|
||||
size_t formatLen = strlen(format);
|
||||
std::string output;
|
||||
output.reserve(formatLen + 20);
|
||||
for (size_t i = 0; i < formatLen; i++) {
|
||||
char c = format[i];
|
||||
if (c != '%') {
|
||||
output.push_back(c);
|
||||
continue;
|
||||
}
|
||||
if (i >= formatLen - 1) {
|
||||
break;
|
||||
}
|
||||
switch (format[i + 1]) {
|
||||
case '1':
|
||||
output += StringFromInt(i1); i++;
|
||||
break;
|
||||
case '2':
|
||||
output += StringFromInt(i2); i++;
|
||||
break;
|
||||
case '3':
|
||||
output += StringFromInt(i3); i++;
|
||||
break;
|
||||
case '4':
|
||||
output += StringFromInt(i4); i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
@ -36,42 +37,36 @@ std::string IndentString(const std::string &str, const std::string &sep, bool sk
|
|||
|
||||
// Other simple string utilities.
|
||||
|
||||
// Optimized for string constants.
|
||||
inline bool startsWith(const std::string &str, const char *key) {
|
||||
size_t keyLen = strlen(key);
|
||||
if (str.size() < keyLen)
|
||||
inline bool startsWith(std::string_view str, std::string_view key) {
|
||||
if (str.size() < key.size())
|
||||
return false;
|
||||
return !memcmp(str.data(), key, keyLen);
|
||||
return !memcmp(str.data(), key.data(), key.size());
|
||||
}
|
||||
|
||||
inline bool startsWith(const std::string &str, const std::string &what) {
|
||||
if (str.size() < what.size())
|
||||
return false;
|
||||
return str.substr(0, what.size()) == what;
|
||||
}
|
||||
|
||||
inline bool endsWith(const std::string &str, const std::string &what) {
|
||||
inline bool endsWith(std::string_view str, std::string_view what) {
|
||||
if (str.size() < what.size())
|
||||
return false;
|
||||
return str.substr(str.size() - what.size()) == what;
|
||||
}
|
||||
|
||||
// Only use on strings where you're only concerned about ASCII.
|
||||
inline bool startsWithNoCase(const std::string &str, const std::string &what) {
|
||||
if (str.size() < what.size())
|
||||
inline bool startsWithNoCase(std::string_view str, std::string_view key) {
|
||||
if (str.size() < key.size())
|
||||
return false;
|
||||
return strncasecmp(str.c_str(), what.c_str(), what.size()) == 0;
|
||||
return strncasecmp(str.data(), key.data(), key.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool endsWithNoCase(const std::string &str, const std::string &what) {
|
||||
if (str.size() < what.size())
|
||||
inline bool endsWithNoCase(std::string_view str, std::string_view key) {
|
||||
if (str.size() < key.size())
|
||||
return false;
|
||||
const size_t offset = str.size() - what.size();
|
||||
return strncasecmp(str.c_str() + offset, what.c_str(), what.size()) == 0;
|
||||
const size_t offset = str.size() - key.size();
|
||||
return strncasecmp(str.data() + offset, key.data(), key.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool equalsNoCase(const std::string &str, const char *what) {
|
||||
return strcasecmp(str.c_str(), what) == 0;
|
||||
inline bool equalsNoCase(std::string_view str, std::string_view key) {
|
||||
if (str.size() != key.size())
|
||||
return false;
|
||||
return strncasecmp(str.data(), key.data(), key.size()) == 0;
|
||||
}
|
||||
|
||||
void DataToHexString(const uint8_t *data, size_t size, std::string *output);
|
||||
|
@ -83,7 +78,12 @@ std::string StringFromInt(int value);
|
|||
std::string StripSpaces(const std::string &s);
|
||||
std::string StripQuotes(const std::string &s);
|
||||
|
||||
void SplitString(const std::string& str, const char delim, std::vector<std::string>& output);
|
||||
std::string_view StripSpaces(std::string_view s);
|
||||
std::string_view StripQuotes(std::string_view s);
|
||||
|
||||
// TODO: Make this a lot more efficient by outputting string_views.
|
||||
void SplitString(std::string_view str, const char delim, std::vector<std::string_view> &output);
|
||||
void SplitString(std::string_view str, const char delim, std::vector<std::string> &output);
|
||||
|
||||
void GetQuotedStrings(const std::string& str, std::vector<std::string>& output);
|
||||
|
||||
|
@ -123,4 +123,6 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
|||
|
||||
// Replaces %1, %2, %3 in format with arg1, arg2, arg3.
|
||||
// Much safer than snprintf and friends.
|
||||
std::string ApplySafeSubstitutions(const char *format, const std::string &string1, const std::string &string2 = "", const std::string &string3 = "");
|
||||
// For mixes of strings and ints, manually convert the ints to strings.
|
||||
std::string ApplySafeSubstitutions(const char *format, std::string_view string1, std::string_view string2 = "", std::string_view string3 = "", std::string_view string4 = "");
|
||||
std::string ApplySafeSubstitutions(const char *format, int i1, int i2 = 0, int i3 = 0, int i4 = 0);
|
||||
|
|
|
@ -55,6 +55,7 @@ bool NativeIsRestarting();
|
|||
void NativeTouch(const TouchInput &touch);
|
||||
bool NativeKey(const KeyInput &key);
|
||||
void NativeAxis(const AxisInput *axis, size_t count);
|
||||
void NativeAccelerometer(float tiltX, float tiltY, float tiltZ);
|
||||
|
||||
// Called when it's process a frame, including rendering. If the device can keep up, this
|
||||
// will be called sixty times per second. Main thread.
|
||||
|
|
|
@ -130,6 +130,7 @@ enum SystemProperty {
|
|||
SYSPROP_HAS_IMAGE_BROWSER,
|
||||
SYSPROP_HAS_BACK_BUTTON,
|
||||
SYSPROP_HAS_KEYBOARD,
|
||||
SYSPROP_HAS_ACCELEROMETER, // Used to enable/disable tilt input settings
|
||||
SYSPROP_HAS_OPEN_DIRECTORY,
|
||||
SYSPROP_HAS_LOGIN_DIALOG,
|
||||
SYSPROP_HAS_TEXT_INPUT_DIALOG, // Indicates that System_InputBoxGetString is available.
|
||||
|
@ -139,6 +140,8 @@ enum SystemProperty {
|
|||
|
||||
SYSPROP_SUPPORTS_HTTPS,
|
||||
|
||||
SYSPROP_DEBUGGER_PRESENT,
|
||||
|
||||
// Available as Int:
|
||||
SYSPROP_SYSTEMVERSION,
|
||||
SYSPROP_DISPLAY_XRES,
|
||||
|
@ -207,6 +210,40 @@ enum class SystemNotification {
|
|||
ACTIVITY,
|
||||
};
|
||||
|
||||
// I guess it's not super great architecturally to centralize this, since it's not general - but same with a lot of
|
||||
// the other stuff, and this is only used by PPSSPP, so... better this than ugly strings.
|
||||
enum class UIMessage {
|
||||
PERMISSION_GRANTED,
|
||||
POWER_SAVING,
|
||||
RECREATE_VIEWS,
|
||||
CONFIG_LOADED,
|
||||
REQUEST_GAME_BOOT,
|
||||
REQUEST_GAME_RUN, // or continue?
|
||||
REQUEST_GAME_PAUSE,
|
||||
REQUEST_GAME_RESET,
|
||||
REQUEST_GAME_STOP,
|
||||
SHOW_CONTROL_MAPPING,
|
||||
SHOW_CHAT_SCREEN,
|
||||
SHOW_DISPLAY_LAYOUT_EDITOR,
|
||||
SHOW_SETTINGS,
|
||||
SHOW_LANGUAGE_SCREEN,
|
||||
REQUEST_GPU_DUMP_NEXT_FRAME,
|
||||
REQUEST_CLEAR_JIT,
|
||||
APP_RESUMED,
|
||||
REQUEST_PLAY_SOUND,
|
||||
WINDOW_MINIMIZED,
|
||||
LOST_FOCUS,
|
||||
GOT_FOCUS,
|
||||
GPU_CONFIG_CHANGED,
|
||||
GPU_RENDER_RESIZED,
|
||||
GPU_DISPLAY_RESIZED,
|
||||
POSTSHADER_UPDATED,
|
||||
ACHIEVEMENT_LOGIN_STATE_CHANGE,
|
||||
SAVESTATE_DISPLAY_SLOT,
|
||||
GAMESETTINGS_SEARCH,
|
||||
SAVEDATA_SEARCH,
|
||||
};
|
||||
|
||||
std::string System_GetProperty(SystemProperty prop);
|
||||
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop);
|
||||
int System_GetPropertyInt(SystemProperty prop);
|
||||
|
@ -221,7 +258,7 @@ bool System_AudioRecordingIsAvailable();
|
|||
bool System_AudioRecordingState();
|
||||
|
||||
// This will be changed to take an enum. Replacement for the old NativeMessageReceived.
|
||||
void System_PostUIMessage(const std::string &message, const std::string ¶m);
|
||||
void System_PostUIMessage(UIMessage message, const std::string ¶m = "");
|
||||
|
||||
// For these functions, most platforms will use the implementation provided in UI/AudioCommon.cpp,
|
||||
// no need to implement separately.
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
template<class T>
|
||||
class Promise {
|
||||
public:
|
||||
// Never fails.
|
||||
static Promise<T> *Spawn(ThreadManager *threadman, std::function<T()> fun, TaskType taskType, TaskPriority taskPriority = TaskPriority::NORMAL) {
|
||||
Mailbox<T> *mailbox = new Mailbox<T>();
|
||||
|
||||
|
|
|
@ -68,6 +68,20 @@ double from_time_raw_relative(uint64_t raw_time) {
|
|||
return from_time_raw(raw_time);
|
||||
}
|
||||
|
||||
double time_now_unix_utc() {
|
||||
const int64_t UNIX_TIME_START = 0x019DB1DED53E8000; //January 1, 1970 (start of Unix epoch) in "ticks"
|
||||
const double TICKS_PER_SECOND = 10000000; //a tick is 100ns
|
||||
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft); //returns ticks in UTC
|
||||
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
|
||||
LARGE_INTEGER li;
|
||||
li.LowPart = ft.dwLowDateTime;
|
||||
li.HighPart = ft.dwHighDateTime;
|
||||
//Convert ticks since 1/1/1970 into seconds
|
||||
return (double)(li.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND;
|
||||
}
|
||||
|
||||
void yield() {
|
||||
YieldProcessor();
|
||||
}
|
||||
|
@ -99,6 +113,12 @@ double from_time_raw_relative(uint64_t raw_time) {
|
|||
return (double)raw_time * (1.0 / nanos);
|
||||
}
|
||||
|
||||
double time_now_unix_utc() {
|
||||
struct timespec tp;
|
||||
clock_gettime(CLOCK_REALTIME, &tp);
|
||||
return tp.tv_sec * 1000000000ULL + tp.tv_nsec;
|
||||
}
|
||||
|
||||
void yield() {
|
||||
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
_mm_pause();
|
||||
|
@ -120,9 +140,14 @@ double time_now_d() {
|
|||
return (double)(tv.tv_sec - start) + (double)tv.tv_usec * (1.0 / micros);
|
||||
}
|
||||
|
||||
// Fake, but usable in a pinch. Don't, though.
|
||||
uint64_t time_now_raw() {
|
||||
return (uint64_t)(time_now_d() * nanos);
|
||||
static time_t start;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
if (start == 0) {
|
||||
start = tv.tv_sec;
|
||||
}
|
||||
return (double)tv.tv_sec + (double)tv.tv_usec * (1.0 / micros);
|
||||
}
|
||||
|
||||
double from_time_raw(uint64_t raw_time) {
|
||||
|
@ -135,6 +160,10 @@ double from_time_raw_relative(uint64_t raw_time) {
|
|||
|
||||
void yield() {}
|
||||
|
||||
double time_now_unix_utc() {
|
||||
return time_now_raw();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void sleep_ms(int ms) {
|
||||
|
|
|
@ -13,6 +13,9 @@ uint64_t time_now_raw();
|
|||
double from_time_raw(uint64_t raw_time);
|
||||
double from_time_raw_relative(uint64_t raw_time);
|
||||
|
||||
// Seconds, Unix UTC time
|
||||
double time_now_unix_utc();
|
||||
|
||||
// Sleep. Does not necessarily have millisecond granularity, especially on Windows.
|
||||
void sleep_ms(int ms);
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ void IconCache::SaveToFile(FILE *file) {
|
|||
|
||||
for (auto &iter : cache_) {
|
||||
DiskCacheEntry entryHeader;
|
||||
memset(&entryHeader, 0, sizeof(entryHeader)); // valgrind complains about padding bytes
|
||||
entryHeader.keyLen = (uint32_t)iter.first.size();
|
||||
entryHeader.dataLen = (uint32_t)iter.second.data.size();
|
||||
entryHeader.format = iter.second.format;
|
||||
|
@ -228,7 +229,7 @@ bool IconCache::MarkPending(const std::string &key) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void IconCache::Cancel(const std::string &key) {
|
||||
void IconCache::CancelPending(const std::string &key) {
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
pending_.erase(key);
|
||||
}
|
||||
|
@ -266,6 +267,7 @@ Draw::Texture *IconCache::BindIconTexture(UIContext *context, const std::string
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: Cut down on how long we're holding this lock here.
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
auto iter = cache_.find(key);
|
||||
if (iter == cache_.end()) {
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
|
||||
// It's okay to call these from any thread.
|
||||
bool MarkPending(const std::string &key); // returns false if already pending or loaded
|
||||
void Cancel(const std::string &key);
|
||||
void CancelPending(const std::string &key);
|
||||
bool InsertIcon(const std::string &key, IconFormat format, std::string &&pngData);
|
||||
bool GetDimensions(const std::string &key, int *width, int *height);
|
||||
bool Contains(const std::string &key);
|
||||
|
|
|
@ -17,9 +17,9 @@ void MessagePopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
|
|||
using namespace UI;
|
||||
UIContext &dc = *screenManager()->getUIContext();
|
||||
|
||||
std::vector<std::string> messageLines;
|
||||
std::vector<std::string_view> messageLines;
|
||||
SplitString(message_, '\n', messageLines);
|
||||
for (const auto &lineOfText : messageLines)
|
||||
for (auto lineOfText : messageLines)
|
||||
parent->Add(new UI::TextView(lineOfText, ALIGN_LEFT | ALIGN_VCENTER, false))->SetTextColor(dc.theme->popupStyle.fgColor);
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,11 @@ void PopupMultiChoice::UpdateText() {
|
|||
if (index < 0 || index >= numChoices_) {
|
||||
valueText_ = "(invalid choice)"; // Shouldn't happen. Should be no need to translate this.
|
||||
} else {
|
||||
valueText_ = T(category_, choices_[index]);
|
||||
if (choices_[index]) {
|
||||
valueText_ = T(category_, choices_[index]);
|
||||
} else {
|
||||
valueText_ = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,12 +167,15 @@ PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, int
|
|||
|
||||
PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, float defaultValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams)
|
||||
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), defaultValue_(defaultValue), step_(1.0f), units_(units), screenManager_(screenManager) {
|
||||
_dbg_assert_(maxValue > minValue);
|
||||
fmt_ = "%2.2f";
|
||||
OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick);
|
||||
}
|
||||
|
||||
PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, float defaultValue, const std::string &text, float step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams)
|
||||
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), defaultValue_(defaultValue), step_(step), units_(units), screenManager_(screenManager) {
|
||||
_dbg_assert_(step > 0.0f);
|
||||
_dbg_assert_(maxValue > minValue);
|
||||
fmt_ = "%2.2f";
|
||||
OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
@ -12,14 +10,12 @@
|
|||
|
||||
namespace UI {
|
||||
|
||||
static std::mutex focusLock;
|
||||
static std::vector<int> focusMoves;
|
||||
extern bool focusForced;
|
||||
|
||||
static View *focusedView;
|
||||
static bool focusMovementEnabled;
|
||||
bool focusForced;
|
||||
static std::mutex eventMutex_;
|
||||
|
||||
static std::function<void(UISound, float)> soundCallback;
|
||||
static bool soundEnabled = true;
|
||||
|
@ -29,29 +25,20 @@ struct DispatchQueueItem {
|
|||
EventParams params;
|
||||
};
|
||||
|
||||
std::atomic<bool> hasDispatchQueue;
|
||||
std::deque<DispatchQueueItem> g_dispatchQueue;
|
||||
|
||||
void EventTriggered(Event *e, EventParams params) {
|
||||
DispatchQueueItem item{ e, params };
|
||||
|
||||
std::unique_lock<std::mutex> guard(eventMutex_);
|
||||
// Set before adding so we lock and check the added value.
|
||||
hasDispatchQueue = true;
|
||||
g_dispatchQueue.push_front(item);
|
||||
}
|
||||
|
||||
void DispatchEvents() {
|
||||
while (hasDispatchQueue) {
|
||||
while (!g_dispatchQueue.empty()) {
|
||||
DispatchQueueItem item;
|
||||
{
|
||||
std::unique_lock<std::mutex> guard(eventMutex_);
|
||||
if (g_dispatchQueue.empty())
|
||||
break;
|
||||
item = g_dispatchQueue.back();
|
||||
g_dispatchQueue.pop_back();
|
||||
hasDispatchQueue = !g_dispatchQueue.empty();
|
||||
}
|
||||
if (g_dispatchQueue.empty())
|
||||
break;
|
||||
item = g_dispatchQueue.back();
|
||||
g_dispatchQueue.pop_back();
|
||||
if (item.e) {
|
||||
item.e->Dispatch(item.params);
|
||||
}
|
||||
|
@ -59,9 +46,6 @@ void DispatchEvents() {
|
|||
}
|
||||
|
||||
void RemoveQueuedEventsByView(View *view) {
|
||||
if (!hasDispatchQueue)
|
||||
return;
|
||||
std::unique_lock<std::mutex> guard(eventMutex_);
|
||||
for (auto it = g_dispatchQueue.begin(); it != g_dispatchQueue.end(); ) {
|
||||
if (it->params.v == view) {
|
||||
it = g_dispatchQueue.erase(it);
|
||||
|
@ -72,9 +56,6 @@ void RemoveQueuedEventsByView(View *view) {
|
|||
}
|
||||
|
||||
void RemoveQueuedEventsByEvent(Event *event) {
|
||||
if (!hasDispatchQueue)
|
||||
return;
|
||||
std::unique_lock<std::mutex> guard(eventMutex_);
|
||||
for (auto it = g_dispatchQueue.begin(); it != g_dispatchQueue.end(); ) {
|
||||
if (it->e == event) {
|
||||
it = g_dispatchQueue.erase(it);
|
||||
|
@ -214,7 +195,6 @@ static KeyEventResult KeyEventToFocusMoves(const KeyInput &key) {
|
|||
hk.deviceId = key.deviceId;
|
||||
hk.triggerTime = time_now_d() + repeatDelay;
|
||||
|
||||
std::lock_guard<std::mutex> lock(focusLock);
|
||||
// Check if the key is already held. If it is, ignore it. This is to avoid
|
||||
// multiple key repeat mechanisms colliding.
|
||||
if (heldKeys.find(hk) != heldKeys.end()) {
|
||||
|
@ -381,7 +361,6 @@ restart:
|
|||
key.flags = KEY_DOWN;
|
||||
KeyEvent(key, root);
|
||||
|
||||
std::lock_guard<std::mutex> lock(focusLock);
|
||||
focusMoves.push_back(key.keyCode);
|
||||
|
||||
// Cannot modify the current item when looping over a set, so let's do this instead.
|
||||
|
@ -404,7 +383,6 @@ void UpdateViewHierarchy(ViewGroup *root) {
|
|||
}
|
||||
|
||||
if (focusMoves.size()) {
|
||||
std::lock_guard<std::mutex> lock(focusLock);
|
||||
EnableFocusMovement(true);
|
||||
if (!GetFocusedView()) {
|
||||
// Find a view to focus.
|
||||
|
|
|
@ -13,6 +13,15 @@
|
|||
|
||||
#include "Core/KeyMap.h"
|
||||
|
||||
void Screen::focusChanged(ScreenFocusChange focusChange) {
|
||||
char *eventName = "";
|
||||
switch (focusChange) {
|
||||
case ScreenFocusChange::FOCUS_LOST_TOP: eventName = "FOCUS_LOST_TOP"; break;
|
||||
case ScreenFocusChange::FOCUS_BECAME_TOP: eventName = "FOCUS_BECAME_TOP"; break;
|
||||
}
|
||||
DEBUG_LOG(SYSTEM, "Screen %s got %s", this->tag(), eventName);
|
||||
}
|
||||
|
||||
ScreenManager::~ScreenManager() {
|
||||
shutdown();
|
||||
}
|
||||
|
@ -68,14 +77,17 @@ void ScreenManager::switchToNext() {
|
|||
Layer temp = {nullptr, 0};
|
||||
if (!stack_.empty()) {
|
||||
temp = stack_.back();
|
||||
temp.screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
|
||||
stack_.pop_back();
|
||||
}
|
||||
stack_.push_back(nextStack_.front());
|
||||
nextStack_.front().screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
|
||||
if (temp.screen) {
|
||||
delete temp.screen;
|
||||
}
|
||||
UI::SetFocusedView(nullptr);
|
||||
|
||||
// When will this ever happen? Should handle focus here too?
|
||||
for (size_t i = 1; i < nextStack_.size(); ++i) {
|
||||
stack_.push_back(nextStack_[i]);
|
||||
}
|
||||
|
@ -117,26 +129,10 @@ bool ScreenManager::key(const KeyInput &key) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void ScreenManager::axis(const AxisInput &axis) {
|
||||
void ScreenManager::axis(const AxisInput *axes, size_t count) {
|
||||
std::lock_guard<std::recursive_mutex> guard(inputLock_);
|
||||
|
||||
// Ignore duplicate values to prevent axis values overwriting each other.
|
||||
uint64_t key = ((uint64_t)axis.axisId << 32) | axis.deviceId;
|
||||
// Center value far from zero just to ensure we send the first zero.
|
||||
// PSP games can't see higher resolution than this.
|
||||
int value = 128 + ceilf(axis.value * 127.5f + 127.5f);
|
||||
if (lastAxis_[key] == value) {
|
||||
return;
|
||||
}
|
||||
lastAxis_[key] = value;
|
||||
|
||||
// Send center axis to every screen layer.
|
||||
if (axis.value == 0) {
|
||||
for (auto &layer : stack_) {
|
||||
layer.screen->UnsyncAxis(axis);
|
||||
}
|
||||
} else if (!stack_.empty()) {
|
||||
stack_.back().screen->UnsyncAxis(axis);
|
||||
if (!stack_.empty()) {
|
||||
stack_.back().screen->UnsyncAxis(axes, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,10 +222,10 @@ void ScreenManager::getFocusPosition(float &x, float &y, float &z) {
|
|||
z = stack_.size();
|
||||
}
|
||||
|
||||
void ScreenManager::sendMessage(const char *msg, const char *value) {
|
||||
if (!strcmp(msg, "recreateviews"))
|
||||
void ScreenManager::sendMessage(UIMessage message, const char *value) {
|
||||
if (message == UIMessage::RECREATE_VIEWS) {
|
||||
RecreateAllViews();
|
||||
if (!strcmp(msg, "lost_focus")) {
|
||||
} else if (message == UIMessage::LOST_FOCUS) {
|
||||
TouchInput input{};
|
||||
input.x = -50000.0f;
|
||||
input.y = -50000.0f;
|
||||
|
@ -238,8 +234,9 @@ void ScreenManager::sendMessage(const char *msg, const char *value) {
|
|||
input.id = 0;
|
||||
touch(input);
|
||||
}
|
||||
|
||||
if (!stack_.empty())
|
||||
stack_.back().screen->sendMessage(msg, value);
|
||||
stack_.back().screen->sendMessage(message, value);
|
||||
}
|
||||
|
||||
Screen *ScreenManager::topScreen() const {
|
||||
|
@ -279,17 +276,30 @@ void ScreenManager::push(Screen *screen, int layerFlags) {
|
|||
touch(input);
|
||||
|
||||
Layer layer = {screen, layerFlags};
|
||||
if (nextStack_.empty())
|
||||
|
||||
if (!stack_.empty()) {
|
||||
stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
|
||||
}
|
||||
|
||||
if (nextStack_.empty()) {
|
||||
layer.screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
|
||||
stack_.push_back(layer);
|
||||
else
|
||||
} else {
|
||||
nextStack_.push_back(layer);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenManager::pop() {
|
||||
std::lock_guard<std::recursive_mutex> guard(inputLock_);
|
||||
if (stack_.size()) {
|
||||
if (!stack_.empty()) {
|
||||
stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
|
||||
|
||||
delete stack_.back().screen;
|
||||
stack_.pop_back();
|
||||
|
||||
if (!stack_.empty()) {
|
||||
stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
|
||||
}
|
||||
} else {
|
||||
ERROR_LOG(SYSTEM, "Can't pop when stack empty");
|
||||
}
|
||||
|
@ -333,12 +343,19 @@ void ScreenManager::processFinishDialog() {
|
|||
std::lock_guard<std::recursive_mutex> guard(inputLock_);
|
||||
// Another dialog may have been pushed before the render, so search for it.
|
||||
Screen *caller = dialogParent(dialogFinished_);
|
||||
bool erased = false;
|
||||
for (size_t i = 0; i < stack_.size(); ++i) {
|
||||
if (stack_[i].screen == dialogFinished_) {
|
||||
stack_[i].screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
|
||||
stack_.erase(stack_.begin() + i);
|
||||
erased = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (erased && !stack_.empty()) {
|
||||
stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
|
||||
}
|
||||
|
||||
if (!caller) {
|
||||
ERROR_LOG(SYSTEM, "ERROR: no top screen when finishing dialog");
|
||||
} else if (caller != topScreen()) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Common/System/System.h"
|
||||
|
||||
namespace UI {
|
||||
class View;
|
||||
|
@ -41,6 +42,11 @@ namespace Draw {
|
|||
class DrawContext;
|
||||
}
|
||||
|
||||
enum class ScreenFocusChange {
|
||||
FOCUS_LOST_TOP, // Another screen was pushed on top
|
||||
FOCUS_BECAME_TOP, // Became the top screen again
|
||||
};
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
Screen() : screenManager_(nullptr) { }
|
||||
|
@ -55,15 +61,17 @@ public:
|
|||
virtual void postRender() {}
|
||||
virtual void resized() {}
|
||||
virtual void dialogFinished(const Screen *dialog, DialogResult result) {}
|
||||
virtual void sendMessage(const char *msg, const char *value) {}
|
||||
virtual void sendMessage(UIMessage message, const char *value) {}
|
||||
virtual void deviceLost() {}
|
||||
virtual void deviceRestored() {}
|
||||
|
||||
virtual void focusChanged(ScreenFocusChange focusChange);
|
||||
|
||||
// Return value of UnsyncTouch is only used to let the overlay screen block touches.
|
||||
virtual bool UnsyncTouch(const TouchInput &touch) = 0;
|
||||
// Return value of UnsyncKey is used to not block certain system keys like volume when unhandled, on Android.
|
||||
virtual bool UnsyncKey(const KeyInput &touch) = 0;
|
||||
virtual void UnsyncAxis(const AxisInput &touch) = 0;
|
||||
virtual void UnsyncAxis(const AxisInput *axes, size_t count) = 0;
|
||||
|
||||
virtual void RecreateViews() {}
|
||||
|
||||
|
@ -135,10 +143,10 @@ public:
|
|||
// Instant touch, separate from the update() mechanism.
|
||||
void touch(const TouchInput &touch);
|
||||
bool key(const KeyInput &key);
|
||||
void axis(const AxisInput &touch);
|
||||
void axis(const AxisInput *axes, size_t count);
|
||||
|
||||
// Generic facility for gross hacks :P
|
||||
void sendMessage(const char *msg, const char *value);
|
||||
void sendMessage(UIMessage message, const char *value);
|
||||
|
||||
Screen *topScreen() const;
|
||||
|
||||
|
|
|
@ -99,12 +99,14 @@ bool UIScreen::UnsyncTouch(const TouchInput &touch) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void UIScreen::UnsyncAxis(const AxisInput &axis) {
|
||||
void UIScreen::UnsyncAxis(const AxisInput *axes, size_t count) {
|
||||
QueuedEvent ev{};
|
||||
ev.type = QueuedEventType::AXIS;
|
||||
ev.axis = axis;
|
||||
std::lock_guard<std::mutex> guard(eventQueueLock_);
|
||||
eventQueue_.push_back(ev);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ev.axis = axes[i];
|
||||
eventQueue_.push_back(ev);
|
||||
}
|
||||
}
|
||||
|
||||
bool UIScreen::UnsyncKey(const KeyInput &key) {
|
||||
|
@ -267,10 +269,10 @@ bool UIDialogScreen::key(const KeyInput &key) {
|
|||
return retval;
|
||||
}
|
||||
|
||||
void UIDialogScreen::sendMessage(const char *msg, const char *value) {
|
||||
void UIDialogScreen::sendMessage(UIMessage message, const char *value) {
|
||||
Screen *screen = screenManager()->dialogParent(this);
|
||||
if (screen) {
|
||||
screen->sendMessage(msg, value);
|
||||
screen->sendMessage(message, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public:
|
|||
|
||||
bool UnsyncTouch(const TouchInput &touch) override;
|
||||
bool UnsyncKey(const KeyInput &key) override;
|
||||
void UnsyncAxis(const AxisInput &axis) override;
|
||||
void UnsyncAxis(const AxisInput *axes, size_t count) override;
|
||||
|
||||
TouchInput transformTouch(const TouchInput &touch) override;
|
||||
|
||||
|
@ -88,7 +88,7 @@ class UIDialogScreen : public UIScreen {
|
|||
public:
|
||||
UIDialogScreen() : UIScreen(), finished_(false) {}
|
||||
bool key(const KeyInput &key) override;
|
||||
void sendMessage(const char *msg, const char *value) override;
|
||||
void sendMessage(UIMessage message, const char *value) override;
|
||||
|
||||
private:
|
||||
bool finished_;
|
||||
|
|
|
@ -307,6 +307,21 @@ bool IsEscapeKey(const KeyInput &key) {
|
|||
}
|
||||
}
|
||||
|
||||
// Corresponds to Triangle
|
||||
bool IsInfoKey(const KeyInput &key) {
|
||||
if (infoKeys.empty()) {
|
||||
// This path is pretty much not used, infoKeys should be set.
|
||||
// TODO: Get rid of this stuff?
|
||||
if (key.deviceId == DEVICE_ID_KEYBOARD) {
|
||||
return key.keyCode == NKCODE_S || key.keyCode == NKCODE_NUMPAD_ADD;
|
||||
} else {
|
||||
return key.keyCode == NKCODE_BUTTON_Y || key.keyCode == NKCODE_BUTTON_3;
|
||||
}
|
||||
} else {
|
||||
return MatchesKeyDef(infoKeys, key);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTabLeftKey(const KeyInput &key) {
|
||||
if (tabLeftKeys.empty()) {
|
||||
// This path is pretty much not used, tabLeftKeys should be set.
|
||||
|
@ -1557,6 +1572,9 @@ bool SliderFloat::ApplyKey(InputKeyCode keyCode) {
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
_dbg_assert_(!my_isnanorinf(*value_));
|
||||
|
||||
EventParams params{};
|
||||
params.v = this;
|
||||
params.a = (uint32_t)(*value_);
|
||||
|
@ -1584,6 +1602,7 @@ bool SliderFloat::Touch(const TouchInput &input) {
|
|||
}
|
||||
|
||||
void SliderFloat::Clamp() {
|
||||
_dbg_assert_(!my_isnanorinf(*value_));
|
||||
if (*value_ < minValue_)
|
||||
*value_ = minValue_;
|
||||
else if (*value_ > maxValue_)
|
||||
|
|
|
@ -927,10 +927,10 @@ private:
|
|||
|
||||
class TextView : public InertView {
|
||||
public:
|
||||
TextView(const std::string &text, LayoutParams *layoutParams = 0)
|
||||
TextView(std::string_view text, LayoutParams *layoutParams = 0)
|
||||
: InertView(layoutParams), text_(text), textAlign_(0), textColor_(0xFFFFFFFF), small_(false), shadow_(false), focusable_(false), clip_(true) {}
|
||||
|
||||
TextView(const std::string &text, int textAlign, bool small, LayoutParams *layoutParams = 0)
|
||||
TextView(std::string_view text, int textAlign, bool small, LayoutParams *layoutParams = 0)
|
||||
: InertView(layoutParams), text_(text), textAlign_(textAlign), textColor_(0xFFFFFFFF), small_(small), shadow_(false), focusable_(false), clip_(true) {}
|
||||
|
||||
void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override;
|
||||
|
@ -1066,6 +1066,7 @@ void ApplyBoundsBySpec(Bounds &bounds, MeasureSpec horiz, MeasureSpec vert);
|
|||
bool IsDPadKey(const KeyInput &key);
|
||||
bool IsAcceptKey(const KeyInput &key);
|
||||
bool IsEscapeKey(const KeyInput &key);
|
||||
bool IsInfoKey(const KeyInput &key);
|
||||
bool IsTabLeftKey(const KeyInput &key);
|
||||
bool IsTabRightKey(const KeyInput &key);
|
||||
|
||||
|
|
|
@ -47,12 +47,12 @@ ViewGroup::~ViewGroup() {
|
|||
Clear();
|
||||
}
|
||||
|
||||
void ViewGroup::RemoveSubview(View *view) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
void ViewGroup::RemoveSubview(View *subView) {
|
||||
// loop counter needed, so can't convert loop.
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
if (views_[i] == view) {
|
||||
if (views_[i] == subView) {
|
||||
views_.erase(views_.begin() + i);
|
||||
delete view;
|
||||
delete subView;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -67,17 +67,13 @@ bool ViewGroup::ContainsSubview(const View *view) const {
|
|||
}
|
||||
|
||||
void ViewGroup::Clear() {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
delete views_[i];
|
||||
views_[i] = nullptr;
|
||||
for (View *view : views_) {
|
||||
delete view;
|
||||
}
|
||||
views_.clear();
|
||||
}
|
||||
|
||||
void ViewGroup::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
|
||||
std::string tag = Tag();
|
||||
if (tag.empty()) {
|
||||
tag = anonId;
|
||||
|
@ -89,12 +85,11 @@ void ViewGroup::PersistData(PersistStatus status, std::string anonId, PersistMap
|
|||
}
|
||||
|
||||
bool ViewGroup::Touch(const TouchInput &input) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
bool any = false;
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
for (View *view : views_) {
|
||||
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
||||
if ((*iter)->GetVisibility() == V_VISIBLE) {
|
||||
bool touch = (*iter)->Touch(input);
|
||||
if (view->GetVisibility() == V_VISIBLE) {
|
||||
bool touch = view->Touch(input);
|
||||
any = any || touch;
|
||||
if (exclusiveTouch_ && touch && (input.flags & TOUCH_DOWN)) {
|
||||
break;
|
||||
|
@ -111,43 +106,39 @@ bool ViewGroup::Touch(const TouchInput &input) {
|
|||
void ViewGroup::Query(float x, float y, std::vector<View *> &list) {
|
||||
if (bounds_.Contains(x, y)) {
|
||||
list.push_back(this);
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
(*iter)->Query(x, y, list);
|
||||
for (View *view : views_) {
|
||||
view->Query(x, y, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ViewGroup::Key(const KeyInput &input) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
bool ret = false;
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
for (View *view : views_) {
|
||||
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
||||
if ((*iter)->GetVisibility() == V_VISIBLE)
|
||||
ret = ret || (*iter)->Key(input);
|
||||
if (view->GetVisibility() == V_VISIBLE)
|
||||
ret = ret || view->Key(input);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ViewGroup::Axis(const AxisInput &input) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
for (View *view : views_) {
|
||||
// TODO: If there is a transformation active, transform input coordinates accordingly.
|
||||
if ((*iter)->GetVisibility() == V_VISIBLE)
|
||||
(*iter)->Axis(input);
|
||||
if (view->GetVisibility() == V_VISIBLE)
|
||||
view->Axis(input);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewGroup::DeviceLost() {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
(*iter)->DeviceLost();
|
||||
for (View *view : views_) {
|
||||
view->DeviceLost();
|
||||
}
|
||||
}
|
||||
|
||||
void ViewGroup::DeviceRestored(Draw::DrawContext *draw) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
|
||||
(*iter)->DeviceRestored(draw);
|
||||
for (View *view : views_) {
|
||||
view->DeviceRestored(draw);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,21 +236,18 @@ void ViewGroup::Update() {
|
|||
}
|
||||
|
||||
bool ViewGroup::SetFocus() {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
if (!CanBeFocused() && !views_.empty()) {
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
if (views_[i]->SetFocus())
|
||||
for (View *view : views_) {
|
||||
if (view->SetFocus())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewGroup::SubviewFocused(View *view) {
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
if (views_[i] == view)
|
||||
return true;
|
||||
if (views_[i]->SubviewFocused(view))
|
||||
bool ViewGroup::SubviewFocused(View *queryView) {
|
||||
for (View *view : views_) {
|
||||
if (view == queryView || view->SubviewFocused(queryView))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -45,7 +45,6 @@ public:
|
|||
// Takes ownership! DO NOT add a view to multiple parents!
|
||||
template <class T>
|
||||
T *Add(T *view) {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
views_.push_back(view);
|
||||
return view;
|
||||
}
|
||||
|
@ -76,9 +75,6 @@ public:
|
|||
void SetExclusiveTouch(bool exclusive) { exclusiveTouch_ = exclusive; }
|
||||
void SetClickableBackground(bool clickableBackground) { clickableBackground_ = clickableBackground; }
|
||||
|
||||
void Lock() { modifyLock_.lock(); }
|
||||
void Unlock() { modifyLock_.unlock(); }
|
||||
|
||||
void SetClip(bool clip) { clip_ = clip; }
|
||||
std::string DescribeLog() const override { return "ViewGroup: " + View::DescribeLog(); }
|
||||
std::string DescribeText() const override;
|
||||
|
@ -87,7 +83,6 @@ protected:
|
|||
std::string DescribeListUnordered(const char *heading) const;
|
||||
std::string DescribeListOrdered(const char *heading) const;
|
||||
|
||||
std::mutex modifyLock_; // Hold this when changing the subviews.
|
||||
std::vector<View *> views_;
|
||||
View *defaultFocusView_ = nullptr;
|
||||
Drawable bg_;
|
||||
|
|
|
@ -26,13 +26,6 @@
|
|||
#include "Core/KeyMap.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
enum VRMatrix {
|
||||
VR_PROJECTION_MATRIX,
|
||||
VR_VIEW_MATRIX_LEFT_EYE,
|
||||
VR_VIEW_MATRIX_RIGHT_EYE,
|
||||
VR_MATRIX_COUNT
|
||||
};
|
||||
|
||||
enum VRMirroring {
|
||||
VR_MIRRORING_AXIS_X,
|
||||
VR_MIRRORING_AXIS_Y,
|
||||
|
@ -51,9 +44,10 @@ static int vr3DGeometryCount = 0;
|
|||
static long vrCompat[VR_COMPAT_MAX];
|
||||
static bool vrFlatForced = false;
|
||||
static bool vrFlatGame = false;
|
||||
static float vrMatrix[VR_MATRIX_COUNT][16];
|
||||
static double vrFov[2] = {};
|
||||
static bool vrMirroring[VR_MIRRORING_COUNT];
|
||||
static int vrMirroringVariant = 0;
|
||||
static float vrViewMatrix[2][16];
|
||||
static XrView vrView[2];
|
||||
|
||||
static void (*cbNativeAxis)(const AxisInput *axis, size_t count);
|
||||
|
@ -473,11 +467,14 @@ void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale) {
|
|||
}
|
||||
}
|
||||
|
||||
bool UpdateVRAxis(const AxisInput &axis) {
|
||||
if (pspAxis.find(axis.deviceId) == pspAxis.end()) {
|
||||
pspAxis[axis.deviceId] = std::map<int, float>();
|
||||
bool UpdateVRAxis(const AxisInput *axes, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const AxisInput &axis = axes[i];
|
||||
if (pspAxis.find(axis.deviceId) == pspAxis.end()) {
|
||||
pspAxis[axis.deviceId] = std::map<int, float>();
|
||||
}
|
||||
pspAxis[axis.deviceId][axis.axisId] = axis.value;
|
||||
}
|
||||
pspAxis[axis.deviceId][axis.axisId] = axis.value;
|
||||
return !pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
|
||||
}
|
||||
|
||||
|
@ -639,28 +636,16 @@ bool StartVRRender() {
|
|||
}
|
||||
UpdateVRViewMatrices();
|
||||
|
||||
// Update projection matrix
|
||||
// Calculate field of view
|
||||
XrFovf fov = {};
|
||||
for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
|
||||
fov.angleLeft += vrView[eye].fov.angleLeft / 2.0f;
|
||||
fov.angleRight += vrView[eye].fov.angleRight / 2.0f;
|
||||
fov.angleUp += vrView[eye].fov.angleUp / 2.0f;
|
||||
fov.angleDown += vrView[eye].fov.angleDown / 2.0f;
|
||||
for (auto & eye : vrView) {
|
||||
fov.angleLeft += eye.fov.angleLeft / 2.0f;
|
||||
fov.angleRight += eye.fov.angleRight / 2.0f;
|
||||
fov.angleUp += eye.fov.angleUp / 2.0f;
|
||||
fov.angleDown += eye.fov.angleDown / 2.0f;
|
||||
}
|
||||
float nearZ = g_Config.fFieldOfViewPercentage / 200.0f;
|
||||
float tanAngleLeft = tanf(fov.angleLeft);
|
||||
float tanAngleRight = tanf(fov.angleRight);
|
||||
float tanAngleDown = tanf(fov.angleDown);
|
||||
float tanAngleUp = tanf(fov.angleUp);
|
||||
float M[16] = {};
|
||||
M[0] = 2 / (tanAngleRight - tanAngleLeft);
|
||||
M[2] = (tanAngleRight + tanAngleLeft) / (tanAngleRight - tanAngleLeft);
|
||||
M[5] = 2 / (tanAngleUp - tanAngleDown);
|
||||
M[6] = (tanAngleUp + tanAngleDown) / (tanAngleUp - tanAngleDown);
|
||||
M[10] = -1;
|
||||
M[11] = -(nearZ + nearZ);
|
||||
M[14] = -1;
|
||||
memcpy(vrMatrix[VR_PROJECTION_MATRIX], M, sizeof(float) * 16);
|
||||
vrFov[0] = 2.0 / (tan(fov.angleRight) - tan(fov.angleLeft));
|
||||
vrFov[1] = 2.0 / (tan(fov.angleUp) - tan(fov.angleDown));
|
||||
|
||||
// Decide if the scene is 3D or not
|
||||
VR_SetConfigFloat(VR_CONFIG_CANVAS_ASPECT, 480.0f / 272.0f);
|
||||
|
@ -805,19 +790,16 @@ void UpdateVRParams(float* projMatrix) {
|
|||
}
|
||||
|
||||
void UpdateVRProjection(float* projMatrix, float* leftEye, float* rightEye) {
|
||||
float* hmdProjection = vrMatrix[VR_PROJECTION_MATRIX];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if ((hmdProjection[i] > 0) != (projMatrix[i] > 0)) {
|
||||
hmdProjection[i] *= -1.0f;
|
||||
}
|
||||
}
|
||||
float hmdProjection[16];
|
||||
memcpy(hmdProjection, projMatrix, 16 * sizeof(float));
|
||||
hmdProjection[0] = vrFov[0];
|
||||
hmdProjection[5] = vrFov[1];
|
||||
memcpy(leftEye, hmdProjection, 16 * sizeof(float));
|
||||
memcpy(rightEye, hmdProjection, 16 * sizeof(float));
|
||||
}
|
||||
|
||||
void UpdateVRView(float* leftEye, float* rightEye) {
|
||||
float* dst[] = {leftEye, rightEye};
|
||||
float* matrix[] = {vrMatrix[VR_VIEW_MATRIX_LEFT_EYE], vrMatrix[VR_VIEW_MATRIX_RIGHT_EYE]};
|
||||
for (int index = 0; index < 2; index++) {
|
||||
|
||||
// Validate the view matrix
|
||||
|
@ -831,7 +813,7 @@ void UpdateVRView(float* leftEye, float* rightEye) {
|
|||
|
||||
// Get view matrix from the headset
|
||||
Lin::Matrix4x4 hmdView = {};
|
||||
memcpy(hmdView.m, matrix[index], 16 * sizeof(float));
|
||||
memcpy(hmdView.m, vrViewMatrix[index], 16 * sizeof(float));
|
||||
|
||||
// Combine the matrices
|
||||
Lin::Matrix4x4 renderView = hmdView * gameView;
|
||||
|
@ -940,12 +922,12 @@ void UpdateVRViewMatrices() {
|
|||
M[11] += side.z;
|
||||
}
|
||||
|
||||
for (int matrix = VR_VIEW_MATRIX_LEFT_EYE; matrix <= VR_VIEW_MATRIX_RIGHT_EYE; matrix++) {
|
||||
for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
|
||||
|
||||
// Stereoscopy
|
||||
bool vrStereo = !PSP_CoreParameter().compat.vrCompat().ForceMono && g_Config.bEnableStereo;
|
||||
if (vrStereo) {
|
||||
bool mirrored = vrMirroring[VR_MIRRORING_AXIS_Z] ^ (matrix == VR_VIEW_MATRIX_RIGHT_EYE);
|
||||
bool mirrored = vrMirroring[VR_MIRRORING_AXIS_Z] ^ (eye == 1);
|
||||
float dx = fabs(vrView[1].pose.position.x - vrView[0].pose.position.x);
|
||||
float dy = fabs(vrView[1].pose.position.y - vrView[0].pose.position.y);
|
||||
float dz = fabs(vrView[1].pose.position.z - vrView[0].pose.position.z);
|
||||
|
@ -958,6 +940,6 @@ void UpdateVRViewMatrices() {
|
|||
M[11] += separation.z;
|
||||
}
|
||||
|
||||
memcpy(vrMatrix[matrix], M, sizeof(float) * 16);
|
||||
memcpy(vrViewMatrix[eye], M, sizeof(float) * 16);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ void SetVRCallbacks(void(*axis)(const AxisInput *axis, size_t count), bool(*key)
|
|||
// VR input integration
|
||||
void SetVRAppMode(VRAppMode mode);
|
||||
void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale);
|
||||
bool UpdateVRAxis(const AxisInput &axis);
|
||||
bool UpdateVRAxis(const AxisInput *axes, size_t count);
|
||||
bool UpdateVRKeys(const KeyInput &key);
|
||||
|
||||
// VR games compatibility
|
||||
|
|
|
@ -109,8 +109,8 @@ static bool ovrFramebuffer_CreateGLES(XrSession session, ovrFramebuffer* frameBu
|
|||
swapChainCreateInfo.arraySize = multiview ? 2 : 1;
|
||||
|
||||
#ifdef ANDROID
|
||||
XrSwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo;
|
||||
if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) {
|
||||
XrSwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo;
|
||||
memset(&swapChainFoveationCreateInfo, 0, sizeof(swapChainFoveationCreateInfo));
|
||||
swapChainFoveationCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB;
|
||||
swapChainCreateInfo.next = &swapChainFoveationCreateInfo;
|
||||
|
@ -203,8 +203,8 @@ static bool ovrFramebuffer_CreateVK(XrSession session, ovrFramebuffer* frameBuff
|
|||
swapChainCreateInfo.arraySize = multiview ? 2 : 1;
|
||||
|
||||
#ifdef ANDROID
|
||||
XrSwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo;
|
||||
if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) {
|
||||
XrSwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo;
|
||||
memset(&swapChainFoveationCreateInfo, 0, sizeof(swapChainFoveationCreateInfo));
|
||||
swapChainFoveationCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB;
|
||||
swapChainCreateInfo.next = &swapChainFoveationCreateInfo;
|
||||
|
|
|
@ -1697,7 +1697,6 @@ void XEmitter::MOVMSKPD(X64Reg dest, OpArg arg) {WriteSSEOp(0x66, 0x50, dest, ar
|
|||
|
||||
void XEmitter::LDDQU(X64Reg dest, OpArg arg) {WriteSSEOp(0xF2, sseLDDQU, dest, arg);} // For integer data only
|
||||
|
||||
// THESE TWO ARE UNTESTED.
|
||||
void XEmitter::UNPCKLPS(X64Reg dest, OpArg arg) {WriteSSEOp(0x00, 0x14, dest, arg);}
|
||||
void XEmitter::UNPCKHPS(X64Reg dest, OpArg arg) {WriteSSEOp(0x00, 0x15, dest, arg);}
|
||||
|
||||
|
@ -1892,6 +1891,9 @@ void XEmitter::PTEST(X64Reg dest, OpArg arg) {WriteSSE41Op(0x66, 0x3817, dest
|
|||
void XEmitter::PACKUSDW(X64Reg dest, OpArg arg) {WriteSSE41Op(0x66, 0x382b, dest, arg);}
|
||||
void XEmitter::DPPS(X64Reg dest, OpArg arg, u8 mask) {WriteSSE41Op(0x66, 0x3A40, dest, arg, 1); Write8(mask);}
|
||||
|
||||
void XEmitter::INSERTPS(X64Reg dest, OpArg arg, u8 dstsubreg, u8 srcsubreg, u8 zmask) { WriteSSE41Op(0x66, 0x3A21, dest, arg, 1); Write8((srcsubreg << 6) | (dstsubreg << 4) | zmask); }
|
||||
void XEmitter::EXTRACTPS(OpArg dest, X64Reg arg, u8 subreg) { WriteSSE41Op(0x66, 0x3A17, arg, dest, 1); Write8(subreg); }
|
||||
|
||||
void XEmitter::PMINSB(X64Reg dest, OpArg arg) {WriteSSE41Op(0x66, 0x3838, dest, arg);}
|
||||
void XEmitter::PMINSD(X64Reg dest, OpArg arg) {WriteSSE41Op(0x66, 0x3839, dest, arg);}
|
||||
void XEmitter::PMINUW(X64Reg dest, OpArg arg) {WriteSSE41Op(0x66, 0x383a, dest, arg);}
|
||||
|
@ -2084,7 +2086,7 @@ void XEmitter::VCVTTPD2DQ(int bits, X64Reg regOp1, OpArg arg) { WriteAVXOp(bits,
|
|||
void XEmitter::VCVTTSS2SI(int bits, X64Reg regOp1, OpArg arg) { WriteAVXOp(0, 0xF3, 0x2C, regOp1, arg, 0, bits == 64 ? 1 : 0); }
|
||||
void XEmitter::VCVTTSD2SI(int bits, X64Reg regOp1, OpArg arg) { WriteAVXOp(0, 0xF2, 0x2C, regOp1, arg, 0, bits == 64 ? 1 : 0); }
|
||||
void XEmitter::VEXTRACTPS(OpArg arg, X64Reg regOp1, u8 subreg) { WriteAVXOp(0, 0x66, 0x3A17, regOp1, arg, 1); Write8(subreg); }
|
||||
void XEmitter::VINSERTPS(X64Reg regOp1, X64Reg regOp2, OpArg arg, u8 subreg) { WriteAVXOp(0, 0x66, 0x3A21, regOp1, regOp2, arg, 1); Write8(subreg); }
|
||||
void XEmitter::VINSERTPS(X64Reg regOp1, X64Reg regOp2, OpArg arg, u8 dstsubreg, u8 srcsubreg, u8 zmask) { WriteAVXOp(0, 0x66, 0x3A21, regOp1, regOp2, arg, 1); Write8((srcsubreg << 6) | (dstsubreg << 4) | zmask); }
|
||||
void XEmitter::VLDDQU(int bits, X64Reg regOp1, OpArg arg) { WriteAVXOp(bits, 0xF2, sseLDDQU, regOp1, arg); }
|
||||
void XEmitter::VMOVAPS(int bits, X64Reg regOp1, OpArg arg) { WriteAVXOp(bits, 0x00, sseMOVAPfromRM, regOp1, arg); }
|
||||
void XEmitter::VMOVAPD(int bits, X64Reg regOp1, OpArg arg) { WriteAVXOp(bits, 0x66, sseMOVAPfromRM, regOp1, arg); }
|
||||
|
|
|
@ -684,12 +684,14 @@ public:
|
|||
|
||||
// SSE4: Further horizontal operations - dot products. These are weirdly flexible, the arg contains both a read mask and a write "mask".
|
||||
void DPPD(X64Reg dest, OpArg src, u8 arg);
|
||||
|
||||
// These are probably useful for VFPU emulation.
|
||||
void INSERTPS(X64Reg dest, OpArg src, u8 arg);
|
||||
void EXTRACTPS(OpArg dest, X64Reg src, u8 arg);
|
||||
#endif
|
||||
|
||||
// SSE4: Insert and extract for floats.
|
||||
// Note: insert from memory or an XMM.
|
||||
void INSERTPS(X64Reg dest, OpArg arg, u8 dstsubreg, u8 srcsubreg = 0, u8 zmask = 0);
|
||||
// Extract to memory or GPR.
|
||||
void EXTRACTPS(OpArg dest, X64Reg arg, u8 subreg);
|
||||
|
||||
// SSE3: Horizontal operations in SIMD registers. Very slow! shufps-based code beats it handily on Ivy.
|
||||
void HADDPS(X64Reg dest, OpArg src);
|
||||
|
||||
|
@ -1040,7 +1042,7 @@ public:
|
|||
// Can only extract from the low 128 bits.
|
||||
void VEXTRACTPS(OpArg arg, X64Reg regOp1, u8 subreg);
|
||||
// Can only insert into the low 128 bits, zeros upper bits. Inserts from XMM.
|
||||
void VINSERTPS(X64Reg regOp1, X64Reg regOp2, OpArg arg, u8 subreg);
|
||||
void VINSERTPS(X64Reg regOp1, X64Reg regOp2, OpArg arg, u8 dstsubreg, u8 srcsubreg = 0, u8 zmask = 0);
|
||||
void VLDDQU(int bits, X64Reg regOp1, OpArg arg);
|
||||
void VMOVAPS(int bits, X64Reg regOp1, OpArg arg);
|
||||
void VMOVAPD(int bits, X64Reg regOp1, OpArg arg);
|
||||
|
|
|
@ -133,6 +133,8 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
|
|||
CheckSetting(iniFile, gameID, "SOCOMClut8Replacement", &flags_.SOCOMClut8Replacement);
|
||||
CheckSetting(iniFile, gameID, "Fontltn12Hack", &flags_.Fontltn12Hack);
|
||||
CheckSetting(iniFile, gameID, "LoadCLUTFromCurrentFrameOnly", &flags_.LoadCLUTFromCurrentFrameOnly);
|
||||
CheckSetting(iniFile, gameID, "ForceUMDReadSpeed", &flags_.ForceUMDReadSpeed);
|
||||
CheckSetting(iniFile, gameID, "AllowDelayedReadbacks", &flags_.AllowDelayedReadbacks);
|
||||
}
|
||||
|
||||
void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) {
|
||||
|
|
|
@ -103,6 +103,8 @@ struct CompatFlags {
|
|||
bool SOCOMClut8Replacement;
|
||||
bool Fontltn12Hack;
|
||||
bool LoadCLUTFromCurrentFrameOnly;
|
||||
bool ForceUMDReadSpeed;
|
||||
bool AllowDelayedReadbacks;
|
||||
};
|
||||
|
||||
struct VRCompat {
|
||||
|
|
136
Core/Config.cpp
136
Core/Config.cpp
|
@ -81,6 +81,33 @@ static const char *logSectionName = "LogDebug";
|
|||
static const char *logSectionName = "Log";
|
||||
#endif
|
||||
|
||||
std::string GPUBackendToString(GPUBackend backend) {
|
||||
switch (backend) {
|
||||
case GPUBackend::OPENGL:
|
||||
return "OPENGL";
|
||||
case GPUBackend::DIRECT3D9:
|
||||
return "DIRECT3D9";
|
||||
case GPUBackend::DIRECT3D11:
|
||||
return "DIRECT3D11";
|
||||
case GPUBackend::VULKAN:
|
||||
return "VULKAN";
|
||||
}
|
||||
// Intentionally not a default so we get a warning.
|
||||
return "INVALID";
|
||||
}
|
||||
|
||||
GPUBackend GPUBackendFromString(std::string_view backend) {
|
||||
if (!equalsNoCase(backend, "OPENGL") || backend == "0")
|
||||
return GPUBackend::OPENGL;
|
||||
if (!equalsNoCase(backend, "DIRECT3D9") || backend == "1")
|
||||
return GPUBackend::DIRECT3D9;
|
||||
if (!equalsNoCase(backend, "DIRECT3D11") || backend == "2")
|
||||
return GPUBackend::DIRECT3D11;
|
||||
if (!equalsNoCase(backend, "VULKAN") || backend == "3")
|
||||
return GPUBackend::VULKAN;
|
||||
return GPUBackend::OPENGL;
|
||||
}
|
||||
|
||||
const char *DefaultLangRegion() {
|
||||
// Unfortunate default. There's no need to use bFirstRun, since this is only a default.
|
||||
static std::string defaultLangRegion = "en_US";
|
||||
|
@ -279,10 +306,11 @@ static bool DefaultSasThread() {
|
|||
static const ConfigSetting achievementSettings[] = {
|
||||
// Core settings
|
||||
ConfigSetting("AchievementsEnable", &g_Config.bAchievementsEnable, true, CfgFlag::DEFAULT),
|
||||
ConfigSetting("AchievementsChallengeMode", &g_Config.bAchievementsChallengeMode, false, CfgFlag::DEFAULT),
|
||||
ConfigSetting("AchievementsChallengeMode", &g_Config.bAchievementsChallengeMode, true, CfgFlag::DEFAULT),
|
||||
ConfigSetting("AchievementsEncoreMode", &g_Config.bAchievementsEncoreMode, false, CfgFlag::DEFAULT),
|
||||
ConfigSetting("AchievementsUnofficial", &g_Config.bAchievementsUnofficial, false, CfgFlag::DEFAULT),
|
||||
ConfigSetting("AchievementsLogBadMemReads", &g_Config.bAchievementsLogBadMemReads, false, CfgFlag::DEFAULT),
|
||||
ConfigSetting("bAchievementsSaveStateInChallengeMode", &g_Config.bAchievementsSaveStateInChallengeMode, false, CfgFlag::DEFAULT),
|
||||
|
||||
// Achievements login info. Note that password is NOT stored, only a login token.
|
||||
// And that login token is stored separately from the ini, see NativeSaveSecret, but it can also be loaded
|
||||
|
@ -513,7 +541,7 @@ bool Config::IsBackendEnabled(GPUBackend backend, bool validate) {
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename T, std::string (*FTo)(T), T (*FFrom)(const std::string &)>
|
||||
template <typename T, std::string (*FTo)(T), T (*FFrom)(std::string_view)>
|
||||
struct ConfigTranslator {
|
||||
static std::string To(int v) {
|
||||
return StringFromInt(v) + " (" + FTo(T(v)) + ")";
|
||||
|
@ -590,7 +618,6 @@ static const ConfigSetting graphicsSettings[] = {
|
|||
ConfigSetting("AnisotropyLevel", &g_Config.iAnisotropyLevel, 4, CfgFlag::PER_GAME),
|
||||
ConfigSetting("MultiSampleLevel", &g_Config.iMultiSampleLevel, 0, CfgFlag::PER_GAME), // Number of samples is 1 << iMultiSampleLevel
|
||||
|
||||
ConfigSetting("VertexDecCache", &g_Config.bVertexCache, false, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("TextureBackoffCache", &g_Config.bTextureBackoffCache, false, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("VertexDecJit", &g_Config.bVertexDecoderJit, &DefaultCodeGen, CfgFlag::DONT_SAVE | CfgFlag::REPORT),
|
||||
|
||||
|
@ -762,6 +789,7 @@ static const ConfigSetting controlSettings[] = {
|
|||
ConfigSetting("TouchButtonOpacity", &g_Config.iTouchButtonOpacity, 65, CfgFlag::PER_GAME),
|
||||
ConfigSetting("TouchButtonHideSeconds", &g_Config.iTouchButtonHideSeconds, 20, CfgFlag::PER_GAME),
|
||||
ConfigSetting("AutoCenterTouchAnalog", &g_Config.bAutoCenterTouchAnalog, false, CfgFlag::PER_GAME),
|
||||
ConfigSetting("StickyTouchDPad", &g_Config.bStickyTouchDPad, false, CfgFlag::PER_GAME),
|
||||
|
||||
// Snap touch control position
|
||||
ConfigSetting("TouchSnapToGrid", &g_Config.bTouchSnapToGrid, false, CfgFlag::PER_GAME),
|
||||
|
@ -907,7 +935,6 @@ static const ConfigSetting vrSettings[] = {
|
|||
ConfigSetting("VRCameraPitch", &g_Config.iCameraPitch, 0, CfgFlag::PER_GAME),
|
||||
ConfigSetting("VRCanvasDistance", &g_Config.fCanvasDistance, 12.0f, CfgFlag::DEFAULT),
|
||||
ConfigSetting("VRCanvas3DDistance", &g_Config.fCanvas3DDistance, 3.0f, CfgFlag::DEFAULT),
|
||||
ConfigSetting("VRFieldOfView", &g_Config.fFieldOfViewPercentage, 100.0f, CfgFlag::PER_GAME),
|
||||
ConfigSetting("VRHeadUpDisplayScale", &g_Config.fHeadUpDisplayScale, 0.3f, CfgFlag::PER_GAME),
|
||||
ConfigSetting("VRMotionLength", &g_Config.fMotionLength, 0.5f, CfgFlag::DEFAULT),
|
||||
ConfigSetting("VRHeadRotationScale", &g_Config.fHeadRotationScale, 5.0f, CfgFlag::PER_GAME),
|
||||
|
@ -1068,6 +1095,8 @@ void Config::UpdateAfterSettingAutoFrameSkip() {
|
|||
}
|
||||
|
||||
void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
|
||||
double startTime = time_now_d();
|
||||
|
||||
if (!bUpdatedInstanceCounter) {
|
||||
InitInstanceCounter();
|
||||
bUpdatedInstanceCounter = true;
|
||||
|
@ -1128,6 +1157,10 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
|
|||
}
|
||||
}
|
||||
|
||||
// Time tracking
|
||||
Section *playTime = iniFile.GetOrCreateSection("PlayTime");
|
||||
playTimeTracker_.Load(playTime);
|
||||
|
||||
auto pinnedPaths = iniFile.GetOrCreateSection("PinnedPaths")->ToMap();
|
||||
vPinnedPaths.clear();
|
||||
for (auto it = pinnedPaths.begin(), end = pinnedPaths.end(); it != end; ++it) {
|
||||
|
@ -1209,10 +1242,11 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
|
|||
|
||||
PostLoadCleanup(false);
|
||||
|
||||
INFO_LOG(LOADER, "Config loaded: '%s'", iniFilename_.c_str());
|
||||
INFO_LOG(LOADER, "Config loaded: '%s' (%0.1f ms)", iniFilename_.c_str(), (time_now_d() - startTime) * 1000.0);
|
||||
}
|
||||
|
||||
bool Config::Save(const char *saveReason) {
|
||||
double startTime = time_now_d();
|
||||
if (!IsFirstInstance()) {
|
||||
// TODO: Should we allow saving config if started from a different directory?
|
||||
// How do we tell?
|
||||
|
@ -1287,11 +1321,15 @@ bool Config::Save(const char *saveReason) {
|
|||
if (LogManager::GetInstance())
|
||||
LogManager::GetInstance()->SaveConfig(log);
|
||||
|
||||
// Time tracking
|
||||
Section *playTime = iniFile.GetOrCreateSection("PlayTime");
|
||||
playTimeTracker_.Save(playTime);
|
||||
|
||||
if (!iniFile.Save(iniFilename_)) {
|
||||
ERROR_LOG(LOADER, "Error saving config (%s)- can't write ini '%s'", saveReason, iniFilename_.c_str());
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(LOADER, "Config saved (%s): '%s'", saveReason, iniFilename_.c_str());
|
||||
INFO_LOG(LOADER, "Config saved (%s): '%s' (%0.1f ms)", saveReason, iniFilename_.c_str(), (time_now_d() - startTime) * 1000.0);
|
||||
|
||||
if (!bGameSpecific) //otherwise we already did this in saveGameConfig()
|
||||
{
|
||||
|
@ -1797,3 +1835,89 @@ int Config::GetPSPLanguage() {
|
|||
return g_Config.iLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayTimeTracker::Start(std::string gameId) {
|
||||
INFO_LOG(SYSTEM, "GameTimeTracker::Start(%s)", gameId.c_str());
|
||||
if (gameId.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = tracker_.find(std::string(gameId));
|
||||
if (iter != tracker_.end()) {
|
||||
if (iter->second.startTime == 0.0) {
|
||||
iter->second.lastTimePlayed = time_now_unix_utc();
|
||||
iter->second.startTime = time_now_d();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PlayTime playTime;
|
||||
playTime.lastTimePlayed = time_now_unix_utc();
|
||||
playTime.totalTimePlayed = 0.0;
|
||||
playTime.startTime = time_now_d();
|
||||
tracker_[gameId] = playTime;
|
||||
}
|
||||
|
||||
void PlayTimeTracker::Stop(std::string gameId) {
|
||||
INFO_LOG(SYSTEM, "GameTimeTracker::Stop(%s)", gameId.c_str());
|
||||
_dbg_assert_(!gameId.empty());
|
||||
|
||||
auto iter = tracker_.find(std::string(gameId));
|
||||
if (iter != tracker_.end()) {
|
||||
if (iter->second.startTime != 0.0) {
|
||||
iter->second.totalTimePlayed += time_now_d() - iter->second.startTime;
|
||||
iter->second.startTime = 0.0;
|
||||
}
|
||||
iter->second.lastTimePlayed = time_now_unix_utc();
|
||||
return;
|
||||
}
|
||||
|
||||
// Shouldn't happen, ignore this case.
|
||||
WARN_LOG(SYSTEM, "GameTimeTracker::Stop called without corresponding GameTimeTracker::Start");
|
||||
}
|
||||
|
||||
void PlayTimeTracker::Load(const Section *section) {
|
||||
tracker_.clear();
|
||||
|
||||
std::vector<std::string> keys;
|
||||
section->GetKeys(keys);
|
||||
|
||||
for (auto key : keys) {
|
||||
std::string value;
|
||||
if (!section->Get(key.c_str(), &value, nullptr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the string.
|
||||
PlayTime gameTime{};
|
||||
if (2 == sscanf(value.c_str(), "%d,%llu", &gameTime.totalTimePlayed, &gameTime.lastTimePlayed)) {
|
||||
tracker_[key] = gameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayTimeTracker::Save(Section *section) {
|
||||
for (auto iter : tracker_) {
|
||||
std::string formatted = StringFromFormat("%d,%llu", iter.second.totalTimePlayed, iter.second.lastTimePlayed);
|
||||
section->Set(iter.first.c_str(), formatted);
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayTimeTracker::GetPlayedTimeString(const std::string &gameId, std::string *str) const {
|
||||
auto ga = GetI18NCategory(I18NCat::GAME);
|
||||
|
||||
auto iter = tracker_.find(gameId);
|
||||
if (iter == tracker_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int totalSeconds = iter->second.totalTimePlayed;
|
||||
int seconds = totalSeconds % 60;
|
||||
totalSeconds /= 60;
|
||||
int minutes = totalSeconds % 60;
|
||||
totalSeconds /= 60;
|
||||
int hours = totalSeconds;
|
||||
|
||||
*str = ApplySafeSubstitutions(ga->T("Time Played: %1h %2m %3s"), hours, minutes, seconds);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,29 @@ namespace http {
|
|||
struct UrlEncoder;
|
||||
struct ConfigPrivate;
|
||||
|
||||
class Section;
|
||||
|
||||
class PlayTimeTracker {
|
||||
public:
|
||||
struct PlayTime {
|
||||
int totalTimePlayed;
|
||||
double startTime; // time_now_d() time
|
||||
uint64_t lastTimePlayed; // UTC Unix time for portability.
|
||||
};
|
||||
|
||||
// It's OK to call these redundantly.
|
||||
void Start(std::string gameId);
|
||||
void Stop(std::string gameId);
|
||||
|
||||
void Load(const Section *section);
|
||||
void Save(Section *section);
|
||||
|
||||
bool GetPlayedTimeString(const std::string &gameId, std::string *str) const;
|
||||
|
||||
private:
|
||||
std::map<std::string, PlayTime> tracker_;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
public:
|
||||
Config();
|
||||
|
@ -176,7 +199,6 @@ public:
|
|||
float fUITint;
|
||||
float fUISaturation;
|
||||
|
||||
bool bVertexCache;
|
||||
bool bTextureBackoffCache;
|
||||
bool bVertexDecoderJit;
|
||||
bool bFullScreen;
|
||||
|
@ -443,7 +465,6 @@ public:
|
|||
float fCameraSide;
|
||||
float fCanvasDistance;
|
||||
float fCanvas3DDistance;
|
||||
float fFieldOfViewPercentage;
|
||||
float fHeadUpDisplayScale;
|
||||
float fMotionLength;
|
||||
float fHeadRotationScale;
|
||||
|
@ -492,6 +513,7 @@ public:
|
|||
bool bAchievementsUnofficial;
|
||||
bool bAchievementsSoundEffects;
|
||||
bool bAchievementsLogBadMemReads;
|
||||
bool bAchievementsSaveStateInChallengeMode;
|
||||
|
||||
// Positioning of the various notifications
|
||||
int iAchievementsLeaderboardTrackerPos;
|
||||
|
@ -579,6 +601,8 @@ public:
|
|||
// Applies the Auto setting if set. Returns an enum value from PSP_SYSTEMPARAM_LANGUAGE_*.
|
||||
int GetPSPLanguage();
|
||||
|
||||
PlayTimeTracker &TimeTracker() { return playTimeTracker_; }
|
||||
|
||||
protected:
|
||||
void LoadStandardControllerIni();
|
||||
void LoadLangValuesMapping();
|
||||
|
@ -593,6 +617,7 @@ private:
|
|||
std::string gameIdTitle_;
|
||||
std::vector<std::string> recentIsos;
|
||||
std::map<std::string, std::pair<std::string, int>> langValuesMapping_;
|
||||
PlayTimeTracker playTimeTracker_;
|
||||
Path iniFilename_;
|
||||
Path controllerIniFilename_;
|
||||
Path searchPath_;
|
||||
|
|
|
@ -90,32 +90,8 @@ enum class RestoreSettingsBits : int {
|
|||
};
|
||||
ENUM_CLASS_BITOPS(RestoreSettingsBits);
|
||||
|
||||
inline std::string GPUBackendToString(GPUBackend backend) {
|
||||
switch (backend) {
|
||||
case GPUBackend::OPENGL:
|
||||
return "OPENGL";
|
||||
case GPUBackend::DIRECT3D9:
|
||||
return "DIRECT3D9";
|
||||
case GPUBackend::DIRECT3D11:
|
||||
return "DIRECT3D11";
|
||||
case GPUBackend::VULKAN:
|
||||
return "VULKAN";
|
||||
}
|
||||
// Intentionally not a default so we get a warning.
|
||||
return "INVALID";
|
||||
}
|
||||
|
||||
inline GPUBackend GPUBackendFromString(const std::string &backend) {
|
||||
if (!strcasecmp(backend.c_str(), "OPENGL") || backend == "0")
|
||||
return GPUBackend::OPENGL;
|
||||
if (!strcasecmp(backend.c_str(), "DIRECT3D9") || backend == "1")
|
||||
return GPUBackend::DIRECT3D9;
|
||||
if (!strcasecmp(backend.c_str(), "DIRECT3D11") || backend == "2")
|
||||
return GPUBackend::DIRECT3D11;
|
||||
if (!strcasecmp(backend.c_str(), "VULKAN") || backend == "3")
|
||||
return GPUBackend::VULKAN;
|
||||
return GPUBackend::OPENGL;
|
||||
}
|
||||
std::string GPUBackendToString(GPUBackend backend);
|
||||
GPUBackend GPUBackendFromString(std::string_view backend);
|
||||
|
||||
enum AudioBackendType {
|
||||
AUDIO_BACKEND_AUTO,
|
||||
|
@ -128,6 +104,7 @@ enum IOTimingMethods {
|
|||
IOTIMING_FAST = 0,
|
||||
IOTIMING_HOST = 1,
|
||||
IOTIMING_REALISTIC = 2,
|
||||
IOTIMING_UMDSLOWREALISTIC = 3,
|
||||
};
|
||||
|
||||
enum class AutoLoadSaveState {
|
||||
|
@ -189,4 +166,5 @@ enum class DebugOverlay : int {
|
|||
AUDIO,
|
||||
GPU_PROFILE,
|
||||
GPU_ALLOCATOR,
|
||||
FRAMEBUFFER_LIST,
|
||||
};
|
||||
|
|
|
@ -476,27 +476,31 @@ void ControlMapper::ToggleSwapAxes() {
|
|||
UpdateAnalogOutput(1);
|
||||
}
|
||||
|
||||
void ControlMapper::Axis(const AxisInput &axis) {
|
||||
void ControlMapper::Axis(const AxisInput *axes, size_t count) {
|
||||
double now = time_now_d();
|
||||
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
if (axis.deviceId < DEVICE_ID_COUNT) {
|
||||
deviceTimestamps_[(int)axis.deviceId] = now;
|
||||
}
|
||||
if (axis.value >= 0.0f) {
|
||||
InputMapping mapping(axis.deviceId, axis.axisId, 1);
|
||||
InputMapping opposite(axis.deviceId, axis.axisId, -1);
|
||||
curInput_[mapping] = { axis.value, now };
|
||||
curInput_[opposite] = { 0.0f, now };
|
||||
UpdatePSPState(mapping, now);
|
||||
UpdatePSPState(opposite, now);
|
||||
} else if (axis.value < 0.0f) {
|
||||
InputMapping mapping(axis.deviceId, axis.axisId, -1);
|
||||
InputMapping opposite(axis.deviceId, axis.axisId, 1);
|
||||
curInput_[mapping] = { -axis.value, now };
|
||||
curInput_[opposite] = { 0.0f, now };
|
||||
UpdatePSPState(mapping, now);
|
||||
UpdatePSPState(opposite, now);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const AxisInput &axis = axes[i];
|
||||
size_t deviceIndex = (size_t)axis.deviceId; // this wraps -1 up high, so will get rejected on the next line.
|
||||
if (deviceIndex < (size_t)DEVICE_ID_COUNT) {
|
||||
deviceTimestamps_[deviceIndex] = now;
|
||||
}
|
||||
if (axis.value >= 0.0f) {
|
||||
InputMapping mapping(axis.deviceId, axis.axisId, 1);
|
||||
InputMapping opposite(axis.deviceId, axis.axisId, -1);
|
||||
curInput_[mapping] = { axis.value, now };
|
||||
curInput_[opposite] = { 0.0f, now };
|
||||
UpdatePSPState(mapping, now);
|
||||
UpdatePSPState(opposite, now);
|
||||
} else if (axis.value < 0.0f) {
|
||||
InputMapping mapping(axis.deviceId, axis.axisId, -1);
|
||||
InputMapping opposite(axis.deviceId, axis.axisId, 1);
|
||||
curInput_[mapping] = { -axis.value, now };
|
||||
curInput_[opposite] = { 0.0f, now };
|
||||
UpdatePSPState(mapping, now);
|
||||
UpdatePSPState(opposite, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ public:
|
|||
// Inputs to the table-based mapping
|
||||
// These functions are free-threaded.
|
||||
bool Key(const KeyInput &key, bool *pauseTrigger);
|
||||
void Axis(const AxisInput &axis);
|
||||
void Axis(const AxisInput *axes, size_t count);
|
||||
|
||||
// Required callbacks.
|
||||
// TODO: These are so many now that a virtual interface might be more appropriate..
|
||||
|
@ -62,7 +62,7 @@ private:
|
|||
float virtKeys_[VIRTKEY_COUNT]{};
|
||||
bool virtKeyOn_[VIRTKEY_COUNT]{}; // Track boolean output separaately since thresholds may differ.
|
||||
|
||||
double deviceTimestamps_[42]{};
|
||||
double deviceTimestamps_[(size_t)DEVICE_ID_COUNT]{};
|
||||
|
||||
int lastNonDeadzoneDeviceID_[2]{};
|
||||
|
||||
|
@ -76,6 +76,8 @@ private:
|
|||
bool swapAxes_ = false;
|
||||
|
||||
// Protects basically all the state.
|
||||
// TODO: Maybe we should piggyback on the screenmanager mutex - it's always locked
|
||||
// when events come in here.
|
||||
std::mutex mutex_;
|
||||
|
||||
std::map<InputMapping, InputSample> curInput_;
|
||||
|
|
|
@ -207,30 +207,12 @@ void UpdateRunLoop(GraphicsContext *ctx) {
|
|||
|
||||
// Note: not used on Android.
|
||||
void Core_RunLoop(GraphicsContext *ctx) {
|
||||
float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
|
||||
|
||||
if (windowHidden && g_Config.bPauseWhenMinimized) {
|
||||
sleep_ms(16);
|
||||
return;
|
||||
}
|
||||
|
||||
bool menuThrottle = (GetUIState() != UISTATE_INGAME || !PSP_IsInited()) && GetUIState() != UISTATE_EXIT;
|
||||
|
||||
double startTime;
|
||||
if (menuThrottle) {
|
||||
startTime = time_now_d();
|
||||
}
|
||||
|
||||
NativeFrame(ctx);
|
||||
|
||||
if (menuThrottle) {
|
||||
// Simple throttling to not burn the GPU in the menu.
|
||||
// TODO: This should move into NativeFrame. Also, it's only necessary in MAILBOX or IMMEDIATE presentation modes.
|
||||
double diffTime = time_now_d() - startTime;
|
||||
int sleepTime = (int)(1000.0 / refreshRate) - (int)(diffTime * 1000.0);
|
||||
if (sleepTime > 0)
|
||||
sleep_ms(sleepTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Core_DoSingleStep() {
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
|
@ -165,7 +165,7 @@
|
|||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
|
@ -193,7 +193,7 @@
|
|||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_DEBUG;_LIB;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
|
@ -221,7 +221,7 @@
|
|||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\arm\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_DEBUG;_LIB;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
|
@ -253,7 +253,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
|
@ -286,7 +286,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
|
@ -321,7 +321,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
|
@ -356,7 +356,7 @@
|
|||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\arm\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
|
||||
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
|
@ -1076,6 +1076,7 @@
|
|||
<ClCompile Include="Util\AudioFormat.cpp" />
|
||||
<ClCompile Include="Util\BlockAllocator.cpp" />
|
||||
<ClCompile Include="Util\DisArm64.cpp" />
|
||||
<ClCompile Include="Util\GameDB.cpp" />
|
||||
<ClCompile Include="Util\GameManager.cpp" />
|
||||
<ClCompile Include="Util\PortManager.cpp" />
|
||||
<ClCompile Include="Util\PPGeDraw.cpp" />
|
||||
|
@ -1444,6 +1445,7 @@
|
|||
<ClInclude Include="Util\AudioFormat.h" />
|
||||
<ClInclude Include="Util\BlockAllocator.h" />
|
||||
<ClInclude Include="Util\DisArm64.h" />
|
||||
<ClInclude Include="Util\GameDB.h" />
|
||||
<ClInclude Include="Util\GameManager.h" />
|
||||
<ClInclude Include="Util\PortManager.h" />
|
||||
<ClInclude Include="Util\PPGeDraw.h" />
|
||||
|
@ -1466,6 +1468,9 @@
|
|||
<ProjectReference Include="..\ext\libarmips.vcxproj">
|
||||
<Project>{129e5e2b-39c1-4d84-96fe-dfd22dbb4a25}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ext\libchdr.vcxproj">
|
||||
<Project>{956f1f48-b612-46d8-89ee-96996dcd9383}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ext\miniupnpc.vcxproj">
|
||||
<Project>{d8a71225-178b-424e-96c1-cc3be2c1b047}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -1297,6 +1297,9 @@
|
|||
<ClCompile Include="MIPS\ARM64\Arm64IRCompFPU.cpp">
|
||||
<Filter>MIPS\ARM64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util\GameDB.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ELF\ElfReader.h">
|
||||
|
@ -2070,6 +2073,9 @@
|
|||
<ClInclude Include="MIPS\ARM64\Arm64IRRegCache.h">
|
||||
<Filter>MIPS\ARM64</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Util\GameDB.h">
|
||||
<Filter>Util</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE.TXT" />
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/Serialize/Serializer.h"
|
||||
|
@ -49,6 +51,8 @@ private:
|
|||
uint64_t ticks = 0;
|
||||
uint32_t pc = 0;
|
||||
bool allocated = false;
|
||||
// Intentionally not save stated.
|
||||
bool bulkStorage = false;
|
||||
char tag[128]{};
|
||||
Slab *prev = nullptr;
|
||||
Slab *next = nullptr;
|
||||
|
@ -72,18 +76,22 @@ private:
|
|||
Slab *first_ = nullptr;
|
||||
Slab *lastFind_ = nullptr;
|
||||
std::vector<Slab *> heads_;
|
||||
Slab *bulkStorage_ = nullptr;
|
||||
};
|
||||
|
||||
struct PendingNotifyMem {
|
||||
MemBlockFlags flags;
|
||||
uint32_t start;
|
||||
uint32_t size;
|
||||
uint32_t copySrc;
|
||||
uint64_t ticks;
|
||||
uint32_t pc;
|
||||
char tag[128];
|
||||
};
|
||||
|
||||
static constexpr size_t MAX_PENDING_NOTIFIES = 512;
|
||||
// 160 KB.
|
||||
static constexpr size_t MAX_PENDING_NOTIFIES = 1024;
|
||||
static constexpr size_t MAX_PENDING_NOTIFIES_THREAD = 1000;
|
||||
static MemSlabMap allocMap;
|
||||
static MemSlabMap suballocMap;
|
||||
static MemSlabMap writeMap;
|
||||
|
@ -93,9 +101,17 @@ static std::atomic<uint32_t> pendingNotifyMinAddr1;
|
|||
static std::atomic<uint32_t> pendingNotifyMaxAddr1;
|
||||
static std::atomic<uint32_t> pendingNotifyMinAddr2;
|
||||
static std::atomic<uint32_t> pendingNotifyMaxAddr2;
|
||||
static std::mutex pendingMutex;
|
||||
// To prevent deadlocks, acquire Read before Write if you're going to acquire both.
|
||||
static std::mutex pendingWriteMutex;
|
||||
static std::mutex pendingReadMutex;
|
||||
static int detailedOverride;
|
||||
|
||||
static std::thread flushThread;
|
||||
static std::atomic<bool> flushThreadRunning;
|
||||
static std::atomic<bool> flushThreadPending;
|
||||
static std::mutex flushLock;
|
||||
static std::condition_variable flushCond;
|
||||
|
||||
MemSlabMap::MemSlabMap() {
|
||||
Reset();
|
||||
}
|
||||
|
@ -184,6 +200,7 @@ void MemSlabMap::DoState(PointerWrap &p) {
|
|||
// Since heads_ is a static size, let's avoid clearing it.
|
||||
// This helps in case a debugger call happens concurrently.
|
||||
Slab *old = first_;
|
||||
Slab *oldBulk = bulkStorage_;
|
||||
Do(p, count);
|
||||
|
||||
first_ = new Slab();
|
||||
|
@ -193,9 +210,12 @@ void MemSlabMap::DoState(PointerWrap &p) {
|
|||
|
||||
FillHeads(first_);
|
||||
|
||||
bulkStorage_ = new Slab[count];
|
||||
|
||||
Slab *slab = first_;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
slab->next = new Slab();
|
||||
slab->next = &bulkStorage_[i];
|
||||
slab->next->bulkStorage = true;
|
||||
slab->next->DoState(p);
|
||||
|
||||
slab->next->prev = slab;
|
||||
|
@ -207,9 +227,11 @@ void MemSlabMap::DoState(PointerWrap &p) {
|
|||
// Now that it's entirely disconnected, delete the old slabs.
|
||||
while (old != nullptr) {
|
||||
Slab *next = old->next;
|
||||
delete old;
|
||||
if (!old->bulkStorage)
|
||||
delete old;
|
||||
old = next;
|
||||
}
|
||||
delete [] oldBulk;
|
||||
} else {
|
||||
for (Slab *slab = first_; slab != nullptr; slab = slab->next)
|
||||
++count;
|
||||
|
@ -253,9 +275,12 @@ void MemSlabMap::Clear() {
|
|||
Slab *s = first_;
|
||||
while (s != nullptr) {
|
||||
Slab *next = s->next;
|
||||
delete s;
|
||||
if (!s->bulkStorage)
|
||||
delete s;
|
||||
s = next;
|
||||
}
|
||||
delete bulkStorage_;
|
||||
bulkStorage_ = nullptr;
|
||||
first_ = nullptr;
|
||||
lastFind_ = nullptr;
|
||||
heads_.clear();
|
||||
|
@ -348,7 +373,8 @@ void MemSlabMap::Merge(Slab *a, Slab *b) {
|
|||
}
|
||||
if (lastFind_ == b)
|
||||
lastFind_ = a;
|
||||
delete b;
|
||||
if (!b->bulkStorage)
|
||||
delete b;
|
||||
}
|
||||
|
||||
void MemSlabMap::FillHeads(Slab *slab) {
|
||||
|
@ -369,9 +395,32 @@ void MemSlabMap::FillHeads(Slab *slab) {
|
|||
}
|
||||
}
|
||||
|
||||
size_t FormatMemWriteTagAtNoFlush(char *buf, size_t sz, const char *prefix, uint32_t start, uint32_t size);
|
||||
|
||||
void FlushPendingMemInfo() {
|
||||
std::lock_guard<std::mutex> guard(pendingMutex);
|
||||
for (const auto &info : pendingNotifies) {
|
||||
// This lock prevents us from another thread reading while we're busy flushing.
|
||||
std::lock_guard<std::mutex> guard(pendingReadMutex);
|
||||
std::vector<PendingNotifyMem> thisBatch;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(pendingWriteMutex);
|
||||
thisBatch = std::move(pendingNotifies);
|
||||
pendingNotifies.clear();
|
||||
pendingNotifies.reserve(MAX_PENDING_NOTIFIES);
|
||||
|
||||
pendingNotifyMinAddr1 = 0xFFFFFFFF;
|
||||
pendingNotifyMaxAddr1 = 0;
|
||||
pendingNotifyMinAddr2 = 0xFFFFFFFF;
|
||||
pendingNotifyMaxAddr2 = 0;
|
||||
}
|
||||
|
||||
for (const auto &info : thisBatch) {
|
||||
if (info.copySrc != 0) {
|
||||
char tagData[128];
|
||||
size_t tagSize = FormatMemWriteTagAtNoFlush(tagData, sizeof(tagData), info.tag, info.copySrc, info.size);
|
||||
writeMap.Mark(info.start, info.size, info.ticks, info.pc, true, tagData);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.flags & MemBlockFlags::ALLOC) {
|
||||
allocMap.Mark(info.start, info.size, info.ticks, info.pc, true, info.tag);
|
||||
} else if (info.flags & MemBlockFlags::FREE) {
|
||||
|
@ -392,11 +441,6 @@ void FlushPendingMemInfo() {
|
|||
writeMap.Mark(info.start, info.size, info.ticks, info.pc, true, info.tag);
|
||||
}
|
||||
}
|
||||
pendingNotifies.clear();
|
||||
pendingNotifyMinAddr1 = 0xFFFFFFFF;
|
||||
pendingNotifyMaxAddr1 = 0;
|
||||
pendingNotifyMinAddr2 = 0xFFFFFFFF;
|
||||
pendingNotifyMaxAddr2 = 0;
|
||||
}
|
||||
|
||||
static inline uint32_t NormalizeAddress(uint32_t addr) {
|
||||
|
@ -411,6 +455,9 @@ static inline bool MergeRecentMemInfo(const PendingNotifyMem &info, size_t copyL
|
|||
|
||||
for (size_t i = 1; i <= 4; ++i) {
|
||||
auto &prev = pendingNotifies[pendingNotifies.size() - i];
|
||||
if (prev.copySrc != 0)
|
||||
return false;
|
||||
|
||||
if (prev.flags != info.flags)
|
||||
continue;
|
||||
|
||||
|
@ -440,7 +487,7 @@ void NotifyMemInfoPC(MemBlockFlags flags, uint32_t start, uint32_t size, uint32_
|
|||
|
||||
bool needFlush = false;
|
||||
// When the setting is off, we skip smaller info to keep things fast.
|
||||
if (MemBlockInfoDetailed(size)) {
|
||||
if (MemBlockInfoDetailed(size) && flags != MemBlockFlags::READ) {
|
||||
PendingNotifyMem info{ flags, start, size };
|
||||
info.ticks = CoreTiming::GetTicks();
|
||||
info.pc = pc;
|
||||
|
@ -452,7 +499,7 @@ void NotifyMemInfoPC(MemBlockFlags flags, uint32_t start, uint32_t size, uint32_
|
|||
memcpy(info.tag, tagStr, copyLength);
|
||||
info.tag[copyLength] = 0;
|
||||
|
||||
std::lock_guard<std::mutex> guard(pendingMutex);
|
||||
std::lock_guard<std::mutex> guard(pendingWriteMutex);
|
||||
// Sometimes we get duplicates, quickly check.
|
||||
if (!MergeRecentMemInfo(info, copyLength)) {
|
||||
if (start < 0x08000000) {
|
||||
|
@ -464,11 +511,15 @@ void NotifyMemInfoPC(MemBlockFlags flags, uint32_t start, uint32_t size, uint32_
|
|||
}
|
||||
pendingNotifies.push_back(info);
|
||||
}
|
||||
needFlush = pendingNotifies.size() > MAX_PENDING_NOTIFIES;
|
||||
needFlush = pendingNotifies.size() > MAX_PENDING_NOTIFIES_THREAD;
|
||||
}
|
||||
|
||||
if (needFlush) {
|
||||
FlushPendingMemInfo();
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(flushLock);
|
||||
flushThreadPending = true;
|
||||
}
|
||||
flushCond.notify_one();
|
||||
}
|
||||
|
||||
if (!(flags & MemBlockFlags::SKIP_MEMCHECK)) {
|
||||
|
@ -484,6 +535,50 @@ void NotifyMemInfo(MemBlockFlags flags, uint32_t start, uint32_t size, const cha
|
|||
NotifyMemInfoPC(flags, start, size, currentMIPS->pc, str, strLength);
|
||||
}
|
||||
|
||||
void NotifyMemInfoCopy(uint32_t destPtr, uint32_t srcPtr, uint32_t size, const char *prefix) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
bool needsFlush = false;
|
||||
if (CBreakPoints::HasMemChecks()) {
|
||||
// This will cause a flush, but it's needed to trigger memchecks with proper data.
|
||||
char tagData[128];
|
||||
size_t tagSize = FormatMemWriteTagAt(tagData, sizeof(tagData), prefix, srcPtr, size);
|
||||
NotifyMemInfo(MemBlockFlags::READ, srcPtr, size, tagData, tagSize);
|
||||
NotifyMemInfo(MemBlockFlags::WRITE, destPtr, size, tagData, tagSize);
|
||||
} else if (MemBlockInfoDetailed(size)) {
|
||||
srcPtr = NormalizeAddress(srcPtr);
|
||||
destPtr = NormalizeAddress(destPtr);
|
||||
|
||||
PendingNotifyMem info{ MemBlockFlags::WRITE, destPtr, size };
|
||||
info.copySrc = srcPtr;
|
||||
info.ticks = CoreTiming::GetTicks();
|
||||
info.pc = currentMIPS->pc;
|
||||
|
||||
// Store the prefix for now. The correct tag will be calculated on flush.
|
||||
truncate_cpy(info.tag, prefix);
|
||||
|
||||
std::lock_guard<std::mutex> guard(pendingWriteMutex);
|
||||
if (destPtr < 0x08000000) {
|
||||
pendingNotifyMinAddr1 = std::min(pendingNotifyMinAddr1.load(), destPtr);
|
||||
pendingNotifyMaxAddr1 = std::max(pendingNotifyMaxAddr1.load(), destPtr + size);
|
||||
} else {
|
||||
pendingNotifyMinAddr2 = std::min(pendingNotifyMinAddr2.load(), destPtr);
|
||||
pendingNotifyMaxAddr2 = std::max(pendingNotifyMaxAddr2.load(), destPtr + size);
|
||||
}
|
||||
pendingNotifies.push_back(info);
|
||||
needsFlush = pendingNotifies.size() > MAX_PENDING_NOTIFIES_THREAD;
|
||||
}
|
||||
|
||||
if (needsFlush) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(flushLock);
|
||||
flushThreadPending = true;
|
||||
}
|
||||
flushCond.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<MemBlockInfo> FindMemInfo(uint32_t start, uint32_t size) {
|
||||
start = NormalizeAddress(start);
|
||||
|
||||
|
@ -520,13 +615,15 @@ std::vector<MemBlockInfo> FindMemInfoByFlag(MemBlockFlags flags, uint32_t start,
|
|||
return results;
|
||||
}
|
||||
|
||||
static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint32_t size) {
|
||||
static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint32_t size, bool flush = true) {
|
||||
start = NormalizeAddress(start);
|
||||
|
||||
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
|
||||
FlushPendingMemInfo();
|
||||
if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start)
|
||||
FlushPendingMemInfo();
|
||||
if (flush) {
|
||||
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
|
||||
FlushPendingMemInfo();
|
||||
if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start)
|
||||
FlushPendingMemInfo();
|
||||
}
|
||||
|
||||
if (flags & MemBlockFlags::ALLOC) {
|
||||
const char *tag = allocMap.FastFindWriteTag(MemBlockFlags::ALLOC, start, size);
|
||||
|
@ -564,22 +661,63 @@ size_t FormatMemWriteTagAt(char *buf, size_t sz, const char *prefix, uint32_t st
|
|||
return snprintf(buf, sz, "%s%08x_size_%08x", prefix, start, size);
|
||||
}
|
||||
|
||||
size_t FormatMemWriteTagAtNoFlush(char *buf, size_t sz, const char *prefix, uint32_t start, uint32_t size) {
|
||||
const char *tag = FindWriteTagByFlag(MemBlockFlags::WRITE, start, size, false);
|
||||
if (tag && strcmp(tag, "MemInit") != 0) {
|
||||
return snprintf(buf, sz, "%s%s", prefix, tag);
|
||||
}
|
||||
// Fall back to alloc and texture, especially for VRAM. We prefer write above.
|
||||
tag = FindWriteTagByFlag(MemBlockFlags::ALLOC | MemBlockFlags::TEXTURE, start, size, false);
|
||||
if (tag) {
|
||||
return snprintf(buf, sz, "%s%s", prefix, tag);
|
||||
}
|
||||
return snprintf(buf, sz, "%s%08x_size_%08x", prefix, start, size);
|
||||
}
|
||||
|
||||
static void FlushMemInfoThread() {
|
||||
while (flushThreadRunning.load()) {
|
||||
flushThreadPending = false;
|
||||
FlushPendingMemInfo();
|
||||
|
||||
std::unique_lock<std::mutex> guard(flushLock);
|
||||
flushCond.wait(guard, [] {
|
||||
return flushThreadPending.load();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MemBlockInfoInit() {
|
||||
std::lock_guard<std::mutex> guard(pendingMutex);
|
||||
std::lock_guard<std::mutex> guard(pendingReadMutex);
|
||||
std::lock_guard<std::mutex> guardW(pendingWriteMutex);
|
||||
pendingNotifies.reserve(MAX_PENDING_NOTIFIES);
|
||||
pendingNotifyMinAddr1 = 0xFFFFFFFF;
|
||||
pendingNotifyMaxAddr1 = 0;
|
||||
pendingNotifyMinAddr2 = 0xFFFFFFFF;
|
||||
pendingNotifyMaxAddr2 = 0;
|
||||
|
||||
flushThreadRunning = true;
|
||||
flushThreadPending = false;
|
||||
flushThread = std::thread(&FlushMemInfoThread);
|
||||
}
|
||||
|
||||
void MemBlockInfoShutdown() {
|
||||
std::lock_guard<std::mutex> guard(pendingMutex);
|
||||
allocMap.Reset();
|
||||
suballocMap.Reset();
|
||||
writeMap.Reset();
|
||||
textureMap.Reset();
|
||||
pendingNotifies.clear();
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(pendingReadMutex);
|
||||
std::lock_guard<std::mutex> guardW(pendingWriteMutex);
|
||||
allocMap.Reset();
|
||||
suballocMap.Reset();
|
||||
writeMap.Reset();
|
||||
textureMap.Reset();
|
||||
pendingNotifies.clear();
|
||||
}
|
||||
|
||||
if (flushThreadRunning.load()) {
|
||||
std::lock_guard<std::mutex> guard(flushLock);
|
||||
flushThreadRunning = false;
|
||||
flushThreadPending = true;
|
||||
}
|
||||
flushCond.notify_one();
|
||||
flushThread.join();
|
||||
}
|
||||
|
||||
void MemBlockInfoDoState(PointerWrap &p) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue