mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-08 16:49:40 -10:00
Compare commits
751
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb44931973 | ||
|
|
59cac100e8 | ||
|
|
371e87fa31 | ||
|
|
e9a7ef5f44 | ||
|
|
40461caf4a | ||
|
|
1e877fa602 | ||
|
|
906009a33a | ||
|
|
08559a02f8 | ||
|
|
1d93626453 | ||
|
|
9d0989374b | ||
|
|
3278afaa1a | ||
|
|
bf221ffbbd | ||
|
|
c636eea355 | ||
|
|
ffaa906cd1 | ||
|
|
d61af961c1 | ||
|
|
bc01b77752 | ||
|
|
a1b01a04a8 | ||
|
|
b44235c495 | ||
|
|
b80d5db176 | ||
|
|
d3c0d258b7 | ||
|
|
9dcdbf245a | ||
|
|
369e218afb | ||
|
|
119d24bf6b | ||
|
|
2a905510d5 | ||
|
|
da29919b5a | ||
|
|
05608ac4d4 | ||
|
|
135b84a501 | ||
|
|
e7cbba37d1 | ||
|
|
d9bb5e15b0 | ||
|
|
46cdc32cf1 | ||
|
|
387572e733 | ||
|
|
9f46c7a50b | ||
|
|
0607bb0b3a | ||
|
|
b89d87cbde | ||
|
|
06e06b9673 | ||
|
|
b896e7a3b9 | ||
|
|
c91daa9147 | ||
|
|
31ea194f3d | ||
|
|
472bb28c88 | ||
|
|
eaa4a7a047 | ||
|
|
4fa467c800 | ||
|
|
beb96e6f0c | ||
|
|
26cb9e2f03 | ||
|
|
6dc74523c3 | ||
|
|
3858a69867 | ||
|
|
49a5f4aa94 | ||
|
|
4cc9f2a3f3 | ||
|
|
b0d7fc01d8 | ||
|
|
9cc40246a3 | ||
|
|
423cf11919 | ||
|
|
33f7213599 | ||
|
|
6f1fb462ed | ||
|
|
336b8851f7 | ||
|
|
5da3cf673c | ||
|
|
9e54e094fb | ||
|
|
eb9ca4ab6b | ||
|
|
e7b6876877 | ||
|
|
c535f6ef6e | ||
|
|
db2e1d7da2 | ||
|
|
f5e204eb1d | ||
|
|
705a5e7b2a | ||
|
|
e60eae3646 | ||
|
|
b04900b1b2 | ||
|
|
6357543b44 | ||
|
|
67fb338e5d | ||
|
|
ba140c4ab5 | ||
|
|
f5fdbc7941 | ||
|
|
90ae85da4d | ||
|
|
f3600fa618 | ||
|
|
2d8befe1cb | ||
|
|
f982ffcaf7 | ||
|
|
255c64551e | ||
|
|
746f28c1b2 | ||
|
|
2b481ba250 | ||
|
|
49622623de | ||
|
|
69f99ade42 | ||
|
|
c236e9711f | ||
|
|
4c2efb6688 | ||
|
|
7eb2d1cf1a | ||
|
|
b4ff83cf9f | ||
|
|
7b20b70c6b | ||
|
|
86207f9d0e | ||
|
|
eda5ece382 | ||
|
|
3404bbc128 | ||
|
|
b5a431aa3c | ||
|
|
49249f6541 | ||
|
|
5614621d6e | ||
|
|
0816562065 | ||
|
|
39618e6862 | ||
|
|
c66e4ba4b3 | ||
|
|
e4b033861f | ||
|
|
85a07a556c | ||
|
|
8dfc48d58b | ||
|
|
82ebdaa0b5 | ||
|
|
ec016f85bc | ||
|
|
c2c812deaa | ||
|
|
eec3428f78 | ||
|
|
4a5e4b76e3 | ||
|
|
43f952b2fb | ||
|
|
0d86bec34d | ||
|
|
47a6f29ad7 | ||
|
|
84834f180e | ||
|
|
bfd445618a | ||
|
|
eddfd37b93 | ||
|
|
d7341863bf | ||
|
|
2f51be7b5a | ||
|
|
fbc0014d14 | ||
|
|
46df6171fc | ||
|
|
761b62914f | ||
|
|
91b0299f77 | ||
|
|
6d08e59d10 | ||
|
|
83cc0fe585 | ||
|
|
c711c627b8 | ||
|
|
0b34690721 | ||
|
|
b640734db5 | ||
|
|
f8feb63dde | ||
|
|
25a5d3fe5d | ||
|
|
41dd93fd25 | ||
|
|
adf3c9405d | ||
|
|
abf4f57b47 | ||
|
|
c1e36975d4 | ||
|
|
0bfa0915d4 | ||
|
|
4da87672a2 | ||
|
|
3ba0d287e9 | ||
|
|
b4a86cb5bb | ||
|
|
0fa1f2f868 | ||
|
|
71610ddfdb | ||
|
|
5c9e683769 | ||
|
|
32a5e83b88 | ||
|
|
88904c08f7 | ||
|
|
9928bfe780 | ||
|
|
a491dec5c4 | ||
|
|
17ef33ee31 | ||
|
|
06caaee343 | ||
|
|
1c161a76dd | ||
|
|
aa05ab03fa | ||
|
|
43ea3f3181 | ||
|
|
3966b27948 | ||
|
|
7e24327621 | ||
|
|
d025aa3c6d | ||
|
|
325f6893c5 | ||
|
|
466419407d | ||
|
|
cbd4ce6636 | ||
|
|
d4130181e7 | ||
|
|
16a9d0af2a | ||
|
|
e51698e01f | ||
|
|
519091dd86 | ||
|
|
972088f1b3 | ||
|
|
542d31daf0 | ||
|
|
8f9a849c48 | ||
|
|
e8bfe6e520 | ||
|
|
15618f357b | ||
|
|
b74010435a | ||
|
|
0a58a7faee | ||
|
|
0d21e2f913 | ||
|
|
ebb82d2dc9 | ||
|
|
557e346810 | ||
|
|
08b2cf0894 | ||
|
|
4e90e03536 | ||
|
|
11d997dc65 | ||
|
|
454193da9b | ||
|
|
cdd5c99bc7 | ||
|
|
fb1da1c39a | ||
|
|
b29ece82c8 | ||
|
|
1330fe4d6c | ||
|
|
0e1f6f15a1 | ||
|
|
4df0e012fd | ||
|
|
48054a86ab | ||
|
|
38d658abf5 | ||
|
|
5547391cfc | ||
|
|
6782e5a717 | ||
|
|
e1a80c1751 | ||
|
|
05933a5ee3 | ||
|
|
1f29cd92a5 | ||
|
|
4f30356c9b | ||
|
|
baebf50197 | ||
|
|
01674b7c6f | ||
|
|
728bfa2043 | ||
|
|
c4de482b6a | ||
|
|
55697535a1 | ||
|
|
65733c23a1 | ||
|
|
fd51b86060 | ||
|
|
d6e4ab1824 | ||
|
|
566c7d703a | ||
|
|
d7eb3256a5 | ||
|
|
c96f2d1070 | ||
|
|
cc8b743ecb | ||
|
|
45422b739b | ||
|
|
f58bf9b23c | ||
|
|
f0e8ef1274 | ||
|
|
bb6d6fee97 | ||
|
|
d59734e375 | ||
|
|
14db12290e | ||
|
|
1e476539df | ||
|
|
3c8e370744 | ||
|
|
3fcd01f536 | ||
|
|
0e2ee35b49 | ||
|
|
27920711f0 | ||
|
|
d4c16b5b59 | ||
|
|
e392364e30 | ||
|
|
3cdb77fd65 | ||
|
|
a91be68992 | ||
|
|
e16f351540 | ||
|
|
efdab60b22 | ||
|
|
8dc24f244c | ||
|
|
b565c4a6e5 | ||
|
|
a999fbdb38 | ||
|
|
52eee568f5 | ||
|
|
05cebd4a4f | ||
|
|
8cfd54a739 | ||
|
|
8565acf12b | ||
|
|
941a6c3ef9 | ||
|
|
fe84c050ad | ||
|
|
f13c885fec | ||
|
|
8ee1cbe3eb | ||
|
|
6c5d6cda73 | ||
|
|
2725ee4512 | ||
|
|
7cc1c93775 | ||
|
|
38c9b733b5 | ||
|
|
27e5f86793 | ||
|
|
a6331e44ae | ||
|
|
f53b844add | ||
|
|
fb34098006 | ||
|
|
c729f4c8e7 | ||
|
|
9f343278c3 | ||
|
|
6beeb29486 | ||
|
|
ade2e982b6 | ||
|
|
8a61a1898b | ||
|
|
dfa3b12f48 | ||
|
|
93382ff241 | ||
|
|
24b74d8346 | ||
|
|
9ed25d8ae5 | ||
|
|
b4b31d5a08 | ||
|
|
f394906726 | ||
|
|
b50dfa76d1 | ||
|
|
3f81ddff9b | ||
|
|
d1dec8d7a4 | ||
|
|
e60097e5cb | ||
|
|
7c7bf2dcbe | ||
|
|
75593cfd29 | ||
|
|
00d0f8cd2b | ||
|
|
faf4b4e8b5 | ||
|
|
5a61d37c5d | ||
|
|
25079ab819 | ||
|
|
736b7bed5e | ||
|
|
a3f4c911e5 | ||
|
|
caa7682c6f | ||
|
|
e217cbe66d | ||
|
|
4914b2cc69 | ||
|
|
051b9a320e | ||
|
|
6ecd6fa1d0 | ||
|
|
0f4e83eefd | ||
|
|
d62525f5af | ||
|
|
71057fefe1 | ||
|
|
f4d2c37fbd | ||
|
|
ffd9f92d7b | ||
|
|
213d110766 | ||
|
|
1a8c91b49b | ||
|
|
9644a4cc7f | ||
|
|
24c250fdb4 | ||
|
|
d4636afa97 | ||
|
|
32930e704e | ||
|
|
a8b18947a1 | ||
|
|
7c422577b6 | ||
|
|
1cc1ee994d | ||
|
|
d8d375cb9a | ||
|
|
49934ce933 | ||
|
|
221644e8be | ||
|
|
5179b54745 | ||
|
|
448feb30bc | ||
|
|
9d891d4db1 | ||
|
|
240f1194c0 | ||
|
|
7b74eebce2 | ||
|
|
7592684b6f | ||
|
|
7b78e1629b | ||
|
|
30741302fb | ||
|
|
645f7d0bf1 | ||
|
|
124f162ee8 | ||
|
|
3c577fd9dd | ||
|
|
659ff4817f | ||
|
|
626adb62af | ||
|
|
71cb100a40 | ||
|
|
68002f3fa7 | ||
|
|
dae7592788 | ||
|
|
e10f5b4430 | ||
|
|
3e32b75a63 | ||
|
|
435f7a1b03 | ||
|
|
1fd8c50225 | ||
|
|
e797bc5f01 | ||
|
|
0cc3fe6bca | ||
|
|
ab8542e4e5 | ||
|
|
c2bcf20172 | ||
|
|
2341659cea | ||
|
|
b2eaf380dc | ||
|
|
197fc3f163 | ||
|
|
9530e7b394 | ||
|
|
0e0f133496 | ||
|
|
d98c211168 | ||
|
|
614b4d4de4 | ||
|
|
43a19a831a | ||
|
|
0d308af024 | ||
|
|
128f73da8f | ||
|
|
e22a55e949 | ||
|
|
67cb005e6c | ||
|
|
73f76f6799 | ||
|
|
961a060461 | ||
|
|
dadbbf841a | ||
|
|
1ce8280371 | ||
|
|
78bdc0e3b5 | ||
|
|
ce5f2f5513 | ||
|
|
0f2792c7ac | ||
|
|
7185b775d4 | ||
|
|
cda6e89c12 | ||
|
|
5f80898c27 | ||
|
|
85560d6471 | ||
|
|
be6c875277 | ||
|
|
91beced154 | ||
|
|
ebc3da08b4 | ||
|
|
41eb780495 | ||
|
|
65fc2a38a3 | ||
|
|
359d1535ac | ||
|
|
4c3a9f027c | ||
|
|
5a6cc3c76e | ||
|
|
89bd7deceb | ||
|
|
169f2c3e34 | ||
|
|
e6a797695a | ||
|
|
f1a2ecc857 | ||
|
|
36916f6bde | ||
|
|
1b96d13a51 | ||
|
|
cb1d70bb14 | ||
|
|
37af590ca2 | ||
|
|
02f356f8c0 | ||
|
|
5448177814 | ||
|
|
ed6ce0ca41 | ||
|
|
9b440686c5 | ||
|
|
e7b7e9dc43 | ||
|
|
1120040aee | ||
|
|
3506a272da | ||
|
|
b752748b7d | ||
|
|
8990e7d7a4 | ||
|
|
c92a423ba1 | ||
|
|
3b82b3e1f8 | ||
|
|
e985ce1975 | ||
|
|
8496ce3e51 | ||
|
|
47188f40ed | ||
|
|
e186c9bc4c | ||
|
|
7705f3c222 | ||
|
|
5675b4897f | ||
|
|
1c87d92ef7 | ||
|
|
031e64dfe1 | ||
|
|
f590dd9763 | ||
|
|
af7dc40538 | ||
|
|
a28aae6e4a | ||
|
|
c024470cc7 | ||
|
|
16ae5ab84a | ||
|
|
144947305a | ||
|
|
563bc6cad7 | ||
|
|
59cde93821 | ||
|
|
c104e1c276 | ||
|
|
b5da0d0db6 | ||
|
|
5defc3e38b | ||
|
|
6a9f52f932 | ||
|
|
06a8d75769 | ||
|
|
ec12ddd0a8 | ||
|
|
570902d5f3 | ||
|
|
6120069975 | ||
|
|
3e337c9bda | ||
|
|
2869e76406 | ||
|
|
61fdb80823 | ||
|
|
fd180a108a | ||
|
|
4de74c740c | ||
|
|
b7c32c92d2 | ||
|
|
8610ca0928 | ||
|
|
a6b1ad37d0 | ||
|
|
97b13f7488 | ||
|
|
5d5b45a012 | ||
|
|
5ebafaabda | ||
|
|
56627a8aaf | ||
|
|
f59ced4e98 | ||
|
|
296e848718 | ||
|
|
8c63413019 | ||
|
|
2861c6d284 | ||
|
|
69c2d8e566 | ||
|
|
414fddf7b8 | ||
|
|
b5ca6145a6 | ||
|
|
5a5948b071 | ||
|
|
e58b5088b3 | ||
|
|
ecbce4c8d3 | ||
|
|
96bf0a3bf3 | ||
|
|
da7522a3ee | ||
|
|
5605e777ca | ||
|
|
a10e2e1fe8 | ||
|
|
4fb623fa33 | ||
|
|
3b4956d088 | ||
|
|
18deaafc34 | ||
|
|
0c4eede6d2 | ||
|
|
54681f2647 | ||
|
|
5271edd972 | ||
|
|
e4bb9856ef | ||
|
|
e462bbb24a | ||
|
|
d8f3090d9b | ||
|
|
d1af8b0114 | ||
|
|
dae64bbe69 | ||
|
|
8b127bed84 | ||
|
|
67187b1340 | ||
|
|
02cac57438 | ||
|
|
277dcd174c | ||
|
|
3c23ae7310 | ||
|
|
8cb13d40b5 | ||
|
|
c950d31960 | ||
|
|
ae570cd5b7 | ||
|
|
0fd1dc4fce | ||
|
|
f0854e8518 | ||
|
|
ceffd3f759 | ||
|
|
dc1816612c | ||
|
|
1d04a7e0e3 | ||
|
|
1e523c4e93 | ||
|
|
d83f766cf3 | ||
|
|
bb5e1ae63d | ||
|
|
f27a4f6c95 | ||
|
|
d7ff92106a | ||
|
|
34a17acaba | ||
|
|
110aec0b6f | ||
|
|
06502bb49b | ||
|
|
c20a166134 | ||
|
|
162fa8e2de | ||
|
|
ae5614d212 | ||
|
|
b72d8a8bab | ||
|
|
d4cf890d8a | ||
|
|
82e494269d | ||
|
|
c982d65675 | ||
|
|
f4e606fb15 | ||
|
|
9dbdb5e771 | ||
|
|
fa12478fdd | ||
|
|
c3b0d8ef03 | ||
|
|
cbb9a168e6 | ||
|
|
73e2fc58a0 | ||
|
|
500f4c69eb | ||
|
|
911ad29004 | ||
|
|
aa843b5a31 | ||
|
|
f249acc50f | ||
|
|
8cdedbe18a | ||
|
|
0f39d71f95 | ||
|
|
1dc49b527b | ||
|
|
9422608960 | ||
|
|
cc3fec0768 | ||
|
|
de37ddcfb5 | ||
|
|
4c90196890 | ||
|
|
5270574e5a | ||
|
|
30f6ca6ffc | ||
|
|
a8af973726 | ||
|
|
ed1f8dd2b6 | ||
|
|
e8aaae1343 | ||
|
|
ecff3240a6 | ||
|
|
ddd4a0c24d | ||
|
|
019949847a | ||
|
|
4dcbf269d2 | ||
|
|
036317f371 | ||
|
|
ce0da01163 | ||
|
|
75ae619e21 | ||
|
|
3efa5acfa6 | ||
|
|
9369dc3031 | ||
|
|
5d84d3fef7 | ||
|
|
51baf75af1 | ||
|
|
20f2c98d18 | ||
|
|
e3ceae412b | ||
|
|
7e6affed12 | ||
|
|
40fc5463f3 | ||
|
|
039948f4d2 | ||
|
|
bbb95e8a60 | ||
|
|
7763d00b78 | ||
|
|
3452730725 | ||
|
|
e50f61f5d9 | ||
|
|
a7a4308e3c | ||
|
|
e811576e79 | ||
|
|
960abec0ed | ||
|
|
855ab88d6c | ||
|
|
f7ee6e78f0 | ||
|
|
293c58aeba | ||
|
|
95b8a4c3c4 | ||
|
|
b7fd52de79 | ||
|
|
53ce764548 | ||
|
|
157e74907a | ||
|
|
015ac26e07 | ||
|
|
1fe7faaa8d | ||
|
|
2e7f99897d | ||
|
|
d7fa65ad20 | ||
|
|
ae614ce476 | ||
|
|
7d6e1cce2f | ||
|
|
bbf46cad9d | ||
|
|
68c7b4b3c8 | ||
|
|
5d38a6c8c5 | ||
|
|
99a0c44f98 | ||
|
|
1812ba20e2 | ||
|
|
771d3c399c | ||
|
|
af74a4449e | ||
|
|
8c171dd2c7 | ||
|
|
3292ed6f1a | ||
|
|
96bbe0c4cc | ||
|
|
039e14de5a | ||
|
|
a419c8097f | ||
|
|
3fac2e8945 | ||
|
|
c2d6d272a3 | ||
|
|
846630e300 | ||
|
|
cc1f3d256b | ||
|
|
243a760a12 | ||
|
|
4f744fc8fe | ||
|
|
54adf9ad68 | ||
|
|
8b66adb8a5 | ||
|
|
9f0c1fc797 | ||
|
|
4a46e16702 | ||
|
|
608d011b44 | ||
|
|
0d52ba66cd | ||
|
|
431fac1fca | ||
|
|
35006d6031 | ||
|
|
7ae66337ef | ||
|
|
b75e0f9800 | ||
|
|
ee3fd4d6b8 | ||
|
|
0352f4b832 | ||
|
|
7fbfeb2edd | ||
|
|
8db69f073b | ||
|
|
226fa1c1ea | ||
|
|
6f41ae58db | ||
|
|
ae9186392f | ||
|
|
4279ffd887 | ||
|
|
ac058ea112 | ||
|
|
5d42e5e87a | ||
|
|
96ac6967e6 | ||
|
|
0c301f3cec | ||
|
|
d7cadd05d6 | ||
|
|
2f8464b7f5 | ||
|
|
96314749da | ||
|
|
33f06d744f | ||
|
|
d6ca48d80a | ||
|
|
c8b628393b | ||
|
|
e9d4eb1e1b | ||
|
|
89f78a9969 | ||
|
|
94f9935ad4 | ||
|
|
66c7db5a47 | ||
|
|
b959de3d26 | ||
|
|
b802485e31 | ||
|
|
ee20fd60c4 | ||
|
|
52174e2ab0 | ||
|
|
216f3c1d62 | ||
|
|
7e6954304a | ||
|
|
8718ae0aa8 | ||
|
|
615311ecd6 | ||
|
|
3a4ef4f6d6 | ||
|
|
403cfa77ae | ||
|
|
c7998a82d5 | ||
|
|
86bc9f4a64 | ||
|
|
2f4c4624ae | ||
|
|
b83e5f87cf | ||
|
|
b49e2753df | ||
|
|
3c09e1d736 | ||
|
|
0f07dafb36 | ||
|
|
38fdca0b97 | ||
|
|
e8efe2d290 | ||
|
|
a1ae55125a | ||
|
|
706692c1fe | ||
|
|
582f031243 | ||
|
|
9c78373c73 | ||
|
|
e9f3416438 | ||
|
|
ea44a77f8a | ||
|
|
63d7051c4b | ||
|
|
4803c9d221 | ||
|
|
6388edb706 | ||
|
|
0774ade030 | ||
|
|
e5ee766961 | ||
|
|
274f3d0c7a | ||
|
|
21be683768 | ||
|
|
695923aed7 | ||
|
|
649153257d | ||
|
|
cbcd3f8023 | ||
|
|
41c5ce8d4e | ||
|
|
78ce23258b | ||
|
|
3e864fda17 | ||
|
|
0cdb945d9c | ||
|
|
15a113f783 | ||
|
|
67f00d2123 | ||
|
|
d68dcd14e1 | ||
|
|
32994210ab | ||
|
|
cfec8ab88a | ||
|
|
87c44c3218 | ||
|
|
35446df6d5 | ||
|
|
921b38bfb4 | ||
|
|
82bb924d0b | ||
|
|
08a6769bc0 | ||
|
|
dce87061d1 | ||
|
|
f4302e4fa9 | ||
|
|
0436953451 | ||
|
|
7165907ba9 | ||
|
|
e938127c7a | ||
|
|
b5c9d6bc50 | ||
|
|
f7af5091cf | ||
|
|
7ac1786a2e | ||
|
|
19a919e096 | ||
|
|
10f5506829 | ||
|
|
d3c6d54a58 | ||
|
|
6980428f82 | ||
|
|
1a3b16f614 | ||
|
|
e0b9f462ce | ||
|
|
f6f7470385 | ||
|
|
214c708b49 | ||
|
|
d567185fc0 | ||
|
|
24188bda10 | ||
|
|
1a870c5c07 | ||
|
|
fa964802d9 | ||
|
|
793180ce12 | ||
|
|
79e7be470c | ||
|
|
921f156128 | ||
|
|
c771fc67ac | ||
|
|
7f2d2ffa4a | ||
|
|
2d9e1c6d95 | ||
|
|
66dafa6021 | ||
|
|
2df3d054e8 | ||
|
|
c9cfa2126e | ||
|
|
2220578cd1 | ||
|
|
5b18807f0e | ||
|
|
d984a4ba31 | ||
|
|
7dc00d9b23 | ||
|
|
59123f1c89 | ||
|
|
aa1df93a22 | ||
|
|
c99617427a | ||
|
|
54a4677f11 | ||
|
|
edeb388233 | ||
|
|
034d484271 | ||
|
|
ebf5da9840 | ||
|
|
f9c9084584 | ||
|
|
d160f464b6 | ||
|
|
c013252a59 | ||
|
|
7fa71dc5c5 | ||
|
|
a19dfb88a0 | ||
|
|
738cd059a2 | ||
|
|
aaa6a0452f | ||
|
|
0cb18839a2 | ||
|
|
991e1af331 | ||
|
|
c8a278d009 | ||
|
|
39f5478ea9 | ||
|
|
d0b1686a84 | ||
|
|
95bd8ec2fe | ||
|
|
85b5151e1a | ||
|
|
a0be13adba | ||
|
|
a0fb8fd73d | ||
|
|
d3e70b99c0 | ||
|
|
0a0ec6acf5 | ||
|
|
50df100569 | ||
|
|
02489e617c | ||
|
|
2f22050add | ||
|
|
0081c8f7c7 | ||
|
|
12c1036f17 | ||
|
|
7ef7129f7a | ||
|
|
5cb6346017 | ||
|
|
5e599e5445 | ||
|
|
146c07a25e | ||
|
|
44265c3fcd | ||
|
|
90c7574e28 | ||
|
|
e4fbb6f956 | ||
|
|
ad2f9f8aa6 | ||
|
|
9c470950ea | ||
|
|
0ddb0be65a | ||
|
|
cbd3bc95a1 | ||
|
|
f43795714d | ||
|
|
586c9c28b4 | ||
|
|
f79dc2da43 | ||
|
|
3bb485a437 | ||
|
|
8dd0043072 | ||
|
|
4c43fe3502 | ||
|
|
92f7709a76 | ||
|
|
04f40282d2 | ||
|
|
4b1804847f | ||
|
|
4e791d486f | ||
|
|
9a8732fba4 | ||
|
|
adddbb4e50 | ||
|
|
af4df4177d | ||
|
|
fa54c5c5a0 | ||
|
|
768e6c8511 | ||
|
|
eb1b9e0d78 | ||
|
|
2f80d97e17 | ||
|
|
d9246c333e | ||
|
|
164a6e009b | ||
|
|
69aba7d0b7 | ||
|
|
60f393f878 | ||
|
|
a14451b5e9 | ||
|
|
946bbc3023 | ||
|
|
474e75f649 | ||
|
|
3c65fd8d9e | ||
|
|
066011c5fb | ||
|
|
e14fc55030 | ||
|
|
d7b61258ce | ||
|
|
ffd544c42a | ||
|
|
cb99269a6d | ||
|
|
74b9ea9f89 | ||
|
|
aa863662b8 | ||
|
|
118e78ceb2 | ||
|
|
b14cdf50e6 | ||
|
|
39dd9b03f9 | ||
|
|
5d55258383 | ||
|
|
3256809066 | ||
|
|
dfbb1e5b98 | ||
|
|
e9915c44f9 | ||
|
|
a90ba862df | ||
|
|
b6ab624862 | ||
|
|
f66138505f | ||
|
|
dab91bdfac | ||
|
|
101d155855 | ||
|
|
10d7b4905b | ||
|
|
3afad00a84 | ||
|
|
ab8b06bacd | ||
|
|
9cfb96e358 | ||
|
|
2fb79735c3 | ||
|
|
a4e59986a8 | ||
|
|
93b4ec47d1 | ||
|
|
a4b1d12f36 | ||
|
|
217485e021 | ||
|
|
1f41d6d282 | ||
|
|
6876a8d649 | ||
|
|
02c159c5fd | ||
|
|
a1e204c117 | ||
|
|
ece9f7cd46 | ||
|
|
16dab991e0 | ||
|
|
c17975943a | ||
|
|
9d9ee98eaf | ||
|
|
b0bcac6926 | ||
|
|
3b7ad53994 | ||
|
|
0fedfae1e6 | ||
|
|
3fa2a35c12 | ||
|
|
a39859a39e | ||
|
|
d2f4a84314 | ||
|
|
0959eacf39 | ||
|
|
78c4b3b264 | ||
|
|
9f4a841eb6 | ||
|
|
9b034fcbb2 | ||
|
|
81cf964168 | ||
|
|
b5158210a9 | ||
|
|
d1602710a8 | ||
|
|
ec1453831d | ||
|
|
c63af629ac | ||
|
|
916b1d46fc | ||
|
|
7df694fe49 | ||
|
|
071289b56e | ||
|
|
eca355016c | ||
|
|
0053d12407 | ||
|
|
4d6ee985b9 | ||
|
|
94d74d35db | ||
|
|
d6a157578d | ||
|
|
0b9f3106fe | ||
|
|
17c75bf671 | ||
|
|
d6a9c2ed90 | ||
|
|
9931dc13a7 | ||
|
|
4a3d5a7d66 |
No files matched your search
@@ -1,67 +0,0 @@
|
||||
language: en-US
|
||||
early_access: false
|
||||
tone_instructions: >-
|
||||
Prioritize actionable correctness, security, and runtime regressions. Keep
|
||||
feedback concise. Do not request explanatory code comments; preserve license
|
||||
headers and compiler-consumed annotations.
|
||||
reviews:
|
||||
profile: chill
|
||||
request_changes_workflow: false
|
||||
high_level_summary: true
|
||||
high_level_summary_in_walkthrough: true
|
||||
poem: false
|
||||
sequence_diagrams: false
|
||||
tools:
|
||||
github-checks:
|
||||
enabled: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
auto_incremental_review: true
|
||||
auto_pause_after_reviewed_commits: 0
|
||||
drafts: false
|
||||
pre_merge_checks:
|
||||
docstrings:
|
||||
mode: "off"
|
||||
title:
|
||||
mode: "off"
|
||||
description:
|
||||
mode: "off"
|
||||
finishing_touches:
|
||||
docstrings:
|
||||
enabled: false
|
||||
unit_tests:
|
||||
enabled: false
|
||||
simplify:
|
||||
enabled: false
|
||||
autofix:
|
||||
enabled: false
|
||||
fix_ci:
|
||||
enabled: false
|
||||
resolve_merge_conflict:
|
||||
enabled: false
|
||||
path_instructions:
|
||||
- path: "**/*"
|
||||
instructions: >-
|
||||
For cleanup changes, compare executable behavior against the PR merge
|
||||
base. Check for removed guards, changed return values, weakened compiler
|
||||
flags, and deleted regression tests. A passing APK build alone does not
|
||||
establish behavior preservation. Avoid style-only or comment-coverage
|
||||
requests.
|
||||
- path: "app/src/main/**"
|
||||
instructions: >-
|
||||
Review Android lifecycle, external-display visibility, input cancellation,
|
||||
permissions, threading, and Compose state. For JNI, native, and Rust
|
||||
changes, check ABI compatibility, resource ownership, synchronization,
|
||||
and error paths. Preserve persisted user settings and existing runtime
|
||||
contracts.
|
||||
- path: "tools/**"
|
||||
instructions: >-
|
||||
Check shell quoting, error handling, path and filesystem semantics, and
|
||||
Python helper behavior. Preserve MSI same-file protection and strict
|
||||
compiler checks. Verify downstream patches apply to their pinned sources
|
||||
and retain intended executable behavior, including embedded scripts.
|
||||
- path: ".github/workflows/**"
|
||||
instructions: >-
|
||||
Check job permissions, triggers, merge validation, build variants,
|
||||
signing, artifact paths, and release conditions. Verify checks apply to
|
||||
the current PR head and keep review configuration advisory.
|
||||
@@ -1,12 +0,0 @@
|
||||
tools/linuxfs/** text=auto eol=lf
|
||||
tools/mangoapp/** text=auto eol=lf
|
||||
tools/comet/LICENSE whitespace=-blank-at-eof
|
||||
tools/comet/droiddeck.patch whitespace=-blank-at-eol,-blank-at-eof
|
||||
tools/gamescope/patches/0900-enhanced-frame-diagnostics.patch whitespace=-blank-at-eol,-blank-at-eof,-space-before-tab
|
||||
|
||||
tools/linuxfs/overlay/** text eol=lf
|
||||
tools/linuxfs/desktop/** text eol=lf
|
||||
tools/mangoapp/mangoapp text eol=lf
|
||||
app/src/main/assets/desktop-removal/*.json text eol=lf
|
||||
app/src/main/assets/desktop-removal/*.txt text eol=lf
|
||||
*.pdf binary
|
||||
@@ -1,5 +0,0 @@
|
||||
ASEbrahim
|
||||
FX192
|
||||
K11MCH1
|
||||
kingokksa
|
||||
ovurrsl
|
||||
@@ -1,40 +0,0 @@
|
||||
name: Build validation
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, edited, ready_for_review]
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_run:
|
||||
workflows: [Build APK]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: Pull request to validate; empty for main
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
pull-requests: read
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate source and build result
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Validate source and APK compilation
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh auth setup-git
|
||||
python3 tools/release/validate_apk.py
|
||||
@@ -1,5 +1,7 @@
|
||||
name: Build AAudio sink module
|
||||
|
||||
# A quick check of tools/aaudio-sink on its own; the APK build compiles the same module into the
|
||||
# audio bundle. Runs on pushes that touch the module.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
name: Build desktop package (KDE Plasma)
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/desktop-kde/**'
|
||||
- '.github/workflows/build-desktop-kde.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. desktop-kde-r1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
kwin:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build KWin with nested desktop scaling
|
||||
run: |
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv patch binutils >/dev/null
|
||||
bash tools/desktop-kde/build-kwin.sh out/kwin
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-kde-kwin
|
||||
retention-days: 7
|
||||
path: out/kwin/
|
||||
build:
|
||||
needs: kwin
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-kde-kwin
|
||||
path: out/kwin
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: components-token
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-Components
|
||||
- name: Refuse a published tag
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
run: |
|
||||
if gh release view '${{ inputs.tag }}' -R Droid-Deck/DroidDeck-Components >/dev/null 2>&1; then
|
||||
echo "::error::${{ inputs.tag }} is already published, and published tags are never replaced: use a new tag"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv gcc-aarch64-linux-gnu >/dev/null
|
||||
KDE_KWIN_OVERRIDE="$PWD/out/kwin" DESKTOP_KDE_RELEASE_TAG='${{ inputs.tag }}' tools/desktop-kde/build.sh out
|
||||
cp out/kwin/desktop-kde.kwin.txt out/
|
||||
echo "size $(stat -c %s out/desktop-kde.tar.zst) sha256 $(cat out/desktop-kde.sha256)"
|
||||
echo "language packs $(ls out/desktop-kde-i18n-*.tar.zst | wc -l)"
|
||||
echo "pacman repository $(ls out/repo | wc -l) files, $(du -sm out/repo | cut -f1) MiB"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-kde
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
path: |
|
||||
out/desktop-kde.tar.zst
|
||||
out/desktop-kde.packages.txt
|
||||
out/desktop-kde.kwin.txt
|
||||
out/desktop-kde.sha256
|
||||
out/desktop-kde.i18n.txt
|
||||
out/desktop-kde-i18n-*.tar.zst
|
||||
out/repo/
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
GH_REPO: Droid-Deck/DroidDeck-Components
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG='${{ inputs.tag }}'
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "::error::$TAG is already published in DroidDeck-Components, and published tags are never replaced: use a new tag"
|
||||
exit 1
|
||||
fi
|
||||
prerelease=false
|
||||
case $TAG in *-rc*) prerelease=true ;; esac
|
||||
gh release create "$TAG" --latest=false --prerelease="$prerelease" --title "$TAG" \
|
||||
--notes "KDE Plasma desktop package for DroidDeck's Linux runtime ($GITHUB_REPOSITORY tools/desktop-kde, $GITHUB_SHA). size $(stat -c %s out/desktop-kde.tar.zst), sha256 $(cat out/desktop-kde.sha256). desktop-kde.packages.txt lists the signature-checked Arch Linux ARM packages it was built from." \
|
||||
out/desktop-kde.tar.zst out/desktop-kde.sha256 out/desktop-kde.packages.txt out/desktop-kde.kwin.txt \
|
||||
out/desktop-kde.i18n.txt out/desktop-kde-i18n-*.tar.zst out/repo/*
|
||||
gh release edit "$TAG" --latest=false --prerelease="$prerelease"
|
||||
@@ -1,45 +0,0 @@
|
||||
name: Build FEX (x86-64 Steam client)
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/fex/**'
|
||||
- '.github/workflows/build-fex.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
thunks:
|
||||
description: 'Build the host thunks (Vulkan/GL/X11/... to the runtime arm64 libraries)'
|
||||
required: false
|
||||
default: 'false'
|
||||
cxxflags:
|
||||
description: 'Extra CXXFLAGS for a diagnostic build (e.g. -DDEBUG_STRACE)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Build in Arch Linux ARM
|
||||
env:
|
||||
FEX_EXTRA_CXXFLAGS: ${{ inputs.cxxflags }}
|
||||
FEX_THUNKS: ${{ inputs.thunks == 'true' && '1' || '0' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -e FEX_EXTRA_CXXFLAGS="$FEX_EXTRA_CXXFLAGS" -e FEX_THUNKS="$FEX_THUNKS" -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/fex/build-in-arch.sh
|
||||
sudo chown -R "$(id -u):$(id -g)" .
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fex
|
||||
path: |
|
||||
fex.tzst
|
||||
fex.tzst.sha256
|
||||
fex-needed.txt
|
||||
fex-files.txt
|
||||
@@ -1,12 +1,13 @@
|
||||
name: Build gamescope (patched)
|
||||
|
||||
# The runtime's own gamescope (Arch Linux ARM's 3.16.29 package) rebuilt with the patches under
|
||||
# tools/gamescope/patches, in an Arch Linux ARM container on an arm64 runner, then published as a
|
||||
# release asset the apk build fetches (tools/gamescope/release.env names the tag and sha). The
|
||||
# hosted runtime image is never changed: the apk stages the binary over /usr/local/bin.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'tools/gamescope/build-in-arch.sh'
|
||||
- 'tools/gamescope/patches/**'
|
||||
- 'tools/gamescope/runtime-sonames.txt'
|
||||
- 'tools/gamescope/**'
|
||||
- '.github/workflows/build-gamescope.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -18,10 +19,6 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ inputs.tag && format('gamescope-tag-{0}', inputs.tag) || format('gamescope-{0}-{1}', github.event_name, github.ref) }}
|
||||
cancel-in-progress: ${{ github.event_name == 'push' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
@@ -39,25 +36,13 @@ jobs:
|
||||
path: |
|
||||
gamescope.tzst
|
||||
gamescope.tzst.sha256
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: components-token
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-Components
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
GH_REPO: Droid-Deck/DroidDeck-Components
|
||||
TAG: ${{ inputs.tag }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "::error::$TAG is already published in DroidDeck-Components, and published tags are never replaced: use a new tag"
|
||||
exit 1
|
||||
fi
|
||||
gh release create "$TAG" --latest=false --prerelease=false --title "$TAG" --notes "gamescope 3.16.29 with tools/gamescope/patches ($GITHUB_REPOSITORY $GITHUB_SHA, run $GITHUB_RUN_ID); staged by the apk over /usr/local/bin/gamescope. sha256 $(cut -d' ' -f1 gamescope.tzst.sha256)" gamescope.tzst gamescope.tzst.sha256
|
||||
gh release edit "$TAG" --latest=false --prerelease=false
|
||||
gh release create "${{ inputs.tag }}" --latest=false --prerelease=false --title "${{ inputs.tag }}" --notes "gamescope 3.16.29 with tools/gamescope/patches; staged by the apk over /usr/local/bin/gamescope. sha256 $(cut -d' ' -f1 gamescope.tzst.sha256)" gamescope.tzst gamescope.tzst.sha256 || gh release upload "${{ inputs.tag }}" gamescope.tzst gamescope.tzst.sha256 --clobber
|
||||
# A component the apk fetches by tag, never something users install: it must not
|
||||
# become the repo's Latest release or appear as a pre-release, also when re-uploaded.
|
||||
gh release edit "${{ inputs.tag }}" --latest=false --prerelease=false
|
||||
@@ -1,95 +0,0 @@
|
||||
name: Build Linux runtime (Ubuntu)
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/ubuntu/**'
|
||||
- '.github/workflows/build-ubuntu-runtime.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. ubuntu-r1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
debs:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Build Mesa with KGSL and the patched KWin
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -v "$PWD:/work" -w /work ubuntu:26.04 bash tools/ubuntu/build-debs.sh /work/out/debs
|
||||
sudo chown -R "$(id -u):$(id -g)" out
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ubuntu-runtime-debs
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
path: out/debs/
|
||||
rootfs:
|
||||
needs: debs
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ubuntu-runtime-debs
|
||||
path: out/debs
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: components-token
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-Components
|
||||
- name: Refuse a published tag
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
run: |
|
||||
if gh release view '${{ inputs.tag }}' -R Droid-Deck/DroidDeck-Components >/dev/null 2>&1; then
|
||||
echo "::error::${{ inputs.tag }} is already published, and published tags are never replaced: use a new tag"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq zstd gpgv ubuntu-keyring >/dev/null
|
||||
tag='${{ inputs.tag }}'
|
||||
tools/ubuntu/build.sh out/debs out "${tag:-ubuntu-dev}"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ubuntu-runtime
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
path: |
|
||||
out/*.tar.zst
|
||||
out/*.sha256
|
||||
out/*.packages.txt
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
GH_REPO: Droid-Deck/DroidDeck-Components
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG='${{ inputs.tag }}'
|
||||
prerelease=false
|
||||
case $TAG in *-rc*) prerelease=true ;; esac
|
||||
gh release create "$TAG" --latest=false --prerelease="$prerelease" --title "$TAG" \
|
||||
--notes "Ubuntu 26.04 LTS arm64 runtime for DroidDeck ($GITHUB_REPOSITORY tools/ubuntu, $GITHUB_SHA): size $(stat -c %s "out/$TAG.tar.zst"), sha256 $(cat "out/$TAG.sha256"). $TAG-desktop.tar.zst is the Kubuntu Plasma desktop built on top of it, holding only what the runtime does not already have: size $(stat -c %s "out/$TAG-desktop.tar.zst"), sha256 $(cat "out/$TAG-desktop.sha256"). The .packages.txt files list every installed package and version." \
|
||||
"out/$TAG.tar.zst" "out/$TAG.sha256" "out/$TAG.packages.txt" \
|
||||
"out/$TAG-desktop.tar.zst" "out/$TAG-desktop.sha256" "out/$TAG-desktop.packages.txt" out/debs/*.deb
|
||||
gh release edit "$TAG" --latest=false --prerelease="$prerelease"
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Build Venus (Mali)
|
||||
# The Mali (Venus) route's binaries from tools/venus (David Cimo's recipes): Mesa's Venus ICD and the
|
||||
# Mali compatibility layer (glibc, for the session) and virglrenderer's vtest server (bionic, run by
|
||||
# the app over the phone's own Vulkan driver), packed as venus.tzst rooted at app/src/main. The apk
|
||||
# build fetches it by tools/venus/release.env (tag + sha256); nothing compiled is kept in this repo.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [test/non-adreno-all]
|
||||
paths: ['tools/venus/**', '.github/workflows/build-venus.yml']
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Tools
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq ninja-build pkg-config zstd python3-pip
|
||||
pip3 install --break-system-packages 'meson>=1.5'
|
||||
- name: Build
|
||||
run: ANDROID_NDK_HOME="$ANDROID_NDK_LATEST_HOME" WORK=$PWD/venus-work tools/venus/ci-build.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: venus
|
||||
path: |
|
||||
venus-work/venus.tzst
|
||||
venus-work/venus.tzst.sha256
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Build wlroots (patched)
|
||||
|
||||
# The desktop's wlroots (Arch Linux ARM's wlroots0.20, what the desktop package's labwc links)
|
||||
# rebuilt with tools/wlroots/patches: a Vulkan DMA-BUF allocator for the Adreno node, which is not
|
||||
# a DRM device, so labwc's vulkan and gles2 renderers can start. Built in an Arch Linux ARM
|
||||
# container on an arm64 runner and published as a release asset the apk build fetches
|
||||
# (tools/wlroots/release.env names the tag and sha). The hosted desktop package is never changed.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/wlroots/**'
|
||||
- '.github/workflows/build-wlroots.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish as (e.g. wlroots-0.20.2-p1); empty = artifact only'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build in Arch Linux ARM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/wlroots/build-in-arch.sh
|
||||
sudo chown -R "$(id -u):$(id -g)" .
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wlroots-patched
|
||||
path: |
|
||||
wlroots.tzst
|
||||
wlroots.tzst.sha256
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release create "${{ inputs.tag }}" --latest=false --prerelease=false --title "${{ inputs.tag }}" --notes "wlroots 0.20.2 with tools/wlroots/patches (Vulkan DMA-BUF allocator for non-DRM GPU nodes); staged by the apk at /usr/local/lib/droiddeck-wlroots for the desktop's GPU renderers. sha256 $(cut -d' ' -f1 wlroots.tzst.sha256)" wlroots.tzst wlroots.tzst.sha256 || gh release upload "${{ inputs.tag }}" wlroots.tzst wlroots.tzst.sha256 --clobber
|
||||
# A component the apk fetches by tag, never something users install: it must not
|
||||
# become the repo's Latest release or appear as a pre-release, also when re-uploaded.
|
||||
gh release edit "${{ inputs.tag }}" --latest=false --prerelease=false
|
||||
+425
-670
File diff suppressed because it is too large.
Load diff
@@ -1,51 +0,0 @@
|
||||
name: Publish the CI Debian image
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish to (a new -rN tag for a refresh; builds pin the one in tools/linuxfs/debian-image.env)'
|
||||
required: true
|
||||
default: 'ci-debian-bullseye-r2'
|
||||
replace:
|
||||
description: 'Overwrite the asset if the release already has one (changes its checksum: re-pin it in debian-image.env)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Pull, save and publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
REPLACE: ${{ inputs.replace }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
image=public.ecr.aws/docker/library/debian:bullseye
|
||||
if [ "$REPLACE" != true ] && gh release view "$TAG" -R "$GITHUB_REPOSITORY" --json assets -q '.assets[].name' 2>/dev/null | grep -qx debian-bullseye.tar.zst; then
|
||||
echo "::error::$TAG already carries debian-bullseye.tar.zst; publish to a new tag, or set replace and re-pin tools/linuxfs/debian-image.env"
|
||||
exit 1
|
||||
fi
|
||||
for attempt in 1 2 3 4 5 6; do
|
||||
docker pull "$image" && break
|
||||
[ "$attempt" -lt 6 ] || { echo "::error::could not pull $image"; exit 1; }
|
||||
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
|
||||
sleep $((attempt * 30))
|
||||
done
|
||||
docker image inspect "$image" --format 'image id {{.Id}} created {{.Created}}'
|
||||
docker save "$image" | zstd -19 -T0 -o debian-bullseye.tar.zst
|
||||
sha256sum debian-bullseye.tar.zst | tee SHA256SUMS
|
||||
ls -l debian-bullseye.tar.zst
|
||||
if ! gh release view "$TAG" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$TAG" -R "$GITHUB_REPOSITORY" --title "CI Debian 11 image ($TAG)" --latest=false \
|
||||
--notes "The debian:bullseye image build.yml compiles the x86 preloads in (glibc 2.31), saved with docker save and compressed with zstd. Pulled from public.ecr.aws/docker/library/debian:bullseye on $(date -u +%Y-%m-%d); image id $(docker image inspect "$image" --format '{{.Id}}'). Pinned in tools/linuxfs/debian-image.env."
|
||||
fi
|
||||
gh release upload "$TAG" -R "$GITHUB_REPOSITORY" debian-bullseye.tar.zst SHA256SUMS --clobber
|
||||
{ echo "Published \`$TAG\`:"; echo; echo '```'; cat SHA256SUMS; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -1,60 +0,0 @@
|
||||
name: Pick runner
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
labels:
|
||||
value: ${{ jobs.pick.outputs.labels }}
|
||||
secrets:
|
||||
RUNNER_STATUS_TOKEN:
|
||||
required: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pick:
|
||||
name: Pick runner
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 2
|
||||
outputs:
|
||||
labels: ${{ steps.runner.outputs.labels || steps.fork.outputs.labels || '"ubuntu-24.04"' }}
|
||||
steps:
|
||||
- name: Pick the compile runner
|
||||
id: runner
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RUNNER_STATUS_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
labels='"ubuntu-24.04"'
|
||||
if [ -n "$GH_TOKEN" ]; then
|
||||
idle=$(gh api "orgs/${GITHUB_REPOSITORY_OWNER}/actions/runners" --jq '[.runners[] | select(.status == "online" and .busy == false and any(.labels[]; .name == "droiddeck-ws"))] | length' 2>/dev/null || echo 0)
|
||||
if [ "${idle:-0}" -gt 0 ]; then
|
||||
labels='["self-hosted","droiddeck-ws"]'
|
||||
fi
|
||||
fi
|
||||
echo "labels=$labels" >> "$GITHUB_OUTPUT"
|
||||
echo "Compile runner: \`$labels\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Pick the fork runner
|
||||
id: fork
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
labels='"ubuntu-24.04"'
|
||||
gist=https://api.github.com/gists/0c7ed7ad6d6493de84f5123689c64ae6
|
||||
body=$(curl -fsS --max-time 15 -H "Authorization: Bearer $GH_TOKEN" "$gist" 2>/dev/null || curl -fsS --max-time 15 "$gist" 2>/dev/null || echo '{}')
|
||||
IDLE_AT=$(jq -r 'select(.owner.login == "maxjivi05") | .files["idle-at"].content // "0"' <<< "$body" 2>/dev/null || echo 0)
|
||||
echo "Heartbeat: ${IDLE_AT:-none}"
|
||||
age=-1
|
||||
if [[ "${IDLE_AT:-0}" =~ ^[0-9]+$ ]] && [ "${IDLE_AT:-0}" -gt 0 ]; then
|
||||
age=$(( $(date +%s) - IDLE_AT ))
|
||||
fi
|
||||
if [ "$age" -ge 0 ] && [ "$age" -lt 180 ]; then
|
||||
labels='"droiddeck-fork"'
|
||||
fi
|
||||
echo "labels=$labels" >> "$GITHUB_OUTPUT"
|
||||
echo "Compile runner: \`$labels\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -1,9 +1,9 @@
|
||||
name: PR Release Cleanup
|
||||
run-name: remove PR
|
||||
|
||||
|
||||
|
||||
run-name: remove PR #${{ github.event.pull_request.number }}'s test build
|
||||
|
||||
# A merged or closed PR's test build leaves DroidDeck-CI, so the app's Updates page stops offering it
|
||||
# (a merged fix is in the next Preview). pull_request_target for the publisher app's secrets; nothing
|
||||
# from the PR is checked out or run.
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ closed ]
|
||||
@@ -13,27 +13,13 @@ permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: pr-release-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check the current PR state
|
||||
id: state
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
state=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .state)
|
||||
echo "closed=$([ "$state" = closed ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
# A token of the org's "DroidDeck CI Publisher" app, as build.yml's announce job uses.
|
||||
- uses: actions/create-github-app-token@v2
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
id: ci-token
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
@@ -41,43 +27,21 @@ jobs:
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-CI
|
||||
- name: Delete the test build
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
retry() {
|
||||
for attempt in 1 2 3 4; do
|
||||
"$@" && return 0
|
||||
[ "$attempt" = 4 ] || sleep $((attempt * 20))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
existing=$(retry gh api --paginate "repos/Droid-Deck/DroidDeck-CI/releases?per_page=100" --jq '.[].tag_name')
|
||||
found=0
|
||||
for tag in "pr-$PR_NUMBER" "pr-$PR_NUMBER.0"; do
|
||||
if grep -qxF "$tag" <<<"$existing"; then
|
||||
retry gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag
|
||||
if gh release view "$tag" -R Droid-Deck/DroidDeck-CI >/dev/null 2>&1; then
|
||||
gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag
|
||||
echo "Deleted $tag"
|
||||
found=1
|
||||
fi
|
||||
done
|
||||
[ "$found" = 1 ] || echo "No test build for PR #$PR_NUMBER"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
- name: Retire the Discord PR announcement
|
||||
if: ${{ steps.state.outputs.closed == 'true' }}
|
||||
env:
|
||||
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: python3 tools/release/pr_discord.py retire
|
||||
|
||||
catalog:
|
||||
needs: cleanup
|
||||
if: ${{ always() }}
|
||||
|
||||
@@ -8,13 +8,11 @@ on:
|
||||
description: Open pull request number
|
||||
required: true
|
||||
type: string
|
||||
automatic_build_run_id:
|
||||
description: Completed build run for automatic publishing
|
||||
required: false
|
||||
type: string
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
branches: [main]
|
||||
# A PR from a branch of this repo is signed on its own after each green build: only people who
|
||||
# can push here can change it. A fork's PR waits for a maintainer's publish-test label.
|
||||
workflow_run:
|
||||
workflows: [Build APK]
|
||||
types: [completed]
|
||||
@@ -28,50 +26,18 @@ permissions:
|
||||
concurrency:
|
||||
group: pr-release-${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
dispatch-fork-builds:
|
||||
if: >-
|
||||
github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.head_repository.full_name != github.repository
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: write
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Find fork PRs for the completed build
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
RUN_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
RUN_REPO: ${{ github.event.workflow_run.head_repository.full_name }}
|
||||
RUN_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
owner=${RUN_REPO%%/*}
|
||||
gh api --paginate --slurp -X GET "repos/$GITHUB_REPOSITORY/pulls" \
|
||||
-f state=open -f base=main -f "head=$owner:$RUN_BRANCH" -f per_page=100 |
|
||||
jq -r --arg sha "$RUN_SHA" --arg repo "$RUN_REPO" --arg branch "$RUN_BRANCH" '
|
||||
.[][] | select(.head.sha == $sha and .head.repo.full_name == $repo and .head.ref == $branch) | .number' |
|
||||
while IFS= read -r number; do
|
||||
gh workflow run pr-release-publish.yml -R "$GITHUB_REPOSITORY" --ref main \
|
||||
-f "pr_number=$number" -f "automatic_build_run_id=$RUN_ID"
|
||||
done
|
||||
|
||||
authorize:
|
||||
if: >-
|
||||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
|
||||
(github.event_name == 'pull_request_target' &&
|
||||
(github.event.action != 'labeled' || github.event.label.name == 'publish-test')) ||
|
||||
(github.event_name == 'pull_request_target' && github.event.label.name == 'publish-test') ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.head_repository.full_name == github.repository &&
|
||||
github.event.workflow_run.pull_requests[0] != null)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 150
|
||||
timeout-minutes: 45
|
||||
outputs:
|
||||
pr_number: ${{ steps.gate.outputs.pr_number }}
|
||||
run_id: ${{ steps.gate.outputs.run_id }}
|
||||
@@ -85,8 +51,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
# Every variant a main build ships, so a PR build can be tried under any of them.
|
||||
- name: The packages a signed build ships as
|
||||
id: variants
|
||||
run: echo "list=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
|
||||
@@ -97,14 +63,24 @@ jobs:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
REQUESTER: ${{ github.actor }}
|
||||
PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
|
||||
ACTION: ${{ github.event.action }}
|
||||
BUILT_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
AUTO_RUN: ${{ inputs.automatic_build_run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid PR number '$PR_NUMBER'"; exit 1; }
|
||||
# The automatic path stands down quietly, never failing: a draft, a PR labelled
|
||||
# no-test-build, or a build that a later push has already overtaken.
|
||||
skip() { echo "::notice::Not publishing PR #$PR_NUMBER: $1"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
|
||||
if [ "$EVENT_NAME" = pull_request_target ] && [ "$ACTION" = labeled ]; then
|
||||
if [ "$EVENT_NAME" = workflow_run ]; then
|
||||
echo "automatic=true" >> "$GITHUB_OUTPUT"
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
[ "$(jq -r .state <<<"$pr")" = open ] || skip "it is not open"
|
||||
[ "$(jq -r .draft <<<"$pr")" = false ] || skip "it is a draft"
|
||||
[ "$(jq -r .base.ref <<<"$pr")" = main ] || skip "it does not target main"
|
||||
[ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] || skip "it comes from a fork"
|
||||
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
|
||||
[ "$(jq -r .head.sha <<<"$pr")" = "$BUILT_SHA" ] || skip "a newer push is building"
|
||||
fi
|
||||
if [ "$EVENT_NAME" = pull_request_target ]; then
|
||||
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$REQUESTER/permission" --jq .permission)
|
||||
case "$permission" in
|
||||
admin|maintain|write) ;;
|
||||
@@ -113,59 +89,34 @@ jobs:
|
||||
fi
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
jq -e '.state == "open" and .draft == false and .base.ref == "main"' <<<"$pr" >/dev/null ||
|
||||
skip "it must be open, ready for review, and target main"
|
||||
if [ "$EVENT_NAME" = workflow_run ]; then
|
||||
[ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] || skip "it comes from a fork"
|
||||
[ "$(jq -r .head.sha <<<"$pr")" = "$BUILT_SHA" ] || skip "a newer push is building"
|
||||
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
|
||||
echo "automatic=true" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$EVENT_NAME" = workflow_dispatch ] && [ -n "$AUTO_RUN" ]; then
|
||||
[[ "$AUTO_RUN" =~ ^[1-9][0-9]*$ ]] || { echo '::error::Invalid automatic build run ID'; exit 1; }
|
||||
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
|
||||
git fetch --no-tags origin main
|
||||
opener=$(jq -r .user.login <<<"$pr")
|
||||
git show origin/main:.github/trusted-test-builds.txt | grep -Fixq -- "$opener" || skip "PR opener @$opener is not trusted for automatic signing"
|
||||
echo "automatic=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
{ echo "::error::PR #$PR_NUMBER must be open, ready for review, and target main"; exit 1; }
|
||||
head=$(jq -r '.head.sha' <<<"$pr")
|
||||
base=$(jq -r '.base.sha' <<<"$pr")
|
||||
|
||||
# The label is often added while the PR's build is still running, or just after a push
|
||||
# before it has started: wait for the latest build of the head commit to finish (a PR
|
||||
# build takes about 4 minutes) rather than fail and need the label added again.
|
||||
run_id=
|
||||
for attempt in $(seq 1 240); do
|
||||
latest=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/build.yml/runs?event=pull_request&head_sha=$head&per_page=100" |
|
||||
jq -c --arg head "$head" --arg repo "$(jq -r .head.repo.full_name <<<"$pr")" --arg ref "$(jq -r .head.ref <<<"$pr")" '
|
||||
[.workflow_runs[] | select(.head_sha == $head and .head_repository.full_name == $repo and .head_branch == $ref)] |
|
||||
sort_by([.created_at, .run_attempt]) | last // empty')
|
||||
if [[ -z "$latest" && "$attempt" == 1 ]]; then
|
||||
git fetch --no-tags origin main
|
||||
git fetch --no-tags origin "refs/pull/$PR_NUMBER/head"
|
||||
git diff --no-renames --name-only -z "$base...FETCH_HEAD" > "$RUNNER_TEMP/pr-changed-paths"
|
||||
excluded=true
|
||||
while IFS= read -r -d '' path; do
|
||||
case "$path" in
|
||||
*.md|LICENSE|.gitignore) ;;
|
||||
*) excluded=false; break ;;
|
||||
esac
|
||||
done < "$RUNNER_TEMP/pr-changed-paths"
|
||||
[ "$excluded" = false ] || skip "its changes do not trigger the APK build"
|
||||
fi
|
||||
for attempt in $(seq 1 60); do
|
||||
latest=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml \
|
||||
--event pull_request --commit "$head" \
|
||||
--json databaseId,headSha,status,conclusion,createdAt,attempt -L 100 |
|
||||
jq -c --arg head "$head" '[.[] | select(.headSha == $head)] |
|
||||
sort_by([.createdAt, .attempt]) | last // empty')
|
||||
if [[ -n "$latest" && $(jq -r .status <<<"$latest") == completed ]]; then
|
||||
conclusion=$(jq -r .conclusion <<<"$latest")
|
||||
if [[ "$conclusion" == success ]]; then
|
||||
run_id=$(jq -r .id <<<"$latest")
|
||||
if [ -n "$AUTO_RUN" ] && [ "$run_id" != "$AUTO_RUN" ]; then
|
||||
skip "a newer build exists"
|
||||
fi
|
||||
break
|
||||
fi
|
||||
[[ "$conclusion" == action_required ]] || { echo "::error::The latest PR build is not green ($conclusion)"; exit 1; }
|
||||
[[ "$conclusion" == success ]] || { echo "::error::The latest PR build is not green ($conclusion)"; exit 1; }
|
||||
run_id=$(jq -r .databaseId <<<"$latest")
|
||||
break
|
||||
fi
|
||||
status=not-started
|
||||
[[ -z "$latest" ]] || status=$(jq -r .status <<<"$latest")
|
||||
echo "Waiting for the PR build of ${head:0:7} to finish ($status)"
|
||||
sleep 30
|
||||
done
|
||||
[[ -n "$run_id" ]] || { echo '::error::The PR build did not finish within 120 minutes'; exit 1; }
|
||||
[[ -n "$run_id" ]] || { echo '::error::The PR build did not finish within 30 minutes'; exit 1; }
|
||||
# GitHub leaves a run's pull_requests empty when its head is in a fork, so the build is
|
||||
# tied to this PR by its head repository, branch and commit instead.
|
||||
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id")
|
||||
jq -e --arg head "$head" --arg repo "$(jq -r .head.repo.full_name <<<"$pr")" --arg ref "$(jq -r .head.ref <<<"$pr")" '
|
||||
.event == "pull_request" and .head_sha == $head and .conclusion == "success" and
|
||||
@@ -175,6 +126,8 @@ jobs:
|
||||
|
||||
sdk=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}
|
||||
tools=$(find "$sdk/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1)
|
||||
# A PR build is made from the PR merged onto main, so it must install over main's own
|
||||
# build. main is what this job checked out; no release has to exist or be marked latest.
|
||||
main_code=$(sed -n 's/^[[:space:]]*versionCode[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' app/build.gradle)
|
||||
[[ "$main_code" =~ ^[0-9]+$ ]] || { echo "::error::No numeric versionCode in main's app/build.gradle"; exit 1; }
|
||||
mkdir -p "$RUNNER_TEMP/pr"
|
||||
@@ -195,13 +148,6 @@ jobs:
|
||||
.head.sha == $head and .base.sha == $base
|
||||
' <<<"$current" >/dev/null ||
|
||||
{ echo "::error::PR #$PR_NUMBER moved on while its build was checked; publish again"; exit 1; }
|
||||
if [ "$EVENT_NAME" = workflow_run ] || { [ "$EVENT_NAME" = workflow_dispatch ] && [ -n "$AUTO_RUN" ]; }; then
|
||||
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$current" >/dev/null && skip "it is labelled no-test-build"
|
||||
if [ "$EVENT_NAME" = workflow_dispatch ]; then
|
||||
git fetch --no-tags origin main
|
||||
git show origin/main:.github/trusted-test-builds.txt | grep -Fixq -- "$(jq -r .user.login <<<"$current")" || skip "the PR opener is no longer trusted"
|
||||
fi
|
||||
fi
|
||||
{
|
||||
echo "pr_number=$PR_NUMBER"
|
||||
echo "run_id=$run_id"
|
||||
@@ -232,36 +178,13 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: droiddeck-pr-signed-*
|
||||
merge-multiple: true
|
||||
path: signed
|
||||
- name: The add-on packs main has not published
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN: ${{ needs.authorize.outputs.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-packs -D packs
|
||||
mkdir -p pr-packs
|
||||
for file in packs/*.tar.zst; do
|
||||
name=$(basename "$file" .tar.zst)
|
||||
sha=$(sha256sum "$file" | cut -d' ' -f1)
|
||||
tag="pack-$name-${sha:0:16}"
|
||||
digest=$({ gh api "repos/Droid-Deck/DroidDeck-Components/releases/tags/$tag" 2>/dev/null || echo '{}'; } |
|
||||
jq -r --arg n "$name.tar.zst" '.assets // [] | .[] | select(.name == $n) | .digest // empty')
|
||||
if [ "$digest" = "sha256:$sha" ]; then
|
||||
echo "$name: served from DroidDeck-Components $tag"
|
||||
else
|
||||
cp "$file" "pr-packs/$tag.tar.zst"
|
||||
echo "$name: attached to the PR release as $tag.tar.zst"
|
||||
fi
|
||||
done
|
||||
# What this build is, for the top of the release notes: the PR's title and the first
|
||||
# paragraph of its description.
|
||||
- name: Describe the change
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -280,6 +203,7 @@ jobs:
|
||||
fi
|
||||
} > notes-head.md
|
||||
cat notes-head.md
|
||||
# A token of the org's "DroidDeck CI Publisher" app, as build.yml's announce job uses.
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: ci-token
|
||||
with:
|
||||
@@ -319,9 +243,9 @@ jobs:
|
||||
first=true
|
||||
fi
|
||||
echo "first=$first" >> "$GITHUB_OUTPUT"
|
||||
cp pr-packs/* signed/ 2>/dev/null || true
|
||||
gh release upload "$tag" signed/* -R "$repo" --clobber
|
||||
gh release edit "$tag" -R "$repo" --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
|
||||
# Drop the previous build's files: anything this build did not just upload.
|
||||
keep=$(cd signed && ls | jq -R . | jq -sc .)
|
||||
gh api "repos/$repo/releases/tags/$tag" |
|
||||
jq -r --argjson keep "$keep" '.assets[] | select(.name as $n | $keep | index($n) | not) | .id' |
|
||||
@@ -336,24 +260,17 @@ jobs:
|
||||
echo "release=$release" >> "$GITHUB_OUTPUT"
|
||||
echo "apk=$GITHUB_SERVER_URL/$repo/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Whether the PR opener is trusted
|
||||
id: trusted-opener
|
||||
- name: Whether the PR's branch is in this repo
|
||||
id: same-repo
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
run: |
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
opener=$(jq -r .user.login <<<"$pr")
|
||||
git fetch --no-tags origin main
|
||||
if [ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] ||
|
||||
git show origin/main:.github/trusted-test-builds.txt | grep -Fixq -- "$opener"; then
|
||||
echo "trusted=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "trusted=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
head_repo=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .head.repo.full_name)
|
||||
echo "same=$([ "$head_repo" = "$GITHUB_REPOSITORY" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clear the publish request label
|
||||
if: ${{ github.event_name == 'pull_request_target' && github.event.action == 'labeled' && steps.publish.outcome == 'success' }}
|
||||
if: ${{ github.event_name == 'pull_request_target' && steps.publish.outcome == 'success' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
@@ -367,12 +284,12 @@ jobs:
|
||||
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
|
||||
RELEASE: ${{ steps.publish.outputs.release }}
|
||||
APK: ${{ steps.publish.outputs.apk }}
|
||||
TRUSTED_OPENER: ${{ steps.trusted-opener.outputs.trusted }}
|
||||
SAME_REPO: ${{ steps.same-repo.outputs.same }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
short=${HEAD_SHA:0:7}
|
||||
mark='<!-- droiddeck-pr-release -->'
|
||||
if [ "$TRUSTED_OPENER" = true ]; then
|
||||
if [ "$SAME_REPO" = true ]; then
|
||||
NEXT='Each green build of a new push is signed and published here on its own. Label the PR `no-test-build` to stop that.'
|
||||
else
|
||||
NEXT='Pushes to this PR do not re-sign automatically. After a new green build, a maintainer can add the `publish-test` label again.'
|
||||
@@ -390,27 +307,39 @@ jobs:
|
||||
$NEXT
|
||||
EOF
|
||||
)
|
||||
comment=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate --slurp | \
|
||||
jq "[.[][] | select(.user.login == \"github-actions[bot]\") | select(.body | startswith(\"$mark\"))] | first // {}")
|
||||
id=$(jq -r '.id // empty' <<<"$comment")
|
||||
id=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate \
|
||||
--jq ".[] | select(.body | startswith(\"$mark\")) | .id" | head -1 || true)
|
||||
if [ -n "$id" ]; then
|
||||
state=$(jq -r '.body | split("\n")[] | select(startswith("<!-- droiddeck-discord: ") and endswith(" -->"))' <<<"$comment")
|
||||
if [ -n "$state" ]; then body=$(printf '%s\n%s' "$body" "$state"); fi
|
||||
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -f body="$body" >/dev/null
|
||||
else
|
||||
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" -f body="$body" >/dev/null
|
||||
fi
|
||||
|
||||
- name: Update the Discord PR announcement
|
||||
if: ${{ always() && steps.publish.outcome == 'success' }}
|
||||
# Asked for (the label, a manual run): always announced. Signed on its own after a push:
|
||||
# only a PR's first test build is, and later ones update its release quietly.
|
||||
- name: Post to Discord
|
||||
if: ${{ always() && steps.publish.outcome == 'success' && (needs.authorize.outputs.automatic != 'true' || steps.publish.outputs.first == 'true') }}
|
||||
env:
|
||||
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
|
||||
WEBHOOK: ${{ secrets.DISCORD_BUILDS_WEBHOOK }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
||||
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
|
||||
RELEASE: ${{ steps.publish.outputs.release }}
|
||||
APK: ${{ steps.publish.outputs.apk }}
|
||||
run: python3 tools/release/pr_discord.py publish
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$WEBHOOK" ]; then
|
||||
echo "::notice::No DISCORD_BUILDS_WEBHOOK secret; nothing posted"
|
||||
exit 0
|
||||
fi
|
||||
short=${HEAD_SHA:0:7}
|
||||
title=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .title)
|
||||
what="[#$PR_NUMBER]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$PR_NUMBER): $title"
|
||||
get=$(printf 'Download: [standard APK](<%s>) · [all variants](<%s>)' "$APK" "$RELEASE")
|
||||
content=$(printf '**New signed PR test build** \`pr-%s\` · \`%s\`\n%s\n_Not merged — for testing._\n%s' "$PR_NUMBER" "$short" "$what" "$get")
|
||||
jq -n --arg content "$content" '{content: $content, allowed_mentions: {parse: []}}' |
|
||||
curl -fsS -H 'Content-Type: application/json' -d @- "$WEBHOOK" -o /dev/null
|
||||
echo "Posted: $content"
|
||||
|
||||
catalog:
|
||||
needs: publish
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
name: PR Release Sweep
|
||||
run-name: sweep test builds of closed PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: pr-release-sweep
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sweep:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: ci-token
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-CI
|
||||
- name: Delete test builds whose PR is no longer open
|
||||
env:
|
||||
CI_TOKEN: ${{ steps.ci-token.outputs.token }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
retry() {
|
||||
for attempt in 1 2 3 4; do
|
||||
"$@" && return 0
|
||||
[ "$attempt" = 4 ] || sleep $((attempt * 20))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
tags=$(GH_TOKEN="$CI_TOKEN" retry gh api --paginate "repos/Droid-Deck/DroidDeck-CI/releases?per_page=100" --jq '.[].tag_name' |
|
||||
grep -E '^pr-[0-9]+(\.0)?$' || true)
|
||||
failed=0
|
||||
for tag in $tags; do
|
||||
number=${tag#pr-}
|
||||
number=${number%.0}
|
||||
state=$(retry gh api "repos/$GITHUB_REPOSITORY/pulls/$number" --jq .state) || { failed=1; continue; }
|
||||
if [ "$state" != open ]; then
|
||||
if GH_TOKEN="$CI_TOKEN" retry gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag; then
|
||||
echo "Deleted $tag (PR #$number is $state)"
|
||||
else
|
||||
failed=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
@@ -1,77 +0,0 @@
|
||||
name: Publish add-on packs
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_id:
|
||||
description: The Build APK run whose packs to publish; empty for the calling run's own
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish add-on packs
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
concurrency:
|
||||
group: publish-packs
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: The packs (this run's build)
|
||||
if: inputs.run_id == ''
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: droiddeck-packs
|
||||
path: packs
|
||||
|
||||
- name: The packs (another Build APK run)
|
||||
if: inputs.run_id != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN: ${{ inputs.run_id }}
|
||||
run: gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-packs -D packs
|
||||
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: components-token
|
||||
with:
|
||||
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
||||
owner: Droid-Deck
|
||||
repositories: DroidDeck-Components
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.components-token.outputs.token }}
|
||||
REPO: Droid-Deck/DroidDeck-Components
|
||||
BUILD_RUN: ${{ inputs.run_id || github.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
published_digest() {
|
||||
{ gh api "repos/$REPO/releases/tags/$1" 2>/dev/null || echo '{}'; } |
|
||||
jq -r --arg n "$2" '.assets // [] | .[] | select(.name == $n) | .digest // empty'
|
||||
}
|
||||
commit=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN" --jq .head_sha 2>/dev/null || echo "$GITHUB_SHA")
|
||||
for name in proot directaudio tracking xbox comet butler powervr; do
|
||||
file="packs/$name.tar.zst"
|
||||
[ -f "$file" ] || { echo "::error::$name.tar.zst is missing from Build APK run $BUILD_RUN"; exit 1; }
|
||||
sha=$(sha256sum "$file" | cut -d' ' -f1)
|
||||
tag="pack-$name-${sha:0:16}"
|
||||
digest=$(published_digest "$tag" "$name.tar.zst")
|
||||
if [ -z "$digest" ]; then
|
||||
notes="DroidDeck add-on pack \`$name\`, built by [Build APK run $BUILD_RUN]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN) from \`$commit\` with tools/packs/build.sh and published by [run $GITHUB_RUN_ID]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID).
|
||||
|
||||
Size: $(stat -c %s "$file") bytes. SHA-256: $sha. APKs built in the same run pin this digest."
|
||||
gh release create "$tag" -R "$REPO" --title "$tag" --notes "$notes" --latest=false "$file" \
|
||||
|| echo "::warning::creating $tag failed; checking whether another run published it"
|
||||
digest=$(published_digest "$tag" "$name.tar.zst")
|
||||
fi
|
||||
if [ "$digest" != "sha256:$sha" ]; then
|
||||
echo "::error::$tag holds ${digest:-nothing} for $name.tar.zst, not sha256:$sha"
|
||||
exit 1
|
||||
fi
|
||||
echo "- \`$name\`: [$tag]($GITHUB_SERVER_URL/$REPO/releases/tag/$tag) \`$sha\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
done
|
||||
@@ -63,19 +63,10 @@ jobs:
|
||||
artifact: droiddeck-release
|
||||
secrets: inherit
|
||||
|
||||
packs:
|
||||
needs: find
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
uses: ./.github/workflows/publish-packs.yml
|
||||
with:
|
||||
run_id: ${{ needs.find.outputs.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
# The release is made here, from the apks this run signed, so it traces back to its Build APK run.
|
||||
publish:
|
||||
name: publish the release
|
||||
needs: [find, sign, packs]
|
||||
needs: [find, sign]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
@@ -103,6 +94,7 @@ jobs:
|
||||
if gh release view "$VERSION" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
echo "::error::release $VERSION already exists"; exit 1
|
||||
fi
|
||||
# The notes' first line is the title; media paths become raw URLs at this release's tag.
|
||||
title=$(sed -n '1s/^# *//p' "$notes")
|
||||
[ -n "$title" ] || title="DroidDeck $VERSION"
|
||||
raw="https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/docs/releases/media/"
|
||||
@@ -118,6 +110,7 @@ jobs:
|
||||
--target "$GITHUB_SHA" --title "$title" --notes-file "$RUNNER_TEMP/body.md" --latest
|
||||
echo "Published [$title]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$VERSION)" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# A release made with this run's token fires no release event, so the in-app catalog is refreshed here.
|
||||
catalog:
|
||||
needs: publish
|
||||
uses: ./.github/workflows/update-catalog.yml
|
||||
|
||||
@@ -13,13 +13,13 @@ permissions:
|
||||
concurrency:
|
||||
group: update-app-catalog
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
catalog:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
# Always use the current trusted generator, even when a release event points at an older tag.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
+6
-8
@@ -8,19 +8,19 @@ captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
.kotlin/
|
||||
# Built in CI from tools/linuxfs and app/src/main/cpp/fakeinput.cpp, never committed, so the two
|
||||
# copies cannot drift.
|
||||
app/src/main/assets/linuxfs/
|
||||
app/src/main/jniLibs/arm64-v8a/libproot.so
|
||||
app/src/main/jniLibs/arm64-v8a/.proot-inputs
|
||||
app/src/main/jniLibs/arm64-v8a/.proot-outputs
|
||||
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
|
||||
app/src/main/jniLibs/arm64-v8a/libsyncthingnative.so
|
||||
app/src/main/jniLibs/arm64-v8a/.syncthing-inputs
|
||||
app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so
|
||||
app/src/main/rust/stores/target/
|
||||
app/src/main/assets/droiddeck-esync/
|
||||
|
||||
# Kotlin compiler session files (build-tool state, never source).
|
||||
.kotlin/
|
||||
|
||||
# Signing keys never enter the repo (DroidDeck's lives in GitHub environment secrets; see
|
||||
# tools/release/make-release-key.sh). keystore/testkey.p12 is the public AOSP testkey.
|
||||
*.jks
|
||||
*.keystore
|
||||
*.p12
|
||||
@@ -32,7 +32,5 @@ droiddeck-release-key/
|
||||
password.txt
|
||||
__pycache__/
|
||||
|
||||
# Local signing with the release key (tools/build_local.sh); holds a password.
|
||||
.signing.env
|
||||
app/src/main/assets/directaudio/
|
||||
app/src/main/assets/hybris/
|
||||
/tools/vr/out/
|
||||
@@ -1,6 +0,0 @@
|
||||
[submodule "external/MaliBridge"]
|
||||
path = external/MaliBridge
|
||||
url = https://github.com/ASEbrahim/MaliBridge.git
|
||||
[submodule "tools/syncthing/syncthing-android"]
|
||||
path = tools/syncthing/syncthing-android
|
||||
url = https://github.com/researchxxl/syncthing-android
|
||||
@@ -1,4 +0,0 @@
|
||||
# Copy to .signing.env, fill in, chmod 600 .signing.env
|
||||
RELEASE_KEYSTORE=/path/to/release.p12
|
||||
RELEASE_STORE_PASSWORD=change-me
|
||||
RELEASE_KEY_ALIAS=droiddeck
|
||||
@@ -1,3 +0,0 @@
|
||||
# Rules
|
||||
|
||||
- Comments should be rare, factual, and explain only non-obvious external constraints or business invariants.
|
||||
@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
DroidDeck brings the SteamOS experience to Android
|
||||
Copyright (C) 2026 DroidDeck contributors
|
||||
Pluvia is an unofficial Steam client app for Android
|
||||
Copyright (C) 2024 Oxters Wyzgowski
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
DroidDeck Copyright (C) 2026 DroidDeck contributors
|
||||
Pluvia Copyright (C) 2024 Oxters Wyzgowski
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
@@ -5,158 +5,32 @@
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
DroidDeck brings the SteamOS experience to Android: Steam Gaming Mode, full Linux desktop, integration with GOG, Epic Games, Xbox, and other storefronts, Steam VR, and more!
|
||||
DroidDeck brings the SteamOS experience to Android: Valve's Steam client in Big Picture on your Adreno handheld, with Windows games through Valve's ARM64 Proton.
|
||||
|
||||
<p align="center"><b>100% open source. No ads. No telemetry. No proprietary components. No source withheld.</b></p>
|
||||
<p align="center"><a href="https://discord.gg/JRGAvawjsm"><img src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white" alt="Join the DroidDeck Discord"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Droid-Deck/DroidDeck/releases/download/0.6.9/DroidDeck-0.6.9.apk"><img src="https://img.shields.io/badge/Download-DroidDeck%200.6.9%20APK-3DDC84?style=for-the-badge&logo=android&logoColor=white" alt="Download DroidDeck 0.6.9"></a>
|
||||
<a href="https://discord.gg/JRGAvawjsm"><img src="https://img.shields.io/badge/Discord-Join%20us-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join the DroidDeck Discord"></a>
|
||||
</p>
|
||||
<p align="center"><img src="docs/releases/media/0.2.0/launch-into-steam.gif" width="80%" alt="Tapping DroidDeck on the Android home screen and landing in Steam Big Picture"></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.star-history.com/droid-deck/droiddeck"><img src="https://api.star-history.com/badge?repo=Droid-Deck/DroidDeck&type=rank&theme=dark" alt="Star History Rank"></a>
|
||||
</p>
|
||||
> Note: DroidDeck does not have a stand-alone website. Do not click on any download links from websites claiming to be the DroidDeck team.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/267852"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/daily?language=C" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"></a>
|
||||
<a href="https://trendshift.io/repositories/267852"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/daily?language=Kotlin" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"></a>
|
||||
</p>
|
||||
## Requirements and install
|
||||
|
||||
<p align="center"><img src="docs/media/steam-to-desktop.gif" width="100%" alt="On a Retroid Pocket Duo: Play Steam from the DroidDeck launcher, open Pac-Man in Steam Big Picture and play it, exit the game, then Switch to Desktop from the Power menu"></p>
|
||||
Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Adreno 6xx is experimental: DirectX 11 uses DXVK 2 and may run, and DirectX 12 games can still crash. Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from [Releases](https://github.com/Droid-Deck/DroidDeck/releases), install the Linux runtime, then press **Play** and sign in. Steam downloads on first launch. Install **Desktop & apps** to use the desktop and emulators. The **Store** installs Linux apps and games from Flathub (ARM64 builds) with Flatpak; with additional options to install Appimages and set up scripts.
|
||||
|
||||
> DroidDeck has no website. Only download it from this repository's [Releases](https://github.com/Droid-Deck/DroidDeck/releases).
|
||||
Before Steam launches, you must turn off **Restrict child processes** in Developer options. If this option is not available in developer settings (Android 12 and 13 devices), first launch of Steam will present a "Fix it for me" button, which will help automate the setup process.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/releases/media/0.6.7/library.gif" width="49%" alt="The Library switching source chips, changing the card size and opening the Filter menu">
|
||||
<img src="docs/releases/media/0.3.0/in-game.gif" width="49%" alt="A Windows game running through Proton with the on-screen controls">
|
||||
<img src="docs/releases/media/0.4.0/multi-controller.gif" width="49%" alt="Two controllers playing split screen on one handheld">
|
||||
<img src="docs/releases/media/0.6.7/ubuntu-desktop-discover.jpg" width="49%" alt="The Kubuntu desktop with Discover open">
|
||||
</p>
|
||||
## Community
|
||||
|
||||
## Install
|
||||
|
||||
1. Android 9 or newer on Adreno 730 or newer, or any 8xx. Adreno 6xx is experimental. Mali, Xclipse, PowerVR and Adreno 710 are unsupported. No root needed.
|
||||
2. About 3 GB free for the runtime, plus 1.1 GB for the desktop and emulators.
|
||||
3. Install the [APK](https://github.com/Droid-Deck/DroidDeck/releases/download/0.6.9/DroidDeck-0.6.9.apk), install the Linux runtime, press **Play** and sign in to Steam.
|
||||
4. On Android 14 and newer, turn on **Disable child process restrictions** in Developer options. On Android 12 and 13, Steam's first launch shows a **Fix it for me** button that walks you through it.
|
||||
|
||||
Smart glasses setup is in the [support matrix](docs/smart-glasses-support.md). Release notes are in [docs/releases](docs/releases).
|
||||
|
||||
Help, Preview builds and device reports are on [Discord](https://discord.gg/JRGAvawjsm). For bugs, post in **#bug-reports** with the zip from **Share logs**.
|
||||
Join the [DroidDeck Discord](https://discord.gg/JRGAvawjsm) for help, Preview builds, and device reports. Bug reports go in its **#bug-reports** forum; attach the session folder from `Download/DroidDeck/` so the logs come with it.
|
||||
|
||||
## Build
|
||||
|
||||
Needs Docker, Java 17, the Android SDK and NDK, Rust via rustup, GitHub CLI, Python 3 and `zstd`.
|
||||
|
||||
```bash
|
||||
tools/build_local.sh
|
||||
```
|
||||
|
||||
The APK lands in `app/build/outputs/apk/release/`. Install it with `tools/deploy_local.sh`. After the first run, `./gradlew :app:assembleRelease` reuses the prepared inputs. Unit tests run with `./gradlew -PskipRust=true :app:testDebugUnitTest`, and the helper and preload suite with `bash tools/test_local.sh`.
|
||||
|
||||
On Windows, see `tools/run_windows.ps1` and `tools/prepare_windows_runtime.py`.
|
||||
Run `tools/build_local.sh` with Docker, Java 17, the Android SDK/NDK, and `zstd` installed. It builds the ARM64 audio sinks from PulseAudio 13.0 and packages them into the APK at `app/build/outputs/apk/release/app-release.apk`. Set `DROIDDECK_PA13_SOURCE_DIR` to an existing PulseAudio 13.0 source directory to skip downloading it. To install the APK on an attached device, run `tools/deploy_local.sh`.
|
||||
|
||||
## Limits
|
||||
|
||||
Compatibility and performance vary by device. Desktop compositing is software rendered, and Firefox sandboxing is reduced under proot.
|
||||
Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. See the session logs in `Download/DroidDeck/` when diagnosing problems.
|
||||
|
||||
## Credits and licence
|
||||
|
||||
DroidDeck is [GPL-3.0](LICENSE). Steam and Proton belong to Valve Corporation. VITURE, RayNeo, XREAL, Rokid, REDMAGIC, Nintendo Switch and Joy-Con are trademarks of their owners. This project is not affiliated with any of them.
|
||||
|
||||
<details>
|
||||
<summary><b>Runtime, Wine and Proton</b></summary>
|
||||
|
||||
- Runtime, shim, input and controller work build on WinNative and Bannerlator by maxjivi05.
|
||||
- mmap ring buffer controller input by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)).
|
||||
- [FEX](https://github.com/FEX-Emu/FEX) (MIT) runs x86 code. DroidDeck's build puts the platform name under `AT_PLATFORM` so [sharun](https://github.com/VHSgunzo/sharun) AppImages by VHSgunzo (MIT) start through its [userland-execve](https://crates.io/crates/userland-execve) loader (MIT). FEX's code disk cache was checked in its source.
|
||||
- x86 AppImages, and arm64 ones whose runtime can't unpack on Android, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence.
|
||||
- x86 programs linking OpenSSL 3 use the copy in the [Steam Linux Runtime 4.0](https://gitlab.steamos.cloud/steamrt/steamrt). x86 Linux games get the `/etc/asound.conf` that [pressure-vessel](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) writes.
|
||||
- `WriteProcessMemory` hooks fall back to `/proc/<pid>/mem` the way [Wine](https://gitlab.winehq.org/wine/wine)'s `server/ptrace.c` did before Wine 11.
|
||||
- Wine write watches use userfaultfd support in [Proton](https://github.com/ValveSoftware/wine)'s `dlls/ntdll/unix/virtual.c` (LGPL-2.1-or-later). libblsession supplies the arm64 syscall number and answers `PAGEMAP_SCAN` from `/proc/self/pagemap`.
|
||||
- libblsession runs Wine's arm64 `SIGUSR2` context handler (`dlls/ntdll/unix/signal_arm64.c`, LGPL-2.1-or-later) in place on a frame laid out like [Linux](https://github.com/torvalds/linux)'s `arch/arm64/kernel/signal.c`, and serves `fill_vm_counters` in `dlls/ntdll/unix/process.c` the kernel's own `/proc/<pid>/status`.
|
||||
- droiddeck-ntsync follows the [Linux](https://github.com/torvalds/linux) ntsync semantics in `Documentation/userspace-api/ntsync.rst` (GPL-2.0).
|
||||
- The proot fast path orders bindings like [PRoot](https://github.com/proot-me/proot)'s `path/binding.c` (GPL-2.0-or-later).
|
||||
- DiRT 3 and DiRT Showdown keep their telemetry socket the way RoccoC's DiRTTelemetryErrorFix (MIT) does, based on the fix by Carsten Gottschling.
|
||||
- Zink on PowerVR keeps colour textures in colour with Icenowy Zheng's upstream [Mesa](https://gitlab.freedesktop.org/mesa/mesa) fix d55418dae7 (MIT), carried as a runtime patch.
|
||||
- Games and AppImage, Flatpak and Snap programs such as emulators use [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's per render pass memory choice. The Steam client and Chromium-based apps keep `TU_DEBUG=sysmem`.
|
||||
- Black Ops III and Dark Souls Remastered start without [umu-protonfixes](https://github.com/Open-Wine-Components/umu-protonfixes) (BSD-2-Clause), whose `311210.py` and `570940.py` fail under ARM64 Proton.
|
||||
- [ReSkate](https://github.com/Dingo-Shenanigans/ReSkate) (GPL-3.0) by Dingo Shenanigans uses the launch option from its README and D3D12 feature level 12_1.
|
||||
- The arm64 client's sign in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Stores</b></summary>
|
||||
|
||||
- Epic Games and GOG build on [Phobos](https://github.com/phobos665)'s adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl). GOG install script registry entries are read the way [Heroic Games Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) (GPL-3.0) reads them.
|
||||
- Epic's official Linux launcher preview runs through FEX. That setup is outside Epic's listed Linux support.
|
||||
- Battle.net is by [@kayakyakr](https://github.com/kayakyakr). It runs Blizzard's own client in Proton (downloaded from Blizzard, not included) and shows key art from the [Battle.net Shop](https://shop.battle.net). Without Steam, its games see the pad as an Xbox 360 controller via `PROTON_SPOOF_STEAMINPUT_VIDPID` in [Proton](https://github.com/ValveSoftware/wine)'s `dlls/winebus.sys`.
|
||||
- Battle.net installs drive Blizzard's Agent the way [@barncastle](https://github.com/barncastle)'s [Battle.Net-Installer](https://github.com/barncastle/Battle.Net-Installer) does, with requests from [wowdev.wiki](https://wowdev.wiki/Agent). Read without taking code: [@mubaraknumann](https://github.com/mubaraknumann)'s [unifideck](https://github.com/mubaraknumann/unifideck), [@djmittens](https://github.com/djmittens)'s [neo-mittens](https://github.com/djmittens/neo-mittens) `bnetswitch`, [@BCD1210](https://github.com/BCD1210)'s [soju](https://github.com/BCD1210/soju) and [Lutris](https://github.com/lutris/lutris)'s `product.db` reader.
|
||||
- World of Warcraft addon imports replace an addon's folder from a temporary copy, as [WowUp](https://github.com/WowUp/WowUp) does, and read flavour folders the way [WoWDeck](https://github.com/sigboe/wowdeck) does. No code from either is used.
|
||||
- The World of Warcraft addon Download window searches [WoWInterface](https://www.wowinterface.com) through its MMOUI API. [CurseForge](https://www.curseforge.com/wow) opens in the user's browser, with no API and no key.
|
||||
- Xbox games load [@domsvoboda](https://github.com/domsvoboda)'s [xodus-nexus](https://github.com/domsvoboda/xodus-nexus) modules (LGPL-2.1-or-later), including `wintypes.dll`. Minecraft's GameInput setup was checked against [@Wyze3306](https://github.com/Wyze3306)'s [BedrockOnLinux](https://github.com/Wyze3306/BedrockOnLinux) (MIT).
|
||||
- The Xbox Cloud Gaming player uses the WebRTC client of [@unknownskl](https://github.com/unknownskl)'s [Greenlight](https://github.com/unknownskl/greenlight) (MIT), and its offer tweaks and sharpening follow [@redphx](https://github.com/redphx)'s [Better xCloud](https://github.com/redphx/better-xcloud) (MIT). See `tools/xcloud-player/NOTICE.md`.
|
||||
- The itch.io butler pack ships butler's `7z.so`, itch.io's build of [7-Zip](https://www.7-zip.org) by Igor Pavlov (LGPL-2.1, unRAR restriction on RAR code), unmodified. Other archives use Apache Commons Compress and the runtime's bsdtar (libarchive).
|
||||
- The Store uses [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1) through DroidDeck's own bubblewrap shim. Chromium and Electron apps start directly, since [zypak](https://github.com/refi64/zypak) by [@refi64](https://github.com/refi64) (BSD-3-Clause) always uses Flatpak's spawn portal.
|
||||
- Folder sync runs [Syncthing](https://github.com/syncthing/syncthing) (MPL-2.0), built from [@researchxxl](https://github.com/researchxxl)'s [syncthing-android](https://github.com/researchxxl/syncthing-android) fork (MPL-2.0).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Audio, video and HUD</b></summary>
|
||||
|
||||
- Steam audio goes through [@The412Banner](https://github.com/The412Banner)'s [DirectAudio](https://github.com/The412Banner/directaudio) PulseAudio sink (LGPL-2.1-or-later), built from `tools/directaudio/sink`.
|
||||
- The DroidDeck HUD is [@The412Banner](https://github.com/The412Banner)'s [FusionHUD](https://github.com/The412Banner/FusionHUD) (GPL-3.0 with an attribution term). Its readings match Valve's [MangoHud](https://github.com/flightlessmango/MangoHud) build (MIT), with frame times from gamescope patch 0125.
|
||||
- The HUD's CPU, GPU load, thermal and battery readers come from [@xXJSONDeruloXx](https://github.com/xXJSONDeruloXx)'s work.
|
||||
- LSFG frame generation comes from Camille LaVey and the [Eden](https://eden-emu.dev) project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported by [@maxjivi05](https://github.com/maxjivi05). It needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders.
|
||||
- Remote Play decodes through Android's MediaCodec or the [FFmpeg](https://ffmpeg.org) (LGPL-2.1-or-later) in Steam's runtime. Qualcomm low latency settings follow [Moonlight](https://github.com/moonlight-stream/moonlight-android) (GPL-3.0).
|
||||
- [PipeWire](https://gitlab.freedesktop.org/pipewire/pipewire) (MIT) sockets use the settings in its `module-access.c`, and [WirePlumber](https://gitlab.freedesktop.org/pipewire/wireplumber) (MIT) keeps those clients at full permissions.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Desktop</b></summary>
|
||||
|
||||
- The KDE pacman repository snapshots [Arch Linux ARM](https://archlinuxarm.org) and installs with [pacman](https://gitlab.archlinux.org/pacman/pacman) (GPL-2.0-or-later).
|
||||
- [KWin](https://invent.kde.org/plasma/kwin) (GPL-2.0-or-later) is patched to pass on the scroll data its `wayland_backend.cpp` leaves as a TODO.
|
||||
- Read without taking code: [Qt](https://code.qt.io/cgit/qt/qtbase.git)'s `qwaylandinputdevice.cpp` and `qguiapplication.cpp`, [Konsole](https://invent.kde.org/utilities/konsole)'s `TerminalDisplay.cpp`, and [Mesa](https://gitlab.freedesktop.org/mesa/mesa)'s `u_threaded_context.c` and `zink_resource.c`.
|
||||
- The Steam Keyboard window rule follows Valve's `steamdeck-kde-presets` with [Bazzite](https://github.com/ublue-os/bazzite)'s `0001-steam-virtual-keyboard.patch`.
|
||||
- Steam's Browse buttons open a GTK 3 file chooser via PyGObject (LGPL-2.1-or-later), shown over Steam by [gamescope](https://github.com/ValveSoftware/gamescope) (BSD-2-Clause) through `GetAppIdFromReaper`.
|
||||
- The AYN Odin 2 phantom second display was confirmed by [@SapphireRhodonite](https://github.com/SapphireRhodonite)'s [WatermelonDS 0.0.5](https://github.com/SapphireRhodonite/WatermelonDS/releases/tag/0.0.5) notes and [iiSU #363](https://github.com/iisu-network/iiSU/issues/363), without taking their code.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Smart glasses</b></summary>
|
||||
|
||||
- Builds on WinNative's VITURE work. Protocol checked against [@wheaney](https://github.com/wheaney)'s [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver), [@badicsalex](https://github.com/badicsalex)'s [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) and [go-macos/iokit](https://pkg.go.dev/github.com/go-macos/iokit/viture). The optional VITURE SDK is not included.
|
||||
- The VITURE protocol V2 frames adapt [@elasticjava](https://github.com/elasticjava)'s [viture-v2](https://github.com/elasticjava/viture-v2) (MIT) by Holger Bartnick. See `docs/third-party/viture-v2`.
|
||||
- RayNeo adapts [@informalTechCode](https://github.com/informalTechCode)'s [RayNeo-Air-Series-3DoF-Scaffold](https://github.com/informalTechCode/RayNeo-Air-Series-3DoF-Scaffold) (Apache-2.0) and references [@verncat](https://github.com/verncat)'s [RayNeo-Air-3S-Pro-OpenVR](https://github.com/verncat/RayNeo-Air-3S-Pro-OpenVR) (MIT) and [Monado](https://gitlab.freedesktop.org/monado/monado) (BSL-1.0). See [docs/third-party/rayneo](docs/third-party/rayneo/README.md).
|
||||
- XREAL 1S references XRLinuxDriver and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [@rohitsangwan01](https://github.com/rohitsangwan01)'s [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) (MIT) and [@0xcaff](https://github.com/0xcaff)'s [xr-tools](https://github.com/0xcaff/xr-tools).
|
||||
- Rokid follows ar-drivers-rs, @badicsalex's [Void Computing write-ups](https://voidcomputing.hu/blog/worse-better-prettier/) and Monado's Rokid driver.
|
||||
- REDMAGIC display findings draw on [@mekhontsev](https://github.com/mekhontsev)'s [MagicDesk investigation](https://github.com/mekhontsev/magicdesk/blob/main/docs/xr-resolution-fix.md) and [@Coding-BR](https://github.com/Coding-BR)'s [decompiled Nubia kernel](https://github.com/Coding-BR/android_kernel_nubia_sm8850_qwjujube).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>VR, Joy-Con and hand tracking</b></summary>
|
||||
|
||||
- Joy-Con VR ports the arm model from Google's [GVR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk) (Apache-2.0) and adapts the hand skeleton from Valve's [OpenVR](https://github.com/ValveSoftware/openvr) `handskeletonsimulation` sample (BSD-3-Clause), which follows Moshi Turner's work in Monado. Index offsets come from Monado's `vive_poses.c` (BSL-1.0).
|
||||
- Joy-Con research: Linux's [hid-nintendo](https://github.com/torvalds/linux/blob/master/drivers/hid/hid-nintendo.c) by Daniel J. Ogorchock, [@dekuNukem](https://github.com/dekuNukem)'s [Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering), [SDL](https://github.com/libsdl-org/SDL) and AOSP. Checked against [ALVR](https://github.com/alvr-org/ALVR), [OpenComposite](https://gitlab.com/znixian/OpenOVR), [PSMoveSteamVRBridge](https://github.com/HipsterSloth/PSMoveSteamVRBridge), [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr), [@krnelpan1c](https://github.com/krnelpan1c)'s [Daydream-Controller-SteamVR](https://github.com/krnelpan1c/Daydream-Controller-SteamVR), [@yoyobuae](https://github.com/yoyobuae)'s [joyconvr](https://github.com/yoyobuae/joyconvr), [Moonlight](https://github.com/moonlight-stream/moonlight-android) and [Dolphin](https://github.com/dolphin-emu/dolphin). See [docs/third-party/joycon](docs/third-party/joycon/README.md).
|
||||
- Camera hand tracking runs Google's [MediaPipe](https://github.com/google-ai-edge/mediapipe) Pose Landmarker (Lite), BlazePose GHUM 3D and Hand Landmarker (Apache-2.0) on [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) frames (Apache-2.0), with usage reports off. Checked against JoyConVr's camera app (MIT).
|
||||
- Tracking math: the GVR arm model's accelerometer mode, Eric Foxlin's zero velocity updates, Frosio, Pedersini and Borghese's accelerometer autocalibration, a Kalman filter (Kalman, 1960), Horn's quaternion method (1987) and the 1€ filter (Casiez, Roussel and Vogel, 2012).
|
||||
- Gestures follow [ALVR](https://github.com/alvr-org/ALVR)'s `hand_gestures.rs` (MIT) and Meta Quest's menu pinch, tuned on [HaGRID](https://github.com/hukenovs/hagrid) photos (CC BY-SA 4.0, not included).
|
||||
- The glove model follows OpenVR's render model reference, is meshed with surface nets (Gibson, 1998; Mikola Lysenko), and uses Inigo Quilez's smooth minimum, distance field ambient occlusion and polygon distance (MIT). See [docs/third-party/handtracking](docs/third-party/handtracking/README.md).
|
||||
|
||||
</details>
|
||||
|
||||
## Star history
|
||||
|
||||
<a href="https://www.star-history.com/#Droid-Deck/DroidDeck&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Droid-Deck/DroidDeck&type=Date&theme=dark">
|
||||
<img alt="Star history chart" src="https://api.star-history.com/svg?repos=Droid-Deck/DroidDeck&type=Date" width="100%">
|
||||
</picture>
|
||||
</a>
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence. See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
+39
-224
@@ -4,31 +4,6 @@ plugins {
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
|
||||
|
||||
def localBuildOptions = new Properties()
|
||||
def localBuildOptionsFile = rootProject.layout.projectDirectory.file('local.properties')
|
||||
if (localBuildOptionsFile.asFile.exists()) {
|
||||
localBuildOptions.load(new StringReader(providers.fileContents(localBuildOptionsFile).asText.get()))
|
||||
}
|
||||
def pythonExecutable = project.findProperty('pythonExecutable')
|
||||
?: localBuildOptions.getProperty('droiddeck.python', 'python3')
|
||||
def vitureSdkDir = project.hasProperty('vitureSdkDir') ? project.property('vitureSdkDir') : localBuildOptions.getProperty('droiddeck.vitureSdk', '')
|
||||
def uvcDir = project.hasProperty('uvcDir') ? project.property('uvcDir') : localBuildOptions.getProperty('droiddeck.uvcDir', '')
|
||||
if (uvcDir) {
|
||||
['arm64-v8a/libusb.so', 'arm64-v8a/libuvc.a', 'include/libusb.h', 'include/libuvc/libuvc.h',
|
||||
'licenses/libusb-COPYING', 'licenses/libuvc-LICENSE.txt'].each { required ->
|
||||
if (!new File(uvcDir, required).isFile()) throw new GradleException("Incomplete UVC build: missing ${required}")
|
||||
}
|
||||
}
|
||||
if (vitureSdkDir) {
|
||||
['android/arm64-v8a/libglasses.so', 'android/arm64-v8a/libcarina_vio.so',
|
||||
'android/arm64-v8a/libcloud_protocol.so', 'include/viture_glasses_provider.h',
|
||||
'android/LICENSE', 'android/NOTICE'].each { required ->
|
||||
if (!new File(vitureSdkDir, required).isFile()) {
|
||||
throw new GradleException("Incomplete VITURE Android SDK: missing ${required}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def gitValue = { List<String> arguments ->
|
||||
try {
|
||||
def output = providers.exec {
|
||||
@@ -74,49 +49,17 @@ if (!(treeState in ['clean', 'dirty'])) {
|
||||
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
|
||||
def escapedBuildLabel = buildLabel.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
// What the in-app updater compares against the published builds: the commit this build is of (a PR
|
||||
// build's head, not the merge CI makes of it), its PR, and whether CI made it at all.
|
||||
def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getOrNull()
|
||||
?: providers.environmentVariable('GITHUB_SHA').getOrNull() ?: gitValue(['rev-parse', 'HEAD']) ?: ''
|
||||
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
|
||||
def prTitle = providers.environmentVariable('DROIDDECK_PR_TITLE').getOrNull() ?: ''
|
||||
def buildBranch = providers.environmentVariable('GITHUB_HEAD_REF').getOrNull()?.trim()
|
||||
?: gitValue(['branch', '--show-current']) ?: ''
|
||||
def escapeJava = { String value -> value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', ' ').replace('\r', ' ') }
|
||||
def ciBuild = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true'
|
||||
// The release key's certificate: a build signed with it, from CI or a computer, updates in place.
|
||||
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', '--end-of-options', sourceCommit]) ?: '0').with { it.isLong() ? it : '0' }
|
||||
def desktopKde = rootProject.file('tools/desktop-kde/release.env').readLines()
|
||||
.findAll { it.contains('=') && !it.startsWith('#') }
|
||||
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
|
||||
def trackingPack = rootProject.file('tools/packs/tracking/release.env').readLines()
|
||||
.findAll { it.contains('=') && !it.startsWith('#') }
|
||||
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
|
||||
def prootPackPin = rootProject.file('tools/packs/proot/release.env').readLines()
|
||||
.findAll { it.contains('=') && !it.startsWith('#') }
|
||||
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
|
||||
def addonPacks = ['proot', 'directaudio', 'tracking', 'xbox', 'comet', 'butler', 'powervr'].collectEntries { name ->
|
||||
def pack = file("build/packs/${name}.tar.zst")
|
||||
if (!pack.isFile()) {
|
||||
if (ciBuild) throw new GradleException("Add-on pack ${pack} is missing: run tools/packs/build.sh app/build/packs")
|
||||
if (name == 'proot' && prootPackPin.PROOT_PACK_SHA256) {
|
||||
return [(name): [sha256: prootPackPin.PROOT_PACK_SHA256, size: (prootPackPin.PROOT_PACK_SIZE ?: '0') as long]]
|
||||
}
|
||||
return [(name): [sha256: '', size: 0L]]
|
||||
}
|
||||
def digest = java.security.MessageDigest.getInstance('SHA-256')
|
||||
pack.withInputStream { input ->
|
||||
byte[] buffer = new byte[1 << 20]
|
||||
int n
|
||||
while ((n = input.read(buffer)) > 0) digest.update(buffer, 0, n)
|
||||
}
|
||||
[(name): [sha256: digest.digest().encodeHex().toString(), size: pack.length()]]
|
||||
}
|
||||
def desktopRemovalSources = new groovy.json.JsonSlurper().parse(file('src/main/assets/desktop-removal/sources.json'))
|
||||
if (desktopRemovalSources.archives[desktopKde.DESKTOP_KDE_TAG] != desktopKde.DESKTOP_KDE_SHA256) {
|
||||
throw new GradleException('Desktop removal inventories do not match the pinned archive')
|
||||
}
|
||||
if (!file('src/main/assets/desktop-removal/versions.txt').readLines().contains(desktopKde.DESKTOP_KDE_TAG)) {
|
||||
throw new GradleException('Regenerate desktop removal inventories for ' + desktopKde.DESKTOP_KDE_TAG)
|
||||
}
|
||||
// When the source was committed, for "built 3 hours ago": fixed per commit, so it costs no rebuilds.
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', 'HEAD']) ?: '0').with { it.isLong() ? it : '0' }
|
||||
// tools/release/variants.txt as "package=suffix;...", so the updater picks the apk of the package it runs as.
|
||||
def variantSuffixes = rootProject.file('tools/release/variants.txt').readLines()
|
||||
.findAll { it.trim() && !it.startsWith('#') }
|
||||
.collect { def f = it.trim().split(/\s+/); "${f[1]}=${f[2] == '-' ? '' : f[2]}" }
|
||||
@@ -125,32 +68,26 @@ def variantSuffixes = rootProject.file('tools/release/variants.txt').readLines()
|
||||
android {
|
||||
namespace 'com.droiddeck.launcher'
|
||||
compileSdk 34
|
||||
// The NDK is pinned by property so CI can hand in whatever the runner already has rather than
|
||||
// downloading a second copy; see gradle.properties.
|
||||
ndkVersion project.findProperty('ndkVersion') ?: '27.3.13750724'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.droiddeck.launcher"
|
||||
minSdkVersion 26
|
||||
// 28 is not a leftover. An app on targetSdk 29+ runs in a domain that may not execute a
|
||||
// file out of its own data directory, and this app's entire job is to exec a rootfs:
|
||||
// proot, gamescope, the Steam client and everything it launches all live under files/.
|
||||
targetSdkVersion 28
|
||||
versionCode 18
|
||||
versionName "0.7.0"
|
||||
versionCode 11
|
||||
versionName "0.3.1"
|
||||
buildConfigField "String", "BUILD_LABEL", "\"${escapedBuildLabel}\""
|
||||
buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\""
|
||||
buildConfigField "int", "PR_NUMBER", "${prNumber}"
|
||||
buildConfigField "String", "PR_TITLE", "\"${escapeJava(prTitle)}\""
|
||||
buildConfigField "String", "BUILD_BRANCH", "\"${escapeJava(buildBranch)}\""
|
||||
buildConfigField "boolean", "CI_BUILD", "${ciBuild}"
|
||||
buildConfigField "String", "RELEASE_SIGNER", "\"${releaseSigner}\""
|
||||
buildConfigField "long", "COMMIT_TIME", "${commitTime}L * 1000L"
|
||||
buildConfigField "String", "VARIANT_SUFFIXES", "\"${variantSuffixes}\""
|
||||
buildConfigField "String", "SGDB_API_KEY", "\"${System.getenv("SGDB_API_KEY") ?: project.findProperty("sgdbApiKey") ?: ""}\""
|
||||
buildConfigField "String", "DESKTOP_KDE_TAG", "\"${desktopKde.DESKTOP_KDE_TAG}\""
|
||||
buildConfigField "String", "DESKTOP_KDE_URL", "\"https://github.com/${desktopKde.DESKTOP_KDE_REPO}/releases/download/${desktopKde.DESKTOP_KDE_TAG}/desktop-kde.tar.zst\""
|
||||
buildConfigField "String", "DESKTOP_KDE_SHA256", "\"${desktopKde.DESKTOP_KDE_SHA256}\""
|
||||
buildConfigField "long", "DESKTOP_KDE_SIZE", "${desktopKde.DESKTOP_KDE_SIZE}L"
|
||||
addonPacks.each { name, pack ->
|
||||
buildConfigField "String", "PACK_${name.toUpperCase()}_SHA256", "\"${pack.sha256}\""
|
||||
buildConfigField "long", "PACK_${name.toUpperCase()}_SIZE", "${pack.size}L"
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a'
|
||||
@@ -158,8 +95,6 @@ android {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_shared'
|
||||
arguments "-DDROIDDECK_VITURE_SDK_DIR=${vitureSdkDir.toString().replace('\\', '/')}"
|
||||
arguments "-DDROIDDECK_UVC_DIR=${uvcDir.toString().replace('\\', '/')}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,6 +107,10 @@ android {
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
// AOSP testkey ([email protected]), the same key Bannerlator ships with. It is publicly
|
||||
// known, so it proves nothing about who built the apk - what it buys is ONE stable
|
||||
// signature: every build installs over the last one instead of asking the user to
|
||||
// uninstall first. v1 (JAR) + v2 + v3 so every installer and Android version accepts it.
|
||||
testkey {
|
||||
storeFile file("${rootDir}/keystore/testkey.p12")
|
||||
storeType "PKCS12"
|
||||
@@ -188,11 +127,6 @@ android {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.testkey
|
||||
def localSuffix = localBuildOptions.getProperty('droiddeck.debugApplicationIdSuffix')
|
||||
if (localSuffix) {
|
||||
applicationIdSuffix localSuffix
|
||||
resValue 'string', 'app_name', 'DroidDeck XR Dev'
|
||||
}
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
@@ -201,43 +135,36 @@ android {
|
||||
}
|
||||
|
||||
lint {
|
||||
// targetSdk 28 is deliberate and structural: an app on 29+ may not execute a file out of
|
||||
// its own data directory, and executing a rootfs is the whole of what this app does. Lint
|
||||
// fails a release build over it because Google Play will not accept an sdk that old -
|
||||
// true, and not something this app can trade away. Every other check stays on.
|
||||
disable 'ExpiredTargetSdkVersion'
|
||||
}
|
||||
|
||||
androidResources {
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~:<dir>droiddeck-xbox:<dir>droiddeck-comet:<dir>directaudio:<dir>hybris'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
// Kadb's Bouncy Castle artifacts repeat the same multi-release JAR manifest.
|
||||
excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF']
|
||||
}
|
||||
jniLibs {
|
||||
// The PulseAudio daemon is executed from the native library directory and its
|
||||
// libraries are read out of the apk by the class loader: both want the old,
|
||||
// uncompressed-and-extracted layout.
|
||||
useLegacyPackaging = true
|
||||
// Prefer the PTY helper built by this app's NDK target over Termux's prebuilt copy.
|
||||
pickFirsts += ['**/libtermux.so']
|
||||
excludes += ['**/libproot.so', '**/libproot-loader.so', '**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so']
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab true
|
||||
compose true
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
testOptions {
|
||||
// Plain JVM tests: a stray android.util.Log call returns a default instead of throwing.
|
||||
unitTests.returnDefaultValues = true
|
||||
unitTests.includeAndroidResources = true
|
||||
unitTests.all {
|
||||
if (ciBuild) {
|
||||
outputs.upToDateWhen { false }
|
||||
outputs.doNotCacheIf('CI executes the test suite on every run') { true }
|
||||
}
|
||||
maxHeapSize = '2g'
|
||||
maxParallelForks = 2
|
||||
def logTarget = providers.systemProperty('robolectric.logging').orNull
|
||||
if (logTarget != null) systemProperty 'robolectric.logging', logTarget
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -249,21 +176,8 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// No instrumented tests; the unused androidTest variant breaks the VS Code Kotlin import.
|
||||
androidComponents {
|
||||
beforeVariants(selector().all()) { it.androidTest.enable = false }
|
||||
}
|
||||
|
||||
// Vendor binaries remain in the user's SDK directory; never vendor them into Git.
|
||||
if (vitureSdkDir) {
|
||||
android.sourceSets.main.jniLibs.srcDir(new File(vitureSdkDir, 'android'))
|
||||
def stageVitureNotices = tasks.register('stageVitureNotices', Copy) {
|
||||
from(new File(vitureSdkDir, 'android')) { include 'LICENSE', 'NOTICE' }
|
||||
into(layout.buildDirectory.dir('vitureAssets/licenses/viture'))
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageVitureNotices) }
|
||||
}
|
||||
|
||||
// Session scripts must follow this checkout even when the native asset bundle is cached.
|
||||
// A stale local bundle can omit a newly required helper on a fresh runtime install.
|
||||
def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
|
||||
from(rootProject.file('tools/linuxfs/overlay')) {
|
||||
include 'usr/local/bin/droiddeck-*', 'usr/local/bin/steam-compatibility', 'usr/bin/**'
|
||||
@@ -272,129 +186,30 @@ def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageSessionScripts) }
|
||||
|
||||
def runtimeAssetsDir = layout.buildDirectory.dir('runtimeAssets')
|
||||
def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
|
||||
dependsOn(stageSessionScripts)
|
||||
from(layout.projectDirectory.dir('src/main/assets')) { exclude 'pulseaudio.tzst' }
|
||||
from(rootProject.file('tools/linuxfs/innoextract'))
|
||||
from(layout.buildDirectory.dir('prepared-assets'))
|
||||
into(runtimeAssetsDir)
|
||||
}
|
||||
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
|
||||
if (vitureSdkDir) android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('vitureAssets'))
|
||||
if (uvcDir) {
|
||||
def stageUvcNotices = tasks.register('stageUvcNotices', Copy) {
|
||||
from(new File(uvcDir, 'licenses'))
|
||||
into(layout.buildDirectory.dir('uvcAssets/licenses/uvc'))
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageUvcNotices) }
|
||||
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('uvcAssets'))
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageRuntimeAssets) }
|
||||
|
||||
def stageGogNotices = tasks.register('stageGogNotices', Copy) {
|
||||
from(layout.projectDirectory.dir('src/main/rust/stores/vendor/xdelta3')) { include 'LICENSE', 'README.droiddeck.md' }
|
||||
into(layout.buildDirectory.dir('gogNotices/licenses/xdelta3'))
|
||||
}
|
||||
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('gogNotices'))
|
||||
tasks.named('preBuild').configure { dependsOn(stageGogNotices) }
|
||||
|
||||
if (!ciBuild) {
|
||||
def stageLocalPacks = tasks.register('stageLocalPacks', Sync) {
|
||||
from(layout.buildDirectory.dir('packs')) { include '*.tar.zst' }
|
||||
into(layout.buildDirectory.dir('localPackAssets/packs'))
|
||||
}
|
||||
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('localPackAssets'))
|
||||
tasks.named('preBuild').configure { dependsOn(stageLocalPacks) }
|
||||
}
|
||||
|
||||
def verifyRuntimeInputs = tasks.register('verifyRuntimeInputs', Exec) {
|
||||
mustRunAfter(stageSessionScripts, stageRuntimeAssets)
|
||||
workingDir rootProject.projectDir
|
||||
def allowStale = (project.findProperty('allowStaleRuntime')
|
||||
?: localBuildOptions.getProperty('droiddeck.allowStaleRuntime', 'false')).toString().toBoolean()
|
||||
commandLine([pythonExecutable, 'tools/release/runtime_inputs.py', 'verify'] + (allowStale ? ['--allow-stale-sources'] : []))
|
||||
}
|
||||
tasks.configureEach { task ->
|
||||
if (task.name in ['packageDebug', 'packageRelease']) task.dependsOn(verifyRuntimeInputs)
|
||||
}
|
||||
|
||||
def rustCrateDir = layout.projectDirectory.dir('src/main/rust/stores')
|
||||
def rustJniLibsDir = layout.buildDirectory.dir('rustJniLibs')
|
||||
def skipRust = (project.findProperty('skipRust')
|
||||
?: localBuildOptions.getProperty('droiddeck.skipRust', 'false')).toString().toBoolean()
|
||||
def buildRustStores = tasks.register('buildRustStores', Exec) {
|
||||
description = 'Cross-compiles the store download engines (libdroiddeckstores.so) with cargo-ndk.'
|
||||
onlyIf { !skipRust }
|
||||
inputs.dir(rustCrateDir.dir('src'))
|
||||
inputs.dir(rustCrateDir.dir('vendor'))
|
||||
inputs.file(rustCrateDir.file('build.rs'))
|
||||
inputs.files(rustCrateDir.file('Cargo.toml'), rustCrateDir.file('Cargo.lock'))
|
||||
inputs.property('ndkVersion', android.ndkVersion)
|
||||
if (!skipRust) inputs.property('rustToolchain', providers.exec {
|
||||
environment 'PATH', "${System.getProperty('user.home')}/.cargo/bin${File.pathSeparator}${System.getenv('PATH')}"
|
||||
commandLine 'rustup', 'run', 'stable', 'rustc', '-Vv'
|
||||
}.standardOutput.asText)
|
||||
outputs.dir(rustJniLibsDir)
|
||||
workingDir rustCrateDir
|
||||
def cargoBin = "${System.getProperty('user.home')}/.cargo/bin"
|
||||
environment 'PATH', "${cargoBin}${File.pathSeparator}${System.getenv('PATH')}"
|
||||
environment 'ANDROID_NDK_HOME', android.ndkDirectory.absolutePath
|
||||
environment 'CARGO_TERM_COLOR', 'never'
|
||||
if (!skipRust) environment 'RUSTC', providers.exec {
|
||||
commandLine 'rustup', 'which', '--toolchain', 'stable', 'rustc'
|
||||
}.standardOutput.asText.get().trim()
|
||||
commandLine 'rustup', 'run', 'stable', 'cargo', 'ndk', '--target', 'arm64-v8a', '--platform', '26',
|
||||
'--output-dir', rustJniLibsDir.get().asFile.absolutePath,
|
||||
'build', '--release', '--locked'
|
||||
}
|
||||
if (!skipRust) android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
|
||||
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
|
||||
|
||||
def maliBridgeDir = rootProject.file('external/MaliBridge')
|
||||
def vkbridgeJniLibsDir = layout.buildDirectory.dir('vkbridgeJniLibs')
|
||||
def buildVkBridgeServer = tasks.register('buildVkBridgeServer', Exec) {
|
||||
description = 'Builds the Vulkan bridge server (libvkbridge_server.so) with the NDK.'
|
||||
inputs.dir(new File(maliBridgeDir, 'common'))
|
||||
inputs.dir(new File(maliBridgeDir, 'server'))
|
||||
inputs.dir(new File(maliBridgeDir, 'third_party'))
|
||||
inputs.file(new File(maliBridgeDir, 'scripts/build-server-android.sh'))
|
||||
outputs.dir(vkbridgeJniLibsDir)
|
||||
commandLine 'bash', new File(maliBridgeDir, 'scripts/build-server-android.sh').absolutePath,
|
||||
android.ndkDirectory.absolutePath, vkbridgeJniLibsDir.get().asFile.absolutePath
|
||||
}
|
||||
android.sourceSets.main.jniLibs.srcDir(vkbridgeJniLibsDir)
|
||||
tasks.named('preBuild').configure { dependsOn(buildVkBridgeServer) }
|
||||
|
||||
dependencies {
|
||||
implementation "org.opencv:opencv:${trackingPack.OPENCV_VERSION}"
|
||||
implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.49'
|
||||
implementation 'com.github.luben:zstd-jni:1.5.7-4@aar'
|
||||
// The runtime tarball is a zstd-compressed tar.
|
||||
implementation 'com.github.luben:zstd-jni:1.5.2-3@aar'
|
||||
implementation 'org.apache.commons:commons-compress:1.27.1'
|
||||
// Bannerlator's Windows components come as .tar.xz (WinComponents).
|
||||
implementation 'org.tukaani:xz:1.10'
|
||||
// The UI: every menu, dialog and overlay is Compose. The session's SurfaceView and the
|
||||
// on-screen pad are Views underneath it, which is what they are in every Compose app.
|
||||
implementation platform('androidx.compose:compose-bom:2024.02.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||
// The File Manager, Bannerlator's: its icon set, image thumbnails and the coroutines it
|
||||
// copies on. Same versions as Bannerlator so the screen stays a straight copy.
|
||||
implementation 'androidx.compose.material:material-icons-extended'
|
||||
implementation 'io.coil-kt:coil-compose:2.6.0'
|
||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
||||
// ADB-over-Wi-Fi pairing and authenticated shell client. Users still approve pairing from
|
||||
// Android's Wireless debugging screen; DroidDeck only runs its one fixed setting command.
|
||||
implementation 'com.flyfishxu:kadb-android:1.2.1'
|
||||
// Android terminal view/emulator used by the in-session secondary-display Linux shell.
|
||||
implementation 'com.termux.termux-app:terminal-view:0.118.0'
|
||||
implementation 'androidx.camera:camera-camera2:1.4.2'
|
||||
implementation 'androidx.camera:camera-lifecycle:1.4.2'
|
||||
implementation 'androidx.webkit:webkit:1.12.1'
|
||||
implementation 'io.github.webrtc-sdk:android:125.6422.07'
|
||||
implementation("com.google.mediapipe:tasks-vision:${trackingPack.MEDIAPIPE_VERSION}") {
|
||||
exclude group: 'com.google.auto.value', module: 'auto-value'
|
||||
}
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.16.1'
|
||||
testImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
testRuntimeOnly 'com.github.luben:zstd-jni:1.5.7-4@jar'
|
||||
}
|
||||
@@ -1,62 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<queries>
|
||||
<package android:name="cn.nubia.gamelauncher" />
|
||||
<intent>
|
||||
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.SECONDARY_HOME" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!-- Session logs go to Downloads so a failed run can be handed over as one folder. -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<!-- The session keeps running while the app is not on screen: a foreground service holds the
|
||||
process at perceptible priority, and a high-performance WiFi lock keeps a backgrounded
|
||||
download from being throttled to nothing by the radio's power-save. -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<!-- Optional Wi-Fi discovery: requested only from Steam settings after an explanation. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<!-- Ends background processes of another app that holds the Steam login. GameHub starts
|
||||
itself with the phone via boot receivers and signs this client out seconds after every
|
||||
login; a normal permission, and it cannot touch anything in the foreground. -->
|
||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
||||
<!-- The microphone for the Linux session, only when the user turns it on: the DirectAudio
|
||||
relay helper opens the input stream under this uid, and PulseAudio exposes it to the
|
||||
Steam client as DirectAudioMic. -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||
<!-- Installs DroidDeck's own updates (Updates page) over itself, after the user allows it. -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.vr.headtracking" android:version="1" android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:allowBackup="false"
|
||||
android:allowAudioPlaybackCapture="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
@@ -66,81 +45,26 @@
|
||||
android:appCategory="game"
|
||||
android:isGame="true">
|
||||
|
||||
<!-- A game, to the OS: Android 12+ routes the category through GameManager and the OEM
|
||||
performance frameworks (Game Booster, Game Turbo, Game Space) key off it. Without it
|
||||
a regular flagship runs the session under its "app" power profile - a conservative
|
||||
governor and GPU DVFS - which is why the same chipset feels slower here than in a
|
||||
gaming phone whose mode is on for everything. The config says what the OS may do. -->
|
||||
<meta-data android:name="android.game_mode_config" android:resource="@xml/game_mode_config" />
|
||||
<service android:name=".glasses.LumaRgbCaptureService" android:exported="false"
|
||||
android:process=":rgb_diagnostic" />
|
||||
<service android:name=".wallpaper.SteamWallService"
|
||||
android:label="@string/steam_wall_label"
|
||||
android:permission="android.permission.BIND_WALLPAPER"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.wallpaper.WallpaperService" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.service.wallpaper" android:resource="@xml/steam_wall_wallpaper" />
|
||||
</service>
|
||||
<activity android:name=".wallpaper.WallpaperSettingsActivity"
|
||||
android:label="@string/steam_wall_label"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:exported="true" />
|
||||
<service android:name=".input.HomeButtonService"
|
||||
android:label="@string/integration_shortcuts_service"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.accessibilityservice" android:resource="@xml/home_button_service" />
|
||||
</service>
|
||||
<service android:name=".session.BottomNotificationListener"
|
||||
android:label="@string/bottom_notifications_listener"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<receiver android:name=".core.ShortcutsListingReceiver" android:exported="false"
|
||||
android:process=":shortcuts_sync">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<meta-data android:name="com.oculus.supportedDevices" android:value="quest2|quest3|quest3s" />
|
||||
<activity android:name=".QuestXrActivity" android:exported="true" android:launchMode="singleTask"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:resizeableActivity="false" android:screenOrientation="landscape"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|navigation|uiMode"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
|
||||
<meta-data android:name="android.app.lib_name" android:value="droiddeckxr" />
|
||||
<intent-filter>
|
||||
<action android:name="com.droiddeck.launcher.XR" />
|
||||
<category android:name="com.oculus.intent.category.VR" />
|
||||
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PairingGuideActivity"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="${applicationId}.pairingguide"
|
||||
android:excludeFromRecents="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|keyboard|keyboardHidden|navigation"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".FrontEndActivity"
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="user"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|keyboard|keyboardHidden|navigation">
|
||||
<layout android:defaultWidth="1024dp" android:defaultHeight="640dp" />
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="keyboard|keyboardHidden|navigation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
<data android:scheme="droiddeck" android:host="game" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
@@ -150,35 +74,13 @@
|
||||
<intent-filter>
|
||||
<action android:name="com.droiddeck.launcher.LAUNCH_GAME" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity-alias
|
||||
android:name=".MainActivity"
|
||||
android:targetActivity=".FrontEndActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".LauncherLight"
|
||||
android:targetActivity=".FrontEndActivity"
|
||||
android:icon="@mipmap/ic_launcher_light"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<!-- Android owns the Home key. This small translucent Home handler routes the resulting
|
||||
Home launch either back into a running Steam session or to the regular front end.
|
||||
Off until the user turns on "Use as a Home screen" (Setup): while it is disabled the
|
||||
app is not a Home app at all - not offered, not in the "choose Home" list. -->
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:enabled="false"
|
||||
@@ -186,7 +88,7 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:taskAffinity="${applicationId}.home"
|
||||
android:screenOrientation="user"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -195,70 +97,34 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PinRequestActivity"
|
||||
android:exported="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity="${applicationId}.pin"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
|
||||
<action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!--
|
||||
A session keeps a Surface, a compositor thread and a proot tree alive; a configuration
|
||||
change must not tear that down and rebuild it, so the activity handles them itself.
|
||||
Landscape only: gamescope is told one output size when it starts.
|
||||
|
||||
Resizable, deliberately: a foldable opening or closing changes the window, and the
|
||||
compositor re-fits the picture with bars. Declared non-resizable, the system keeps a
|
||||
fixed-shape window instead and STRETCHES it onto the other panel - the app never sees a
|
||||
size change, so nothing it does can help.
|
||||
-->
|
||||
<activity
|
||||
android:name=".sync.SyncthingWebActivity"
|
||||
android:name=".input.ControllerEditorActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/SessionTheme"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
|
||||
android:theme="@style/AppTheme" />
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".files.FileManagerActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="user"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".files.FilePickerActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="user"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity android:name=".stores.xbox.XboxLoginActivity" android:exported="false"
|
||||
android:process=":xboxlogin" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity android:name=".stores.xbox.XboxCloudPlayerActivity" android:exported="false"
|
||||
android:screenOrientation="sensorLandscape" android:hardwareAccelerated="true"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation|uiMode"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<service android:name=".stores.xbox.XboxCloudTokenService" android:exported="false" />
|
||||
|
||||
<activity android:name=".stores.xbox.XboxNativePlayerActivity" android:exported="false"
|
||||
android:process=":xcloudnative" android:screenOrientation="sensorLandscape" android:hardwareAccelerated="true"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation|uiMode"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity android:name=".stores.xbox.XboxCloudActivity" android:exported="false"
|
||||
android:process=":xboxcloud" android:launchMode="singleTop" android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation|uiMode"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity android:name=".stores.xbox.XboxCloudSettingsActivity" android:exported="false"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".stores.StoreLoginActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="user"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
@@ -280,59 +146,24 @@
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.DualScreenActivity"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity="${applicationId}.dualscreen"
|
||||
android:theme="@style/SessionTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.SessionTypingActivity"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:taskAffinity="${applicationId}.typing"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
|
||||
android:resizeableActivity="true"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".gpu.SteamVrRestartActivity"
|
||||
android:exported="false"
|
||||
android:process=":vr_recovery"
|
||||
android:taskAffinity="${applicationId}.vr_recovery"
|
||||
android:excludeFromRecents="true"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density"
|
||||
android:theme="@style/SessionTheme" />
|
||||
|
||||
<!--
|
||||
Owns the session - gamescope, the Steam client, audio - so it survives the activity
|
||||
going away. Without it Android demotes the process as soon as the last visible
|
||||
activity goes, and the low-memory killer reaps the guest: leaving Big Picture to
|
||||
answer a message would come back to a dead Steam and a half-finished download.
|
||||
-->
|
||||
<service
|
||||
android:name=".session.SessionService"
|
||||
android:exported="false"
|
||||
android:stopWithTask="false" />
|
||||
<!-- The compositor's native state is one per process, so the bottom-screen desktop's compositor runs in its own. -->
|
||||
<service
|
||||
android:name=".wayland.BottomDesktopCompositorService"
|
||||
android:process=":bottom_desktop"
|
||||
android:exported="false" />
|
||||
<!-- Holds the process in the foreground while the Linux runtime downloads and unpacks. -->
|
||||
<service
|
||||
android:name=".runtime.RuntimeInstallService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".core.WirelessAdbPairingService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".stores.download.StoreDownloadService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<service
|
||||
android:name=".sync.SyncthingService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<!-- Hands a zipped session folder to the share sheet; nothing else is exposed. -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.logs"
|
||||
@@ -342,6 +173,8 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/log_share_paths" />
|
||||
</provider>
|
||||
<!-- The app's data folder in Android's file picker ("Open from" > DroidDeck). Only the
|
||||
system's documents UI may bind it; apps get a file only when the user picks it. -->
|
||||
<provider
|
||||
android:name=".files.AppFilesProvider"
|
||||
android:authorities="${applicationId}.documents"
|
||||
@@ -358,13 +191,5 @@
|
||||
android:exported="true"
|
||||
android:permission="android.permission.DUMP"
|
||||
android:grantUriPermissions="false" />
|
||||
<activity android:name=".ui.SpatialTrackingActivity" android:exported="false" />
|
||||
<service
|
||||
android:name="com.google.android.datatransport.runtime.backends.TransportBackendDiscovery"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="backend:com.google.android.datatransport.cct.CctBackendFactory"
|
||||
tools:node="remove" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,102 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"identity": "luma-ultra-base-v1",
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"fisheye": false,
|
||||
"error": 1.1209395390660726,
|
||||
"verified": false,
|
||||
"k1": {
|
||||
"rows": 3,
|
||||
"cols": 3,
|
||||
"data": [
|
||||
226.0499636040297,
|
||||
0,
|
||||
321.62348950766386,
|
||||
0,
|
||||
228.36987999988767,
|
||||
242.2674105020684,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"d1": {
|
||||
"rows": 5,
|
||||
"cols": 1,
|
||||
"data": [
|
||||
-0.28622915867944043,
|
||||
0.1194613930041054,
|
||||
0.007984341063494766,
|
||||
-0.0008220922923559441,
|
||||
-0.025579805296512992
|
||||
]
|
||||
},
|
||||
"k2": {
|
||||
"rows": 3,
|
||||
"cols": 3,
|
||||
"data": [
|
||||
240.3338202496282,
|
||||
0,
|
||||
320.8790079027563,
|
||||
0,
|
||||
241.14047924325277,
|
||||
260.45566640784716,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"d2": {
|
||||
"rows": 5,
|
||||
"cols": 1,
|
||||
"data": [
|
||||
-0.27073208928390136,
|
||||
0.08065508519805098,
|
||||
-0.001460778493930769,
|
||||
-0.0003694718992635806,
|
||||
-0.011452505620454932
|
||||
]
|
||||
},
|
||||
"r": {
|
||||
"rows": 3,
|
||||
"cols": 3,
|
||||
"data": [
|
||||
0.900501479835182,
|
||||
0.04473903344740378,
|
||||
-0.43254537762047496,
|
||||
-0.08255720039597626,
|
||||
0.9941919046988283,
|
||||
-0.06904176485356042,
|
||||
0.4269442510181227,
|
||||
0.0978819468416306,
|
||||
0.8989648107712929
|
||||
]
|
||||
},
|
||||
"t": {
|
||||
"rows": 3,
|
||||
"cols": 1,
|
||||
"data": [
|
||||
-0.14117171913441395,
|
||||
0.0014051874404824838,
|
||||
-0.007555740272006347
|
||||
]
|
||||
},
|
||||
"cameraToHead": [
|
||||
-0.05607918,
|
||||
0.023585627,
|
||||
-0.07998472,
|
||||
0.01024512,
|
||||
0.9945703,
|
||||
0.005859665,
|
||||
-0.10339546
|
||||
],
|
||||
"deviceModel": "VITURE Luma Ultra",
|
||||
"cameraPair": "left0-right0",
|
||||
"sourceValidation": {
|
||||
"exportDate": "2026-10-16",
|
||||
"sourceDeviceVerified": true,
|
||||
"capturedViews": 25,
|
||||
"scope": "Starting geometry from one calibrated Luma Ultra; other units have not been individually validated."
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,70 +0,0 @@
|
||||
{
|
||||
"archives": {
|
||||
"lxqt-r1": "c2bb860b3b1688287451e2495be5db313ec07f8c8af501dfac2768a9f417bc86",
|
||||
"desktop-kde-r1": "55747e75d0ec055ab7ea1e92a757f3be227cc3205bb9101f5b53cadef6318579",
|
||||
"desktop-kde-r2": "9ba832677b498654136c69d8c8330434d17e4d53cd099acac9bde3ba429b35bf",
|
||||
"emulators-r1": "a1f4806a9663d28f424b4cab1277e0ede80a92e69de5bc063cdc8b45af220164",
|
||||
"desktop-kde-r3": "8b7142fcec8d86143ae39fc14ffc86907d30d9a5b5a45f49cb1b55109d6fc4eb"
|
||||
},
|
||||
"desktopApplications": {
|
||||
"7zip-26.05-1-aarch64.pkg.tar.xz": "17569bafbc5bd1a044ca1db164b96cb15857a22a3575f36679de46e693ae285e",
|
||||
"ark-26.08.2-1-aarch64.pkg.tar.xz": "b93cc90c4fc8a6cb0247fe8fb0f9de1c2074f5173fa2735d3d1959c9162c08f2",
|
||||
"breeze-6.8.1-1-aarch64.pkg.tar.xz": "70a2ff3afd41602f184814af5c6e298ff84ef7cb7e5880ca7cb4f6fb28521511",
|
||||
"breeze-gtk-6.8.1-1-any.pkg.tar.xz": "e92b9acf3155db22d4a288a43e8d95c20c72b8d8cffd028dd6800eb01a304bf0",
|
||||
"breeze-icons-6.31.0-1-aarch64.pkg.tar.xz": "b252ef02395621b5891d72c5f7fccd1a1ead1ce770886e9fced4bf21e1130096",
|
||||
"discover-6.8.1-1-aarch64.pkg.tar.xz": "64cfca615e7c18530c684325eb36a5c45e18c63b31f651335474f53b0178958c",
|
||||
"dolphin-26.08.2-1-aarch64.pkg.tar.xz": "4b5f7a9212c88d47146b374742f67f5620ab128ad893d945721bb43501066b70",
|
||||
"elisa-26.08.2-1-aarch64.pkg.tar.xz": "fd2df84a2905b59fba0ab1b7fadafe567c5a1dfd44d53ae4cd4996f683b83ba2",
|
||||
"filelight-26.08.2-1-aarch64.pkg.tar.xz": "17f39435104baf0743ab508bdde6e8bd1b3417b025b67ca8f343032d2fdef3e6",
|
||||
"firefox-158.0.1-1-aarch64.pkg.tar.xz": "4940e14c05522bfee8df901e1cfe81f56036c82e8f3d95f9f7a2a12e6b38d232",
|
||||
"flatpak-kcm-6.8.1-1-aarch64.pkg.tar.xz": "3145601002fb87d61307949a3011a8f27a7b1e9851fc59835460f934f8e00098",
|
||||
"git-2.56.0-1-aarch64.pkg.tar.xz": "b152a20edb215e98cada4b2dfbd02a9e925bda0b2de6ac1441e9e60b6698d3b5",
|
||||
"gwenview-26.08.2-1-aarch64.pkg.tar.xz": "02aae3973ec0563022c89e312ce7696d05d33514101ae2a01df3275717929de2",
|
||||
"htop-3.5.3-1-aarch64.pkg.tar.xz": "a58899bd2596d55575493509b3a5aa6f53075cb3d88bb6c712088c57068d9eb7",
|
||||
"kate-26.08.2-1-aarch64.pkg.tar.xz": "89c21ad8e62d20961b13f8fac8d9f4d358b1a3feebed243f6412e4ad996d5e9e",
|
||||
"kcalc-26.08.2-1-aarch64.pkg.tar.xz": "79b3303ccc5ae956e97313439456243e59f0b79a8166ca91606a25d1c9e41029",
|
||||
"kde-cli-tools-6.8.1-1-aarch64.pkg.tar.xz": "b052e9c25e7d6896be67846696f14c9e00430c0445f358ee64cf2a8ea16aa2eb",
|
||||
"kde-gtk-config-6.8.1-1-aarch64.pkg.tar.xz": "3095da034ac5fe8594165e3396f61f083b0c011cd96807146ddfb53690118608",
|
||||
"kfind-26.08.2-1-aarch64.pkg.tar.xz": "b4108cc3bd9efd62970379aadb3cb22e99dd1f54873ff432392edd0a833bb393",
|
||||
"kinfocenter-6.8.1-1-aarch64.pkg.tar.xz": "758882ef13a166205acab0e6bb96ce756d638d91e501db400cb579b9e1331d31",
|
||||
"konsole-26.08.2-1-aarch64.pkg.tar.xz": "0cf27f69d1792a094d4a935f578c82f8151b2d359198d9cd32bfdbd8dec544e4",
|
||||
"kscreen-6.8.1-1-aarch64.pkg.tar.xz": "72c948dfdebfef305623527de27274baf1a2328c87e71033fd852e386d9fdf5c",
|
||||
"kwin-6.8.1-2-aarch64.pkg.tar.xz": "726c98a3f8682b487c2b313633edbd80aa8b20ff657a6b63bbb1b73454f6e0c4",
|
||||
"less-1:710-1-aarch64.pkg.tar.xz": "8e1d4cbecf6d2ae734cfc1c5495367c0a9245f92dfd2bc03d4d0ab14f8b26ff2",
|
||||
"man-db-2.13.1-2-aarch64.pkg.tar.xz": "67515751f848717e1f6d3470f57ad2ea6900ee07618fd2726b6fcaa695687e80",
|
||||
"man-pages-6.19-1-any.pkg.tar.xz": "69df38f1a1aa35240d239ff82a2e067950ac394f23b8a6c7307580ccd7e95f07",
|
||||
"mpv-1:0.41.0-6-aarch64.pkg.tar.xz": "6c89d00736820210a3c7f32af4cf95d155191d463f9df8bd22137c45f30a554a",
|
||||
"okular-26.08.2-1-aarch64.pkg.tar.xz": "aef87cf74730d2345092184d7e9b14a0068ba4030de58564aa770506175cbf06",
|
||||
"plasma-desktop-6.8.1-1-aarch64.pkg.tar.xz": "4b24662a2059b21ca98b5b35b72216f22c9e316057247ea018bf6318b07cfdb6",
|
||||
"plasma-integration-6.8.1-1-aarch64.pkg.tar.xz": "514f7d2a96370814b9374d7c52e1873257af6e18190d130e5a8d3d6d1fffdc54",
|
||||
"plasma-pa-6.8.1-1-aarch64.pkg.tar.xz": "13dff66d30072744c7870bd1a294ae96f0fee086a1533b8052d7dfc038d57c45",
|
||||
"plasma-systemmonitor-6.8.1-1-aarch64.pkg.tar.xz": "551b7c1f24cd5df64de4f5ba199bc71c00e2924c16a8241b21c020584794e10d",
|
||||
"plasma-workspace-6.8.1-1-aarch64.pkg.tar.xz": "4c713990aeab118d50865c818f9689ae7637dc6556e4a0e2fec5e0b348cdcd29",
|
||||
"spectacle-1:6.8.1-1-aarch64.pkg.tar.xz": "0d697803bd0135701688be3869d4d99eec6b8b43ca761610da73f44cb77bfa5c",
|
||||
"systemsettings-6.8.1-1-aarch64.pkg.tar.xz": "db528089fa328d67dadb50be3b822858238785d8c659a9ef95389b993efcb234",
|
||||
"unrar-1:7.3.1-1-aarch64.pkg.tar.xz": "9883e19b1f1adfa430285191b1f96db4cc208608baebd955f3fe9fee92cd3bdd",
|
||||
"unzip-6.0-23-aarch64.pkg.tar.xz": "edc91c7e4e400f20345439744262d1ceded104c78d940659a6b9876076ef33fc",
|
||||
"wget-1.25.0-6-aarch64.pkg.tar.xz": "e2bf0067cca93877c737c7d48bc36fff32e184c7874829429b9473c10a12dd39",
|
||||
"wl-clipboard-1:2.3.0-1-aarch64.pkg.tar.xz": "723a63b140e00085a533ebb1ec36fa9305b5e6cbd8fb80c0521fbeff77a72982",
|
||||
"xclip-0.13-6-aarch64.pkg.tar.xz": "772ec9336b679b0ef277d1ef0b69b74777467239e16ec845af00c07fa849d166",
|
||||
"xdg-desktop-portal-kde-6.8.1-1-aarch64.pkg.tar.xz": "8098c0ef0454335bb7c806049d66977c512657346222606737d72a7ae1de3146",
|
||||
"zip-3.0-14-aarch64.pkg.tar.xz": "9887f177544021809d065d0298514e98f76b55f9120119f748f5a28c8bb51994"
|
||||
},
|
||||
"legacyApplications": {
|
||||
"featherpad-1.6.3-1-aarch64.pkg.tar.xz": "fcac55215815c0b712453f9bcc09abfe4b518e36025a111a7a7598ef04913f3d",
|
||||
"labwc-0.20.2-1-aarch64.pkg.tar.xz": "bcd4cc3945165b0fdd33c0117cd7b9dfd05cf8c327326c37f3c3a27ef23ee5ca",
|
||||
"lximage-qt-2.4.0-1-aarch64.pkg.tar.xz": "5d6bdd5472ea6bcb4b993b36df3ab7010bb2e62b36a78a649708fc9a52cb0928",
|
||||
"lxqt-config-2.4.0-1-aarch64.pkg.tar.xz": "4b601d798fc7689ee76787dbef8c8958c02429ee60248cd99d950c919dbefba5",
|
||||
"lxqt-panel-2.4.1-1-aarch64.pkg.tar.xz": "a141e0f8049e73f859d843510c6f45d6ed9087c990bcebf44f5f2430621024c4",
|
||||
"lxqt-qtplugin-2.4.0-3-aarch64.pkg.tar.xz": "61c8f828be73111ee0bd5e3163b0d64c2098dd0189710a190f42be11137695cf",
|
||||
"lxqt-runner-2.4.0-1-aarch64.pkg.tar.xz": "5afa74652a37366ea4e7774cf33379977582fbdca599cef1005ebf637cbcae4b",
|
||||
"lxqt-session-2.4.0-2-aarch64.pkg.tar.xz": "e9c7c8a0011859029622ad202e0d3826e4e5bc8d64659031f6efa211456ff6eb",
|
||||
"lxqt-themes-2.4.0-1-any.pkg.tar.xz": "7ea16c62a1fa70cdac13cb3f8fdcdaad299ae1f5bd514acfda671484755787bf",
|
||||
"lxqt-wayland-session-0.4.1-1-any.pkg.tar.xz": "cd8dc11f5035469d23b6bbd937a90593402428c43de37517412aa43fe1fc6b10",
|
||||
"papirus-icon-theme-20260801-1-any.pkg.tar.xz": "4aae3f47b800a60d6fd360e55adc581408ceaafc481c5a420a681741a3966510",
|
||||
"pcmanfm-qt-2.4.1-1-aarch64.pkg.tar.xz": "7ec1a6b39dd0e4055b39f0e2c0549ea263d83004e1b821fb9e42f1d7f5fff0f9",
|
||||
"qterminal-2.4.0-1-aarch64.pkg.tar.xz": "aa4adc9c9d47641b9452ec8d879038a9cb4762c3b6b5299045fac968fd0ed22d",
|
||||
"swaybg-1.2.2-1-aarch64.pkg.tar.xz": "a26caa16ca7d581b5ca05758677058b4b60af10dc8d83e447ad6824b762b48b8",
|
||||
"wlr-randr-0.5.0-1-aarch64.pkg.tar.xz": "58c3651c6a15dc8f95a310c7e225f759aa766be2fb3c0981a9a8b567854675b3"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
desktop-kde-r1
|
||||
desktop-kde-r2
|
||||
desktop-kde-r3
|
||||
@@ -0,0 +1,115 @@
|
||||
# DirectAudio for the Linux Steam client (relay build)
|
||||
|
||||
This is the driver for games that run under **Valve's ARM64 Proton inside a Linux
|
||||
runtime on Android** - Bannerlator's linuxfs session with the native Steam client -
|
||||
where the game process is a glibc process and cannot touch Android's audio
|
||||
libraries itself.
|
||||
|
||||
Two pieces, both required:
|
||||
|
||||
| piece | what it is | where it runs |
|
||||
|---|---|---|
|
||||
| `aarch64-unix/winedirectaudio.so` + the two PE shells | the Wine `mmdevapi` driver, glibc build, **no AAudio inside** | in the game, inside the Linux rootfs |
|
||||
| `directaudio-relay` | a small bionic program that owns the AAudio output and microphone streams | on the Android side, under the app's uid |
|
||||
|
||||
They meet over a unix socket. The driver connects, sends its launch config, and
|
||||
gets back two shared-memory rings: one it fills with mixed game audio, one the
|
||||
helper fills with microphone audio. The helper plays and records; the driver
|
||||
keeps doing everything else it always did.
|
||||
|
||||
## Which Proton
|
||||
|
||||
Built inside `ValveSoftware/wine` at `proton_11.0`. The private `mmdevapi`
|
||||
interface it implements is byte-identical across **Proton 11.0 (ARM64)**,
|
||||
**Proton Experimental (ARM64)** and our own Wine-11 layers (checked 2026-09-19),
|
||||
so this one build serves any of them. A future Proton on a Wine 12 base will
|
||||
need a rebuild - the interface is not versioned, and a mismatch is silence, not
|
||||
an error.
|
||||
|
||||
## Installing the driver next to Proton (not into it)
|
||||
|
||||
Steam verifies and updates its Proton depots, so files added under
|
||||
`steamapps/common/Proton .../files/lib/wine/` will not survive. Put the driver
|
||||
in a directory of its own and point Wine at it:
|
||||
|
||||
```
|
||||
<somewhere>/directaudio/lib/wine/aarch64-unix/winedirectaudio.so
|
||||
<somewhere>/directaudio/lib/wine/aarch64-windows/winedirectaudio.drv
|
||||
<somewhere>/directaudio/lib/wine/i386-windows/winedirectaudio.drv
|
||||
```
|
||||
|
||||
and set, for the game process:
|
||||
|
||||
```
|
||||
WINEDLLPATH=<somewhere>/directaudio/lib/wine
|
||||
```
|
||||
|
||||
Wine searches `WINEDLLPATH` entries with the per-arch subdirectories appended,
|
||||
so both the PE shell and the unixlib are found there. In Bannerlator's runtime
|
||||
the natural place to export it is the `bannerlator-proton` compatibility-tool
|
||||
wrapper, which every Proton launch goes through.
|
||||
|
||||
## Selecting the driver
|
||||
|
||||
`mmdevapi` only tries the drivers named in the registry (or its built-in
|
||||
`pulse,alsa,oss,coreaudio` list, which does not know about this one). Set, in the
|
||||
game's prefix:
|
||||
|
||||
```
|
||||
wine reg add "HKCU\Software\Wine\Drivers" /v Audio /d directaudio /f
|
||||
```
|
||||
|
||||
Under Proton the prefix is `compatdata/<appid>/pfx`; run the command with that
|
||||
prefix as `WINEPREFIX` using Proton's own `bin-arm64/wine`, or write the key from
|
||||
the wrapper before it hands off to Proton.
|
||||
|
||||
## Running the helper
|
||||
|
||||
```
|
||||
directaudio-relay --socket <path> [--log]
|
||||
```
|
||||
|
||||
- Run it **under the app's uid**, the way the app already runs its PulseAudio
|
||||
daemon. Android checks `RECORD_AUDIO` against the calling uid, so a helper
|
||||
started any other way can play but cannot record.
|
||||
- Start it **before** the game: the driver asks "is a helper there?" once, when
|
||||
Wine picks an audio driver, and falls back to unavailable if not.
|
||||
- The socket path must be reachable from inside the Linux rootfs. Bannerlator
|
||||
binds the app's files directory and its runtime directory into the session at
|
||||
their own paths, so any socket under either works unchanged on both sides.
|
||||
- The helper serves any number of game processes; each gets its own streams.
|
||||
It exits on `SIGTERM`; a game exiting tears down that game's streams only.
|
||||
|
||||
## Telling the driver where the helper is
|
||||
|
||||
```
|
||||
BANNER_AUDIO_DIRECT_RELAY=<the same socket path>
|
||||
```
|
||||
|
||||
Unset, the driver looks for `$XDG_RUNTIME_DIR/directaudio-relay`.
|
||||
|
||||
## Everything else is unchanged
|
||||
|
||||
All `BANNER_AUDIO_DIRECT_*` knobs mean what they mean in the in-process build:
|
||||
`_PERF`, `_ADAPTIVE`, `_DECAY`, `_MS`, `_MAXMS`, `_BF`, `_MBF`, `_EXCLUSIVE`,
|
||||
`_WATCHDOG`, `_STALL_MS`, `_DECAY_*`, `_LOG`, `_PERIOD_MS`, `_MINPERIOD_MS`, and
|
||||
the live-config mailbox `_RUNTIME` (the helper watches the file; it must be a
|
||||
path the helper can read on the Android side). **`BANNER_AUDIO_DIRECT_MIC=1`**
|
||||
exposes the capture endpoint exactly as before: the helper opens the input
|
||||
stream (`VOICE_COMMUNICATION` preset) when the game connects and makes it hot on
|
||||
the game's first capture start.
|
||||
|
||||
One thing is different: there is one extra hand-off, the ring. The helper's
|
||||
callback pulls a burst from it, so the driver keeps two bursts queued (about 8 ms
|
||||
on a 192-frame device) on top of the AAudio buffer the presets control, and the
|
||||
helper raises that if a callback ever finds the ring short. `get_latency` reports
|
||||
the sum, so a game that sizes its own buffers from it still sees the truth.
|
||||
|
||||
## Proving it is running
|
||||
|
||||
- Helper side: `logcat -s DA-Relay:I` shows `hello from <exe>`, then `open: buffer
|
||||
... ring target ...`, then `capture open` / `capture start` for the mic.
|
||||
- Game side: the session log carries `DirectAudio: relay: connected - ...`.
|
||||
- The game process's `/proc/<pid>/maps` shows `winedirectaudio.so` and **no**
|
||||
`libaaudio.so` (it is in the helper's maps instead). The AudioFlinger track is
|
||||
owned by the helper's pid.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
directaudio linux-wine11 relay build
|
||||
source 3548a4a730e8d01cb0cb0268d51a2fb8f23dcd33
|
||||
wine ValveSoftware/wine@proton_11.0
|
||||
unixlib.h sha256 fdbed263338d0719c4d12e02b045c23721de33219ec8878e4c2d7ebc9efcbe09
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Holger Bartnick
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Binary file not shown.
@@ -1,18 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; background: #000; overflow: hidden; }
|
||||
#player { position: absolute; inset: 0; }
|
||||
#status { position: absolute; left: 0; right: 0; bottom: 12%; text-align: center; color: #ddd;
|
||||
font: 16px sans-serif; text-shadow: 0 0 4px #000; pointer-events: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="player"></div>
|
||||
<div id="status"></div>
|
||||
<script src="player.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,187 +0,0 @@
|
||||
/* DroidDeck Xbox Cloud Gaming player. Includes Greenlight's player (c) UnknownSKL (Jim Kroon), MIT. */
|
||||
(()=>{var _=class{_player;_dataChannel;constructor(e){this._player=e,this._dataChannel=this._player._peerConnection.createDataChannel(this.getChannelName(),this.getChannelConfig()),this._dataChannel.onopen=this.onOpen.bind(this),this._dataChannel.onmessage=this.onMessage.bind(this),this._dataChannel.onclosing=this.onClosing.bind(this),this._dataChannel.onclose=this.onClose.bind(this),this._dataChannel.onerror=this.onError.bind(this)}getChannelName(){return"channel"}getChannelConfig(){return{}}onOpen(e){console.log(this.getChannelName(),"Opening channel:",e)}onMessage(e){console.log(this.getChannelName(),"Message channel:",e)}onClosing(e){console.log(this.getChannelName(),"Closing channel:",e)}onClose(e){console.log(this.getChannelName(),"Closed channel:",e)}onError(e){console.log(this.getChannelName(),"Error channel:",e)}send(e){if(this._dataChannel.readyState!=="open"){console.log("["+this.getChannelName()+"] Tried to send data while channel is not open. readyState is:",this._dataChannel.readyState);return}return typeof e=="string"&&(e=new TextEncoder().encode(e)),this._dataChannel.send(e)}getPlayer(){return this._player}destroy(){}};var S=class extends _{_micPermissions="prompt";_micStream;constructor(e){super(e),navigator.permissions.query({name:"microphone"}).then(t=>{this._micPermissions=t.state,t.onchange=()=>{this._micPermissions=t.state}})}getChannelName(){return"chat"}getChannelConfig(){return{ordered:!0,protocol:"chatV1"}}destroy(){}};var v=class r{_player;_index;_physicalGamepadId=-1;_rumbleInterval;_isFocused=!0;_gamepadOverride={buttons:{}};_options={enable_keyboard:!1,enable_gamepad:!0,enable_vibration:!0,keyboard_mapping:{A:"Enter",B:"Backspace",X:"x",Y:"y",DPadUp:"ArrowUp",DPadDown:"ArrowDown",DPadLeft:"ArrowLeft",DPadRight:"ArrowRight",LeftShoulder:"[",RightShoulder:"]",LeftThumb:"l",RightThumb:"r",LeftTrigger:"-",RightTrigger:"=",Menu:"m",View:"v",Nexus:"n"},gamepad_mapping:{A:"0",B:"1",X:"2",Y:"3",DPadUp:"12",DPadDown:"13",DPadLeft:"14",DPadRight:"15",LeftShoulder:"4",RightShoulder:"5",LeftThumb:"10",RightThumb:"11",LeftTrigger:"6",RightTrigger:"7",Menu:"9",View:"8",Nexus:"16"},gamepad_axes_mapping:{LeftThumbXAxis:"0",LeftThumbYAxis:"1",RightThumbXAxis:"2",RightThumbYAxis:"3"},gamepad_deadzone:.08,gamepad_force_capture:!1};_listener={keyDown:this.onKeyDown.bind(this),keyUp:this.onKeyUp.bind(this),gamepadConnected:this.onGamepadConnected.bind(this),gamepadDisconnected:this.onGamepadDisconnected.bind(this),windowFocus:this.onWindowFocus.bind(this),windowBlur:this.onWindowBlur.bind(this)};constructor(e,t={}){this._index=e,this._options={...this._options,...t}}attach(e){this._player=e,this._player._channels.control.sendGamepadState(this._index,!0,this),this._options.enable_keyboard===!0&&(window.addEventListener("keydown",this._listener.keyDown),window.addEventListener("keyup",this._listener.keyUp)),this._options.enable_gamepad===!0&&(window.addEventListener("gamepadconnected",this._listener.gamepadConnected),window.addEventListener("gamepaddisconnected",this._listener.gamepadDisconnected)),this._options.enable_gamepad===!0&&this._options.gamepad_force_capture===!1&&(window.addEventListener("blur",this._listener.windowBlur),window.addEventListener("focus",this._listener.windowFocus)),this.detectActiveGamepad()}detach(){if(this._player===void 0){console.log("[Gamepad] Player is not attached. this._player is:",this._player);return}this._player._channels.control.sendGamepadState(this._index,!1),this._physicalGamepadId=-1,this._rumbleInterval!==void 0&&clearInterval(this._rumbleInterval),this._options.enable_keyboard===!0&&(window.removeEventListener("keydown",this._listener.keyDown),window.removeEventListener("keyup",this._listener.keyUp)),this._options.enable_gamepad===!0&&(window.removeEventListener("gamepadconnected",this._listener.gamepadConnected),window.removeEventListener("gamepaddisconnected",this._listener.gamepadDisconnected)),this._options.enable_gamepad===!0&&this._options.gamepad_force_capture===!1&&(window.removeEventListener("blur",this._listener.windowBlur),window.removeEventListener("focus",this._listener.windowFocus))}sendButtonState(e,t){if(this._player===void 0){console.log("[Gamepad] Player is not attached. this._player is:",this._player);return}if(!(e in this.getDefaultFamepadFrame())){console.log("[Gamepad] Invalid button:",e);return}let n=this.getDefaultFamepadFrame();n.GamepadIndex=this._index,this._gamepadOverride.buttons[e]=t}onKeyDown(e){if(this._isFocused!==!1)for(let t in this._options.keyboard_mapping)this._options.keyboard_mapping[t]===e.key&&(this._gamepadOverride.buttons[t]=1,e.preventDefault())}onKeyUp(e){if(this._isFocused!==!1)for(let t in this._options.keyboard_mapping)this._options.keyboard_mapping[t]===e.key&&(this._gamepadOverride.buttons[t]=0,e.preventDefault())}onWindowFocus(){this._isFocused=!0}onWindowBlur(){}getDefaultFamepadFrame(){return{GamepadIndex:0,Nexus:0,Menu:0,View:0,A:0,B:0,X:0,Y:0,DPadUp:0,DPadDown:0,DPadLeft:0,DPadRight:0,LeftShoulder:0,Line truncated
|
||||
|
||||
`+t.ContentText)?this.completeTransaction(e.id,{Result:0}):this.completeTransaction(e.id,{Result:1})}};var M=class extends _{_handshakeCompleted=!1;getChannelName(){return"message"}getChannelConfig(){return{protocol:"messageV1",ordered:!0}}onOpen(){console.log("[MessageChannel] Sending handshake...");let e=JSON.stringify({type:"Handshake",version:"messageV1",id:"be0bfc6d-1e83-4c8a-90ed-fa8601c5a179",cv:"0"});this.send(e)}onMessage(e){let t=JSON.parse(e.data);switch(t.type){case"HandshakeAck":console.log("[MessageChannel] Handshake completed"),this._handshakeCompleted=!0,this.getPlayer()._channels.control.sendAuthorization(),this.getPlayer()._channels.input.start(),this.sendConfig();break;case"TransactionStart":case"Message":new D(this,t);break;default:console.log("[MessageChannel] Unhandled message type:",t.type,t)}}sendConfig(){let e=[],t=[0,2,0],n=JSON.stringify(this.generateMessage("/streaming/systemUi/configuration",{version:t,systemUis:e}));this.send(n);let i=JSON.stringify(this.generateMessage("/streaming/properties/clientappinstallidchanged",{clientAppInstallId:"c97d7ee0-73b2-4239-bf1d-9d805a338429"}));this.send(i);let a=JSON.stringify(this.generateMessage("/streaming/characteristics/orientationchanged",{orientation:0}));this.send(a);let s=JSON.stringify(this.generateMessage("/streaming/characteristics/touchinputenabledchanged",{touchInputEnabled:!1}));this.send(s);let o=JSON.stringify(this.generateMessage("/streaming/characteristics/clientdevicecapabilities",{}));this.send(o);let d=JSON.stringify(this.generateMessage("/streaming/characteristics/dimensionschanged",{horizontal:1920,vertical:1080,preferredWidth:1920,preferredHeight:1080,safeAreaLeft:0,safeAreaTop:0,safeAreaRight:1920,safeAreaBottom:1080,supportsCustomResolution:!0}));this.send(d)}generateMessage(e,t){return{type:"Message",content:JSON.stringify(t),id:Q(),target:e,cv:""}}destroy(){}};var E=class{_address;_ipv4Address;_ipv4Port;constructor(e){this._address=e;let t=this._address.split(":"),n=t[6]+t[7],i=t[5],a=~parseInt(n.substring(0,2),16)&255,s=~parseInt(n.substring(2,4),16)&255,o=~parseInt(n.substring(4,6),16)&255,d=~parseInt(n.substring(6,8),16)&255,u=~parseInt(i.substring(0,4),16)&65535;this._ipv4Address=a+"."+s+"."+o+"."+d,this._ipv4Port=u}getIpv4Address(){return this._ipv4Address}getIpv4Port(){return this._ipv4Port}};var R=class{_peerConnection;_iceCandidates=[];constructor(e){this._peerConnection=e._peerConnection,this._peerConnection.addEventListener("icecandidate",t=>{t.candidate&&this._iceCandidates.push(t.candidate)})}getCandidates(){return this._iceCandidates}setRemoteCandidates(e){e.length===0&&console.warn("Error: No candidates received!");for(let t in e){if(e[t].candidate==="a=end-of-candidates")continue;let n=e[t].candidate.split(" ");if(n.length>4&&n[4].substr(0,4)==="2001"){let i=new E(n[4]);this._peerConnection.addIceCandidate({candidate:"a=candidate:10 1 UDP 1 "+i.getIpv4Address()+" 9002 typ host ",sdpMLineIndex:e[t].sdpMLineIndex,sdpMid:e[t].sdpMid}),this._peerConnection.addIceCandidate({candidate:"a=candidate:11 1 UDP 1 "+i.getIpv4Address()+" "+i.getIpv4Port()+" typ host ",sdpMLineIndex:e[t].sdpMLineIndex,sdpMid:e[t].sdpMid})}this._peerConnection.addIceCandidate({candidate:e[t].candidate,sdpMid:e[t].sdpMid,sdpMLineIndex:e[t].sdpMLineIndex})}}};var I=class{_player;_peerConnection;constructor(e){this._player=e,this._peerConnection=e._peerConnection}getAvailableCodecs(){let e=RTCRtpReceiver.getCapabilities("video")?.codecs;if(e===void 0)return[];let t=[];for(let n in e)t.includes(e[n].mimeType)||t.push(e[n].mimeType);return t}setLocalSDP(e){return console.log("[SDP] setLocalSDP() Setting local SDP:",e),e.sdp=e.sdp?.replace("useinbandfec=1","useinbandfec=1; stereo=1"),e}setRemoteSDP(e){return console.log("[SDP] setRemoteSDP() Setting remote SDP:",e),e}getDefaultCodecPreferences(){let e=RTCRtpReceiver.getCapabilities("video")?.codecs;if(e===void 0)return[];let t=[],n=[],i=[],a=[];for(let o in e)e[o].mimeType.includes("H264")?e[o].sdpFmtpLine?.includes("profile-level-id=4d")?t.push(e[o]):e[o].sdpFmtpLine?.includes("profile-level-id=42e")?n.push(e[o]):e[o].sdpFmtpLine?.includes("profile-level-id=420")&&i.push(e[o]):(e[o].mimeType.includes("ulpfec")||e[o].mimeType.includes("flexfec")||e[o].mimeType.includes("VP9")||e[o].mimeType.includes("VP8"))&&a.push(e[o]);return[...t,...n,...i,...a]}_setBitrate(e,t,n){let i=e.split(`
|
||||
`),a=-1;for(let o=0;o<i.length;o++)if(i[o].indexOf("m="+t)===0){a=o;break}if(a===-1)return console.debug("Could not find the m line for",t),e;for(a++;i[a].indexOf("i=")===0||i[a].indexOf("c=")===0;)a++;if(i[a].indexOf("b")===0)return i[a]="b=AS:"+n,i.join(`
|
||||
`);let s=i.slice(0,a);return s.push("b=AS:"+n),s=s.concat(i.slice(a,i.length)),s.join(`
|
||||
`)}};var P=class{_player;_peerConnection;_videoCodec="";_audioCodec="";_videoWidth=0;_videoHeight=0;_videoFps=0;_rtt=0;_remoteHost="";_remotePort=0;_remoteIsLocal=!1;_remoteIsIpv6=!1;_activeRemoteCandidate="";constructor(e){this._player=e,this._peerConnection=e._peerConnection,setInterval(this.loop.bind(this),1e3)}loop(){this._peerConnection.getStats().then(e=>{e.forEach(t=>{t.type==="inbound-rtp"?this.readInboundRtp(t):t.type==="candidate-pair"&&t.packetsReceived!==void 0&&t.packetsReceived>0?this.readCandidatePair(t):t.type==="remote-candidate"&&this.readRemoteCandidate(t)})})}readInboundRtp(e){e.kind==="video"&&e.codecId&&e.packetsReceived!==void 0&&e.packetsReceived>0?(e.codecId.includes("profile-level-id=4d")?this._videoCodec="H264 (High)":e.codecId.includes("profile-level-id=42e")?this._videoCodec="H264 (Normal)":e.codecId.includes("profile-level-id=420")?this._videoCodec="H264 (Low)":this._videoCodec="Other ("+e.codecId+")",e.frameWidth&&(this._videoWidth=e.frameWidth),e.frameHeight&&(this._videoHeight=e.frameHeight),e.framesPerSecond&&(this._videoFps=e.framesPerSecond)):e.kind==="audio"&&e.codecId&&e.packetsReceived!==void 0&&e.packetsReceived>0&&(this._audioCodec=e.codecId)}readCandidatePair(e){this._activeRemoteCandidate=e.remoteCandidateId,this._rtt=e.currentRoundTripTime}readRemoteCandidate(e){e.id===this._activeRemoteCandidate&&(this.isPrivateIP(e.address)?this._remoteIsLocal=!0:this._remoteIsLocal=!1,this.isIpv6(e.address)?this._remoteIsIpv6=!0:this._remoteIsIpv6=!1,this._remoteHost=e.address,this._remotePort=e.port)}isPrivateIP(e){let t=e.split(".");return t[0]==="10"||t[0]==="172"&&parseInt(t[1],10)>=16&&parseInt(t[1],10)<=31||t[0]==="192"&&t[1]==="168"}isIpv6(e){return e.split(":").length>3}};var G=class{_player;_videoComponent;_overlays={debug:void 0};constructor(e,t){this._videoComponent=e,this._player=t}toggleDebug(){this._overlays.debug===void 0?this.createDebugOverlay():this.destroy()}createDebugOverlay(){this._overlays.debug=document.createElement("div"),this._overlays.debug.id="playerDebugOverlay",this._overlays.debug.style.position="absolute",this._overlays.debug.style.top="0",this._overlays.debug.style.left="0",this._overlays.debug.style.width=this._videoComponent.getElement()?.style.width||"100%",this._overlays.debug.style.height=this._videoComponent.getElement()?.style.height||"auto",this._overlays.debug.style.background="linear-gradient(0deg, rgba(0,0,0, 0) 0%, rgba(0,0,0, 0) 75%, rgba(0,0,0, 0.5) 100%)",this._overlays.debug.style.padding="10px",this._overlays.debug.style.pointerEvents="none",document.getElementById(this._player.getElementId())?.appendChild(this._overlays.debug),setTimeout(()=>{this.refreshDebugOverlay()},500),this.refreshDebugOverlay()}destroy(){this._overlays.debug?.remove(),this._overlays.debug=void 0}refreshDebugOverlay(){if(this._overlays.debug!==void 0){this._overlays.debug.innerHTML="",this._overlays.debug.appendChild(this.createLabel("Local Play",this._player.getStats()._remoteIsLocal?"Local":"Remote","ok")),this._overlays.debug.appendChild(this.createLabel("Resolution",this._player.getStats()._videoWidth+"x"+this._player.getStats()._videoHeight,"ok")),this._overlays.debug.appendChild(this.createLabel("Stream FPS",this._player.getStats()._videoFps.toString(),this._player.getStats()._videoFps>=58?"ok":"warning"));let e=this._videoComponent.getFrameStats?.();e&&(this._overlays.debug.appendChild(this.createLabel("Render FPS",e.renderingFps.toString(),e.renderingFps>=58?"ok":"warning")),this._overlays.debug.appendChild(this.createLabel("Render Delay",e.renderDelayMs.toString()+" ms",e.renderDelayMs<16?"ok":e.renderDelayMs<33?"warning":"error")),this._overlays.debug.appendChild(this.createLabel("Dropped Frames",e.droppedFrames.toString(),e.droppedFrames===0?"ok":e.droppedFrames<30?"warning":"error"))),this._overlays.debug.appendChild(this.createLabel("Connection",this._player.getStats()._remoteIsIpv6?"IPv6":"IPv4","ok")),this._overlays.debug.appendChild(this.createLabel("Video",this._player.getStats()._videoCodec,"ok"));let t=this._player.getStats()._rtt*1e3>75?"error":this._player.getStats()._rtt*1e3>40?"warning":"ok";this._overlays.debug.appendChild(this.createLabel("RTT",(this._player.getStats()._rtt*1e3).toString()+" ms",t)),setTimeout(()=>{this.refreshDebugOverlay()},500)}}createLabel(e,t="",n=""){let i={borderRadius:"5px",background:"linear-gradient(0deg, rgba(28,26,26,1) 0%, rgba(47,45,45,1) 100%)",padding:"10px",fontSize:"12px",marginTop:"3px",marginRight:"10px",marginBottom:"2px",textTransform:"uppercase",color:"white",display:"inline-block"},a=document.createElement("span"),s={error:"rgb(220, 53, 69)",warning:"rgb(255, 193, 7)",ok:"rgb(25, 135, 84)"};t!==""?a.innerHTML=`${e}: <span style="background: ${s[n]}; border-radius: 5px; padding: 4px; padding-left: 8px; padding-right: 8px;">${t}</span>`:a.innerText=e;for(let o in i)a.style[o]=i[o];return a}};var L=class{_player;_element;_overlay;constructor(e){this._player=e,this._overlay=new G(this,this._pLine truncated
|
||||
in vec2 position;
|
||||
out vec2 vUV;
|
||||
void main() { vUV = position * 0.5 + 0.5; gl_Position = vec4(position, 0.0, 1.0); }`,j=`attribute vec2 position;
|
||||
varying vec2 vUV;
|
||||
void main() { vUV = position * 0.5 + 0.5; gl_Position = vec4(position, 0.0, 1.0); }`,Z=`#version 300 es
|
||||
precision mediump float;
|
||||
uniform sampler2D screenTexture;
|
||||
uniform vec2 resolution;
|
||||
uniform int filterId;
|
||||
uniform float sharpenFactor;
|
||||
uniform float brightness;
|
||||
uniform float contrast;
|
||||
uniform float gamma;
|
||||
uniform float saturation;
|
||||
uniform float deband;
|
||||
out vec4 fragColor;
|
||||
const float CAS_CONTRAST_PEAK = 0.8 * -3.0 + 8.0;
|
||||
|
||||
vec3 sharpen(vec2 coord, vec3 e) {
|
||||
vec2 px = 1.0 / resolution;
|
||||
vec3 b = texture(screenTexture, coord + px * vec2(0, 1)).rgb;
|
||||
vec3 d = texture(screenTexture, coord + px * vec2(-1, 0)).rgb;
|
||||
vec3 f = texture(screenTexture, coord + px * vec2(1, 0)).rgb;
|
||||
vec3 h = texture(screenTexture, coord + px * vec2(0, -1)).rgb;
|
||||
vec3 a = texture(screenTexture, coord + px * vec2(-1, 1)).rgb;
|
||||
vec3 c = texture(screenTexture, coord + px * vec2(1, 1)).rgb;
|
||||
vec3 g = texture(screenTexture, coord + px * vec2(-1, -1)).rgb;
|
||||
vec3 i = texture(screenTexture, coord + px * vec2(1, -1)).rgb;
|
||||
if (filterId == 1) {
|
||||
vec3 blur = ((a + c + g + i) + (b + d + f + h) * 2.0 + e * 4.0) / 16.0;
|
||||
return e + (e - blur) * sharpenFactor / 3.0;
|
||||
}
|
||||
vec3 minRgb = min(min(min(d, e), min(f, b)), h) + min(min(a, c), min(g, i));
|
||||
vec3 maxRgb = max(max(max(d, e), max(f, b)), h) + max(max(a, c), max(g, i));
|
||||
vec3 amplify = inversesqrt(clamp(min(minRgb, 2.0 - maxRgb) / maxRgb, 0.0, 1.0));
|
||||
vec3 weight = -(1.0 / (amplify * CAS_CONTRAST_PEAK));
|
||||
vec3 outColor = clamp(((b + d + f + h) * weight + e) / (4.0 * weight + 1.0), 0.0, 1.0);
|
||||
return mix(e, outColor, sharpenFactor / 2.0);
|
||||
}
|
||||
|
||||
float ign(vec2 p) { return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); }
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / resolution;
|
||||
vec3 color = texture(screenTexture, uv).rgb;
|
||||
if (sharpenFactor > 0.0) color = sharpen(uv, color);
|
||||
color = clamp(color + brightness, 0.0, 1.0);
|
||||
color = (color - 0.5) * clamp(contrast + 1.0, 0.5, 2.0) + 0.5;
|
||||
color = mix(vec3(dot(color, vec3(0.2126, 0.7152, 0.0722))), color, saturation);
|
||||
color = pow(max(color, 0.0), vec3(1.0 / gamma));
|
||||
if (deband > 0.0) color += vec3(((ign(gl_FragCoord.xy) + ign(gl_FragCoord.xy + vec2(11.0, 17.0))) - 1.0) * (deband / 255.0));
|
||||
fragColor = vec4(color, 1.0);
|
||||
}`,ee=`precision highp float;
|
||||
#define FXAA_MIN_REDUCE (1.0 / 128.0)
|
||||
#define FXAA_MUL_REDUCE (1.0 / 8.0)
|
||||
#define MAX_SPAN 8.0
|
||||
uniform sampler2D screenTexture;
|
||||
uniform vec2 resolution;
|
||||
const vec3 luma = vec3(0.299, 0.587, 0.114);
|
||||
void main() {
|
||||
vec2 invResolution = 1.0 / resolution;
|
||||
vec3 rgbNW = texture2D(screenTexture, (gl_FragCoord.xy + vec2(-1.0, -1.0)) * invResolution).rgb;
|
||||
vec3 rgbNE = texture2D(screenTexture, (gl_FragCoord.xy + vec2( 1.0, -1.0)) * invResolution).rgb;
|
||||
vec3 rgbSW = texture2D(screenTexture, (gl_FragCoord.xy + vec2(-1.0, 1.0)) * invResolution).rgb;
|
||||
vec3 rgbSE = texture2D(screenTexture, (gl_FragCoord.xy + vec2( 1.0, 1.0)) * invResolution).rgb;
|
||||
vec3 rgbM = texture2D(screenTexture, gl_FragCoord.xy * invResolution).rgb;
|
||||
float lumaNW = dot(rgbNW, luma);
|
||||
float lumaNE = dot(rgbNE, luma);
|
||||
float lumaSW = dot(rgbSW, luma);
|
||||
float lumaSE = dot(rgbSE, luma);
|
||||
float lumaM = dot(rgbM, luma);
|
||||
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
|
||||
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
|
||||
vec2 dir;
|
||||
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
|
||||
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
|
||||
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 0.25 * FXAA_MUL_REDUCE, FXAA_MIN_REDUCE);
|
||||
float minDirFactor = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
|
||||
dir = clamp(dir * minDirFactor, vec2(-MAX_SPAN), vec2(MAX_SPAN)) * invResolution;
|
||||
vec4 rgbA = 0.5 * (
|
||||
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * (1.0 / 3.0 - 0.5)) +
|
||||
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * (2.0 / 3.0 - 0.5)));
|
||||
vec4 rgbB = rgbA * 0.5 + 0.25 * (
|
||||
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * -0.5) +
|
||||
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * 0.5));
|
||||
float lumaB = dot(rgbB, vec4(luma, 0.0));
|
||||
gl_FragColor = lumaB < lumaMin || lumaB > lumaMax ? rgbA : rgbB;
|
||||
}`,te=`precision highp float;
|
||||
uniform sampler2D screenTexture;
|
||||
uniform vec2 resolution;
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / resolution;
|
||||
float edgeThreshold = 0.2;
|
||||
vec2 offset = vec2(1.0) / resolution;
|
||||
vec3 colorCenter = texture2D(screenTexture, uv).rgb;
|
||||
vec3 colorLeft = texture2D(screenTexture, uv - vec2(offset.x, 0.0)).rgb;
|
||||
vec3 colorRight = texture2D(screenTexture, uv + vec2(offset.x, 0.0)).rgb;
|
||||
vec3 colorUp = texture2D(screenTexture, uv - vec2(0.0, offset.y)).rgb;
|
||||
vec3 colorDown = texture2D(screenTexture, uv + vec2(0.0, offset.y)).rgb;
|
||||
float diffHorizontal = length(colorRight - colorLeft);
|
||||
float diffVertical = length(colorUp - colorDown);
|
||||
float edgeFactor = step(edgeThreshold, diffHorizontal + diffVertical);
|
||||
vec3 outlineColor = mix(colorCenter, vec3(0.0), edgeFactor);
|
||||
gl_FragColor = vec4(outlineColor, 1.0);
|
||||
}`,ne=`precision highp float;
|
||||
#define CA_AMOUNT 1.0025
|
||||
#define SCANLINE_INTENSITY_X 0.125
|
||||
#define SCANLINE_INTENSITY_Y 0.375
|
||||
#define SCANLINE_SIZE 1024.0
|
||||
uniform sampler2D screenTexture;
|
||||
varying vec2 vUV;
|
||||
void main() {
|
||||
vec4 finalColor = texture2D(screenTexture, vUV);
|
||||
finalColor.rgb = vec3(
|
||||
texture2D(screenTexture, (vUV - 0.5) * CA_AMOUNT + 0.5).r,
|
||||
finalColor.g,
|
||||
texture2D(screenTexture, (vUV - 0.5) / CA_AMOUNT + 0.5).b
|
||||
);
|
||||
float scanlineX = abs(sin(vUV.x * SCANLINE_SIZE) * 0.5 * SCANLINE_INTENSITY_X);
|
||||
float scanlineY = abs(sin(vUV.y * SCANLINE_SIZE) * 0.5 * SCANLINE_INTENSITY_Y);
|
||||
gl_FragColor = vec4(mix(finalColor.rgb, vec3(0.0), scanlineX + scanlineY), finalColor.a);
|
||||
}`,ie=`precision highp float;
|
||||
#define PI 3.14159265
|
||||
#define SCANLINE_INTENSITY 0.35
|
||||
#define CHROMA_OFFSET 0.005
|
||||
#define BLUR_RADIUS 0.002
|
||||
#define WARP_AMOUNT 0.01
|
||||
#define SCANLINE_DARKEN 0.5
|
||||
uniform sampler2D screenTexture;
|
||||
uniform int FrameCount;
|
||||
uniform vec2 TextureSize;
|
||||
uniform vec2 resolution;
|
||||
varying vec2 vUV;
|
||||
const mat3 yiq_mat = mat3(
|
||||
0.299, 0.587, 0.114,
|
||||
0.596, -0.275, -0.321,
|
||||
0.212, -0.523, 0.311
|
||||
);
|
||||
const mat3 yiq2rgb_mat = mat3(
|
||||
1.0, 0.956, 0.621,
|
||||
1.0, -0.272, -0.647,
|
||||
1.0, -1.106, 1.705
|
||||
);
|
||||
vec3 applyNTSC(vec2 uv) {
|
||||
vec3 col = texture2D(screenTexture, uv).rgb;
|
||||
vec3 yiq = col * yiq_mat;
|
||||
float chromaPhase = PI * (mod(uv.y * TextureSize.y, 2.0) + float(FrameCount));
|
||||
yiq.y *= cos(chromaPhase * 0.5);
|
||||
yiq.z *= sin(chromaPhase * 0.5);
|
||||
vec3 rgb = yiq * yiq2rgb_mat;
|
||||
vec3 finalColor;
|
||||
finalColor.r = texture2D(screenTexture, uv + vec2(CHROMA_OFFSET, 0.0)).r;
|
||||
finalColor.g = texture2D(screenTexture, uv + vec2(0.0, BLUR_RADIUS)).g;
|
||||
finalColor.b = texture2D(screenTexture, uv - vec2(CHROMA_OFFSET, 0.0)).b;
|
||||
return finalColor;
|
||||
}
|
||||
vec3 applyScanlines(vec2 uv) {
|
||||
vec3 col = texture2D(screenTexture, uv).rgb;
|
||||
float scanline = abs(sin(uv.y * resolution.y * 2.0)) * SCANLINE_INTENSITY;
|
||||
col *= 1.0 - (scanline * SCANLINE_DARKEN);
|
||||
return col;
|
||||
}
|
||||
vec2 applyWarp(vec2 uv) {
|
||||
uv = uv * 2.0 - 1.0;
|
||||
float r = sqrt(uv.x * uv.x + uv.y * uv.y);
|
||||
uv += uv * (r * r) * WARP_AMOUNT;
|
||||
return uv * 0.5 + 0.5;
|
||||
}
|
||||
void main() {
|
||||
vec2 warpedUV = applyWarp(vUV);
|
||||
vec3 ntscColor = applyNTSC(warpedUV);
|
||||
vec3 scanlineColor = applyScanlines(warpedUV);
|
||||
vec3 finalColor = mix(ntscColor, scanlineColor, 0.7);
|
||||
gl_FragColor = vec4(finalColor, 1.0);
|
||||
}`,H=class{constructor(e,t){this.video=e;this.effects=t;this.canvas=document.createElement("canvas"),Object.assign(this.canvas.style,{position:"absolute",inset:"0",width:"100%",height:"100%",objectFit:"contain",zIndex:"1"}),e.parentElement?.appendChild(this.canvas);let n=this.canvas.getContext("webgl2",{alpha:!1,antialias:!1,depth:!1,desynchronized:!0,powerPreference:"high-performance"});if(n===null)throw new Error("WebGL2 unavailable");this.gl=n;let i=n.createBuffer();n.bindBuffer(n.ARRAY_BUFFER,i),n.bufferData(n.ARRAY_BUFFER,new Float32Array([-1,-1,3,-1,-1,3]),n.STATIC_DRAW),this.passes.set("base",this.compile(J,Z,["screenTexture","resolution","filterId","sharpenFactor","brightness","contrast","gamma","saturation","deband"])),this.videoTexture=this.texture(),this.schedule()}canvas;gl;passes=new Map;videoTexture;targets=[];width=0;height=0;frame=0;lastDraw=0;stopped=!1;idle=!1;pending=!1;update(e){this.effects=e}pause(){this.idle||this.stopped||(this.idle=!0,this.canvas.style.display="none")}resume(e){this.effects=e,!(!this.idle||this.stopped)&&(this.idle=!1,this.canvas.style.display="",this.schedule())}schedule(){this.pending||(this.pending=!0,this.video.requestVideoFrameCallback(this.onFrame))}compile(e,t,n){let i=this.gl,a=(o,d)=>{let u=i.createShader(o);if(i.shaderSource(u,d),i.compileShader(u),!i.getShaderParameter(u,i.COMPILE_STATUS))throw new Error(`shader: ${i.getShaderInfoLog(u)}`);return u},s=i.createProgram();if(i.attachShader(s,a(i.VERTEX_SHADER,e)),i.attachShader(s,a(i.FRAGMENT_SHADER,t)),i.bindAttribLocation(s,0,"position"),i.linkProgram(s),!i.getProgramParameter(s,i.LINK_STATUS))throw new Error(`program: ${i.getProgramInfoLog(s)}`);return{program:s,uniforms:new Map(n.map(o=>[o,i.getUniformLocation(s,o)]))}}extra(e){let t=this.passes.get(e);if(t===void 0){let n={fxaa:ee,toon:te,crt:ne,ntsc:ie}[e];t=this.compile(j,n,["screenTexture","resolution","TextureSize","FrameCount"]),this.passes.set(e,t)}return t}texture(){let e=this.gl,t=e.createTexture();return e.bindTexture(e.TEXTURE_2D,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),t}resize(e,t){let n=this.gl;this.width=e,this.height=t,this.canvas.width=e,this.canvas.height=t;for(let i of this.targets)n.deleteFramebuffer(i.framebuffer),n.deleteTexture(i.texture);this.targets=[0,1].map(()=>{let i=this.texture();n.texImage2D(n.TEXTURE_2D,0,n.RGBA,e,t,0,n.RGBA,n.UNSIGNED_BYTE,null);let a=n.createFramebuffer();return n.bindFramebuffer(n.FRAMEBUFFER,a),n.framebufferTexture2D(n.FRAMEBUFFER,n.COLOR_ATTACHMENT0,n.TEXTURE_2D,i,0),{framebuffer:a,texture:i}}),n.bindFramebuffer(n.FRAMEBUFFER,null)}onFrame=e=>{if(this.pending=!1,this.stopped||this.idle)return;this.schedule();let t=this.effects.maxFps;if(!(t>0&&e-this.lastDraw<1e3/t-2)){this.lastDraw=e;try{this.draw()}catch(n){window.DDCloud?.log(`effects: ${String(n).slice(0,200)}`),this.destroy()}}};draw(){let e=this.gl,t=this.effects,n=this.video.videoWidth,i=this.video.videoHeight;if(n===0||i===0)return;(n!==this.width||i!==this.height)&&this.resize(n,i),this.frame++,e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.videoTexture),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,this.video);let a=["fxaa","toon","crt","ntsc"].filter(d=>t[d]);e.viewport(0,0,n,i),e.enableVertexAttribArray(0),e.vertexAttribPointer(0,2,e.FLOAT,!1,0,0);let s=this.passes.get("base");e.bindFramebuffer(e.FRAMEBUFFER,a.length>0?this.targets[0].framebuffer:null),e.useProgram(s.program);let o=s.uniforms;e.uniform1i(o.get("screenTexture"),0),e.uniform2f(o.get("resolution"),n,i),e.uniform1i(o.get("filterId"),t.sharpenMode==="usm"?1:2),e.uniform1f(o.get("sharpenFactor"),t.sharpen),e.uniform1f(o.get("brightness"),t.brightness/100),e.uniform1f(o.get("contrast"),t.contrast/100),e.uniform1f(o.get("gamma"),Math.min(5,Math.max(.1,t.gamma))),e.uniform1f(o.get("saturation"),t.saturation/100),e.uniform1f(o.get("deband"),t.deband),e.drawArrays(e.TRIANGLES,0,3),a.forEach((d,u)=>{let h=this.extra(d),g=this.targets[u%2],p=u===a.length-1;e.bindFramebuffer(e.FRAMEBUFFER,p?null:this.targets[(u+1)%2].framebuffer),e.useProgram(h.program),e.bindTexture(e.TEXTURE_2D,g.texture);let c=h.uniforms;e.uniform1i(c.get("screenTexture")??null,0),e.uniform2f(c.get("resolution")??null,n,i),e.uniform2f(c.get("TextureSize")??null,n,i),e.uniform1i(c.get("FrameCount")??null,this.frame),e.drawArrays(e.TRIANGLES,0,3)})}destroy(){this.stopped=!0,this.canvas.remove(),this.gl.getExtension("WEBGL_lose_context")?.loseContext()}};var V=class{constructor(e){this.pc=e;this.element=document.createElement("div"),Object.assign(this.element.style,{position:"absolute",top:"8px",right:"8px",zIndex:"3",padding:"4px 8px",borderRadius:"6px",background:"rgba(0,0,0,0.55)",color:"#e8e8e8",font:"12px monospace",lineHeight:"16px",pointerEvent
Line truncated
|
||||
`:" \xB7 ")}destroy(){this.show(0,this.vertical,this.server,this.left),this.element.remove()}};function z(r,e){let t=e==="high"?"4d":e==="low"?"420":"42e",n=[];for(let d of r.matchAll(/a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g))d[2].startsWith(t)&&n.push(d[1]);if(n.length===0)return r;let i=r.split(`\r
|
||||
`),a=i.findIndex(d=>d.startsWith("m=video"));if(a<0)return r;let s=i[a].trim().split(" "),o=s.slice(3).filter(d=>!n.includes(d));return i[a]=s.slice(0,3).concat(n,o).join(" "),i.join(`\r
|
||||
`)}function K(r,e){let t=r.split(`\r
|
||||
`),n=t.findIndex(a=>a.startsWith("m=video"));if(n<0)return r;let i=`b=AS:${e}`;for(let a=n+1;a<=t.length;a++){let s=t[a];if(s===void 0||s.startsWith("m=")){t.splice(a,0,i);break}if(!(s.startsWith("i=")||s.startsWith("c="))){if(s.startsWith("b=AS:")){t[a]=i;break}t.splice(a,0,i);break}}return t.join(`\r
|
||||
`)}var m=JSON.parse(DDCloud.settings()),ae=RTCPeerConnection.prototype.setLocalDescription;RTCPeerConnection.prototype.setLocalDescription=function(r){return r?.sdp&&((m.codec==="high"||m.codec==="normal"||m.codec==="low")&&(r.sdp=z(r.sdp,m.codec)),m.bitrateMbps>0&&(r.sdp=K(r.sdp,m.bitrateMbps*1e3))),ae.call(this,r)};window.__ddRumble=m.rumble/100;window.addEventListener("error",r=>DDCloud.log(`error: ${r.message} @${r.lineno}`));window.addEventListener("unhandledrejection",r=>DDCloud.log(`rejection: ${String(r.reason).slice(0,200)}`));window.addEventListener("gamepadconnected",r=>DDCloud.log(`controller: ${r.gamepad.id.slice(0,60)} mapping=${r.gamepad.mapping||"none"} buttons=${r.gamepad.buttons.length} axes=${r.gamepad.axes.length}`));var W=new Map,re=1;window.__ddResolve=(r,e,t)=>{let n=W.get(r);W.delete(r),n&&n({status:e,body:t})};function C(r,e=""){return new Promise(t=>{let n=re++;W.set(n,t),DDCloud.call(n,r,typeof e=="string"?e:JSON.stringify(e))})}var B=r=>new Promise(e=>setTimeout(e,r)),se=r=>r.status>=200&&r.status<300;function x(r){let e=document.getElementById("status");e&&(e.textContent=r),DDCloud.status(r)}var T=class extends Error{constructor(t,n){super(t);this.stage=t;this.detail=n}};function k(r,e){if(!se(e))throw new T(r,`${e.status} ${e.body.slice(0,300)}`);return e}function oe(r,e){return new Promise(t=>{if(r.iceGatheringState==="complete")return t();let n=setTimeout(t,e);r.addEventListener("icegatheringstatechange",()=>{r.iceGatheringState==="complete"&&(clearTimeout(n),t())})})}async function de(){x("Starting the game on Xbox Cloud Gaming\u2026"),k("start",await C("start"));let r=!1,e=0;for(;;){let l=k("state",await C("state")),f=JSON.parse(l.body||"{}");if(f.state==="Provisioned")break;if(f.state==="Failed")throw new T("provision",JSON.stringify(f.errorDetails??{}).slice(0,300));f.state==="ReadyToConnect"&&!r?(r=!0,x("Signing in to the cloud console\u2026"),k("connect",await C("connect"))):f.state==="WaitingForResources"?x(`In the queue for a cloud console\u2026 ${Math.round(e/1e3)} s`):x(`Getting a cloud console ready\u2026 ${Math.round(e/1e3)} s`),await B(1e3),e+=1e3}x("Connecting\u2026");let t=new U("player");t.init();let n=t._peerConnection,i=t._channels.input,a=i.queueMetadataFrame.bind(i);i.queueMetadataFrame=l=>{if(m.preventDrops){let f=l.frameRenderedTimeMs-l.frameDecodedTimeMs;l.frameDecodedTimeMs=l.frameSubmittedTimeMs+10,l.frameRenderedTimeMs=l.frameDecodedTimeMs+f}a(l)};let s,o,d=()=>{if(!(o===void 0||s===void 0&&m.volume===100)){if(s===void 0){let l=new AudioContext;s=l.createGain(),l.createMediaStreamSource(o).connect(s).connect(l.destination);let f=document.querySelector("#player audio");f&&(f.muted=!0)}s.gain.value=m.volume/100}};n.addEventListener("track",l=>{l.track.kind==="audio"&&(o=l.streams[0],d())});let u=new V(n),h,g=()=>{let l=t.getVideoElement();if(l!==void 0){if(!X(m)){h?.pause();return}if(h===void 0)try{h=new H(l,m)}catch(f){DDCloud.log(`effects unavailable: ${String(f).slice(0,200)}`)}else h.resume(m)}};window.__ddApply=l=>{m=JSON.parse(l),window.__ddRumble=m.rumble/100,u.show(m.statsLevel,m.statsLayout==="vertical",m.server,m.statsCorner==="left"),d(),g()},n.addEventListener("connectionstatechange",()=>{n.connectionState==="connected"?(x(""),DDCloud.playing(),u.show(m.statsLevel,m.statsLayout==="vertical",m.server,m.statsCorner==="left"),setTimeout(g,500)):n.connectionState==="failed"?DDCloud.failed("webrtc","connection failed"):n.connectionState==="disconnected"&&x("Connection lost\u2026")});let p=await t.createOffer(),c=k("sdp",await C("sdp",p.sdp??"")),O=JSON.parse(JSON.parse(c.body).exchangeResponse);if(O.status&&O.status!=="success")throw new T("sdp-answer",JSON.stringify(O).slice(0,300));await t.setRemoteSDP(O.sdp),await oe(n,3e3);let Y=(await t.getICECandidates()).map(l=>JSON.stringify({candidate:l.candidate,sdpMid:l.sdpMid,sdpMLineIndex:l.sdpMLineIndex,usernameFragment:l.usernameFragment})),$=k("ice",await C("ice",Y));t.setRemoteIceCandidates(JSON.parse(JSON.parse($.body).exchangeResponse));let q=Date.now();for(;!t._channels.message._handshakeCompleted&&Date.now()-q<2e4;)await B(100);DDCloud.log(`handshake ${t._channels.message._handshakeCompleted?"done":"missing"} after ${Date.now()-q} ms`),await B(300);let b=new v(0,{enable_keyboard:!1});b.attach(t),DDCloud.log(`controllers at attach: ${Array.from(navigator.getGamepads()).filter(Boolean).length}`),window.__ddNexus=()=>{b.sendButtonState("Nexus",1),setTimeout(()=>b.sendButtonState("Nexus",0),200)},window.__ddButton=(l,f)=>b.sendButtonState(l,f),window.__ddInput=l=>{l?b.onWindowFocus():b.onWindowBlur()},setInterval(()=>{C("keepalive")},3e4),window.__ddStop=()=>{try{h?.destroy(),u.destroy(),b.detach(),t.destroy()}catch{}}}de().catch(r=>{r instanceof T?DDCloud.failed(r.stage,r.detail):DDCloud.failed("page",String(r).slice(0,300))});})();
|
||||
@@ -1,65 +1,54 @@
|
||||
# The app's native side: the Wayland compositor the session presents into, the adrenotools loader
|
||||
# it opens Turnip with, and one small library for the fake-input ring's memory fence.
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(droiddeck C CXX)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-unused-function -Wimplicit-function-declaration")
|
||||
|
||||
# Every library the app builds is linked with 16 KB-aligned LOAD segments. A 16 KB-page kernel
|
||||
# (Android 15+ devices) refuses to load a 4 KB-only ELF; a 4 KB kernel loads a 16 KB-aligned one
|
||||
# unchanged (the alignment is a multiple of its page), so this costs nothing on today's devices
|
||||
# but a little padding in the file. Prebuilt libraries under jniLibs/ carry their own alignment.
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_subdirectory(adrenotools)
|
||||
|
||||
# DXVK's DXBC->SPIR-V translator (zlib licence), vendored under thirdparty/dxbc from the same
|
||||
# subset upstream lsfg-vk uses. Native LSFG frame generation needs it: the Lossless Scaling
|
||||
# shader chain is DXBC in every build downloadable from Steam.
|
||||
add_subdirectory(thirdparty/dxbc)
|
||||
|
||||
set(DDFG_UPSTREAM "v0.3.0")
|
||||
set(DDFG_CHAIN_SRC
|
||||
framegen/ddfg/framegen.cpp
|
||||
framegen/ddfg/framegen.hpp
|
||||
framegen/ddfg/record_impl.inc
|
||||
framegen/ddfg/config.hpp
|
||||
framegen/ddfg/embedded_shaders.hpp
|
||||
framegen/ddfg/ddfg_engine.cpp
|
||||
# The Win-FG chain self-identifies by two halves: the upstream tag it was copied from (hand-set,
|
||||
# UPDATE IT when re-copying) and a hash over the sources themselves, computed here, which cannot
|
||||
# rot. The compositor's frame-generation leg (waylandcomp/CMakeLists.txt) stamps both in.
|
||||
set(WINFG_UPSTREAM "v0.3.0")
|
||||
set(WINFG_CHAIN_SRC
|
||||
framegen/winfg/framegen.cpp
|
||||
framegen/winfg/framegen.hpp
|
||||
framegen/winfg/record_impl.inc
|
||||
framegen/winfg/config.hpp
|
||||
framegen/winfg/embedded_shaders.hpp
|
||||
framegen/winfg/winfg_engine.cpp
|
||||
)
|
||||
set(DDFG_HASH_INPUT "")
|
||||
foreach(f ${DDFG_CHAIN_SRC})
|
||||
set(WINFG_HASH_INPUT "")
|
||||
foreach(f ${WINFG_CHAIN_SRC})
|
||||
file(SHA256 "${CMAKE_CURRENT_SOURCE_DIR}/${f}" _h)
|
||||
string(APPEND DDFG_HASH_INPUT "${_h}")
|
||||
string(APPEND WINFG_HASH_INPUT "${_h}")
|
||||
endforeach()
|
||||
string(SHA256 DDFG_CHAIN_HASH_FULL "${DDFG_HASH_INPUT}")
|
||||
string(SUBSTRING "${DDFG_CHAIN_HASH_FULL}" 0 12 DDFG_CHAIN_HASH)
|
||||
message(STATUS "dd-fg chain: win-fg ${DDFG_UPSTREAM} (src ${DDFG_CHAIN_HASH})")
|
||||
string(SHA256 WINFG_CHAIN_HASH_FULL "${WINFG_HASH_INPUT}")
|
||||
string(SUBSTRING "${WINFG_CHAIN_HASH_FULL}" 0 12 WINFG_CHAIN_HASH)
|
||||
message(STATUS "win-fg chain: ${WINFG_UPSTREAM} (src ${WINFG_CHAIN_HASH})")
|
||||
|
||||
add_subdirectory(waylandcomp)
|
||||
add_subdirectory(questxr)
|
||||
add_library(sdnative SHARED sdnative/sdnative.c)
|
||||
target_link_libraries(sdnative log)
|
||||
|
||||
# The device info window's Vulkan section: the phone's own driver, asked once through the system
|
||||
# loader (opened with dlopen, so a phone without Vulkan still loads the app).
|
||||
add_library(deviceinfo SHARED deviceinfo/vkinfo.c)
|
||||
target_link_libraries(deviceinfo dl)
|
||||
|
||||
add_library(gpuclock SHARED gpuclock/kgsl_clock.c gpuclock/kgsl_busy.c)
|
||||
|
||||
# Rebuild Termux terminal-emulator's PTY JNI helper with the app's NDK instead of carrying its
|
||||
# prebuilt libtermux.so with a redundant libstdc++.so dependency.
|
||||
add_library(termux SHARED termux/termux.c)
|
||||
|
||||
add_library(ddvenc SHARED venc/venc_server.cpp)
|
||||
target_compile_features(ddvenc PRIVATE cxx_std_17)
|
||||
target_link_libraries(ddvenc mediandk log)
|
||||
|
||||
if(DROIDDECK_VITURE_SDK_DIR)
|
||||
add_library(droiddeck_viture SHARED viture/sdk_bridge.cpp)
|
||||
target_compile_features(droiddeck_viture PRIVATE cxx_std_17)
|
||||
target_include_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/include")
|
||||
target_link_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/android/${ANDROID_ABI}")
|
||||
target_link_libraries(droiddeck_viture PRIVATE glasses log)
|
||||
endif()
|
||||
|
||||
if(DROIDDECK_UVC_DIR)
|
||||
add_library(rgb_usb SHARED IMPORTED)
|
||||
set_target_properties(rgb_usb PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libusb.so")
|
||||
add_library(rgb_uvc STATIC IMPORTED)
|
||||
set_target_properties(rgb_uvc PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libuvc.a")
|
||||
add_library(droiddeck_uvc SHARED viture/uvc_bridge.cpp)
|
||||
target_compile_features(droiddeck_uvc PRIVATE cxx_std_17)
|
||||
target_include_directories(droiddeck_uvc PRIVATE "${DROIDDECK_UVC_DIR}/include")
|
||||
target_link_libraries(droiddeck_uvc PRIVATE rgb_uvc rgb_usb log)
|
||||
target_link_options(droiddeck_uvc PRIVATE -Wl,-z,max-page-size=16384)
|
||||
endif()
|
||||
@@ -3,6 +3,7 @@
|
||||
aarch64-linux-gnu-gcc -c src/bcenabler_patch.s -o /tmp/patch.o
|
||||
aarch64-linux-gnu-objcopy -O binary -j .text /tmp/patch.o /tmp/patch.bin
|
||||
|
||||
# Must be smaller than a single page
|
||||
bin2c -o gen/bcenabler_patch.h -n PatchRawData /tmp/patch.bin
|
||||
|
||||
rm /tmp/patch.o
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* Generated by bin2c, do not edit manually */
|
||||
|
||||
/* Contents of file /tmp/patch.bin */
|
||||
const long int PatchRawData_size = 264;
|
||||
const unsigned char PatchRawData[264] = {
|
||||
0x1F, 0x0C, 0x02, 0x71, 0x61, 0x00, 0x00, 0x54, 0xE0, 0x08, 0x80, 0x52, 0x3E, 0x00, 0x00, 0x14,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -12,14 +11,29 @@ extern "C" {
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Describes the level of BCeNabler support for a driver
|
||||
*/
|
||||
enum adrenotools_bcn_type {
|
||||
ADRENOTOOLS_BCN_INCOMPATIBLE,
|
||||
ADRENOTOOLS_BCN_BLOB,
|
||||
ADRENOTOOLS_BCN_PATCH
|
||||
ADRENOTOOLS_BCN_INCOMPATIBLE, //!< Driver isn't supported by BCeNabler
|
||||
ADRENOTOOLS_BCN_BLOB, //!< Driver already supports BCn textures so BCeNabler isn't necessary
|
||||
ADRENOTOOLS_BCN_PATCH //!< Driver can be patched with BCeNabler to support BCn textures
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks the status of BCn support in the supplied driver
|
||||
* @param major The major part of VkPhysicalDeviceProperties::driverVersion
|
||||
* @param minor The minor part of VkPhysicalDeviceProperties::driverVersion
|
||||
* @param vendorId VkPhysicalDeviceProperties::vendorId
|
||||
*/
|
||||
enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t minor, uint32_t vendorId);
|
||||
|
||||
/**
|
||||
* @brief Patches the Adreno graphics driver to enable support for BCn compressed formats
|
||||
* @note adrenotools_get_bcn_type MUST be checked to equal ADRENOTOOLS_BCN_PATCH before calling this
|
||||
* @param vkGetPhysicalDeviceFormatPropertiesFn A pointer to vkGetPhysicalDeviceFormatProperties obtained through vkGetInstanceProcAddr. This is used to find the correct function to patch
|
||||
* @return If the patching succeeded, if false the driver will be in an undefined state
|
||||
*/
|
||||
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -11,16 +10,60 @@ extern "C" {
|
||||
#include <stdbool.h>
|
||||
#include "priv.h"
|
||||
|
||||
/**
|
||||
* @brief Opens a new libvulkan.so instance according to `flags`
|
||||
* @remark IMPORTANT: The app MUST be packaged w/ useLegacyPackaging = true. The reason for this is
|
||||
* that we install the hook in hookLibDir; but this folder won't be set correctly when useLegacyPackaging == false
|
||||
* because Android expects to read the *.so directly from the apk (instead of decompressing them into nativeLibraryDir).
|
||||
* @param dlopenMode The dlopen mode to use when opening libvulkan
|
||||
* @param featureFlags Which adrenotools driver features to enable
|
||||
* @param tmpLibDir A writable directory to hold patched libraries, only used on api < 29 due to the lack of memfd support. If nullptr is passed and the API version is < 29 memfd usage will be attempted and if unsupported nullptr will be returned
|
||||
* @param hookLibDir The directory holding the built hooks.
|
||||
* IMPORTANT: This path MUST point to whatever `getApplicationInfo().nativeLibraryDir` (Kotlin code) returns.
|
||||
* Failing to do so will result in this function returning a valid ptr but then the hook will fail and either the original driver is loaded as fallback, or vkEnumeratePhysicalDevices returns 0 devices.
|
||||
* @param customDriverDir The directory to load a custom GPU driver named according to `customDriverName` from. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`.
|
||||
* IMPORTANT: This path MUST NOT be on sdcard/storage. i.e. use ANativeActivity::internalDataPath and often looks like "/data/user/0/com.example.app/files/".
|
||||
* Otherwise you will get permission denied because dlopen() can't be done on *.so libraries that any random user or application can manipulate (which would be a security risk).
|
||||
* @param customDriverName The soname of the custom driver to load. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`
|
||||
* @param fileRedirectDir The directory which to redirect all file accesses performed by the driver to. Only used if ADRENOTOOLS_DRIVER_FILE_REDIRECT is set in `featureFlags`
|
||||
* @param userMappingHandle A pointer to a void* which will be set to the mapping handle if ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT is set in `featureFlags`
|
||||
*/
|
||||
void *adrenotools_open_libvulkan(int dlopenMode, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle);
|
||||
|
||||
/**
|
||||
* @brief Imports the given CPU mapped memory range into the GSL allocator. This should then be followed by a call to vkAllocateMemory with a matching size which will return a VkDeviceMemory view over the input region
|
||||
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
|
||||
* @param hostPtr The host pointer to import
|
||||
* @param size The size of the region to import
|
||||
* @return True on success
|
||||
*/
|
||||
bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size);
|
||||
|
||||
/**
|
||||
* @brief Maps GPU memory and imports it into the GSL allocator. This should then be followed by a call to adrenotools_mem_cpu_map to map the memory on the CPU, then finally vkAllocateMemory with a matching size
|
||||
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
|
||||
* @param size Pointer to a variable containing the size of the region to import, will be updated to contain the required size of the region to allocate CPU side
|
||||
* @return true on success
|
||||
*/
|
||||
bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size);
|
||||
|
||||
/**
|
||||
* @brief Maps the last mapping allocated using adrenotools_mem_gpu_allocate into the given host memory region, such that vkAllocateMemory can then be called
|
||||
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
|
||||
* @param hostPtr A pointer to where the mapping should be mapped
|
||||
* @param size The size of the mapping. MUST be equal to the size returned by adrenotools_mem_gpu_allocate
|
||||
*/
|
||||
bool adrenotools_mem_cpu_map(void *handle, void *hostPtr, uint64_t size);
|
||||
|
||||
/**
|
||||
* @note This function should be called after adrenotools_open_libvulkan and Vulkan driver init to check if the mapping import hook loaded successfully
|
||||
* @return True if the mapping was successfully imported (or initialization succeeded)
|
||||
*/
|
||||
bool adrenotools_validate_gpu_mapping(void *handle);
|
||||
|
||||
/**
|
||||
* @brief Provides a way to force the GPU to run at the maximum possible clocks (thermal constraints will still be applied)
|
||||
*/
|
||||
void adrenotools_set_turbo(bool turbo);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Bitfield enum of additional driver features that can be used with adrenotools_open_libvulkan
|
||||
*/
|
||||
enum {
|
||||
ADRENOTOOLS_DRIVER_CUSTOM = 1 << 0,
|
||||
ADRENOTOOLS_DRIVER_FILE_REDIRECT = 1 << 1,
|
||||
@@ -14,9 +16,12 @@ enum {
|
||||
|
||||
#define ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC 0xDEADBEEF
|
||||
|
||||
/**
|
||||
* @brief A replacement GPU memory mapping for use with ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT
|
||||
*/
|
||||
struct adrenotools_gpu_mapping {
|
||||
void *host_ptr;
|
||||
uint64_t gpu_addr;
|
||||
uint64_t gpu_addr; //!< The GPU address of the mapping to import, if mapping import/init succeeds this will be set to ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC
|
||||
uint64_t size;
|
||||
uint64_t flags;
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -22,6 +21,7 @@ static loader_android_create_namespace_t loader_android_create_namespace;
|
||||
static bool lib_loaded;
|
||||
|
||||
|
||||
/* Public API */
|
||||
bool linkernsbypass_load_status() {
|
||||
return lib_loaded;
|
||||
}
|
||||
@@ -55,6 +55,8 @@ android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
|
||||
android_link_namespaces_t android_link_namespaces;
|
||||
|
||||
bool linkernsbypass_link_namespace_to_default_all_libs(android_namespace_t *to) {
|
||||
// Creating a shared namespace with the default parent will give a copy of the default namespace that we can actually access
|
||||
// This is needed since there is no way to access a direct handle to the default namespace as it's not exported
|
||||
static auto defaultNs{android_create_namespace_escape("default_copy", nullptr, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
|
||||
return android_link_namespaces_all_libs(to, defaultNs);
|
||||
}
|
||||
@@ -79,6 +81,7 @@ void *linkernsbypass_namespace_dlopen(const char *filename, int flags, android_n
|
||||
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, android_namespace_t *ns) {
|
||||
static std::array<char, PATH_MAX> PathBuf{};
|
||||
|
||||
// Used as a unique ID for overwriting soname and creating target lib files
|
||||
static uint16_t TargetId{};
|
||||
|
||||
int libTargetFd{[&] () {
|
||||
@@ -86,6 +89,7 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
|
||||
snprintf(PathBuf.data(), PathBuf.size(), "%s/%d_patched.so", libTargetDir, TargetId);
|
||||
return open(PathBuf.data(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
} else {
|
||||
// If memfd isn't supported errno will contain ENOSYS after calling
|
||||
errno = 0;
|
||||
int fd{static_cast<int>(syscall(__NR_memfd_create, libPath, 0))};
|
||||
if (errno == ENOSYS || fd < 0)
|
||||
@@ -97,18 +101,21 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
|
||||
if (libTargetFd == -1)
|
||||
return nullptr;
|
||||
|
||||
// Partially overwrite soname with 3 digits (replacing lib...) with to make sure a cached so isn't loaded
|
||||
std::array<char, 3> sonameOverwrite{};
|
||||
snprintf(sonameOverwrite.data(), sonameOverwrite.size(), "%03u", TargetId++);
|
||||
|
||||
if (!elf_soname_patch(libPath, libTargetFd, sonameOverwrite.data()))
|
||||
return nullptr;
|
||||
|
||||
// Load our patched library into the hook namespace
|
||||
android_dlextinfo hookExtInfo{
|
||||
.flags = ANDROID_DLEXT_USE_NAMESPACE | ANDROID_DLEXT_USE_LIBRARY_FD,
|
||||
.library_fd = libTargetFd,
|
||||
.library_namespace = ns
|
||||
};
|
||||
|
||||
// Make a path that looks about right
|
||||
snprintf(PathBuf.data(), PathBuf.size(), "/proc/self/fd/%d", libTargetFd);
|
||||
|
||||
return android_dlopen_ext(PathBuf.data(), flags, &hookExtInfo);
|
||||
@@ -118,19 +125,21 @@ static void *align_ptr(void *ptr) {
|
||||
return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) & ~(getpagesize() - 1));
|
||||
}
|
||||
|
||||
/* Private */
|
||||
__attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
using loader_dlopen_t = void *(*)(const char *, int, const void *);
|
||||
|
||||
if (android_get_device_api_level() < 28)
|
||||
return;
|
||||
|
||||
// ARM64 specific function walking to locate the internal dlopen handler
|
||||
auto loader_dlopen{[]() {
|
||||
union BranchLinked {
|
||||
uint32_t raw;
|
||||
|
||||
struct {
|
||||
int32_t offset : 26;
|
||||
uint8_t sig : 6;
|
||||
int32_t offset : 26; //!< 26-bit branch offset
|
||||
uint8_t sig : 6; //!< 6-bit signature
|
||||
};
|
||||
|
||||
bool Verify() {
|
||||
@@ -139,8 +148,10 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
};
|
||||
static_assert(sizeof(BranchLinked) == 4, "BranchLinked is wrong size");
|
||||
|
||||
// Some devices ship with --X mapping for exexecutables so work around that
|
||||
mprotect(align_ptr(reinterpret_cast<void *>(&dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
|
||||
|
||||
// dlopen is just a wrapper for __loader_dlopen that passes the return address as the third arg hence we can just walk it to find __loader_dlopen
|
||||
auto blInstr{reinterpret_cast<BranchLinked *>(&dlopen)};
|
||||
while (!blInstr->Verify())
|
||||
blInstr++;
|
||||
@@ -148,8 +159,10 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
return reinterpret_cast<loader_dlopen_t>(blInstr + blInstr->offset);
|
||||
}()};
|
||||
|
||||
// Protect the loader_dlopen function to remove the BTI attribute (since this is an internal function that isn't intended to be jumped indirectly to)
|
||||
mprotect(align_ptr(reinterpret_cast<void *>(&loader_dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
|
||||
|
||||
// Passing dlopen as a caller address tricks the linker into using the internal unrestricted namespace letting us access libraries that are normally forbidden in the classloader namespace imposed on apps
|
||||
auto ldHandle{loader_dlopen("ld-android.so", RTLD_LAZY, reinterpret_cast<void *>(&dlopen))};
|
||||
if (!ldHandle)
|
||||
return;
|
||||
@@ -174,5 +187,6 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
|
||||
if (!android_get_exported_namespace)
|
||||
return;
|
||||
|
||||
// Lib is now safe to use
|
||||
lib_loaded = true;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -10,6 +9,7 @@ extern "C" {
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=25;bpv=1;bpt=1
|
||||
enum {
|
||||
ANDROID_NAMESPACE_TYPE_REGULAR = 0,
|
||||
ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
|
||||
@@ -19,8 +19,14 @@ enum {
|
||||
ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks if linkernsbypass loaded successfully and is safe to use
|
||||
* @note IMPORTANT: This should be called before any calls to the rest of the library are made
|
||||
* @return true if loading succeeded
|
||||
*/
|
||||
bool linkernsbypass_load_status();
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=86;bpv=1;bpt=1
|
||||
struct android_namespace_t *android_create_namespace(const char *name,
|
||||
const char *ld_library_path,
|
||||
const char *default_library_path,
|
||||
@@ -35,19 +41,39 @@ struct android_namespace_t *android_create_namespace_escape(const char *name,
|
||||
const char *permitted_when_isolated_path,
|
||||
struct android_namespace_t *parent_namespace);
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=3554
|
||||
typedef struct android_namespace_t *(*android_get_exported_namespace_t)(const char *);
|
||||
extern android_get_exported_namespace_t android_get_exported_namespace;
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2499
|
||||
typedef bool (*android_link_namespaces_all_libs_t)(struct android_namespace_t *, struct android_namespace_t *);
|
||||
extern android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2473
|
||||
typedef bool (*android_link_namespaces_t)(struct android_namespace_t *, struct android_namespace_t *, const char *);
|
||||
extern android_link_namespaces_t android_link_namespaces;
|
||||
|
||||
/**
|
||||
* @brief Like android_link_namespaces_all_libs but links from the default namespace
|
||||
*/
|
||||
bool linkernsbypass_link_namespace_to_default_all_libs(struct android_namespace_t *to);
|
||||
|
||||
/**
|
||||
* @brief Loads a library into a namespace
|
||||
* @note IMPORTANT: If `filename` is compiled with the '-z global' linker flag and RTLD_GLOBAL is supplied in `flags` the library will be added to the namespace's LD_PRELOAD list
|
||||
* @param filename The name of the library to load
|
||||
* @param flags The rtld flags for `filename`
|
||||
* @param ns The namespace to dlopen into
|
||||
*/
|
||||
void *linkernsbypass_namespace_dlopen(const char *filename, int flags, struct android_namespace_t *ns);
|
||||
|
||||
/**
|
||||
* @brief Force loads a unique instance of a library into a namespace
|
||||
* @param libPath The path to the library to load with hooks applied
|
||||
* @param libTargetDir A temporary directory to hold the soname patched library at `libPath`, will attempt to use memfd if nullptr
|
||||
* @param flags The rtld flags for `libName`
|
||||
* @param ns The namespace to dlopen into
|
||||
*/
|
||||
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, struct android_namespace_t *ns);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <initializer_list>
|
||||
#include <cstdint>
|
||||
#include <errno.h>
|
||||
@@ -21,6 +20,7 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
|
||||
if (ftruncate(targetFd, libStat.st_size) == -1)
|
||||
return false;
|
||||
|
||||
// Map the memory so we can read our elf into it
|
||||
auto mappedLib{reinterpret_cast<uint8_t *>(mmap(nullptr, libStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, targetFd, 0))};
|
||||
if (!mappedLib)
|
||||
return false;
|
||||
@@ -29,25 +29,30 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
|
||||
if (libFd == false)
|
||||
return false;
|
||||
|
||||
// Read lib elf into target file
|
||||
if (read(libFd, mappedLib, libStat.st_size) != libStat.st_size)
|
||||
return false;
|
||||
|
||||
// No longer needed
|
||||
close(libFd);
|
||||
|
||||
auto eHdr{reinterpret_cast<ElfW(Ehdr) *>(mappedLib)};
|
||||
auto sHdrEntries{reinterpret_cast<ElfW(Shdr) *>(mappedLib + eHdr->e_shoff)};
|
||||
|
||||
// Iterate over section headers to find the .dynamic section
|
||||
for (ElfW(Half) i{}; i < eHdr->e_shnum; i++) {
|
||||
auto &sHdr{sHdrEntries[i]};
|
||||
if (sHdr.sh_type == SHT_DYNAMIC) {
|
||||
auto strTab{reinterpret_cast<char *>(mappedLib + sHdrEntries[sHdr.sh_link].sh_offset)};
|
||||
auto dynHdrEntries{reinterpret_cast<ElfW(Dyn) *>(mappedLib + sHdr.sh_offset)};
|
||||
|
||||
// Iterate over .dynamic entries to find DT_SONAME
|
||||
for (ElfW(Xword) k{}; k < (sHdr.sh_size / sHdr.sh_entsize); k++) {
|
||||
auto &dynHdrEntry{dynHdrEntries[k]};
|
||||
if (dynHdrEntry.d_tag == DT_SONAME) {
|
||||
char *soname{strTab + dynHdrEntry.d_un.d_val};
|
||||
|
||||
// Partially replace the old soname with the soname patch
|
||||
size_t charIdx{};
|
||||
for (; soname[charIdx] != 0 && sonamePatch[charIdx] != 0; charIdx++)
|
||||
soname[charIdx] = sonamePatch[charIdx];
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Overwrites a portion of the soname in an elf by loading it into shared memory and modifying .dynstr
|
||||
* @note IMPORTANT: The supplied soname patch will overwrite the first strlen(sonamePatch) chars of the soname
|
||||
* @param elfPath Full path to the elf to patch
|
||||
* @param targetFd FD to use for storing the patched library
|
||||
* @return True on success
|
||||
*/
|
||||
bool elf_soname_patch(const char *elfPath, int targetFd, const char *newSoname);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -11,3 +11,8 @@ linkernsbypass_lib = static_library (
|
||||
include_directories: ([linkernsbypass_inc, adrenotools_inc]),
|
||||
dependencies: ([libandroid_dep, libdl_dep])
|
||||
)
|
||||
|
||||
# linkernsbypass_include = static_library('linkernsbypass', sources)
|
||||
# target_compile_options(['linkernsbypass', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_libraries(['linkernsbypass', 'android', 'dl'])
|
||||
# target_include_directories(['linkernsbypass', 'PUBLIC', '.'])
|
||||
@@ -33,3 +33,18 @@ adrenotools_lib = shared_library(
|
||||
)
|
||||
|
||||
subdir('src/hook')
|
||||
|
||||
# target_include_directories(['adrenotools', 'PUBLIC', 'include'])
|
||||
# target_include_directories(['adrenotools', 'PRIVATE', '.'])
|
||||
# target_compile_options(['adrenotools', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_libraries(['adrenotools', 'android', 'linkernsbypass'])
|
||||
# hooks_deb = subproject('src/hook')
|
||||
# if build_shared_libs
|
||||
# target_link_options(['adrenotools', 'PRIVATE', '-Wl,-s,--exclude-libs,liblinkernsbypass.a'])
|
||||
# endif
|
||||
# if gen_install_target
|
||||
# configure_file(['adrenotools.pc.in', 'adrenotools.pc', '@ONLY'])
|
||||
# install(['FILES', cmake_binary_dir, '/adrenotools.pc', 'DESTINATION', 'lib/pkgconfig'])
|
||||
# install(['TARGETS', 'adrenotools', 'main_hook', 'file_redirect_hook', 'gsl_alloc_hook', 'hook_impl'])
|
||||
# install(['DIRECTORY', 'include/', 'DESTINATION', 'include', 'FILES_MATCHING', 'PATTERN', '*.h*'])
|
||||
# endif
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
@@ -20,6 +19,7 @@ enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t mino
|
||||
return ADRENOTOOLS_BCN_PATCH;
|
||||
}
|
||||
|
||||
// Searches /proc/self/maps for the first free page after the given address
|
||||
static void *find_free_page(uintptr_t address) {
|
||||
std::ifstream procMaps("/proc/self/maps");
|
||||
|
||||
@@ -45,48 +45,62 @@ static void *align_ptr(void *ptr) {
|
||||
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
|
||||
union Branch {
|
||||
struct {
|
||||
int32_t offset : 26;
|
||||
uint8_t sig : 6;
|
||||
int32_t offset : 26; //!< 26-bit branch offset
|
||||
uint8_t sig : 6; //!< 6-bit signature (0x25 for linked, 0x5 for jump)
|
||||
};
|
||||
|
||||
uint32_t raw{};
|
||||
};
|
||||
static_assert(sizeof(Branch) == 4, "Branch size is invalid");
|
||||
|
||||
// Find the nearest unmapped page where we can place patch code
|
||||
void *patchPage{find_free_page(reinterpret_cast<uintptr_t>(vkGetPhysicalDeviceFormatPropertiesFn))};
|
||||
if (!patchPage)
|
||||
return false;
|
||||
|
||||
// Map patch region
|
||||
void *ptr{mmap(patchPage, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0)};
|
||||
if (ptr != patchPage)
|
||||
return false;
|
||||
|
||||
// Allow reading from the blob's .text section since some devices enable ---X
|
||||
// Protect two pages just in case we happen to land on a page boundary
|
||||
if (mprotect(align_ptr(vkGetPhysicalDeviceFormatPropertiesFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
|
||||
return false;
|
||||
|
||||
// First branch in this function is targeted at the function we want to patch
|
||||
Branch *blInst{reinterpret_cast<Branch *>(vkGetPhysicalDeviceFormatPropertiesFn)};
|
||||
|
||||
constexpr uint8_t BranchLinkSignature{0x25};
|
||||
|
||||
// Search for first instruction with the BL signature
|
||||
while (blInst->sig != BranchLinkSignature)
|
||||
blInst++;
|
||||
|
||||
// Internal QGL format conversion function that we need to patch
|
||||
uint32_t *convFormatFn{reinterpret_cast<uint32_t *>(blInst) + blInst->offset};
|
||||
|
||||
// See mprotect call above
|
||||
// This time we also set PROT_WRITE so we can write our patch to the page
|
||||
if (mprotect(align_ptr(convFormatFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
|
||||
return false;
|
||||
|
||||
// This would normally set the default result to 0 (error) in the format not found case
|
||||
constexpr uint32_t ClearResultSignature{0x2a1f03e0};
|
||||
|
||||
// We replace it with a branch to our own extended if statement which adds in the extra things for BCn
|
||||
uint32_t *clearResultPtr{convFormatFn};
|
||||
while (*clearResultPtr != ClearResultSignature)
|
||||
clearResultPtr++;
|
||||
|
||||
// Ensure we don't write out of bounds
|
||||
if (PatchRawData_size > getpagesize())
|
||||
return false;
|
||||
|
||||
// Copy the patch function to our mapped page
|
||||
memcpy(patchPage, PatchRawData, PatchRawData_size);
|
||||
|
||||
// Fixup the patch code so it correctly returns back to the driver after running
|
||||
constexpr uint32_t PatchReturnFixupMagic{0xffffffff};
|
||||
constexpr uint8_t BranchSignature{0x5};
|
||||
|
||||
@@ -116,5 +130,6 @@ bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
|
||||
|
||||
asm volatile("ISB");
|
||||
|
||||
// Done!
|
||||
return true;
|
||||
}
|
||||
@@ -80,4 +80,4 @@
|
||||
.L18:
|
||||
mov w0, 0
|
||||
.L3:
|
||||
.word 0xffffffff
|
||||
.word 0xffffffff // Branch fixup
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
@@ -18,14 +17,17 @@
|
||||
#include <unistd.h>
|
||||
|
||||
void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle) {
|
||||
// Bail out if linkernsbypass failed to load, this probably means we're on api < 28
|
||||
if (!linkernsbypass_load_status()) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Could not load linkernsbypass\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use memfd on Q+ since it's guaranteed to work, but only if tmpLibDir wasn't set
|
||||
if (android_get_device_api_level() >= 29 && !tmpLibDir)
|
||||
tmpLibDir = nullptr;
|
||||
|
||||
// Verify that params for specific features are only passed if they are enabled
|
||||
if (!(featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) && fileRedirectDir) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_FILE_REDIRECT present but no file redirect folder found\n");
|
||||
return nullptr;
|
||||
@@ -41,6 +43,7 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Verify that params for enabled features are correct
|
||||
struct stat buf{};
|
||||
|
||||
if (featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
|
||||
@@ -55,6 +58,7 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that params for enabled features are correct
|
||||
if (featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) {
|
||||
if (!fileRedirectDir) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_REDIRECT_DIR present but no folder parameter was found\n");
|
||||
@@ -67,18 +71,22 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
}
|
||||
}
|
||||
|
||||
// Create a namespace that can isolate our hook from the classloader namespace
|
||||
auto hookNs{android_create_namespace("adrenotools-libvulkan", hookLibDir, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
|
||||
|
||||
// Link it to the default namespace so the hook can use libandroid etc
|
||||
if (!linkernsbypass_link_namespace_to_default_all_libs(hookNs)) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to hook our namespace to the default one\n");
|
||||
return nullptr;
|
||||
}
|
||||
// Preload the hook implementation, otherwise we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
|
||||
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, hookNs)};
|
||||
if (!hookImpl) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't preload the hook implementation\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pass parameters to the hook implementation
|
||||
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
|
||||
if (!initHookParam) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't init hook params\n");
|
||||
@@ -87,6 +95,7 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
|
||||
auto importMapping{[&]() -> adrenotools_gpu_mapping * {
|
||||
if (featureFlags & ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT) {
|
||||
// This will be leaked, but it's not a big deal since it's only a few bytes
|
||||
adrenotools_gpu_mapping *mapping{new adrenotools_gpu_mapping{}};
|
||||
*userMappingHandle = mapping;
|
||||
return mapping;
|
||||
@@ -98,6 +107,7 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
|
||||
|
||||
initHookParam(new HookImplParams(featureFlags, tmpLibDir, hookLibDir, customDriverDir, customDriverName, fileRedirectDir, importMapping));
|
||||
|
||||
// Load the libvulkan hook into the isolated namespace
|
||||
if (!linkernsbypass_namespace_dlopen("libmain_hook.so", RTLD_GLOBAL, hookNs)) {
|
||||
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to load libvulkan into the isolated namespace\n");
|
||||
return nullptr;
|
||||
@@ -140,7 +150,7 @@ bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size) {
|
||||
importMapping->host_ptr = hostPtr;
|
||||
importMapping->gpu_addr = info.gpuaddr;
|
||||
importMapping->size = size;
|
||||
importMapping->flags = 0xc2600;
|
||||
importMapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
|
||||
|
||||
close(kgslFd);
|
||||
return true;
|
||||
@@ -179,7 +189,7 @@ bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size) {
|
||||
mapping->host_ptr = nullptr;
|
||||
mapping->gpu_addr = info.gpuaddr;
|
||||
mapping->size = *size;
|
||||
mapping->flags = 0xc2600;
|
||||
mapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
|
||||
|
||||
close(kgslFd);
|
||||
return true;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#define TAG "hook_impl"
|
||||
#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
const HookImplParams *hook_params;
|
||||
const HookImplParams *hook_params; //!< Bunch of info needed to load/patch the driver
|
||||
int (*gsl_memory_alloc_pure_sym)(uint32_t, uint32_t, void *);
|
||||
int (*gsl_memory_alloc_pure_64_sym)(uint64_t, uint32_t, void *);
|
||||
int (*gsl_memory_free_pure_sym)(void *);
|
||||
@@ -43,6 +43,7 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
|
||||
LOGI("hook_android_dlopen_ext: filename: %s", filename);
|
||||
|
||||
// Ignore non-vulkan libraries
|
||||
if (!strstr(filename, "vulkan."))
|
||||
return android_dlopen_ext(filename, flags, extinfo);
|
||||
|
||||
@@ -51,6 +52,7 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
return fallback();
|
||||
}
|
||||
|
||||
// customDriverDir will be empty if ADRENOTOOLS_DRIVER_CUSTOM isn't set therefore it's fine to have either way
|
||||
auto driverNs{android_create_namespace(filename, hook_params->customDriverDir.c_str(),
|
||||
hook_params->hookLibDir.c_str(), ANDROID_NAMESPACE_TYPE_SHARED,
|
||||
nullptr, extinfo->library_namespace)};
|
||||
@@ -59,12 +61,16 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
return fallback();
|
||||
}
|
||||
|
||||
// We depend on libandroid which is unlikely to be in the supplied driver namespace so we have to link it over
|
||||
android_link_namespaces(driverNs, nullptr, "libandroid.so");
|
||||
|
||||
// Preload ourself, a new instance will be created since we have different linker ancestory
|
||||
// If we don't preload we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
|
||||
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, driverNs)};
|
||||
if (!hookImpl)
|
||||
return nullptr;
|
||||
|
||||
// Pass parameters to ourself
|
||||
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
|
||||
if (!initHookParam)
|
||||
return nullptr;
|
||||
@@ -80,6 +86,7 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
LOGI("hook_android_dlopen_ext: applied libfile_redirect_hook");
|
||||
}
|
||||
|
||||
// Use our new namespace to load the vulkan driver
|
||||
auto newExtinfo{*extinfo};
|
||||
newExtinfo.library_namespace = driverNs;
|
||||
|
||||
@@ -115,6 +122,8 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
LOGI("hook_android_dlopen_ext: hook failed: failed to apply libgsl_alloc_hook!");
|
||||
}
|
||||
|
||||
// TODO: If there is already an instance of a Vulkan driver loaded hooks won't be applied, this will only be the case for skiavk generally
|
||||
// To fix this we would need to search /proc/self/maps for the file to a loaded instance of the library in order to read it to patch the soname and load it uniquely
|
||||
if (hook_params->featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
|
||||
LOGI("hook_android_dlopen_ext: loading custom driver: %s%s", hook_params->customDriverDir.c_str(), hook_params->customDriverName.c_str());
|
||||
void *handle{android_dlopen_ext(hook_params->customDriverName.c_str(), flags, &newExtinfo)};
|
||||
@@ -133,6 +142,7 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
|
||||
__attribute__((visibility("default"))) void *hook_android_load_sphal_library(const char *filename, int flags) {
|
||||
LOGI("hook_android_load_sphal_library: filename: %s", filename);
|
||||
|
||||
// https://android.googlesource.com/platform/system/core/+/master/libvndksupport/linker.cpp
|
||||
for (const char *name : {"sphal", "vendor", "default"}) {
|
||||
if (auto vendorNs{android_get_exported_namespace(name)}) {
|
||||
android_dlextinfo dlextinfo{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright © 2021 Billy Laws
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <adrenotools/priv.h>
|
||||
|
||||
/**
|
||||
* @brief Holds the parameters needed for all hooks
|
||||
* @note See comments for adrenotools_open_libvulkan as a reference for member variables
|
||||
*/
|
||||
struct HookImplParams {
|
||||
int featureFlags;
|
||||
std::string tmpLibDir;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,32 @@
|
||||
hook_impl_files = files(['hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h'])
|
||||
hook_impl_inc = include_directories('../../include')
|
||||
|
||||
# hook_impl_lib = shared_library('hook_impl', 'hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h')
|
||||
# target_compile_options(['hook_impl', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_libraries(['hook_impl', 'linkernsbypass', 'log'])
|
||||
# target_include_directories(['hook_impl', 'PRIVATE', '../../include'])
|
||||
# set_target_properties(['hook_impl', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
main_hook_files = files(['main_hook.c'])
|
||||
# main_hook_lib = shared_library('main_hook', 'main_hook.c')
|
||||
# target_compile_options(['main_hook', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_options(['main_hook', 'PRIVATE', '-z', 'global'])
|
||||
# target_link_libraries(['main_hook', 'hook_impl'])
|
||||
# set_target_properties(['main_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
file_redirect_hook_files = files(['file_redirect_hook.c'])
|
||||
# file_redirect_hook_lib = shared_library('file_redirect_hook', 'file_redirect_hook.c')
|
||||
# target_compile_options(['file_redirect_hook', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_options(['file_redirect_hook', 'PRIVATE', '-z', 'global'])
|
||||
# target_link_libraries(['file_redirect_hook', 'hook_impl'])
|
||||
# set_target_properties(['file_redirect_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
gsl_alloc_hook_files = files(['gsl_alloc_hook.c'])
|
||||
# gsl_alloc_hook_lib = shared_library('gsl_alloc_hook', 'gsl_alloc_hook.c')
|
||||
# target_compile_options(['gsl_alloc_hook', 'PRIVATE', '-Wall', '-Wextra'])
|
||||
# target_link_options(['gsl_alloc_hook', 'PRIVATE', '-z', 'global'])
|
||||
# target_link_libraries(['gsl_alloc_hook', 'hook_impl'])
|
||||
# set_target_properties(['gsl_alloc_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
|
||||
|
||||
hook_impl_lib = shared_library(
|
||||
'hook_impl',
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
thread_local int satisfy_driver_emutls;
|
||||
|
||||
// Check libllvm-glnext.so in older drivers for a full list of these, newer ones are slightly more awkward due to the split
|
||||
enum class OptionType : uint32_t {
|
||||
ArgsStringList = 1,
|
||||
QcArgsString = 4,
|
||||
@@ -20,6 +21,7 @@ struct StringListVal {
|
||||
struct Option
|
||||
{
|
||||
OptionType type;
|
||||
// PAD
|
||||
union {
|
||||
bool boolVal;
|
||||
int intVal;
|
||||
@@ -47,6 +49,7 @@ int CompileHook(void *state, OptionsSet *opts) {
|
||||
Option replacementOpts[ReplacementOptsNum];
|
||||
uint32_t patchedOptNum{};
|
||||
|
||||
// Insert your desired args here - results will be written to stderr
|
||||
const char *argv[] = {"-help", "-help-hidden", "", NULL };
|
||||
StringListVal listVal{
|
||||
.len = 3,
|
||||
@@ -80,6 +83,7 @@ int CompileHook(void *state, OptionsSet *opts) {
|
||||
|
||||
struct LlvmInterface {
|
||||
uint32_t unk0[3];
|
||||
// PAD
|
||||
void *makeContext;
|
||||
void *delContext;
|
||||
decltype(CompileHook) *compile;
|
||||
|
||||
@@ -30,31 +30,53 @@
|
||||
#ifndef __QTIGRALLOCDEFS_H__
|
||||
#define __QTIGRALLOCDEFS_H__
|
||||
|
||||
// From gralloc_priv.h
|
||||
|
||||
/* Gralloc usage bits indicating the type of allocation that should be used */
|
||||
/* Refer to BufferUsage in hardware/interfaces/graphics/common/<ver>/types.hal */
|
||||
|
||||
/* The bits below are in officially defined vendor space
|
||||
* i.e bits 28-31 and 48-63*/
|
||||
/* Non linear, Universal Bandwidth Compression */
|
||||
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC (UINT32_C(1) << 28)
|
||||
|
||||
/* Set this for allocating uncached memory (using O_DSYNC),
|
||||
* cannot be used with noncontiguous heaps */
|
||||
#define GRALLOC_USAGE_PRIVATE_UNCACHED (UINT32_C(1) << 29)
|
||||
|
||||
/* This flag is used to indicate 10 bit format.
|
||||
* When both GRALLOC_USAGE_PRIVATE_ALLOC_UBWC & GRALLOC_USAGE_PRIVATE_10BIT
|
||||
* are set then it will indicate UBWC_TP10 format.
|
||||
* When only GRALLOC_USAGE_PRIVATE_10BIT is set it will indicate linear P010 format.
|
||||
*/
|
||||
#define GRALLOC_USAGE_PRIVATE_10BIT (UINT32_C(1) << 30)
|
||||
|
||||
/* This flag is used for SECURE display usecase */
|
||||
#define GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY (UINT32_C(1) << 31)
|
||||
|
||||
/* This flag is used to indicate video NV21 format */
|
||||
#define GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER 1ULL << 48
|
||||
|
||||
/* unused legacy flags */
|
||||
#define GRALLOC_USAGE_PRIVATE_MM_HEAP 0
|
||||
#define GRALLOC_USAGE_PRIVATE_IOMMU_HEAP 0
|
||||
|
||||
/* This flag is set for WFD usecase */
|
||||
#define GRALLOC_USAGE_PRIVATE_WFD 1ULL << 51
|
||||
|
||||
/* This flag is set for HEIF usecase */
|
||||
#define GRALLOC_USAGE_PRIVATE_HEIF (UINT32_C(1) << 27)
|
||||
|
||||
/* TODO(user): Remove when clients stop referencing this flag */
|
||||
#define GRALLOC_USAGE_PRIVATE_10BIT_TP 0
|
||||
|
||||
/* This flag indicates PI format is being used */
|
||||
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI 1ULL << 49
|
||||
|
||||
/* This flag is set while CDSP accesses the buffer */
|
||||
#define GRALLOC_USAGE_PRIVATE_CDSP 1ULL << 50
|
||||
|
||||
// OEM specific HAL formats
|
||||
#define HAL_PIXEL_FORMAT_RGBA_5551 6
|
||||
#define HAL_PIXEL_FORMAT_RGBA_4444 7
|
||||
#define HAL_PIXEL_FORMAT_NV12_ENCODEABLE 0x102
|
||||
@@ -76,6 +98,7 @@
|
||||
#define HAL_PIXEL_FORMAT_RAW8 0x123
|
||||
#define HAL_PIXEL_FORMAT_NV12_HEIF 0x116
|
||||
|
||||
// 10 bit
|
||||
#define HAL_PIXEL_FORMAT_ARGB_2101010 0x117
|
||||
#define HAL_PIXEL_FORMAT_RGBX_1010102 0x118
|
||||
#define HAL_PIXEL_FORMAT_XRGB_2101010 0x119
|
||||
@@ -83,7 +106,7 @@
|
||||
#define HAL_PIXEL_FORMAT_ABGR_2101010 0x11B
|
||||
#define HAL_PIXEL_FORMAT_BGRX_1010102 0x11C
|
||||
#define HAL_PIXEL_FORMAT_XBGR_2101010 0x11D
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36 // HAL_PIXEL_FORMAT_YCBCR_P010
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_UBWC 0x124
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_VENUS 0x7FA30C0A
|
||||
|
||||
@@ -92,6 +115,7 @@
|
||||
|
||||
#define HAL_PIXEL_FORMAT_INTERLACE 0x180
|
||||
|
||||
// Camera utils format
|
||||
#define HAL_PIXEL_FORMAT_NV12_LINEAR_FLEX 0x125
|
||||
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX 0x126
|
||||
#define HAL_PIXEL_FORMAT_MULTIPLANAR_FLEX 0x127
|
||||
@@ -99,16 +123,22 @@
|
||||
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_4_BATCH 0x129
|
||||
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_8_BATCH 0x130
|
||||
|
||||
// FSC Formats
|
||||
#define HAL_PIXEL_FORMAT_RGB888_UBWC_FSC 0x131
|
||||
#define HAL_PIXEL_FORMAT_RGB101010_UBWC_FSC 0x132
|
||||
|
||||
// v4l2_fourcc('Y', 'U', 'Y', 'L'). 24 bpp YUYV 4:2:2 10 bit per component
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT 0x4C595559
|
||||
|
||||
// v4l2_fourcc('Y', 'B', 'W', 'C'). 10 bit per component. This compressed
|
||||
// format reduces the memory access bandwidth
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT_COMPRESSED 0x43574259
|
||||
|
||||
// UBWC aligned Venus format
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC 0x7FA30C06
|
||||
#define HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC 0x7FA30C09
|
||||
|
||||
// Khronos ASTC formats
|
||||
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0
|
||||
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1
|
||||
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2
|
||||
@@ -140,4 +170,4 @@
|
||||
|
||||
enum { BUFFER_TYPE_UI = 0, BUFFER_TYPE_VIDEO };
|
||||
|
||||
#endif
|
||||
#endif //__QTIGRALLOCDEFS_H__
|
||||
@@ -26,6 +26,7 @@
|
||||
using namespace android::hardware;
|
||||
using namespace android;
|
||||
|
||||
/* Simplified version of hidl::base::V1_0::IBase */
|
||||
struct IBase : virtual public RefBase {
|
||||
virtual bool isRemote() const;
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ AdrenoMemInfo::AdrenoMemInfo() {
|
||||
ALOGE(" Failed to load libadreno_utils.so");
|
||||
}
|
||||
|
||||
// Check if the overriding property debug.gralloc.gfx_ubwc_disable
|
||||
// that disables UBWC allocations for the graphics stack is set
|
||||
char property[PROPERTY_VALUE_MAX];
|
||||
property_get(DISABLE_UBWC_PROP, property, "0");
|
||||
if (!(strncmp(property, "1", PROPERTY_VALUE_MAX)) ||
|
||||
@@ -121,13 +123,16 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
|
||||
break;
|
||||
}
|
||||
|
||||
int raster_mode = 0;
|
||||
int padding_threshold = 512;
|
||||
|
||||
|
||||
int raster_mode = 0; // Adreno unknown raster mode.
|
||||
int padding_threshold = 512; // Threshold for padding surfaces.
|
||||
// the function below computes aligned width and aligned height
|
||||
// based on linear or macro tile mode selected.
|
||||
if (LINK_adreno_compute_fmt_aligned_width_and_height) {
|
||||
// We call into adreno_utils only for RGB formats. So plane_id is 0 and
|
||||
// num_samples is 1 always. We may have to add uitility function to
|
||||
// find out these if there is a need to call this API for YUV formats.
|
||||
LINK_adreno_compute_fmt_aligned_width_and_height(
|
||||
width, height, 0 , GetGpuPixelFormat(format), 1 , tile_enabled,
|
||||
width, height, 0 /*plane_id*/, GetGpuPixelFormat(format), 1 /*num_samples*/, tile_enabled,
|
||||
raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
|
||||
reinterpret_cast<int*>(aligned_h));
|
||||
} else if (LINK_adreno_compute_aligned_width_and_height) {
|
||||
@@ -135,7 +140,7 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
|
||||
width, height, bpp, tile_enabled, raster_mode, padding_threshold,
|
||||
reinterpret_cast<int*>(aligned_w), reinterpret_cast<int*>(aligned_h));
|
||||
} else if (LINK_adreno_compute_padding) {
|
||||
int surface_tile_height = 1;
|
||||
int surface_tile_height = 1; // Linear surface
|
||||
*aligned_w = UINT(LINK_adreno_compute_padding(width, bpp, surface_tile_height, raster_mode,
|
||||
padding_threshold));
|
||||
ALOGW("%s: Warning!! Old GFX API is used to calculate stride", __FUNCTION__);
|
||||
@@ -152,9 +157,9 @@ void AdrenoMemInfo::AlignCompressedRGB(int width, int height, int format, unsign
|
||||
unsigned int* aligned_h) {
|
||||
if (LINK_adreno_compute_compressedfmt_aligned_width_and_height) {
|
||||
int bytesPerPixel = 0;
|
||||
int raster_mode = 0;
|
||||
int padding_threshold = 512;
|
||||
|
||||
int raster_mode = 0; // Adreno unknown raster mode.
|
||||
int padding_threshold = 512; // Threshold for padding
|
||||
// surfaces.
|
||||
|
||||
LINK_adreno_compute_compressedfmt_aligned_width_and_height(
|
||||
width, height, format, 0, raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
|
||||
@@ -336,4 +341,4 @@ bool AdrenoMemInfo::IsPISupportedByGPU(int format, uint64_t usage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace gralloc
|
||||
@@ -35,10 +35,11 @@
|
||||
|
||||
namespace gralloc {
|
||||
|
||||
// Adreno Pixel Formats
|
||||
typedef enum {
|
||||
ADRENO_PIXELFORMAT_UNKNOWN = 0,
|
||||
ADRENO_PIXELFORMAT_R16G16B16A16_FLOAT = 10,
|
||||
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24,
|
||||
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24, // Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
|
||||
ADRENO_PIXELFORMAT_R8G8B8A8 = 28,
|
||||
ADRENO_PIXELFORMAT_R8G8B8A8_SRGB = 29,
|
||||
ADRENO_PIXELFORMAT_D32_FLOAT = 40,
|
||||
@@ -54,87 +55,140 @@ typedef enum {
|
||||
ADRENO_PIXELFORMAT_P010 = 104,
|
||||
ADRENO_PIXELFORMAT_YUY2 = 107,
|
||||
ADRENO_PIXELFORMAT_B4G4R4A4 = 115,
|
||||
ADRENO_PIXELFORMAT_NV12_EXT = 506,
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8 = 507,
|
||||
ADRENO_PIXELFORMAT_R8G8B8 = 508,
|
||||
ADRENO_PIXELFORMAT_A1B5G5R5 = 519,
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520,
|
||||
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521,
|
||||
ADRENO_PIXELFORMAT_NV12_EXT = 506, // NV12 with non-std alignment and offsets
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8 = 507, // GL_RGB8 (Internal)
|
||||
ADRENO_PIXELFORMAT_R8G8B8 = 508, // GL_RGB8
|
||||
ADRENO_PIXELFORMAT_A1B5G5R5 = 519, // GL_RGB5_A1
|
||||
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520, // GL_SRGB8
|
||||
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521, // GL_SRGB8
|
||||
ADRENO_PIXELFORMAT_A2B10G10R10_UNORM = 532,
|
||||
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
|
||||
ADRENO_PIXELFORMAT_R10G10B10X2_UNORM = 537,
|
||||
ADRENO_PIXELFORMAT_D24_UNORM_X8_UINT = 548,
|
||||
ADRENO_PIXELFORMAT_D24_UNORM = 549,
|
||||
ADRENO_PIXELFORMAT_D32_FLOAT_X24S8_UINT = 551,
|
||||
ADRENO_PIXELFORMAT_S8_UINT = 552,
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4 = 568,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4 = 569,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5 = 570,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5 = 571,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6 = 572,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5 = 573,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6 = 574,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8 = 575,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5 = 576,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6 = 577,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8 = 578,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10 = 579,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10 = 580,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12 = 581,
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583,
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585,
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588,
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592,
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594,
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595,
|
||||
|
||||
ADRENO_PIXELFORMAT_R5G6B5 = 610,
|
||||
ADRENO_PIXELFORMAT_R5G5B5A1 = 611,
|
||||
ADRENO_PIXELFORMAT_R4G4B4A4 = 612,
|
||||
ADRENO_PIXELFORMAT_UYVY = 614,
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4 = 568, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4 = 569, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5 = 570, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5 = 571, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6 = 572, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5 = 573, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6 = 574, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8 = 575, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5 = 576, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6 = 577, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8 = 578, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10 = 579, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10 = 580, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12 = 581, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594, // ASTC Compressed
|
||||
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595, // ASTC Compressed
|
||||
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
|
||||
ADRENO_PIXELFORMAT_R5G6B5 = 610, // RGBA version of B5G6R5
|
||||
ADRENO_PIXELFORMAT_R5G5B5A1 = 611, // RGBA version of B5G5R5A1
|
||||
ADRENO_PIXELFORMAT_R4G4B4A4 = 612, // RGBA version of B4G4R4A4
|
||||
ADRENO_PIXELFORMAT_UYVY = 614, // YUV 4:2:2 packed progressive (1 plane)
|
||||
ADRENO_PIXELFORMAT_NV21 = 619,
|
||||
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620,
|
||||
ADRENO_PIXELFORMAT_Y8 = 625,
|
||||
ADRENO_PIXELFORMAT_TP10 = 654,
|
||||
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620, // YUV 4:4:4 packed (1 plane)
|
||||
ADRENO_PIXELFORMAT_Y8 = 625, // Single 8-bit luma only channel YUV format
|
||||
ADRENO_PIXELFORMAT_TP10 = 654, // YUV 4:2:0 planar 10 bits/comp (2 planes)
|
||||
} ADRENOPIXELFORMAT;
|
||||
|
||||
typedef enum {
|
||||
SURFACE_TILE_MODE_DISABLE = 0x0,
|
||||
SURFACE_TILE_MODE_ENABLE = 0x1
|
||||
SURFACE_TILE_MODE_DISABLE = 0x0, // used for linear surface
|
||||
SURFACE_TILE_MODE_ENABLE = 0x1 // used for tiled surface
|
||||
} surface_tile_mode_t;
|
||||
|
||||
class AdrenoMemInfo {
|
||||
public:
|
||||
/*
|
||||
* Function to compute aligned width and aligned height based on
|
||||
* width, height, format and usage flags.
|
||||
*
|
||||
* @return aligned width, aligned height
|
||||
*/
|
||||
void GetAlignedWidthAndHeight(int width, int height, int format, int usage,
|
||||
unsigned int* aligned_w, unsigned int* aligned_h, bool ubwc_enabled,
|
||||
bool tile_enabled);
|
||||
|
||||
/*
|
||||
* Function to compute the adreno aligned width and aligned height
|
||||
* based on the width and format.
|
||||
*
|
||||
* @return aligned width, aligned height
|
||||
*/
|
||||
void AlignUnCompressedRGB(int width, int height, int format, int tileEnabled,
|
||||
unsigned int* aligned_w, unsigned int* aligned_h);
|
||||
|
||||
/*
|
||||
* Function to compute the adreno aligned width and aligned height
|
||||
* based on the width and format.
|
||||
*
|
||||
* @return aligned width, aligned height
|
||||
*/
|
||||
void AlignCompressedRGB(int width, int height, int format, unsigned int* aligned_w,
|
||||
unsigned int* aligned_h);
|
||||
|
||||
/*
|
||||
* Function to compute the pixel alignment requirement.
|
||||
*
|
||||
* @return alignment
|
||||
*/
|
||||
uint32_t GetGpuPixelAlignment();
|
||||
|
||||
/*
|
||||
* Function to query whether GPU supports UBWC for given HAL format
|
||||
* @return > 0 : supported
|
||||
* 0 : not supported
|
||||
*/
|
||||
bool IsUBWCSupportedByGPU(int format);
|
||||
|
||||
/*
|
||||
* Function to check if GPU supports PI or not
|
||||
*/
|
||||
bool IsPISupportedByGPU(int format, uint64_t usage);
|
||||
/*
|
||||
* Function to get the corresponding Adreno format for given HAL format
|
||||
*/
|
||||
ADRENOPIXELFORMAT GetGpuPixelFormat(int hal_format);
|
||||
|
||||
/*
|
||||
* Function to get graphics metadata blob size
|
||||
* @return graphics metadata size
|
||||
*/
|
||||
uint32_t AdrenoGetMetadataBlobSize();
|
||||
|
||||
/*
|
||||
* Function to populate the graphics metadata blob
|
||||
* @return 1 : Successful
|
||||
* 2 : Unsuccessful
|
||||
*/
|
||||
int AdrenoInitMemoryLayout(void* metadata_blob, int width, int height, int depth, int format,
|
||||
int num_samples, int isUBWC, uint64_t usage, uint32_t num_planes);
|
||||
/*
|
||||
* Function to get buffer size for based on graphcis metadata
|
||||
* @return buffer size
|
||||
*/
|
||||
uint32_t AdrenoGetAlignedGpuBufferSize(void* metadata_blob);
|
||||
|
||||
/*
|
||||
* Function to check if adreno size calculation APIs are avaliable
|
||||
* @return true : Avaliable
|
||||
* false : Unavaliable
|
||||
*/
|
||||
bool AdrenoSizeAPIAvaliable();
|
||||
|
||||
static AdrenoMemInfo* GetInstance();
|
||||
@@ -142,6 +196,7 @@ class AdrenoMemInfo {
|
||||
private:
|
||||
AdrenoMemInfo();
|
||||
~AdrenoMemInfo();
|
||||
// link(s)to adreno surface padding library.
|
||||
int (*LINK_adreno_compute_padding)(int width, int bpp, int surface_tile_height,
|
||||
int screen_tile_height, int padding_threshold) = NULL;
|
||||
void (*LINK_adreno_compute_aligned_width_and_height)(int width, int height, int bpp,
|
||||
@@ -174,6 +229,6 @@ class AdrenoMemInfo {
|
||||
static AdrenoMemInfo* s_instance;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace gralloc
|
||||
|
||||
#endif
|
||||
#endif // __GR_ADRENO_INFO_H__
|
||||
@@ -57,27 +57,29 @@ struct private_handle_t {
|
||||
PRIV_FLAGS_CAMERA_READ = 0x00040000,
|
||||
PRIV_FLAGS_HW_COMPOSER = 0x00080000,
|
||||
PRIV_FLAGS_HW_TEXTURE = 0x00100000,
|
||||
PRIV_FLAGS_ITU_R_601 = 0x00200000,
|
||||
PRIV_FLAGS_ITU_R_601_FR = 0x00400000,
|
||||
PRIV_FLAGS_ITU_R_709 = 0x00800000,
|
||||
PRIV_FLAGS_ITU_R_601 = 0x00200000, // Unused from display
|
||||
PRIV_FLAGS_ITU_R_601_FR = 0x00400000, // Unused from display
|
||||
PRIV_FLAGS_ITU_R_709 = 0x00800000, // Unused from display
|
||||
PRIV_FLAGS_SECURE_DISPLAY = 0x01000000,
|
||||
PRIV_FLAGS_TILE_RENDERED = 0x02000000,
|
||||
PRIV_FLAGS_CPU_RENDERED = 0x04000000,
|
||||
PRIV_FLAGS_UBWC_ALIGNED = 0x08000000,
|
||||
PRIV_FLAGS_DISP_CONSUMER = 0x10000000,
|
||||
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000,
|
||||
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000,
|
||||
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000, // Ion buffer allocated outside of gralloc
|
||||
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000, // PI format
|
||||
};
|
||||
|
||||
// file-descriptors dup'd over IPC
|
||||
int fd;
|
||||
int fd_metadata;
|
||||
|
||||
// values sent over IPC
|
||||
int magic;
|
||||
int flags;
|
||||
int width;
|
||||
int height;
|
||||
int unaligned_width;
|
||||
int unaligned_height;
|
||||
int width; // holds width of the actual buffer allocated
|
||||
int height; // holds height of the actual buffer allocated
|
||||
int unaligned_width; // holds width client asked to allocate
|
||||
int unaligned_height; // holds height client asked to allocate
|
||||
int format;
|
||||
int buffer_type;
|
||||
unsigned int layer_count;
|
||||
@@ -124,6 +126,7 @@ struct private_handle_t {
|
||||
numFds = kNumFds;
|
||||
}
|
||||
|
||||
// Legacy constructor used by some clients
|
||||
private_handle_t(int fd, unsigned int size, int usage, int buf_type, int format, int w, int h)
|
||||
: private_handle_t(fd, -1, PRIV_FLAGS_CLIENT_ALLOCATED, w, h, 0, 0, format, buf_type, size,
|
||||
static_cast<uint64_t>(usage)) {}
|
||||
@@ -173,6 +176,7 @@ struct private_handle_t {
|
||||
unsigned int GetLayerCount() const { return layer_count; }
|
||||
|
||||
int GetStride() const {
|
||||
// In handle we currently store aligned width after allocation.
|
||||
return width;
|
||||
}
|
||||
|
||||
@@ -183,4 +187,4 @@ struct private_handle_t {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif
|
||||
#endif // __GR_PRIV_HANDLE_H__
|
||||
@@ -162,6 +162,7 @@ bool CpuCanRead(uint64_t usage) {
|
||||
|
||||
bool CpuCanWrite(uint64_t usage) {
|
||||
if (usage & BufferUsage::CPU_WRITE_MASK) {
|
||||
// Application intends to use CPU for rendering
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,6 +177,7 @@ uint32_t GetDataAlignment(int format, uint64_t usage) {
|
||||
|
||||
if (usage & BufferUsage::PROTECTED) {
|
||||
if ((usage & BufferUsage::CAMERA_OUTPUT) || (usage & GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY)) {
|
||||
// The alignment here reflects qsee mmu V7L/V8L requirement
|
||||
align = SZ_2M;
|
||||
} else {
|
||||
align = SECURE_ALIGN;
|
||||
@@ -200,6 +202,7 @@ bool IsGPUFlagSupported(uint64_t usage) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Returns the final buffer size meant to be allocated with ion
|
||||
unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int alignedh) {
|
||||
unsigned int size = 0;
|
||||
int format = info.format;
|
||||
@@ -220,6 +223,7 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
|
||||
} else if (IsCompressedRGBFormat(format)) {
|
||||
size = alignedw * alignedh * ASTC_BLOCK_SIZE;
|
||||
} else {
|
||||
// Below switch should be for only YUV/custom formats
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_RAW16:
|
||||
case HAL_PIXEL_FORMAT_Y16:
|
||||
@@ -233,15 +237,15 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
|
||||
case HAL_PIXEL_FORMAT_Y8:
|
||||
size = alignedw * alignedh * 1;
|
||||
break;
|
||||
|
||||
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO:
|
||||
// adreno formats
|
||||
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO: // NV21
|
||||
size = ALIGN(alignedw * alignedh, SIZE_4K);
|
||||
size += (unsigned int)ALIGN(2 * ALIGN(width / 2, 32) * ALIGN(height / 2, 32), SIZE_4K);
|
||||
break;
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED:
|
||||
|
||||
|
||||
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED: // NV12
|
||||
// The chroma plane is subsampled,
|
||||
// but the pitch in bytes is unchanged
|
||||
// The GPU needs 4K alignment, but the video decoder needs 8K
|
||||
size = ALIGN(alignedw * alignedh, SIZE_8K);
|
||||
size += ALIGN(alignedw * (unsigned int)ALIGN(height / 2, 32), SIZE_8K);
|
||||
break;
|
||||
@@ -292,6 +296,7 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
|
||||
return size;
|
||||
}
|
||||
|
||||
// Explicitly defined UBWC formats
|
||||
bool IsUBwcFormat(int format) {
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
|
||||
@@ -304,6 +309,7 @@ bool IsUBwcFormat(int format) {
|
||||
}
|
||||
|
||||
bool IsUBwcSupported(int format) {
|
||||
// Existing HAL formats with UBWC support
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_BGR_565:
|
||||
case HAL_PIXEL_FORMAT_RGBA_8888:
|
||||
@@ -330,6 +336,7 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// As of now only two formats
|
||||
switch (format) {
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
|
||||
case HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC: {
|
||||
@@ -347,21 +354,27 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
|
||||
}
|
||||
|
||||
bool IsUBwcEnabled(int format, uint64_t usage) {
|
||||
// Allow UBWC, if client is using an explicitly defined UBWC pixel format.
|
||||
if (IsUBwcFormat(format)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow UBWC, if an OpenGL client sets UBWC usage flag and GPU plus MDP
|
||||
// support the format. OR if a non-OpenGL client like Rotator, sets UBWC
|
||||
// usage flag and MDP supports the format.
|
||||
if (((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC) ||
|
||||
(usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) ||
|
||||
(usage & BufferUsage::COMPOSER_CLIENT_TARGET)) &&
|
||||
IsUBwcSupported(format)) {
|
||||
bool enable = true;
|
||||
// Query GPU for UBWC only if buffer is intended to be used by GPU.
|
||||
if ((usage & BufferUsage::GPU_TEXTURE) || (usage & BufferUsage::GPU_RENDER_TARGET)) {
|
||||
if (AdrenoMemInfo::GetInstance()) {
|
||||
enable = AdrenoMemInfo::GetInstance()->IsUBWCSupportedByGPU(format);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow UBWC, only if CPU usage flags are not set
|
||||
if (enable && !(CpuCanAccess(usage))) {
|
||||
return true;
|
||||
}
|
||||
@@ -405,10 +418,13 @@ unsigned int GetRgbUBwcMetaBufferSize(int width, int height, uint32_t bpp) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Align meta buffer height to 16 blocks
|
||||
meta_height = ALIGN(((height + block_height - 1) / block_height), 16);
|
||||
|
||||
// Align meta buffer width to 64 blocks
|
||||
meta_width = ALIGN(((width + block_width - 1) / block_width), 64);
|
||||
|
||||
// Align meta buffer size to 4K
|
||||
size = (unsigned int)ALIGN((meta_width * meta_height), 4096);
|
||||
|
||||
return size;
|
||||
@@ -484,10 +500,12 @@ bool UseUncached(int format, uint64_t usage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CPU read rarely
|
||||
if ((usage & BufferUsage::CPU_READ_MASK) == static_cast<uint64_t>(BufferUsage::CPU_READ_RARELY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CPU write rarely
|
||||
if ((usage & BufferUsage::CPU_WRITE_MASK) ==
|
||||
static_cast<uint64_t>(BufferUsage::CPU_WRITE_RARELY)) {
|
||||
return true;
|
||||
@@ -558,6 +576,8 @@ uint64_t GetHandleFlags(int format, uint64_t usage) {
|
||||
int GetImplDefinedFormat(uint64_t usage, int format) {
|
||||
int gr_format = format;
|
||||
|
||||
// If input format is HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED then based on
|
||||
// the usage bits, gralloc assigns a format.
|
||||
if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ||
|
||||
format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
if ((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC || usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) &&
|
||||
@@ -565,30 +585,33 @@ int GetImplDefinedFormat(uint64_t usage, int format) {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC;
|
||||
} else if (usage & BufferUsage::VIDEO_ENCODER) {
|
||||
if (usage & GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER) {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE;
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE; // NV21
|
||||
} else if (usage & GRALLOC_USAGE_PRIVATE_HEIF) {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV12_HEIF;
|
||||
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
|
||||
} else {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE;
|
||||
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE; // NV12
|
||||
}
|
||||
} else if (usage & BufferUsage::CAMERA_INPUT) {
|
||||
if (usage & BufferUsage::CAMERA_OUTPUT) {
|
||||
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
|
||||
// Assumed ZSL if both producer and consumer camera flags set
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
|
||||
} else {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP;
|
||||
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP; // NV21
|
||||
}
|
||||
} else if (usage & BufferUsage::CAMERA_OUTPUT) {
|
||||
if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
|
||||
} else {
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
|
||||
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS; // NV12 preview
|
||||
}
|
||||
} else if (usage & BufferUsage::COMPOSER_OVERLAY) {
|
||||
// XXX: If we still haven't set a format, default to RGBA8888
|
||||
gr_format = HAL_PIXEL_FORMAT_RGBA_8888;
|
||||
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
|
||||
// If no other usage flags are detected, default the
|
||||
// flexible YUV format to NV21_ZSL
|
||||
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
|
||||
}
|
||||
}
|
||||
@@ -614,7 +637,7 @@ bool HasAlphaComponent(int32_t format) {
|
||||
}
|
||||
|
||||
void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int32_t height,
|
||||
int32_t , int* plane_count, PlaneLayoutInfo* plane_info) {
|
||||
int32_t /* flags */, int* plane_count, PlaneLayoutInfo* plane_info) {
|
||||
uint64_t usage = info.usage;
|
||||
*plane_count = 1;
|
||||
uint32_t bpp = 0;
|
||||
@@ -636,4 +659,4 @@ void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int3
|
||||
plane_info->scanlines = height;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace gralloc
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
#ifdef SLAVE_SIDE_CP
|
||||
#define SECURE_ALIGN SZ_1M
|
||||
#else
|
||||
#else // MASTER_SIDE_CP
|
||||
#define SECURE_ALIGN SZ_4K
|
||||
#endif
|
||||
|
||||
@@ -68,38 +68,59 @@ inline Type1 ALIGN(Type1 x, Type2 align) {
|
||||
}
|
||||
|
||||
enum PlaneComponent {
|
||||
/* luma */
|
||||
PLANE_COMPONENT_Y = 1 << 0,
|
||||
/* chroma blue */
|
||||
PLANE_COMPONENT_Cb = 1 << 1,
|
||||
/* chroma red */
|
||||
PLANE_COMPONENT_Cr = 1 << 2,
|
||||
|
||||
/* red */
|
||||
PLANE_COMPONENT_R = 1 << 10,
|
||||
/* green */
|
||||
PLANE_COMPONENT_G = 1 << 11,
|
||||
/* blue */
|
||||
PLANE_COMPONENT_B = 1 << 12,
|
||||
|
||||
/* alpha */
|
||||
PLANE_COMPONENT_A = 1 << 20,
|
||||
|
||||
/* raw data plane */
|
||||
PLANE_COMPONENT_RAW = 1 << 30,
|
||||
|
||||
/* meta information plane */
|
||||
PLANE_COMPONENT_META = 1 << 31,
|
||||
};
|
||||
|
||||
struct PlaneLayoutInfo {
|
||||
/** Components represented the type of plane. */
|
||||
PlaneComponent component;
|
||||
|
||||
/** horizontal subsampling. Must be a positive power of 2. */
|
||||
uint32_t h_subsampling;
|
||||
|
||||
/** vertical subsampling. Must be a positive power of 2. */
|
||||
uint32_t v_subsampling;
|
||||
|
||||
/** offset to the first byte of the top-left pixel of the plane
|
||||
* and it is calculated from the start of the buffer.
|
||||
* Add base of the handle with offset to get the first byte of the plane.
|
||||
*/
|
||||
uint32_t offset;
|
||||
|
||||
/** step is the distance in bytes from one pixel value to the next. */
|
||||
int32_t step;
|
||||
|
||||
/** stride of the plane in pixels */
|
||||
int32_t stride;
|
||||
|
||||
/** stride of the plane in in bytes */
|
||||
int32_t stride_bytes;
|
||||
|
||||
/** plane height or vertical stride */
|
||||
int32_t scanlines;
|
||||
|
||||
/** size of the plane in bytes */
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
@@ -130,6 +151,6 @@ int GetImplDefinedFormat(uint64_t usage, int format);
|
||||
int GetCustomFormatFlags(int format, uint64_t usage, int* custom_format, uint64_t* priv_flags);
|
||||
bool IsGPUFlagSupported(uint64_t usage);
|
||||
bool HasAlphaComponent(int32_t format);
|
||||
}
|
||||
} // namespace gralloc
|
||||
|
||||
#endif
|
||||
#endif // __GR_UTILS_H__
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "QtiGrallocDefs.h"
|
||||
#include "gr_priv_handle.h"
|
||||
|
||||
//#pragma message "Warning: gralloc_priv.h is deprecated"
|
||||
|
||||
#define GRALLOC_PROP_PREFIX "vendor.gralloc."
|
||||
#define GRALLOC_PROP(prop_name) GRALLOC_PROP_PREFIX prop_name
|
||||
@@ -41,6 +42,8 @@ inline int roundUpToPageSize(int x) {
|
||||
return (x + (getpagesize() - 1)) & ~(getpagesize() - 1);
|
||||
}
|
||||
|
||||
/* Legacy gralloc1 definitions */
|
||||
/* Some clients may still be using the old flags */
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ADSP_HEAP GRALLOC_USAGE_PRIVATE_ADSP_HEAP
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ALLOC_UBWC GRALLOC_USAGE_PRIVATE_ALLOC_UBWC
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_UNCACHED GRALLOC_USAGE_PRIVATE_UNCACHED
|
||||
@@ -51,6 +54,7 @@ inline int roundUpToPageSize(int x) {
|
||||
#define GRALLOC1_CONSUMER_USAGE_PRIVATE_10BIT_TP GRALLOC_USAGE_PRIVATE_10BIT_TP
|
||||
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_VIDEO_NV21_ENCODER GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER
|
||||
|
||||
// for PERFORM API :
|
||||
#define GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER 1
|
||||
#define GRALLOC_MODULE_PERFORM_GET_STRIDE 2
|
||||
#define GRALLOC_MODULE_PERFORM_GET_CUSTOM_STRIDE_FROM_HANDLE 3
|
||||
@@ -68,9 +72,11 @@ inline int roundUpToPageSize(int x) {
|
||||
#define GRALLOC1_MODULE_PERFORM_GET_INTERLACE_FLAG 15
|
||||
#define GRALLOC_MODULE_PERFORM_GET_GRAPHICS_METADATA 16
|
||||
|
||||
/* possible values for inverse gamma correction */
|
||||
#define HAL_IGC_NOT_SPECIFIED 0
|
||||
#define HAL_IGC_s_RGB 1
|
||||
|
||||
/* Color Space: Values maps to ColorSpace_t in qdMetadata.h */
|
||||
#define HAL_CSC_ITU_R_601 0
|
||||
#define HAL_CSC_ITU_R_601_FR 1
|
||||
#define HAL_CSC_ITU_R_709 2
|
||||
@@ -78,16 +84,20 @@ inline int roundUpToPageSize(int x) {
|
||||
#define HAL_CSC_ITU_R_2020 4
|
||||
#define HAL_CSC_ITU_R_2020_FR 5
|
||||
|
||||
/* possible formats for 3D content*/
|
||||
enum {
|
||||
HAL_NO_3D = 0x0,
|
||||
HAL_3D_SIDE_BY_SIDE_L_R = 0x1,
|
||||
HAL_3D_SIDE_BY_SIDE_R_L = 0x2,
|
||||
HAL_3D_TOP_BOTTOM = 0x4,
|
||||
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000,
|
||||
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000, // unused legacy format
|
||||
};
|
||||
|
||||
/* Flag to determine interlaced content
|
||||
* Value maps to Flags presents in types.hal of QtiMapperextensions
|
||||
*/
|
||||
enum {
|
||||
LAYOUT_INTERLACED_FLAG = 1 << 0,
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // __GRALLOC_PRIV_H__
|
||||
@@ -60,43 +60,43 @@ struct IMapper : public IBase {
|
||||
int32_t height __attribute__((aligned(4)));
|
||||
};
|
||||
|
||||
static sp<IMapper> getService(const std::string& , const bool );
|
||||
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace V2_0
|
||||
namespace V3_0 {
|
||||
|
||||
struct IMapper : public IBase {
|
||||
static sp<IMapper> getService(const std::string& , const bool );
|
||||
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace V3_0
|
||||
namespace V4_0 {
|
||||
|
||||
struct IMapper : public IBase {
|
||||
static sp<IMapper> getService(const std::string& , const bool );
|
||||
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace V4_0
|
||||
} // namespace android::hardware::graphics::mapper
|
||||
|
||||
namespace vendor::qti::hardware::display::mapper {
|
||||
namespace V4_0 {
|
||||
|
||||
struct IQtiMapper : public IBase {
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& ,
|
||||
bool );
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& /* parent */,
|
||||
bool /* emitError */);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace V4_0
|
||||
namespace V3_0 {
|
||||
|
||||
struct IQtiMapper : public IBase {
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& ,
|
||||
bool );
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& /* parent */,
|
||||
bool /* emitError */);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace V3_0
|
||||
namespace V2_0 {
|
||||
|
||||
struct IQtiMapper : public IBase {
|
||||
@@ -175,9 +175,9 @@ struct IQtiMapper : public IBase {
|
||||
V1_0::IQtiMapperExtensions>& extensions)>;
|
||||
virtual Return<void> getMapperExtensions(getMapperExtensions_cb _hidl_cb);
|
||||
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& ,
|
||||
bool );
|
||||
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& /* parent */,
|
||||
bool /* emitError */);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace V2_0
|
||||
} // namespace vendor::qti::hardware::display::mapper
|
||||
@@ -45,7 +45,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
IQtiMapperExtensions(){};
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace V1_0
|
||||
namespace V1_1 {
|
||||
using Error = V1_0::Error;
|
||||
using PlaneLayout = V1_0::PlaneLayout;
|
||||
@@ -64,6 +64,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passthrough
|
||||
using getInterlacedFlag_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, bool interlaced)>;
|
||||
virtual ::android::hardware::Return<void> getInterlacedFlag(void* buffer,
|
||||
@@ -78,6 +79,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passthrough
|
||||
using getRgbDataAddress_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, void* rgb_data)>;
|
||||
virtual ::android::hardware::Return<void> getRgbDataAddress(void* buffer,
|
||||
@@ -93,6 +95,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passhthrough
|
||||
using getColorSpace_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t color_space)>;
|
||||
virtual ::android::hardware::Return<void> getColorSpace(void* buffer, getColorSpace_cb _hidl_cb) {
|
||||
@@ -100,6 +103,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, passthrough with hidl fixups
|
||||
using getYuvPlaneInfo_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error,
|
||||
const ::android::hardware::hidl_vec<
|
||||
@@ -126,22 +130,27 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getFd_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t fd)>;
|
||||
virtual ::android::hardware::Return<void> getFd(void* buffer, getFd_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getWidth_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t width)>;
|
||||
virtual ::android::hardware::Return<void> getWidth(void* buffer, getWidth_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getHeight_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t height)>;
|
||||
virtual ::android::hardware::Return<void> getHeight(void* buffer, getHeight_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getOffset_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t offset)>;
|
||||
virtual ::android::hardware::Return<void> getOffset(void* buffer, getOffset_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getSize_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size)>;
|
||||
virtual ::android::hardware::Return<void> getSize(void* buffer, getSize_cb _hidl_cb);
|
||||
@@ -170,10 +179,12 @@ struct IQtiMapperExtensions : public IBase {
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t usage)>;
|
||||
virtual ::android::hardware::Return<void> getUsageFlags(void* buffer, getUsageFlags_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getFormat_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t format)>;
|
||||
virtual ::android::hardware::Return<void> getFormat(void* buffer, getFormat_cb _hidl_cb);
|
||||
|
||||
// Used, cast private data with no passthrough
|
||||
using getPrivateFlags_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t flags)>;
|
||||
virtual ::android::hardware::Return<void> getPrivateFlags(void* buffer,
|
||||
@@ -187,6 +198,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Used, PAIN
|
||||
using getFormatLayout_cb = std::function<void(
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size,
|
||||
const ::android::hardware::hidl_vec<
|
||||
@@ -196,6 +208,7 @@ struct IQtiMapperExtensions : public IBase {
|
||||
int32_t height,
|
||||
getFormatLayout_cb _hidl_cb);
|
||||
|
||||
// V1.1, Used, passthrough
|
||||
virtual ::android::hardware::Return<
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error>
|
||||
getSurfaceMetadata_V1(void* buffer, void* metadata) {
|
||||
@@ -204,8 +217,8 @@ struct IQtiMapperExtensions : public IBase {
|
||||
}
|
||||
|
||||
static Return<sp<IQtiMapperExtensions>> castFrom(
|
||||
android::sp<V1_0::IQtiMapperExtensions> const& , bool );
|
||||
android::sp<V1_0::IQtiMapperExtensions> const& /* parent */, bool /* emitError */);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace V1_1
|
||||
} // namespace vendor::qti::hardware::display::mapperextensions
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,7 @@ namespace graphics {
|
||||
namespace mapper {
|
||||
namespace V2_0 {
|
||||
|
||||
// Forward declaration for forward reference support:
|
||||
enum class Error : int32_t;
|
||||
struct YCbCrLayout;
|
||||
|
||||
@@ -24,8 +25,34 @@ enum class Error : int32_t {
|
||||
UNSUPPORTED = 7,
|
||||
};
|
||||
|
||||
/**
|
||||
* A buffer descriptor is an implementation-defined opaque data returned by
|
||||
* createDescriptor. It describes the properties of a buffer and is consumed
|
||||
* by the allocator.
|
||||
*/
|
||||
typedef ::android::hardware::hidl_vec<uint32_t> BufferDescriptor;
|
||||
|
||||
/**
|
||||
* Structure for describing YCbCr formats for consumption by applications.
|
||||
* This is used with PixelFormat::YCBCR_*_888.
|
||||
*
|
||||
* Buffer chroma subsampling is defined in the format.
|
||||
* e.g. PixelFormat::YCBCR_420_888 has subsampling 4:2:0.
|
||||
*
|
||||
* Buffers must have a 8 bit depth.
|
||||
*
|
||||
* y, cb, and cr point to the first byte of their respective planes.
|
||||
*
|
||||
* Stride describes the distance in bytes from the first value of one row of
|
||||
* the image to the first value of the next row. It includes the width of the
|
||||
* image plus padding.
|
||||
* yStride is the stride of the luma plane.
|
||||
* cStride is the stride of the chroma planes.
|
||||
*
|
||||
* chromaStep is the distance in bytes from one chroma pixel value to the
|
||||
* next. This is 2 bytes for semiplanar (because chroma values are interleaved
|
||||
* and each chroma value is one byte) and 1 for planar.
|
||||
*/
|
||||
struct YCbCrLayout final {
|
||||
void* y;
|
||||
void* cb;
|
||||
@@ -35,6 +62,9 @@ struct YCbCrLayout final {
|
||||
uint32_t chromaStep;
|
||||
};
|
||||
|
||||
//
|
||||
// type declarations for package
|
||||
//
|
||||
|
||||
template <typename>
|
||||
static inline std::string toString(int32_t o);
|
||||
@@ -77,7 +107,11 @@ constexpr int32_t& operator&=(int32_t& v,
|
||||
|
||||
static inline std::string toString(
|
||||
const ::android::hardware::graphics::mapper::V2_0::YCbCrLayout& o);
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
//
|
||||
// type header definitions for package
|
||||
//
|
||||
|
||||
template <>
|
||||
inline std::string toString<::android::hardware::graphics::mapper::V2_0::Error>(int32_t o) {
|
||||
@@ -184,17 +218,17 @@ static inline std::string toString(
|
||||
return os;
|
||||
}
|
||||
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
} // namespace V2_0
|
||||
} // namespace mapper
|
||||
} // namespace graphics
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// global type declarations for package
|
||||
//
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -209,8 +243,8 @@ constexpr std::array<::android::hardware::graphics::mapper::V2_0::Error, 6>
|
||||
::android::hardware::graphics::mapper::V2_0::Error::NO_RESOURCES,
|
||||
::android::hardware::graphics::mapper::V2_0::Error::UNSUPPORTED,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
#endif
|
||||
#endif // HIDL_GENERATED_ANDROID_HARDWARE_GRAPHICS_MAPPER_V2_0_TYPES_H
|
||||
@@ -13,6 +13,7 @@ namespace display {
|
||||
namespace mapperextensions {
|
||||
namespace V1_0 {
|
||||
|
||||
// Forward declaration for forward reference support:
|
||||
enum class Error : int32_t;
|
||||
struct YCbCrLayout;
|
||||
enum class PlaneComponent : uint32_t;
|
||||
@@ -35,22 +36,61 @@ struct YCbCrLayout final {
|
||||
uint32_t chromaStep;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plane Component type definitions.
|
||||
*/
|
||||
enum class PlaneComponent : uint32_t {
|
||||
PLANE_COMPONENT_Y = 1u ,
|
||||
PLANE_COMPONENT_Cb = 2u ,
|
||||
PLANE_COMPONENT_Cr = 4u ,
|
||||
PLANE_COMPONENT_R = 1024u ,
|
||||
PLANE_COMPONENT_G = 2048u ,
|
||||
PLANE_COMPONENT_B = 4096u ,
|
||||
PLANE_COMPONENT_A = 1048576u ,
|
||||
PLANE_COMPONENT_RAW = 1073741824u ,
|
||||
PLANE_COMPONENT_META = 2147483648u ,
|
||||
PLANE_COMPONENT_Y = 1u /* (1 << 0) */,
|
||||
PLANE_COMPONENT_Cb = 2u /* (1 << 1) */,
|
||||
PLANE_COMPONENT_Cr = 4u /* (1 << 2) */,
|
||||
PLANE_COMPONENT_R = 1024u /* (1 << 10) */,
|
||||
PLANE_COMPONENT_G = 2048u /* (1 << 11) */,
|
||||
PLANE_COMPONENT_B = 4096u /* (1 << 12) */,
|
||||
PLANE_COMPONENT_A = 1048576u /* (1 << 20) */,
|
||||
PLANE_COMPONENT_RAW = 1073741824u /* (1 << 30) */,
|
||||
PLANE_COMPONENT_META = 2147483648u /* (1 << 31) */,
|
||||
};
|
||||
|
||||
/**
|
||||
* Flags to send with getFormatLayout for extra information for e.g. interlaced,
|
||||
* otherwise send 0.
|
||||
*/
|
||||
enum class Flags : int32_t {
|
||||
LAYOUT_INTERLACED_FLAG = 1 ,
|
||||
LAYOUT_INTERLACED_FLAG = 1 /* (1 << 0) */,
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for describing plane layout information for YUVA/RGBA/RAW formats
|
||||
* YUV formats can be planar or semi-planar.
|
||||
*
|
||||
* This layout structure is used with any pixel format that can be
|
||||
* represented by it, such as:
|
||||
* - HAL_PIXEL_FORMAT_YCbCr_*
|
||||
* - HAL_PIXEL_FORMAT_RGB*_*
|
||||
* - even implementation defined formats that can be represented by
|
||||
* the structures
|
||||
*
|
||||
* Each plane can be made up of one or more PlaneComponents depending on
|
||||
* the format.
|
||||
*
|
||||
* Each plane can be subsampled either vertically or horizontally by
|
||||
* a power-of-two factor.
|
||||
*
|
||||
* Pixel increment describes the distance in bytes from one pixel to the
|
||||
* next pixel (to the right) on the same row for the component plane.
|
||||
* This can be negative.
|
||||
*
|
||||
* Stride is provided in both pixels and bytes.
|
||||
* Stride_bytes describes the distance in bytes from the first pixel of
|
||||
* one row to the first pixel of the next row (below) for the component plane.
|
||||
* This can be negative.
|
||||
*
|
||||
* Scanlines describes the height of the plane in terms of number of rows.
|
||||
*
|
||||
* Size of plane is stride_bytes * scanlines + any padding required for
|
||||
* alignment as per format specification.
|
||||
*
|
||||
*/
|
||||
struct PlaneLayout final {
|
||||
::android::hardware::hidl_bitfield<
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent>
|
||||
@@ -97,6 +137,9 @@ static_assert(sizeof(::vendor::qti::hardware::display::mapperextensions::V1_0::P
|
||||
static_assert(__alignof(::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout) == 4,
|
||||
"wrong alignment");
|
||||
|
||||
//
|
||||
// type declarations for package
|
||||
//
|
||||
|
||||
template <typename>
|
||||
static inline std::string toString(int32_t o);
|
||||
@@ -142,6 +185,7 @@ constexpr int32_t& operator&=(
|
||||
|
||||
static inline std::string toString(
|
||||
const ::vendor::qti::hardware::display::mapperextensions::V1_0::YCbCrLayout& o);
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
template <typename>
|
||||
static inline std::string toString(uint32_t o);
|
||||
@@ -240,6 +284,9 @@ static inline bool operator!=(
|
||||
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& lhs,
|
||||
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& rhs);
|
||||
|
||||
//
|
||||
// type header definitions for package
|
||||
//
|
||||
|
||||
template <>
|
||||
inline std::string toString<::vendor::qti::hardware::display::mapperextensions::V1_0::Error>(
|
||||
@@ -332,6 +379,7 @@ static inline std::string toString(
|
||||
return os;
|
||||
}
|
||||
|
||||
// operator== and operator!= are not generated for YCbCrLayout
|
||||
|
||||
template <>
|
||||
inline std::string
|
||||
@@ -594,13 +642,16 @@ static inline bool operator!=(
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace V1_0
|
||||
} // namespace mapperextensions
|
||||
} // namespace display
|
||||
} // namespace hardware
|
||||
} // namespace qti
|
||||
} // namespace vendor
|
||||
|
||||
//
|
||||
// global type declarations for package
|
||||
//
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -613,9 +664,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::E
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::NO_RESOURCES,
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::UNSUPPORTED,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -637,9 +688,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::P
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent::
|
||||
PLANE_COMPONENT_META,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
@@ -649,8 +700,8 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::F
|
||||
hidl_enum_values<::vendor::qti::hardware::display::mapperextensions::V1_0::Flags> = {
|
||||
::vendor::qti::hardware::display::mapperextensions::V1_0::Flags::LAYOUT_INTERLACED_FLAG,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace details
|
||||
} // namespace hardware
|
||||
} // namespace android
|
||||
|
||||
#endif
|
||||
#endif // HIDL_GENERATED_VENDOR_QTI_HARDWARE_DISPLAY_MAPPEREXTENSIONS_V1_0_TYPES_H
|
||||
@@ -46,13 +46,14 @@
|
||||
using namespace android::hardware;
|
||||
using namespace android;
|
||||
|
||||
// These two functions are used by the blob and aren't present on Q so weakly stub them
|
||||
extern "C" uint64_t __attribute__((weak)) atrace_get_enabled_tags() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace android::hardware::details {
|
||||
void __attribute__((weak)) return_status::onValueRetrieval() const {}
|
||||
}
|
||||
} // namespace android::hardware::details
|
||||
|
||||
bool IBase::isRemote() const {
|
||||
return false;
|
||||
@@ -145,7 +146,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace V2_0
|
||||
namespace V3_0 {
|
||||
|
||||
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
|
||||
@@ -153,7 +154,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace V3_0
|
||||
namespace V4_0 {
|
||||
|
||||
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
|
||||
@@ -161,8 +162,8 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace V4_0
|
||||
} // namespace android::hardware::graphics::mapper
|
||||
namespace vendor::qti::hardware::display {
|
||||
namespace mapper {
|
||||
namespace V4_0 {
|
||||
@@ -173,7 +174,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V4_0::IMa
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace V4_0
|
||||
namespace V3_0 {
|
||||
|
||||
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMapper>& parent,
|
||||
@@ -182,7 +183,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMa
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace V3_0
|
||||
namespace V2_0 {
|
||||
|
||||
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V2_0::IMapper>& parent,
|
||||
@@ -199,8 +200,8 @@ Return<void> IQtiMapper::getMapperExtensions(getMapperExtensions_cb _hidl_cb) {
|
||||
return Void();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace V2_0
|
||||
} // namespace mapper
|
||||
namespace mapperextensions {
|
||||
namespace V1_1 {
|
||||
|
||||
@@ -466,6 +467,6 @@ Return<void> IQtiMapperExtensions::getFormatLayout(int32_t format, uint64_t usag
|
||||
return Void();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace V1_1
|
||||
} // namespace mapperextensions
|
||||
} // namespace vendor::qti::hardware::display
|
||||
@@ -1,3 +1,6 @@
|
||||
// What the phone's own Vulkan driver says about its GPU, for the app's device info window: one
|
||||
// instance on the system loader, the first physical device's properties, a few limits and the
|
||||
// extensions DXVK and VKD3D-Proton lean on. Returned as "key=value" lines; nothing is kept open.
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
@@ -59,11 +62,12 @@ Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz
|
||||
LINE("device=%s\n", p.deviceName);
|
||||
LINE("type=%s\n", type_name(p.deviceType));
|
||||
LINE("api=%u.%u.%u\n", VK_API_VERSION_MAJOR(p.apiVersion), VK_API_VERSION_MINOR(p.apiVersion), VK_API_VERSION_PATCH(p.apiVersion));
|
||||
// Qualcomm packs its driver version as 10.10.12 bits, the same layout as VK_MAKE_VERSION.
|
||||
LINE("driver_version=%u.%u.%u\n", p.driverVersion >> 22, (p.driverVersion >> 12) & 0x3ff, p.driverVersion & 0xfff);
|
||||
LINE("vendor_id=0x%04x\ndevice_id=0x%08x\n", p.vendorID, p.deviceID);
|
||||
LINE("max_image_2d=%u\n", p.limits.maxImageDimension2D);
|
||||
LINE("max_push_constants=%u\n", p.limits.maxPushConstantsSize);
|
||||
|
||||
// Driver name and build string (Vulkan 1.2 or VK_KHR_driver_properties).
|
||||
uint32_t extCount = 0;
|
||||
if (vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, NULL);
|
||||
static VkExtensionProperties exts[512];
|
||||
@@ -92,7 +96,6 @@ Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz
|
||||
vkGetPhysicalDeviceFeatures(device, &f);
|
||||
LINE("geometry_shader=%s\ntessellation_shader=%s\ntexture_bc=%s\n",
|
||||
f.geometryShader ? "yes" : "no", f.tessellationShader ? "yes" : "no", f.textureCompressionBC ? "yes" : "no");
|
||||
LINE("shader_int64=%s\n", f.shaderInt64 ? "yes" : "no");
|
||||
}
|
||||
if (vkGetPhysicalDeviceMemoryProperties) {
|
||||
VkPhysicalDeviceMemoryProperties m;
|
||||
|
||||
+334
-1801
File diff suppressed because it is too large.
Load diff
@@ -84,11 +84,12 @@ struct VkTable {
|
||||
PFN_vkCmdSetScissor CmdSetScissor;
|
||||
PFN_vkCmdPipelineBarrier CmdPipelineBarrier;
|
||||
PFN_vkCmdCopyImage CmdCopyImage;
|
||||
PFN_vkCmdBlitImage CmdBlitImage;
|
||||
|
||||
PFN_vkCmdBlitImage CmdBlitImage; // frame-gen capture resolution: composite -> swapchain upscale
|
||||
// Compute: the native LSFG chain is 25 compute dispatches.
|
||||
PFN_vkCmdDispatch CmdDispatch;
|
||||
PFN_vkCreateComputePipelines CreateComputePipelines;
|
||||
PFN_vkUnmapMemory UnmapMemory;
|
||||
// win-fg native: clears its flow scratch, resets its scratch pool on resize.
|
||||
PFN_vkCmdClearColorImage CmdClearColorImage;
|
||||
PFN_vkResetDescriptorPool ResetDescriptorPool;
|
||||
PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage;
|
||||
@@ -124,94 +125,103 @@ struct VkTable {
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
|
||||
// Renderer-neutral direct-scanout impl (owns the SurfaceControl/AHB state).
|
||||
// Included after SCANOUT_LOG is defined above; its own definition is guarded.
|
||||
#include "../scanout/ScanoutContext.h"
|
||||
|
||||
// Native (compositor-side) LSFG frame generation - capability gate.
|
||||
#include "lsfg/lsfg_probe.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace lsfg { class Engine; }
|
||||
namespace ddfg { class Engine; }
|
||||
namespace winfg { class Engine; }
|
||||
|
||||
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
|
||||
|
||||
|
||||
|
||||
|
||||
struct SgsrPushConstants {
|
||||
// Push-constant layouts for the spatial-upscaler post passes. The leading vec4
|
||||
// `ndc` matches upscale.vert; the remaining members are read only by the
|
||||
// fragment stage. std430 offsets line up with these C structs.
|
||||
struct SgsrPushConstants { // 36 bytes
|
||||
float ndc[4];
|
||||
float viewportInfo[4];
|
||||
float edgeSharpness;
|
||||
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
|
||||
float edgeSharpness; // SGSR EdgeSharpness, from the slider
|
||||
};
|
||||
struct NisPushConstants {
|
||||
struct NisPushConstants { // 36 bytes
|
||||
float ndc[4];
|
||||
float viewportInfo[4];
|
||||
float sharpness;
|
||||
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
|
||||
float sharpness; // NIS sharpness [0..1], from the slider
|
||||
};
|
||||
struct EasuPushConstants {
|
||||
struct EasuPushConstants { // 88 bytes
|
||||
float ndc[4];
|
||||
uint32_t con0[4], con1[4], con2[4], con3[4];
|
||||
float outW, outH;
|
||||
};
|
||||
struct RcasPushConstants {
|
||||
struct RcasPushConstants { // 40 bytes
|
||||
float ndc[4];
|
||||
uint32_t con[4];
|
||||
uint32_t con[4]; // con.x = bit-packed sharpness
|
||||
float outW, outH;
|
||||
};
|
||||
|
||||
|
||||
struct CasPushConstants {
|
||||
// Composable post effects (AMD CAS sharpen + fake-HDR). Both lead with vec4 ndc
|
||||
// (offset 0) like the upscaler PCs and stay well under the 88-byte PC range.
|
||||
struct CasPushConstants { // 32 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float sharpness;
|
||||
float resolution[2]; // input texture size in px
|
||||
float sharpness; // CAS SHARPNESS term [0..1]
|
||||
float _pad;
|
||||
};
|
||||
struct HdrPushConstants {
|
||||
struct HdrPushConstants { // 24 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float resolution[2]; // input texture size in px
|
||||
};
|
||||
|
||||
|
||||
struct FxaaPushConstants {
|
||||
// Phase 2 screen effects ported from the GL EffectComposer. Each leads with vec4
|
||||
// ndc (offset 0) like the upscaler PCs and stays well under the 88-byte PC range.
|
||||
struct FxaaPushConstants { // 24 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float resolution[2]; // input texture size in px
|
||||
};
|
||||
struct ToonPushConstants {
|
||||
struct ToonPushConstants { // 24 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float resolution[2]; // input texture size in px
|
||||
};
|
||||
struct ColorPushConstants {
|
||||
struct ColorPushConstants { // 32 bytes
|
||||
float ndc[4];
|
||||
float brightness;
|
||||
float contrast;
|
||||
float gamma;
|
||||
float saturation;
|
||||
|
||||
|
||||
float brightness; // additive [-1,1]
|
||||
float contrast; // [0,2]
|
||||
float gamma; // [0.1,5]
|
||||
float saturation; // [0,2]; 1 = unchanged, 0 = greyscale.
|
||||
// APPENDED last so the three offsets above
|
||||
// stay where color.frag already had them.
|
||||
};
|
||||
struct NtscPushConstants {
|
||||
struct NtscPushConstants { // 28 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float frameCount;
|
||||
float resolution[2]; // screen size in px (TextureSize/resolution)
|
||||
float frameCount; // animated chroma-phase counter
|
||||
};
|
||||
struct CrtPushConstants {
|
||||
struct CrtPushConstants { // 24 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float resolution[2]; // input texture size in px (unused by shader)
|
||||
};
|
||||
struct DebandPushConstants {
|
||||
struct DebandPushConstants { // 28 bytes
|
||||
float ndc[4];
|
||||
float resolution[2];
|
||||
float strength;
|
||||
float resolution[2]; // input texture size in px (layout parity)
|
||||
float strength; // dither amplitude in LSBs (1.0 = +/-1/255)
|
||||
};
|
||||
|
||||
class VulkanRendererContext {
|
||||
public:
|
||||
// `lsfgVk11Compat` (experimental) lets the LSFG capability probe accept a
|
||||
// Vulkan 1.1/1.2 device that offers the extensions the chain needs; it has
|
||||
// to be known before vkCreateDevice, hence a constructor argument.
|
||||
VulkanRendererContext(ANativeWindow* window, int cWidth, int cHeight, void* adrenotoolsHandle = nullptr,
|
||||
bool lsfgVk11Compat = false);
|
||||
~VulkanRendererContext();
|
||||
|
||||
void onSurfaceResized(int width, int height);
|
||||
void setTransform(float ox, float oy, float sx, float sy);
|
||||
// #413: game-region clip rect (surface px). Stored and applied as the swapchain scissor in
|
||||
// recordCmdBuf so a FILL/STRETCH game confined to a half (TOP/BOTTOM alignment) can't bleed into
|
||||
// the on-screen-controls half. A full-surface (or w<=0) rect means "no clip" (CENTER).
|
||||
void setClipRegion(int x, int y, int w, int h);
|
||||
void updatePointerPosition(short x, short y);
|
||||
void updateWindowContent(int64_t id, void* pixels, short w, short h, short stride, int x, int y);
|
||||
@@ -231,7 +241,13 @@ public:
|
||||
void scanoutSetBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd = -1);
|
||||
void scanoutSetCursorImage(void* pixels, short w, short h, short stride);
|
||||
void scanoutSetCursorPos(short x, short y, short hotX, short hotY);
|
||||
// The renderer-neutral scanout implementation. The scanout methods above are
|
||||
// thin forwarders to this (see VulkanRendererScanout.cpp); the cursor render-
|
||||
// thread signalling stays in the forwarders, not in ScanoutContext.
|
||||
ScanoutContext scanout;
|
||||
// Bound to scanout's atomics so the existing JNI ABI (r->scanoutActive /
|
||||
// r->gameFrameDelivered direct member access) keeps compiling against a
|
||||
// single source of truth. Must be declared after `scanout`.
|
||||
std::atomic<bool>& scanoutActive = scanout.scanoutActive;
|
||||
std::atomic<bool>& gameFrameDelivered = scanout.gameFrameDelivered;
|
||||
std::atomic<bool> surfaceDetached{false};
|
||||
@@ -270,17 +286,48 @@ public:
|
||||
void setPresentMode(VkPresentModeKHR mode);
|
||||
std::vector<int> getSupportedPresentModes() const;
|
||||
|
||||
// --- Native LSFG frame generation -------------------------------------
|
||||
// Capability verdict for the compositor-side LSFG engine. Filled at device
|
||||
// creation and completed once the swapchain format is known; read by the
|
||||
// UI (through JNI) to grey the engine out with a reason.
|
||||
const lsfg::Caps& lsfgCaps() const { return lsfgCaps_; }
|
||||
|
||||
// Arm/disarm native LSFG frame generation for this session. Changing the
|
||||
// armed state recreates the swapchain, because the composite path needs
|
||||
// TRANSFER_DST usage and a deeper image queue that a normal session does
|
||||
// not pay for.
|
||||
void setFrameGenArmed(bool armed, int multiplier);
|
||||
bool frameGenArmed() const { return fgArmed_.load(std::memory_order_relaxed); }
|
||||
// Live frame-gen telemetry for the in-game readout:
|
||||
// [0] generations the governor currently trusts
|
||||
// [1] generations actually planned for the last source frame
|
||||
// [2] measured source (real) frames per second
|
||||
// [3] measured presented frames per second
|
||||
// [4] thermal status, -1 when the device gives no signal
|
||||
// [5] GPU milliseconds the chain spends per generated frame, -1 unknown
|
||||
void frameGenStats(float out[6]) const;
|
||||
// Why the selected native frame-gen engine cannot run here, for the UI:
|
||||
// -1 not known yet (caps not probed), 0 fine, 1 this driver lacks what the
|
||||
// engine needs (lsfgCaps().reason says which gate), 2 the engine failed to start.
|
||||
int frameGenProblem() const;
|
||||
// Path to the SPIR-V cache built from the user's Lossless.dll. Setting it
|
||||
// drops any existing engine so the next armed frame rebuilds from it.
|
||||
void setLsfgCachePath(const char* path);
|
||||
// Flow scale (0.25-1.0) and the panel's real refresh rate. The pacer never
|
||||
// generates above the refresh rate.
|
||||
// Experimental: `captureHeight` sizes the composite ring the chain runs on,
|
||||
// the width following the swapchain's aspect, so a phone GPU generates at
|
||||
// game-like resolution and the result is blitted up. 0 = panel;
|
||||
// kFgCaptureGame = the X screen's height (containerHeight), which already
|
||||
// carries the per-game override and render scale; otherwise a pixel height.
|
||||
static constexpr int32_t kFgCaptureGame = -1;
|
||||
void setFrameGenTuning(float flowScale, float refreshHz, int32_t captureHeight = 0);
|
||||
// Which native engine generates: 0 = LSFG (needs the cache built from the
|
||||
// user's Lossless.dll), 1 = win-fg (our own chain, embedded, needs nothing).
|
||||
// Switching drops the other engine so only one ever holds GPU resources.
|
||||
void setFrameGenEngine(int kind);
|
||||
void setDdFgTuning(int model, int perfPreset);
|
||||
// win-fg only: interpolation model (3/4) and performance preset (0..2).
|
||||
void setWinFgTuning(int model, int perfPreset);
|
||||
|
||||
private:
|
||||
struct WinTex {
|
||||
@@ -312,9 +359,11 @@ private:
|
||||
|
||||
ANativeWindow* window;
|
||||
int surfaceWidth, surfaceHeight, containerWidth, containerHeight;
|
||||
// #413 compositor clip rect (surface px). clipRegionW<=0 => disabled (whole swapchain). Written from
|
||||
// the renderer thread (setClipRegion), read from the render thread (recordCmdBuf) -> atomic.
|
||||
std::atomic<int> clipRegionX{0}, clipRegionY{0}, clipRegionW{0}, clipRegionH{0};
|
||||
void* adrenotoolsHandle = nullptr;
|
||||
bool lsfgVk11Compat_ = false;
|
||||
bool lsfgVk11Compat_ = false; // see the constructor
|
||||
int filterMode = 0;
|
||||
bool swapRB = false;
|
||||
float maxAnisotropy = 1.0f;
|
||||
@@ -337,6 +386,9 @@ private:
|
||||
std::vector<VkImageMemoryBarrier> framePreUpload;
|
||||
std::vector<VkImageMemoryBarrier> framePostUpload;
|
||||
|
||||
// [P0] All SurfaceControl / AHardwareBuffer scanout state moved to
|
||||
// ScanoutContext (member `scanout` above). Dead members (lastDst*,
|
||||
// ScanoutPending/scanoutPending*, fnSTSetBackPressure) were dropped.
|
||||
|
||||
std::atomic<int> pointerX{0}, pointerY{0};
|
||||
float sceneOffsetX=0.f, sceneOffsetY=0.f, sceneScaleX=1.f, sceneScaleY=1.f;
|
||||
@@ -376,69 +428,138 @@ private:
|
||||
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
|
||||
// ---- Spatial upscaler (SGSR / FSR1) ----
|
||||
// upscalerMode enum (mirrored by JNI nativeSetUpscaler):
|
||||
// 0 = none (passthrough; current sampler governs scaling)
|
||||
// 1 = linear (rebuild base sampler LINEAR; no shader pass)
|
||||
// 2 = nearest (rebuild base sampler NEAREST; no shader pass)
|
||||
// 3 = sgsr (Snapdragon GSR 1.0; single pass; aspect-fit / letterbox)
|
||||
// 4 = fsr (AMD FSR1 EASU+RCAS; two passes; fill / stretch)
|
||||
// 5 = fsr_fit (AMD FSR1 EASU+RCAS; two passes; aspect-fit / letterbox)
|
||||
// 6 = sharpen (RCAS-only; any resolution; aspect-fit / letterbox)
|
||||
// 7 = nis (NVIDIA Image Scaling NVScaler; single pass; aspect-fit)
|
||||
// 8 = sgsr_quality (SGSR 1 edge-direction variant; single pass; aspect-fit)
|
||||
// Shader upscaling only engages for modes 3-8 AND when the game render
|
||||
// resolution (container) is smaller than the swapchain. Otherwise the
|
||||
// existing direct-to-swapchain path is used unchanged.
|
||||
int upscalerMode = 0;
|
||||
// Independent of upscalerMode: high-quality Lanczos downscale for the
|
||||
// supersampling case (game render res > display res). When enabled and the
|
||||
// render res exceeds the swapchain, replaces the bilinear minify with a
|
||||
// proper downscale pass. Supersampling and the upscale modes are mutually
|
||||
// exclusive in practice (one is render>display, the other render<display).
|
||||
bool hqDownscale = false;
|
||||
|
||||
// Composable post effects, independent of the scaling mode. CAS layers a sharpen
|
||||
// on top of any mode (incl. native res); HDR is a binary fake-HDR. The upscaler
|
||||
// sharpness (RCAS lobe scale / SGSR EdgeSharpness) is driven by its own slider as a
|
||||
// linear 0..1 value: 0 = neutral (no sharpening; the upscale still runs), 1 = max.
|
||||
bool casEnabled = false;
|
||||
int casSharpness = 60;
|
||||
int casSharpness = 60; // slider 0..100 -> CAS SHARPNESS
|
||||
bool hdrEnabled = false;
|
||||
float upscaleSharpness01 = 0.75f;
|
||||
|
||||
float upscaleSharpness01 = 0.75f; // slider/100; RCAS lobe scale, SGSR edge derived
|
||||
// Debanding: terminal TPDF/IGN dither pass before the 8-bit swapchain quantize.
|
||||
bool debandEnabled = false;
|
||||
int debandStrength = 100;
|
||||
int debandStrength = 100; // slider 0..200 -> strength/100 LSBs
|
||||
|
||||
// Phase 2 screen effects (GL EffectComposer parity). FXAA/Toon/CRT/NTSC are
|
||||
// binary; Color is the brightness/contrast/gamma grade (always-applied via the
|
||||
// sliders, no toggle) and is "enabled" only when not at neutral (0,0,1).
|
||||
bool fxaaEnabled = false;
|
||||
bool toonEnabled = false;
|
||||
bool crtEnabled = false;
|
||||
bool ntscEnabled = false;
|
||||
bool colorEnabled = false;
|
||||
float colorBrightness = 0.0f;
|
||||
float colorContrast = 0.0f;
|
||||
float colorGamma = 1.0f;
|
||||
float colorSaturation = 1.0f;
|
||||
uint32_t ntscFrameCounter = 0;
|
||||
bool colorEnabled = false; // derived: !neutral grade
|
||||
float colorBrightness = 0.0f; // [-1,1] (slider/100, clamped)
|
||||
float colorContrast = 0.0f; // [0,2] (slider/100, clamped)
|
||||
float colorGamma = 1.0f; // [0.1,5]
|
||||
float colorSaturation = 1.0f; // [0,2] (slider/100, clamped)
|
||||
uint32_t ntscFrameCounter = 0; // animates NTSC chroma phase
|
||||
|
||||
VkSampler upscaleSampler = VK_NULL_HANDLE;
|
||||
VkSampler upscaleSampler = VK_NULL_HANDLE; // linear clamp; offscreen/mid input
|
||||
|
||||
// === Native LSFG: composite target ring ===================================
|
||||
// Frame generation cannot composite straight into a swapchain image: the
|
||||
// finished frame has to be READABLE (it becomes the next frame's LSFG
|
||||
// input) and generated frames have to be STORAGE-WRITABLE by a compute
|
||||
// dispatch. Android swapchain images are COLOR_ATTACHMENT only, and
|
||||
// storage support on a swapchain format is not something a driver owes us.
|
||||
//
|
||||
// So when frame gen is armed the whole existing recording is redirected at
|
||||
// a composite image we own - format-identical to the swapchain, so every
|
||||
// existing pipeline stays render-pass compatible - and a copy moves it into
|
||||
// the acquired swapchain image at the end. With frame gen off, not one of
|
||||
// these objects is created and the direct-to-swapchain path is untouched.
|
||||
struct CompositeTarget {
|
||||
VkImage img = VK_NULL_HANDLE;
|
||||
VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageView storageView = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE; // colour attachment + sampled
|
||||
VkImageView storageView = VK_NULL_HANDLE; // compute writes (generate)
|
||||
VkFramebuffer fb = VK_NULL_HANDLE;
|
||||
VkDescriptorSet ds = VK_NULL_HANDLE;
|
||||
VkDescriptorSet ds = VK_NULL_HANDLE; // sampled, for later passes
|
||||
};
|
||||
// Hard ceiling: (max generations + 1) presentable frames per source frame,
|
||||
// times a queue depth of 2, capped so the footprint stays bounded.
|
||||
static constexpr uint32_t kMaxCompositeTargets = 7;
|
||||
|
||||
std::vector<CompositeTarget> compositeTargets;
|
||||
VkRenderPass compositeRenderPass = VK_NULL_HANDLE;
|
||||
VkRenderPass compositeRenderPass = VK_NULL_HANDLE; // CLEAR -> GENERAL
|
||||
uint32_t compositeW = 0, compositeH = 0;
|
||||
uint32_t compositeIndex = 0;
|
||||
bool compositeArmed = false;
|
||||
bool swapchainTransferDst = false;
|
||||
uint32_t compositeIndex = 0; // rotates per composite; gives history for free
|
||||
bool compositeArmed = false; // targets exist AND this frame uses them
|
||||
bool swapchainTransferDst = false; // swapchain was created with TRANSFER_DST
|
||||
|
||||
// Set from the app when the native LSFG engine is selected for this
|
||||
// session. Read on the render thread; false keeps every path as it was.
|
||||
std::atomic<bool> fgArmed_{false};
|
||||
std::atomic<int> fgMultiplier_{0};
|
||||
// Tuning is written by the UI thread and consumed by the render thread, so
|
||||
// it is published through atomics and applied to the engine at the top of
|
||||
// the frame-gen block. The UI thread never touches the engine itself.
|
||||
std::atomic<float> fgFlowScale_{1.0f};
|
||||
std::atomic<float> fgRefreshHz_{0.0f};
|
||||
std::atomic<bool> fgConfigDirty_{true};
|
||||
// Experimental (see setFrameGenTuning): capture height of the composite
|
||||
// ring. 0 = panel resolution, kFgCaptureGame = the X screen's height.
|
||||
std::atomic<int32_t> fgCaptureHeight_{0};
|
||||
// Extent the composite ring should have for the current swapchain and
|
||||
// capture height (width follows the swapchain aspect, both even).
|
||||
void compositeExtentFor(uint32_t& w, uint32_t& h) const;
|
||||
// The extent the scene is rendered at this frame: the composite ring while
|
||||
// frame gen runs on a ring smaller than the swapchain (experimental capture
|
||||
// resolution), otherwise the swapchain. Every full-target pass (the effect
|
||||
// chain, the spatial upscalers, the render-scale downscale) sizes itself by
|
||||
// this, so those keep working below panel resolution and their
|
||||
// intermediates shrink with the ring.
|
||||
VkExtent2D renderExtent() const {
|
||||
if (compositeActive() && (compositeW != swapchainExt.width || compositeH != swapchainExt.height))
|
||||
return VkExtent2D{compositeW, compositeH};
|
||||
return swapchainExt;
|
||||
}
|
||||
// Copy (same extent) or blit (composite smaller than the swapchain) a
|
||||
// composite-ring image, already in TRANSFER_SRC, into a swapchain image
|
||||
// already in TRANSFER_DST.
|
||||
void recordCompositeToSwapchainTransfer(VkCommandBuffer cb, VkImage src, uint32_t imgIdx);
|
||||
|
||||
bool createCompositeRenderPass();
|
||||
bool ensureCompositeTargets(uint32_t w, uint32_t h, uint32_t count);
|
||||
void destroyCompositeTargets();
|
||||
// True when this frame should composite off-swapchain. Call on the render
|
||||
// thread; every gate must hold or we fall back to the untouched path.
|
||||
bool compositeActive() const;
|
||||
// Render pass / framebuffer this frame draws its FINAL pass into.
|
||||
VkRenderPass targetRenderPass() const;
|
||||
VkFramebuffer targetFramebuffer(uint32_t imgIdx) const;
|
||||
// Copy the finished composite into the acquired swapchain image and leave
|
||||
// it in PRESENT_SRC. No-op when the composite path is not active.
|
||||
void copyCompositeToSwapchain(VkCommandBuffer cb, uint32_t imgIdx);
|
||||
|
||||
// === Native LSFG: the software cursor ====================================
|
||||
// LSFG interpolates whatever it is given, so a cursor composited into the
|
||||
// frame gets warped along the flow field and smears. It is therefore
|
||||
// excluded from the composite while frame gen is armed and drawn once into
|
||||
// EVERY presented image instead - real and generated alike - through a
|
||||
// load-op render pass that leaves the image ready to present.
|
||||
VkRenderPass cursorOverlayRenderPass = VK_NULL_HANDLE;
|
||||
struct CursorOverlay {
|
||||
bool draw = false;
|
||||
@@ -448,16 +569,19 @@ private:
|
||||
CursorOverlay cursorOverlay_{};
|
||||
|
||||
bool createCursorOverlayRenderPass();
|
||||
// Draw the cursor into an already-copied swapchain image and leave it in
|
||||
// PRESENT_SRC. Takes over the final transition from the copy helpers.
|
||||
void recordCursorOverlay(VkCommandBuffer cb, uint32_t imgIdx);
|
||||
// True when the cursor is being drawn per present rather than composited.
|
||||
bool cursorDrawnPerPresent() const;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static constexpr uint32_t kMaxPresentsPerFrame = 4;
|
||||
// === Native LSFG: the per-source-frame present plan =======================
|
||||
// Interpolation produces frames that belong BETWEEN N-1 and N, so the
|
||||
// generated frames are presented FIRST and real frame N is held back one
|
||||
// slot. All presents for one source frame are queued together: with FIFO
|
||||
// the driver then shows them on consecutive vblanks, so the pacing falls
|
||||
// out of the present mode for free - no sleeps, no render-mode change.
|
||||
static constexpr uint32_t kMaxPresentsPerFrame = 4; // 1 real + up to 3 generated
|
||||
struct FrameGenPlan {
|
||||
uint32_t generations = 0;
|
||||
uint32_t presents = 1;
|
||||
@@ -468,19 +592,32 @@ private:
|
||||
uint64_t fgSourceFrames_ = 0;
|
||||
std::string lsfgCachePath_;
|
||||
bool lsfgEngineTried_ = false;
|
||||
std::unique_ptr<ddfg::Engine> ddfgEngine_;
|
||||
bool ddfgEngineTried_ = false;
|
||||
std::atomic<int> fgEngineKind_{0};
|
||||
std::unique_ptr<winfg::Engine> winfgEngine_;
|
||||
bool winfgEngineTried_ = false;
|
||||
std::atomic<int> fgEngineKind_{0}; // 0 = lsfg, 1 = win-fg
|
||||
std::atomic<int> fgModel_{4};
|
||||
std::atomic<int> fgPerfPreset_{1};
|
||||
bool ensureDdFgEngine();
|
||||
bool ensureWinFgEngine();
|
||||
// Capability gate for the SELECTED engine. win-fg's shaders need only a
|
||||
// storage-capable swapchain format; the LSFG chain also needs the fp16 /
|
||||
// memory-model feature set, which some otherwise capable GPUs lack.
|
||||
bool fgCapsOk() const;
|
||||
|
||||
// Sync objects are indexed per PRESENT, not per composite: each pending
|
||||
// present needs its own image-available and render-finished semaphore.
|
||||
uint32_t syncSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
|
||||
// Destroy and rebuild every acquire/present semaphore. Called with the
|
||||
// swapchain, so no stale pending signal can survive into the new one.
|
||||
void recreateSyncObjects();
|
||||
uint32_t fgAcquireFailLog_ = 0;
|
||||
|
||||
// Measured PRESENTS per second - the rate that actually reaches the panel,
|
||||
// counting generated frames. The pacer's own "loop rate" cannot serve this
|
||||
// purpose: it is sampled once per SOURCE frame, so it always equals the
|
||||
// guest rate and contains no evidence that generation happened at all.
|
||||
float fgPresentedRate_ = 0.0f;
|
||||
// Measured SOURCE frames per second over the same window, for engines that
|
||||
// do not measure it themselves (win-fg).
|
||||
float fgSourceRate_ = 0.0f;
|
||||
uint32_t fgSourceAccum_ = 0;
|
||||
uint32_t fgPresentAccum_ = 0;
|
||||
@@ -489,10 +626,22 @@ private:
|
||||
void trackPresentedRate(uint32_t presents);
|
||||
|
||||
bool ensureLsfgEngine();
|
||||
// One command buffer per pending present: slot 0 carries the composite,
|
||||
// the chain's shared passes and generated frame 0; each later generated
|
||||
// frame and the real frame are recorded and submitted on their own, so a
|
||||
// finished frame reaches the presentation engine without waiting for the
|
||||
// rest of the chain.
|
||||
uint32_t cmdSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
|
||||
// Shared chain passes (the 24 shaders every generated frame depends on).
|
||||
void recordFrameGenProcess(VkCommandBuffer cb);
|
||||
// Generated frame g: synthesise into a spare composite target and copy it
|
||||
// into the swapchain image reserved for it.
|
||||
void recordFrameGenGeneration(VkCommandBuffer cb, uint32_t g);
|
||||
|
||||
// Chain cost: a timestamp pair per frame slot, start before the shared
|
||||
// passes, end after the last generation's compute (before its copy, so a
|
||||
// vblank wait on the swapchain image is not counted). Read back after the
|
||||
// slot's fence wait, one frame later.
|
||||
VkQueryPool fgQueryPool_ = VK_NULL_HANDLE;
|
||||
bool fgTimestampsOk_ = false;
|
||||
float fgTimestampPeriodNs_ = 0.0f;
|
||||
@@ -505,21 +654,22 @@ private:
|
||||
void readFgQueryResult();
|
||||
|
||||
VkFormat offscreenFmt = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE;
|
||||
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE; // CLEAR -> SHADER_READ_ONLY
|
||||
VkPipelineLayout postPipeLayout = VK_NULL_HANDLE;
|
||||
VkPipeline sgsrPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline nisPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline nisPipeline = VK_NULL_HANDLE; // NVIDIA Image Scaling (mode 7)
|
||||
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE; // SGSR 1 edge-direction (mode 8)
|
||||
VkPipeline easuPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline rcasPipeline = VK_NULL_HANDLE;
|
||||
VkPipeline downscalePipeline = VK_NULL_HANDLE;
|
||||
|
||||
|
||||
|
||||
VkPipeline casPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline casPipelineSwap = VK_NULL_HANDLE;
|
||||
// CAS/HDR can be either a non-final pass (writes an fx target via
|
||||
// offscreenRenderPass) or the final pass (writes the swapchain via renderPass);
|
||||
// a pipeline is render-pass-specific, so keep one variant of each.
|
||||
VkPipeline casPipelineOff = VK_NULL_HANDLE; // -> fx target (offscreenRenderPass)
|
||||
VkPipeline casPipelineSwap = VK_NULL_HANDLE; // -> swapchain (renderPass)
|
||||
VkPipeline hdrPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline hdrPipelineSwap = VK_NULL_HANDLE;
|
||||
// Phase 2 effects: same Off (-> fx target) / Swap (-> swapchain) variant pair.
|
||||
VkPipeline fxaaPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline fxaaPipelineSwap = VK_NULL_HANDLE;
|
||||
VkPipeline toonPipelineOff = VK_NULL_HANDLE;
|
||||
@@ -530,8 +680,10 @@ private:
|
||||
VkPipeline ntscPipelineSwap = VK_NULL_HANDLE;
|
||||
VkPipeline crtPipelineOff = VK_NULL_HANDLE;
|
||||
VkPipeline crtPipelineSwap = VK_NULL_HANDLE;
|
||||
// Debanding is always the LAST effect (terminal) -> only a swapchain variant.
|
||||
VkPipeline debandPipelineSwap = VK_NULL_HANDLE;
|
||||
|
||||
// offscreen composite target @ game/container resolution
|
||||
VkImage offscreenImg = VK_NULL_HANDLE;
|
||||
VkDeviceMemory offscreenMem = VK_NULL_HANDLE;
|
||||
VkImageView offscreenView = VK_NULL_HANDLE;
|
||||
@@ -539,6 +691,7 @@ private:
|
||||
VkDescriptorSet offscreenDS = VK_NULL_HANDLE;
|
||||
int offscreenW = 0, offscreenH = 0;
|
||||
|
||||
// FSR intermediate (EASU output) @ upscale output resolution
|
||||
VkImage midImg = VK_NULL_HANDLE;
|
||||
VkDeviceMemory midMem = VK_NULL_HANDLE;
|
||||
VkImageView midView = VK_NULL_HANDLE;
|
||||
@@ -546,6 +699,8 @@ private:
|
||||
VkDescriptorSet midDS = VK_NULL_HANDLE;
|
||||
int midW = 0, midH = 0;
|
||||
|
||||
// Effect-chain intermediates @ swapchain resolution. fx1 holds the scaled (or
|
||||
// composited) image fed into CAS/HDR; fx2 ping-pongs when both effects run.
|
||||
VkImage fx1Img = VK_NULL_HANDLE;
|
||||
VkDeviceMemory fx1Mem = VK_NULL_HANDLE;
|
||||
VkImageView fx1View = VK_NULL_HANDLE;
|
||||
@@ -560,18 +715,21 @@ private:
|
||||
VkDescriptorSet fx2DS = VK_NULL_HANDLE;
|
||||
int fx2W = 0, fx2H = 0;
|
||||
|
||||
// Per-frame upscale plan, computed in renderFrame, consumed by recordCmdBuf.
|
||||
// upFrame.mode reuses the upscalerMode enum (3=sgsr,4=fsr,5=fsr_fit,6=sharpen,7=nis,8=sgsr_quality)
|
||||
// plus an internal sentinel (UPMODE_DOWNSCALE) for the supersampling path.
|
||||
struct UpscaleFrame {
|
||||
bool active = false;
|
||||
int mode = 0;
|
||||
int outX = 0, outY = 0, outW = 0, outH = 0;
|
||||
bool cas = false;
|
||||
bool hdr = false;
|
||||
bool fxaa = false;
|
||||
bool toon = false;
|
||||
bool color = false;
|
||||
bool ntsc = false;
|
||||
bool crt = false;
|
||||
bool deband = false;
|
||||
bool cas = false; // run CAS this frame (snapshot of casEnabled)
|
||||
bool hdr = false; // run HDR this frame (snapshot of hdrEnabled)
|
||||
bool fxaa = false; // snapshot of fxaaEnabled
|
||||
bool toon = false; // snapshot of toonEnabled
|
||||
bool color = false; // snapshot of colorEnabled (non-neutral grade)
|
||||
bool ntsc = false; // snapshot of ntscEnabled
|
||||
bool crt = false; // snapshot of crtEnabled
|
||||
bool deband = false;// snapshot of debandEnabled (terminal dither)
|
||||
SgsrPushConstants sgsrPC{};
|
||||
NisPushConstants nisPC{};
|
||||
EasuPushConstants easuPC{};
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ddfg {
|
||||
|
||||
struct Config {
|
||||
bool enabled = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool debug = false;
|
||||
bool pacing = false;
|
||||
int model = 4;
|
||||
int multiplier = 2;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int extraImages = 2;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int perfPreset = 1;
|
||||
float flowScale = 1.0f;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int gmMode = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int frMode = 0;
|
||||
int frIters = 4;
|
||||
float frLambda = 2.0f;
|
||||
float frDt = 0.25f;
|
||||
float frEdge = 8.0f;
|
||||
float frEps = 0.05f;
|
||||
|
||||
float beta = 8.0f;
|
||||
float lambda = 0.6f;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float epsilon = 0.07f;
|
||||
float photoScale = 7.5f;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float hudX0 = 0.0f;
|
||||
float hudY0 = 0.0f;
|
||||
float hudX1 = 0.0f;
|
||||
float hudY1 = 0.0f;
|
||||
|
||||
bool capture = false;
|
||||
int captureW = 1280;
|
||||
int captureH = 720;
|
||||
int capMode = 0;
|
||||
int capPatches = 3;
|
||||
int capPatchSize = 256;
|
||||
|
||||
|
||||
float capMotion = 2.0f;
|
||||
std::string capDir;
|
||||
int capShardMB = 1024;
|
||||
|
||||
void sanitize() {
|
||||
if (model < 3) model = 3; if (model > 4) model = 4;
|
||||
if (multiplier < 2) multiplier = 2; if (multiplier > 4) multiplier = 4;
|
||||
if (extraImages < 0) extraImages = 0; if (extraImages > 8) extraImages = 8;
|
||||
if (perfPreset < 0 || perfPreset > 2) perfPreset = 1;
|
||||
if (gmMode < 0) gmMode = 0; if (gmMode > 2) gmMode = 2;
|
||||
if (frMode < 0) frMode = 0; if (frMode > 2) frMode = 2;
|
||||
if (frIters < 0) frIters = 0; if (frIters > 16) frIters = 16;
|
||||
if (frLambda < 0.0f) frLambda = 0.0f;
|
||||
if (frDt < 0.01f) frDt = 0.01f; if (frDt > 4.0f) frDt = 4.0f;
|
||||
if (frEdge < 0.0f) frEdge = 0.0f;
|
||||
if (frEps < 1e-3f) frEps = 1e-3f;
|
||||
if (flowScale < 0.05f) flowScale = 0.05f; if (flowScale > 4.0f) flowScale = 4.0f;
|
||||
if (beta < 0.0f) beta = 0.0f; if (lambda < 0.0f) lambda = 0.0f;
|
||||
if (captureW < 16) captureW = 16; if (captureW > 7680) captureW = 7680;
|
||||
if (captureH < 16) captureH = 16; if (captureH > 4320) captureH = 4320;
|
||||
if (capMode < 0) capMode = 0; if (capMode > 1) capMode = 1;
|
||||
if (capPatches < 1) capPatches = 1; if (capPatches > 16) capPatches = 16;
|
||||
if (capPatchSize < 16) capPatchSize = 16; if (capPatchSize > 1024) capPatchSize = 1024;
|
||||
if (capMotion < 0.0f) capMotion = 0.0f;
|
||||
if (capShardMB < 16) capShardMB = 16; if (capShardMB > 65536) capShardMB = 65536;
|
||||
}
|
||||
};
|
||||
|
||||
static inline float envf(const char* k, float d) {
|
||||
const char* v = std::getenv(k); return v ? std::strtof(v, nullptr) : d;
|
||||
}
|
||||
static inline int envi(const char* k, int d) {
|
||||
const char* v = std::getenv(k); return v ? std::atoi(v) : d;
|
||||
}
|
||||
|
||||
static inline int parse_tristate(const std::string& v, int dflt) {
|
||||
if (v.empty()) return dflt;
|
||||
if (v == "auto") return 0;
|
||||
if (v == "on" || v == "1" || v == "force" || v == "true" || v == "yes") return 1;
|
||||
if (v == "off" || v == "0" || v == "false" || v == "no") return 2;
|
||||
return dflt;
|
||||
}
|
||||
|
||||
static inline bool parse_bool(const std::string& v, bool dflt) {
|
||||
int t = parse_tristate(v, dflt ? 1 : 2);
|
||||
return t == 1;
|
||||
}
|
||||
static inline int parse_capmode(const std::string& v, int dflt) {
|
||||
if (v == "patch" || v == "patches" || v == "0") return 0;
|
||||
if (v == "frame" || v == "frames" || v == "1") return 1;
|
||||
return dflt;
|
||||
}
|
||||
|
||||
static inline void apply_toml(Config& c, const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f) return;
|
||||
std::string line;
|
||||
while (std::getline(f, line)) {
|
||||
auto h = line.find('#'); if (h != std::string::npos) line = line.substr(0, h);
|
||||
auto eq = line.find('='); if (eq == std::string::npos) continue;
|
||||
auto trim = [](std::string s){
|
||||
size_t a = s.find_first_not_of(" \t\r\n"); size_t b = s.find_last_not_of(" \t\r\n");
|
||||
return (a == std::string::npos) ? std::string() : s.substr(a, b - a + 1); };
|
||||
std::string k = trim(line.substr(0, eq)), v = trim(line.substr(eq + 1));
|
||||
if (k.empty() || v.empty()) continue;
|
||||
if (k == "enabled") c.enabled = (v == "1" || v == "true");
|
||||
else if (k == "debug") c.debug = parse_bool(v, c.debug);
|
||||
else if (k == "pacing") c.pacing = parse_bool(v, c.pacing);
|
||||
else if (k == "model") c.model = std::atoi(v.c_str());
|
||||
else if (k == "multiplier") c.multiplier = std::atoi(v.c_str());
|
||||
else if (k == "extra_images") c.extraImages = std::atoi(v.c_str());
|
||||
else if (k == "perf_preset") c.perfPreset = std::atoi(v.c_str());
|
||||
else if (k == "global_motion") c.gmMode = parse_tristate(v, c.gmMode);
|
||||
else if (k == "flow_reg") c.frMode = parse_tristate(v, c.frMode);
|
||||
else if (k == "fr_iters") c.frIters = std::atoi(v.c_str());
|
||||
else if (k == "fr_lambda") c.frLambda = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "fr_dt") c.frDt = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "fr_edge") c.frEdge = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "fr_eps") c.frEps = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "flowScale") c.flowScale = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "beta") c.beta = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "lambda") c.lambda = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "epsilon") c.epsilon = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "photoScale") c.photoScale = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "capture") c.capture = parse_bool(v, c.capture);
|
||||
else if (k == "capture_dir") c.capDir = v;
|
||||
else if (k == "capture_width") c.captureW = std::atoi(v.c_str());
|
||||
else if (k == "capture_height") c.captureH = std::atoi(v.c_str());
|
||||
else if (k == "capture_mode") c.capMode = parse_capmode(v, c.capMode);
|
||||
else if (k == "capture_patches") c.capPatches = std::atoi(v.c_str());
|
||||
else if (k == "capture_patch_size") c.capPatchSize = std::atoi(v.c_str());
|
||||
else if (k == "capture_motion") c.capMotion = std::strtof(v.c_str(), nullptr);
|
||||
else if (k == "capture_shard_mb") c.capShardMB = std::atoi(v.c_str());
|
||||
else if (k == "hudRect") {
|
||||
float r[4] = {0,0,0,0}; int n = 0;
|
||||
size_t start = 0; std::string s = v;
|
||||
for (; n < 4; ++n) {
|
||||
size_t comma = s.find(',', start);
|
||||
std::string tok = s.substr(start, comma == std::string::npos ? std::string::npos : comma - start);
|
||||
r[n] = std::strtof(tok.c_str(), nullptr);
|
||||
if (comma == std::string::npos) break;
|
||||
start = comma + 1;
|
||||
}
|
||||
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::string conf_path() {
|
||||
if (const char* p = std::getenv("DD_FG_CONF")) return p;
|
||||
const char* home = std::getenv("HOME");
|
||||
return home ? std::string(home) + "/.config/dd-fg/conf.toml" : std::string();
|
||||
}
|
||||
|
||||
static inline std::string capture_root(const Config& c) {
|
||||
if (!c.capDir.empty()) return c.capDir;
|
||||
if (const char* home = std::getenv("HOME")) return std::string(home) + "/.cache/ddfg-capture";
|
||||
return std::string("/tmp/ddfg-capture");
|
||||
}
|
||||
|
||||
static inline Config load_config() {
|
||||
Config c;
|
||||
c.enabled = envi("DD_FG_ENABLE", 0) != 0;
|
||||
if (const char* d = std::getenv("DD_FG_DEBUG")) c.debug = parse_bool(d, c.debug);
|
||||
if (const char* pc = std::getenv("DD_FG_PACING")) c.pacing = parse_bool(pc, c.pacing);
|
||||
c.model = envi("DD_FG_MODEL", c.model);
|
||||
c.multiplier = envi("DD_FG_MULT", c.multiplier);
|
||||
c.extraImages = envi("DD_FG_EXTRA_IMAGES", c.extraImages);
|
||||
c.perfPreset = envi("DD_FG_PERF_PRESET", c.perfPreset);
|
||||
if (const char* g = std::getenv("DD_FG_GM")) c.gmMode = parse_tristate(g, c.gmMode);
|
||||
if (const char* r = std::getenv("DD_FG_FLOWREG")) c.frMode = parse_tristate(r, c.frMode);
|
||||
c.frIters = envi("DD_FG_FR_ITERS", c.frIters);
|
||||
c.frLambda = envf("DD_FG_FR_LAMBDA", c.frLambda);
|
||||
c.frDt = envf("DD_FG_FR_DT", c.frDt);
|
||||
c.frEdge = envf("DD_FG_FR_EDGE", c.frEdge);
|
||||
c.frEps = envf("DD_FG_FR_EPS", c.frEps);
|
||||
c.flowScale = envf("DD_FG_FLOWSCALE", c.flowScale);
|
||||
c.beta = envf("DD_FG_BETA", c.beta);
|
||||
c.lambda = envf("DD_FG_LAMBDA", c.lambda);
|
||||
c.epsilon = envf("DD_FG_EPSILON", c.epsilon);
|
||||
c.photoScale = envf("DD_FG_PHOTOSCALE", c.photoScale);
|
||||
if (const char* cc = std::getenv("DD_FG_CAPTURE")) c.capture = parse_bool(cc, c.capture);
|
||||
c.captureW = envi("DD_FG_CAPTURE_W", c.captureW);
|
||||
c.captureH = envi("DD_FG_CAPTURE_H", c.captureH);
|
||||
if (const char* cm = std::getenv("DD_FG_CAPTURE_MODE")) c.capMode = parse_capmode(cm, c.capMode);
|
||||
c.capPatches = envi("DD_FG_CAPTURE_PATCHES", c.capPatches);
|
||||
c.capPatchSize = envi("DD_FG_CAPTURE_PATCH", c.capPatchSize);
|
||||
c.capMotion = envf("DD_FG_CAPTURE_MOTION", c.capMotion);
|
||||
c.capShardMB = envi("DD_FG_CAPTURE_SHARD_MB", c.capShardMB);
|
||||
if (const char* cd = std::getenv("DD_FG_CAPTURE_DIR")) c.capDir = cd;
|
||||
if (const char* h = std::getenv("DD_FG_HUD_RECT")) {
|
||||
float r[4] = {0,0,0,0}; int n = 0;
|
||||
std::string s = h; size_t start = 0;
|
||||
for (; n < 4; ++n) {
|
||||
size_t comma = s.find(',', start);
|
||||
r[n] = std::strtof(s.substr(start, comma == std::string::npos ? std::string::npos : comma - start).c_str(), nullptr);
|
||||
if (comma == std::string::npos) break;
|
||||
start = comma + 1;
|
||||
}
|
||||
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
|
||||
}
|
||||
const char* home = std::getenv("HOME");
|
||||
if (home) apply_toml(c, std::string(home) + "/.config/dd-fg/conf.toml");
|
||||
if (const char* p = std::getenv("DD_FG_CONF")) apply_toml(c, p);
|
||||
c.sanitize();
|
||||
return c;
|
||||
}
|
||||
|
||||
struct SynthUBO {
|
||||
float flowScale, alpha, beta, lambda, epsilon, photoScale, pad0, pad1;
|
||||
float hudX0, hudY0, hudX1, hudY1;
|
||||
};
|
||||
struct FlowUBO { float flowScale; uint32_t level; float occlLo; float occlHi; };
|
||||
|
||||
struct GMUBO {
|
||||
float lin[4];
|
||||
float linT[4];
|
||||
float app[4];
|
||||
float appT[4];
|
||||
};
|
||||
|
||||
struct FlowRegUBO {
|
||||
float dt;
|
||||
float lambda;
|
||||
float edgeAlpha;
|
||||
float epsTV;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "framegen.hpp"
|
||||
#include "vk_dispatch.hpp"
|
||||
|
||||
struct VkTable;
|
||||
|
||||
namespace ddfg {
|
||||
|
||||
constexpr uint32_t kMaxGenerations = 3;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
Engine();
|
||||
~Engine();
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
|
||||
bool init(const VkTable& table, VkPhysicalDevice phys, VkDevice dev,
|
||||
uint32_t queueFamily, VkQueue queue);
|
||||
bool valid() const { return fg_.valid() && !unavailable_; }
|
||||
bool unavailable() const { return unavailable_; }
|
||||
|
||||
void configure(uint32_t multiplier, int model, int perfPreset, float flowScale);
|
||||
|
||||
bool prepare(uint32_t width, uint32_t height, VkFormat format);
|
||||
|
||||
uint32_t plan(uint32_t capacity) const;
|
||||
|
||||
void process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32_t height,
|
||||
uint32_t generations);
|
||||
|
||||
void generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t count,
|
||||
VkImage targetImage, VkImageView targetView,
|
||||
uint32_t width, uint32_t height);
|
||||
|
||||
void reset();
|
||||
|
||||
private:
|
||||
struct Slot { VkImage img = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE; bool valid = false; };
|
||||
bool makeSlot(Slot& s, uint32_t w, uint32_t h, VkFormat fmt);
|
||||
void destroySlot(Slot& s);
|
||||
void destroyRing();
|
||||
uint32_t memType(uint32_t bits, VkMemoryPropertyFlags props) const;
|
||||
|
||||
DeviceDispatch dd_{};
|
||||
InstanceDispatch id_{};
|
||||
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
|
||||
VkDevice dev_ = VK_NULL_HANDLE;
|
||||
uint32_t qf_ = 0;
|
||||
VkQueue queue_ = VK_NULL_HANDLE;
|
||||
VkPhysicalDeviceMemoryProperties memProps_{};
|
||||
|
||||
FrameGen fg_;
|
||||
Config cfg_;
|
||||
bool unavailable_ = false;
|
||||
|
||||
Slot ring_[2];
|
||||
int curr_ = 0;
|
||||
uint32_t history_ = 0;
|
||||
uint32_t gmSlot_ = 0;
|
||||
uint32_t builtW_ = 0, builtH_ = 0;
|
||||
VkFormat builtFmt_ = VK_FORMAT_UNDEFINED;
|
||||
uint64_t frames_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
#include "vk_dispatch.hpp"
|
||||
struct VkTable;
|
||||
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id);
|
||||
@@ -1,112 +0,0 @@
|
||||
#pragma once
|
||||
#include "vk_dispatch.hpp"
|
||||
#include "config.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace ddfg {
|
||||
|
||||
class FrameGen {
|
||||
public:
|
||||
bool init(const DeviceDispatch* dd, const InstanceDispatch* id,
|
||||
VkPhysicalDevice phys, VkDevice dev, uint32_t queueFamily, VkQueue queue);
|
||||
void configure(const Config& c);
|
||||
bool onResize(VkExtent2D extent, VkFormat colorFormat, bool force = false);
|
||||
void record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
|
||||
VkImageView outView, float alpha, uint32_t gmSlot = 0,
|
||||
uint32_t synthSlot = 0, bool reuseFlow = false);
|
||||
void destroy();
|
||||
bool valid() const { return device_ != VK_NULL_HANDLE && ready_; }
|
||||
const Config& config() const { return cfg_; }
|
||||
|
||||
struct Img { VkImage image = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE; VkExtent2D extent{}; VkFormat fmt{}; };
|
||||
struct Pipe { VkPipeline pipeline = VK_NULL_HANDLE; VkPipelineLayout layout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; VkShaderModule module = VK_NULL_HANDLE; };
|
||||
|
||||
|
||||
|
||||
static constexpr int kGmSlots = 3;
|
||||
static constexpr int kSynthSlots = 3;
|
||||
static constexpr int kGmLevel = 3;
|
||||
static constexpr int kGmThreads = 512;
|
||||
static constexpr int kGmStride = 32;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static constexpr int kFlowFinestDefault = 2;
|
||||
|
||||
|
||||
|
||||
static int flowFinestForPreset(int preset) {
|
||||
switch (preset) {
|
||||
case 0: return 1;
|
||||
case 2: return 3;
|
||||
case 1: default: return kFlowFinestDefault;
|
||||
}
|
||||
}
|
||||
int flowFinest() const { return flowFinest_; }
|
||||
|
||||
private:
|
||||
uint32_t findMemType(uint32_t bits, VkMemoryPropertyFlags props) const;
|
||||
bool makeImage(Img& out, VkExtent2D ext, VkFormat fmt, VkImageUsageFlags usage);
|
||||
void destroyImage(Img& i);
|
||||
bool makeGmBuffers();
|
||||
|
||||
|
||||
|
||||
bool updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent);
|
||||
bool makePipe(Pipe& p, int shaderId,
|
||||
const std::vector<VkDescriptorType>& bindings);
|
||||
void destroyPipe(Pipe& p);
|
||||
void barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkImageLayout to);
|
||||
|
||||
const DeviceDispatch* dd_ = nullptr;
|
||||
const InstanceDispatch* id_ = nullptr;
|
||||
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
|
||||
VkDevice device_ = VK_NULL_HANDLE;
|
||||
VkQueue queue_ = VK_NULL_HANDLE;
|
||||
uint32_t queueFamily_ = 0;
|
||||
VkPhysicalDeviceMemoryProperties memProps_{};
|
||||
VkSampler linear_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descPool_ = VK_NULL_HANDLE;
|
||||
|
||||
Config cfg_;
|
||||
VkExtent2D extent_{};
|
||||
bool ready_ = false;
|
||||
int flowFinest_ = kFlowFinestDefault;
|
||||
|
||||
Pipe pLuma_, pDown_, pFlow_, pFlowM4_, pExpand_, pExpandM4_, pSynth_;
|
||||
Pipe pGmReduce_, pGmPrewarp_;
|
||||
Pipe pFlowReg_;
|
||||
|
||||
static const int kLevels = 7;
|
||||
std::vector<Img> pyrA_, pyrB_;
|
||||
std::vector<Img> pyrAs_;
|
||||
std::vector<Img> flowLvl_;
|
||||
Img flowExpA_, flowExpB_;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Img flowRegA_, flowRegB_;
|
||||
|
||||
VkBuffer gmSsbo_[kGmSlots] {};
|
||||
VkDeviceMemory gmSsboMem_[kGmSlots]{};
|
||||
void* gmSsboPtr_[kGmSlots]{};
|
||||
bool gmBuffersReady_ = false;
|
||||
double gmRun_[6] {};
|
||||
double gmLinAt_[kGmSlots][6] {};
|
||||
bool gmSlotValid_[kGmSlots]{};
|
||||
bool gmHaveEst_ = false;
|
||||
int gmStableCount_ = 0;
|
||||
bool gmEngaged_ = false;
|
||||
float gmLastTransPx_ = 0.0f;
|
||||
unsigned long long gmFrames_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#define DDFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "dd-fg", __VA_ARGS__)
|
||||
#define DDFG_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "dd-fg", __VA_ARGS__)
|
||||
#else
|
||||
#include <cstdio>
|
||||
#define DDFG_LOGI(...) do { std::fprintf(stderr, "[dd-fg] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#define DDFG_LOGE(...) do { std::fprintf(stderr, "[dd-fg][E] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#endif
|
||||
|
||||
#define DDFG_LOGD(cond, ...) do { if (cond) DDFG_LOGI(__VA_ARGS__); } while(0)
|
||||
@@ -1,535 +0,0 @@
|
||||
|
||||
namespace {
|
||||
struct ScratchState {
|
||||
VkBuffer synthUbo[ddfg::FrameGen::kSynthSlots]{};
|
||||
VkDeviceMemory synthUboMem[ddfg::FrameGen::kSynthSlots]{};
|
||||
void* synthUboPtr[ddfg::FrameGen::kSynthSlots]{};
|
||||
std::vector<VkBuffer> flowUbo; std::vector<VkDeviceMemory> flowUboMem; std::vector<void*> flowUboPtr;
|
||||
VkBuffer expandUbo = VK_NULL_HANDLE; VkDeviceMemory expandUboMem = VK_NULL_HANDLE; void* expandUboPtr = nullptr;
|
||||
VkBuffer flowRegUbo = VK_NULL_HANDLE; VkDeviceMemory flowRegUboMem = VK_NULL_HANDLE; void* flowRegUboPtr = nullptr;
|
||||
VkBuffer gmUbo[ddfg::FrameGen::kGmSlots]{};
|
||||
VkDeviceMemory gmUboMem[ddfg::FrameGen::kGmSlots]{};
|
||||
void* gmUboPtr[ddfg::FrameGen::kGmSlots]{};
|
||||
VkDescriptorSet sLumaCurr = VK_NULL_HANDLE, sLumaPrev = VK_NULL_HANDLE;
|
||||
std::vector<VkDescriptorSet> sDownA, sDownB, sFlow;
|
||||
VkDescriptorSet sExpand[ddfg::FrameGen::kGmSlots]{};
|
||||
|
||||
|
||||
|
||||
VkDescriptorSet sFlowReg0 = VK_NULL_HANDLE, sFlowRegAB = VK_NULL_HANDLE, sFlowRegBA = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sSynth[ddfg::FrameGen::kSynthSlots]{};
|
||||
VkDescriptorSet sGmReduce[ddfg::FrameGen::kGmSlots]{};
|
||||
std::vector<VkDescriptorSet> sGmPrewarp[ddfg::FrameGen::kGmSlots];
|
||||
VkExtent2D builtFor{0,0};
|
||||
bool built = false;
|
||||
bool flowInited = false;
|
||||
bool flowReady = false;
|
||||
};
|
||||
ScratchState g_scratch;
|
||||
}
|
||||
|
||||
static bool makeUbo(const ddfg::DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp,
|
||||
VkDeviceSize size, VkBuffer& buf, VkDeviceMemory& mem, void*& ptr) {
|
||||
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bi.size = size; bi.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (dd->CreateBuffer(dev, &bi, nullptr, &buf) != VK_SUCCESS) return false;
|
||||
VkMemoryRequirements mr; dd->GetBufferMemoryRequirements(dev, buf, &mr);
|
||||
uint32_t idx = 0; VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; ++i)
|
||||
if ((mr.memoryTypeBits & (1u<<i)) && (mp.memoryTypes[i].propertyFlags & want) == want) { idx = i; break; }
|
||||
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; ai.allocationSize = mr.size; ai.memoryTypeIndex = idx;
|
||||
if (dd->AllocateMemory(dev, &ai, nullptr, &mem) != VK_SUCCESS) return false;
|
||||
if (dd->BindBufferMemory(dev, buf, mem, 0) != VK_SUCCESS) return false;
|
||||
return dd->MapMemory(dev, mem, 0, size, 0, &ptr) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkDescriptorSet allocSet(const ddfg::DeviceDispatch* dd, VkDevice dev,
|
||||
VkDescriptorPool pool, VkDescriptorSetLayout layout) {
|
||||
VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
ai.descriptorPool = pool; ai.descriptorSetCount = 1; ai.pSetLayouts = &layout;
|
||||
VkDescriptorSet s = VK_NULL_HANDLE; dd->AllocateDescriptorSets(dev, &ai, &s); return s;
|
||||
}
|
||||
|
||||
static void wImg(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkDescriptorType type, VkImageView view, VkSampler samp, VkImageLayout layout) {
|
||||
VkDescriptorImageInfo ii{}; ii.imageView = view; ii.sampler = samp; ii.imageLayout = layout;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1; w.descriptorType = type; w.pImageInfo = ⅈ
|
||||
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
|
||||
}
|
||||
static void wUbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkBuffer buf, VkDeviceSize range) {
|
||||
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1;
|
||||
w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; w.pBufferInfo = &bi;
|
||||
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
|
||||
}
|
||||
static void wSsbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkBuffer buf, VkDeviceSize range) {
|
||||
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1;
|
||||
w.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; w.pBufferInfo = &bi;
|
||||
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
|
||||
}
|
||||
|
||||
static bool cholSolve6(double A[6][6], const double b[6], double x[6]) {
|
||||
double L[6][6] = {{0}};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
for (int j = 0; j <= i; ++j) {
|
||||
double s = A[i][j];
|
||||
for (int k = 0; k < j; ++k) s -= L[i][k] * L[j][k];
|
||||
if (i == j) {
|
||||
if (s <= 1e-20) return false;
|
||||
L[i][j] = std::sqrt(s);
|
||||
} else {
|
||||
L[i][j] = s / L[j][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
double y[6];
|
||||
for (int i = 0; i < 6; ++i) { double s = b[i]; for (int k = 0; k < i; ++k) s -= L[i][k]*y[k]; y[i] = s / L[i][i]; }
|
||||
for (int i = 5; i >= 0; --i) { double s = y[i]; for (int k = i+1; k < 6; ++k) s -= L[k][i]*x[k]; x[i] = s / L[i][i]; }
|
||||
return true;
|
||||
}
|
||||
static void affFromParams(const double p[6], double M[3][3]) {
|
||||
M[0][0]=1.0+p[0]; M[0][1]=p[2]; M[0][2]=p[4];
|
||||
M[1][0]=p[1]; M[1][1]=1.0+p[3]; M[1][2]=p[5];
|
||||
M[2][0]=0.0; M[2][1]=0.0; M[2][2]=1.0;
|
||||
}
|
||||
static void paramsFromAff(const double M[3][3], double p[6]) {
|
||||
p[0]=M[0][0]-1.0; p[2]=M[0][1]; p[4]=M[0][2];
|
||||
p[1]=M[1][0]; p[3]=M[1][1]-1.0; p[5]=M[1][2];
|
||||
}
|
||||
static bool invAff(const double M[3][3], double out[3][3]) {
|
||||
double a=M[0][0], b=M[0][1], tx=M[0][2], c=M[1][0], d=M[1][1], ty=M[1][2];
|
||||
double det = a*d - b*c;
|
||||
if (std::fabs(det) < 1e-12) return false;
|
||||
double id = 1.0/det;
|
||||
out[0][0]= d*id; out[0][1]= -b*id; out[0][2]= (b*ty - d*tx)*id;
|
||||
out[1][0]= -c*id; out[1][1]= a*id; out[1][2]= (c*tx - a*ty)*id;
|
||||
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
|
||||
return true;
|
||||
}
|
||||
static void mulAff(const double A[3][3], const double B[3][3], double out[3][3]) {
|
||||
for (int i = 0; i < 2; ++i)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
out[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + A[i][2]*B[2][j];
|
||||
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
|
||||
}
|
||||
|
||||
namespace ddfg {
|
||||
|
||||
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
|
||||
VkSampler samp, VkExtent2D ext,
|
||||
std::vector<FrameGen::Img>& pyrA, std::vector<FrameGen::Img>& pyrB,
|
||||
std::vector<FrameGen::Img>& pyrAs,
|
||||
std::vector<FrameGen::Img>& flowLvl, FrameGen::Img& expA, FrameGen::Img& expB,
|
||||
const FrameGen::Pipe& pLuma, const FrameGen::Pipe& pDown,
|
||||
const FrameGen::Pipe& pFlow, const FrameGen::Pipe& pExpand,
|
||||
const FrameGen::Pipe& pSynth, const FrameGen::Pipe& pGmReduce,
|
||||
const FrameGen::Pipe& pGmPrewarp, const FrameGen::Pipe& pFlowReg,
|
||||
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
|
||||
int kLevels, const Config& cfg);
|
||||
|
||||
static void destroyScratch(const DeviceDispatch* dd, VkDevice dev, VkDescriptorPool pool) {
|
||||
auto freeUbo = [&](VkBuffer& b, VkDeviceMemory& m, void*& p) {
|
||||
if (m) dd->UnmapMemory(dev, m);
|
||||
if (b) dd->DestroyBuffer(dev, b, nullptr);
|
||||
if (m) dd->FreeMemory(dev, m, nullptr);
|
||||
b = VK_NULL_HANDLE; m = VK_NULL_HANDLE; p = nullptr;
|
||||
};
|
||||
auto& S = g_scratch;
|
||||
for (int s = 0; s < FrameGen::kSynthSlots; ++s) freeUbo(S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
|
||||
freeUbo(S.expandUbo, S.expandUboMem, S.expandUboPtr);
|
||||
freeUbo(S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
|
||||
for (size_t i = 0; i < S.flowUbo.size(); ++i) freeUbo(S.flowUbo[i], S.flowUboMem[i], S.flowUboPtr[i]);
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) freeUbo(S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
|
||||
if (pool && dd->ResetDescriptorPool) dd->ResetDescriptorPool(dev, pool, 0);
|
||||
S = ScratchState{};
|
||||
}
|
||||
|
||||
void FrameGen::configure(const Config& c) {
|
||||
cfg_ = c; cfg_.sanitize();
|
||||
int want = flowFinestForPreset(cfg_.perfPreset);
|
||||
const int maxFinest = kLevels - 2;
|
||||
if (want > maxFinest) {
|
||||
DDFG_LOGE("perf-preset=%d wants kFlowFinest=%d but only %d pyramid levels exist "
|
||||
"(need >=2 solved) - clamping to %d", cfg_.perfPreset, want, kLevels, maxFinest);
|
||||
want = maxFinest;
|
||||
}
|
||||
if (want < 1) want = 1;
|
||||
|
||||
const int prevFinest = flowFinest_;
|
||||
flowFinest_ = want;
|
||||
const bool live = (flowFinest_ != prevFinest) && ready_;
|
||||
DDFG_LOGI("perf-preset=%d -> kFlowFinest=%d (was %d) live-rebuild=%d ready=%d",
|
||||
cfg_.perfPreset, flowFinest_, prevFinest, live ? 1 : 0, ready_ ? 1 : 0);
|
||||
|
||||
if (!live) return;
|
||||
|
||||
|
||||
|
||||
|
||||
dd_->DeviceWaitIdle(device_);
|
||||
destroyScratch(dd_, device_, descPool_);
|
||||
if (!onResize(extent_, VK_FORMAT_UNDEFINED, true))
|
||||
DDFG_LOGE("perf-preset live rebuild: onResize FAILED at %ux%u - FG disabled until next resize",
|
||||
extent_.width, extent_.height);
|
||||
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
|
||||
for (int s = 0; s < kGmSlots; ++s) { gmSlotValid_[s] = false; for (int i = 0; i < 6; ++i) gmLinAt_[s][i] = 0.0; }
|
||||
gmHaveEst_ = false; gmEngaged_ = false; gmStableCount_ = 0; gmLastTransPx_ = 0.0f;
|
||||
DDFG_LOGI("perf-preset live rebuild DONE (kFlowFinest=%d, flow predictor reset)", flowFinest_);
|
||||
}
|
||||
|
||||
bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
|
||||
++gmFrames_;
|
||||
if (!gmBuffersReady_ || cfg_.gmMode == 2 ) {
|
||||
|
||||
gmEngaged_ = false; gmStableCount_ = 0;
|
||||
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
|
||||
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
|
||||
gmLastTransPx_ = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool good = false;
|
||||
if (gmSlotValid_[gmSlot]) {
|
||||
const float* pt = (const float*)gmSsboPtr_[gmSlot];
|
||||
double sums[32]; for (int k = 0; k < 32; ++k) sums[k] = 0.0;
|
||||
for (int t = 0; t < kGmThreads; ++t) {
|
||||
const float* row = pt + (size_t)t * kGmStride;
|
||||
for (int k = 0; k < kGmStride; ++k) sums[k] += (double)row[k];
|
||||
}
|
||||
double coverage = sums[27];
|
||||
double lvlpx = double((fullExtent.width >> kGmLevel) * (fullExtent.height >> kGmLevel));
|
||||
double minCov = 0.03 * (lvlpx > 0 ? lvlpx : 1.0);
|
||||
if (coverage > minCov && coverage > 64.0) {
|
||||
double H[6][6]; int idx = 0;
|
||||
for (int r = 0; r < 6; ++r) for (int c = r; c < 6; ++c) { H[r][c] = H[c][r] = sums[idx]; ++idx; }
|
||||
double b[6]; for (int i = 0; i < 6; ++i) b[i] = sums[21 + i];
|
||||
for (int i = 0; i < 6; ++i) H[i][i] += 1e-3 * H[i][i] + 1e-9;
|
||||
double dp[6];
|
||||
if (cholSolve6(H, b, dp)) {
|
||||
bool bounded = std::fabs(dp[0]) < 0.5 && std::fabs(dp[1]) < 0.5 &&
|
||||
std::fabs(dp[2]) < 0.5 && std::fabs(dp[3]) < 0.5 &&
|
||||
std::fabs(dp[4]) < 0.25 && std::fabs(dp[5]) < 0.25;
|
||||
if (bounded) {
|
||||
double Mlin[3][3], Md[3][3], Mdi[3][3], Mnew[3][3];
|
||||
affFromParams(gmLinAt_[gmSlot], Mlin);
|
||||
affFromParams(dp, Md);
|
||||
if (invAff(Md, Mdi)) {
|
||||
mulAff(Mlin, Mdi, Mnew);
|
||||
double pnew[6]; paramsFromAff(Mnew, pnew);
|
||||
bool sane = std::fabs(pnew[0]) < 0.6 && std::fabs(pnew[1]) < 0.6 &&
|
||||
std::fabs(pnew[2]) < 0.6 && std::fabs(pnew[3]) < 0.6 &&
|
||||
std::fabs(pnew[4]) < 0.5 && std::fabs(pnew[5]) < 0.5;
|
||||
if (sane) { for (int i = 0; i < 6; ++i) gmRun_[i] = pnew[i]; good = true; gmHaveEst_ = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (good) ++gmStableCount_; else gmStableCount_ = 0;
|
||||
|
||||
switch (cfg_.gmMode) {
|
||||
case 1: gmEngaged_ = gmHaveEst_; break;
|
||||
case 2: gmEngaged_ = false; break;
|
||||
default: gmEngaged_ = (gmStableCount_ >= 3); break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
|
||||
gmSlotValid_[gmSlot] = true;
|
||||
|
||||
gmLastTransPx_ = (float)std::sqrt(
|
||||
(gmRun_[4]*fullExtent.width) * (gmRun_[4]*fullExtent.width) +
|
||||
(gmRun_[5]*fullExtent.height) * (gmRun_[5]*fullExtent.height));
|
||||
return gmEngaged_;
|
||||
}
|
||||
|
||||
void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
|
||||
VkImageView outView, float alpha, uint32_t gmSlot, uint32_t synthSlot, bool reuseFlow) {
|
||||
if (!valid()) return;
|
||||
if (gmSlot >= (uint32_t)kGmSlots) gmSlot = 0;
|
||||
if (synthSlot >= (uint32_t)kSynthSlots) synthSlot = 0;
|
||||
const bool m4 = (cfg_.model == 4);
|
||||
const Pipe& flowP = m4 ? pFlowM4_ : pFlow_;
|
||||
const Pipe& expandP = m4 ? pExpandM4_ : pExpand_;
|
||||
|
||||
if (!g_scratch.built || g_scratch.builtFor.width != extent_.width || g_scratch.builtFor.height != extent_.height) {
|
||||
ensureScratch(this, dd_, device_, memProps_, descPool_, linear_, extent_,
|
||||
pyrA_, pyrB_, pyrAs_, flowLvl_, flowExpA_, flowExpB_,
|
||||
pLuma_, pDown_, flowP, expandP, pSynth_, pGmReduce_, pGmPrewarp_,
|
||||
pFlowReg_, flowRegA_, flowRegB_, gmSsbo_, kLevels, cfg_);
|
||||
}
|
||||
|
||||
if (reuseFlow && !g_scratch.flowReady) return;
|
||||
if (!reuseFlow) {
|
||||
bool gmEngaged = updateGlobalMotion(gmSlot, extent_);
|
||||
if (g_scratch.gmUboPtr[gmSlot]) {
|
||||
GMUBO g{};
|
||||
g.lin[0]=(float)gmRun_[0]; g.lin[1]=(float)gmRun_[1]; g.lin[2]=(float)gmRun_[2]; g.lin[3]=(float)gmRun_[3];
|
||||
g.linT[0]=(float)gmRun_[4]; g.linT[1]=(float)gmRun_[5]; g.linT[2]=gmEngaged?1.0f:0.0f; g.linT[3]=0.0f;
|
||||
if (gmEngaged) {
|
||||
g.app[0]=(float)gmRun_[0]; g.app[1]=(float)gmRun_[1]; g.app[2]=(float)gmRun_[2]; g.app[3]=(float)gmRun_[3];
|
||||
g.appT[0]=(float)gmRun_[4]; g.appT[1]=(float)gmRun_[5];
|
||||
}
|
||||
std::memcpy(g_scratch.gmUboPtr[gmSlot], &g, sizeof(g));
|
||||
}
|
||||
{
|
||||
static int lastEng = -1;
|
||||
if (gmEngaged != (lastEng == 1) || (gmFrames_ % 300ull) == 0) {
|
||||
DDFG_LOGI("C1 global-motion: engaged=%d mode=%d stable=%d transPx=%.1f (model=%d)",
|
||||
gmEngaged ? 1 : 0, cfg_.gmMode, gmStableCount_, gmLastTransPx_, cfg_.model);
|
||||
lastEng = gmEngaged ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
wImg(dd_, device_, g_scratch.sLumaCurr, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sLumaPrev, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
SynthUBO su{cfg_.flowScale, alpha, cfg_.beta, cfg_.lambda, cfg_.epsilon, cfg_.photoScale, 0, 0,
|
||||
cfg_.hudX0, cfg_.hudY0, cfg_.hudX1, cfg_.hudY1};
|
||||
if (g_scratch.synthUboPtr[synthSlot]) std::memcpy(g_scratch.synthUboPtr[synthSlot], &su, sizeof(su));
|
||||
|
||||
const VkDescriptorSet synthSet = g_scratch.sSynth[synthSlot];
|
||||
wImg(dd_, device_, synthSet, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, synthSet, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, synthSet, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL);
|
||||
|
||||
auto disp = [&](const Pipe& p, VkDescriptorSet set, VkExtent2D e) {
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.layout, 0, 1, &set, 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (e.width + 15) / 16, (e.height + 15) / 16, 1);
|
||||
};
|
||||
auto bar = [&](FrameGen::Img& i){ barrier(cmd, i.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); };
|
||||
|
||||
if (!reuseFlow) {
|
||||
for (auto& i : pyrA_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (auto& i : pyrB_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (int l = flowFinest_; l < kLevels; ++l) barrier(cmd, pyrAs_[l].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
{
|
||||
VkClearColorValue zero{};
|
||||
VkImageSubresourceRange rng{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
for (int l = flowFinest_; l < kLevels; ++l) {
|
||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
b.srcAccessMask = 0; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
b.image = flowLvl_[l].image; b.subresourceRange = rng;
|
||||
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
dd_->CmdClearColorImage(cmd, flowLvl_[l].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &rng);
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
}
|
||||
}
|
||||
|
||||
disp(pLuma_, g_scratch.sLumaCurr, levelExtent(extent_, 0));
|
||||
disp(pLuma_, g_scratch.sLumaPrev, levelExtent(extent_, 0));
|
||||
bar(pyrA_[0]); bar(pyrB_[0]);
|
||||
for (int l = 1; l < kLevels; ++l) {
|
||||
disp(pDown_, g_scratch.sDownA[l - 1], levelExtent(extent_, l));
|
||||
disp(pDown_, g_scratch.sDownB[l - 1], levelExtent(extent_, l));
|
||||
bar(pyrA_[l]); bar(pyrB_[l]);
|
||||
}
|
||||
if (gmBuffersReady_ && g_scratch.sGmReduce[gmSlot]) {
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.layout, 0, 1, &g_scratch.sGmReduce[gmSlot], 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (uint32_t)(kGmThreads / 64), 1, 1);
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; mb.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
for (int l = flowFinest_; l < kLevels; ++l) {
|
||||
disp(pGmPrewarp_, g_scratch.sGmPrewarp[gmSlot][l], levelExtent(extent_, l));
|
||||
bar(pyrAs_[l]);
|
||||
}
|
||||
for (int l = kLevels - 1; l >= flowFinest_; --l) {
|
||||
disp(flowP, g_scratch.sFlow[l], levelExtent(extent_, l));
|
||||
bar(flowLvl_[l]);
|
||||
}
|
||||
|
||||
const bool frEngaged = (cfg_.frMode != 2) && (cfg_.frIters > 0);
|
||||
if (frEngaged) {
|
||||
const int F = flowFinest_;
|
||||
const VkExtent2D fe = levelExtent(extent_, F);
|
||||
if (g_scratch.flowRegUboPtr) {
|
||||
FlowRegUBO fu{cfg_.frDt, cfg_.frLambda, cfg_.frEdge, cfg_.frEps};
|
||||
std::memcpy(g_scratch.flowRegUboPtr, &fu, sizeof(fu));
|
||||
}
|
||||
FrameGen::Img* last = nullptr;
|
||||
for (int it = 0; it < cfg_.frIters; ++it) {
|
||||
VkDescriptorSet set; FrameGen::Img* outImg;
|
||||
if (it == 0) { set = g_scratch.sFlowReg0; outImg = &flowRegA_; }
|
||||
else if (it % 2 == 1) { set = g_scratch.sFlowRegAB; outImg = &flowRegB_; }
|
||||
else { set = g_scratch.sFlowRegBA; outImg = &flowRegA_; }
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.layout, 0, 1, &set, 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (fe.width + 15) / 16, (fe.height + 15) / 16, 1);
|
||||
bar(*outImg);
|
||||
last = outImg;
|
||||
}
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
mb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
VkImageCopy region{};
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.extent = {fe.width, fe.height, 1};
|
||||
dd_->CmdCopyImage(cmd, last->image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
flowLvl_[F].image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
||||
VkMemoryBarrier mb2{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
mb2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, 1, &mb2, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
{
|
||||
static int lastFr = -1;
|
||||
int fe = frEngaged ? 1 : 0;
|
||||
if (fe != lastFr || (gmFrames_ % 300ull) == 0) {
|
||||
DDFG_LOGI("C2 flow-reg: engaged=%d mode=%d iters=%d lambda=%.2f dt=%.2f edge=%.1f eps=%.3f (model=%d)",
|
||||
fe, cfg_.frMode, cfg_.frIters, cfg_.frLambda, cfg_.frDt, cfg_.frEdge, cfg_.frEps, cfg_.model);
|
||||
lastFr = fe;
|
||||
}
|
||||
}
|
||||
|
||||
disp(expandP, g_scratch.sExpand[gmSlot], levelExtent(extent_, 0));
|
||||
bar(flowExpA_); bar(flowExpB_);
|
||||
g_scratch.flowReady = true;
|
||||
}
|
||||
disp(pSynth_, synthSet, levelExtent(extent_, 0));
|
||||
}
|
||||
|
||||
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
|
||||
VkSampler samp, VkExtent2D ext,
|
||||
std::vector<FrameGen::Img>& pyrA, std::vector<FrameGen::Img>& pyrB,
|
||||
std::vector<FrameGen::Img>& pyrAs,
|
||||
std::vector<FrameGen::Img>& flowLvl, FrameGen::Img& expA, FrameGen::Img& expB,
|
||||
const FrameGen::Pipe& pLuma, const FrameGen::Pipe& pDown,
|
||||
const FrameGen::Pipe& pFlow, const FrameGen::Pipe& pExpand,
|
||||
const FrameGen::Pipe& pSynth, const FrameGen::Pipe& pGmReduce,
|
||||
const FrameGen::Pipe& pGmPrewarp, const FrameGen::Pipe& pFlowReg,
|
||||
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
|
||||
int kLevels, const Config& cfg) {
|
||||
auto& S = g_scratch;
|
||||
S = ScratchState{};
|
||||
const auto GEN = VK_IMAGE_LAYOUT_GENERAL;
|
||||
|
||||
for (int s = 0; s < FrameGen::kSynthSlots; ++s)
|
||||
makeUbo(dd, dev, mp, sizeof(SynthUBO), S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
|
||||
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.expandUbo, S.expandUboMem, S.expandUboPtr);
|
||||
if (S.expandUboPtr) { FlowUBO u{cfg.flowScale, 0, 0.6f, 1.6f}; std::memcpy(S.expandUboPtr, &u, sizeof(u)); }
|
||||
makeUbo(dd, dev, mp, sizeof(FlowRegUBO), S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
|
||||
if (S.flowRegUboPtr) { FlowRegUBO fu{cfg.frDt, cfg.frLambda, cfg.frEdge, cfg.frEps}; std::memcpy(S.flowRegUboPtr, &fu, sizeof(fu)); }
|
||||
S.flowUbo.resize(kLevels); S.flowUboMem.resize(kLevels); S.flowUboPtr.resize(kLevels);
|
||||
for (int l = fg->flowFinest(); l < kLevels; ++l) {
|
||||
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.flowUbo[l], S.flowUboMem[l], S.flowUboPtr[l]);
|
||||
if (S.flowUboPtr[l]) { FlowUBO u{cfg.flowScale, (uint32_t)l, 0.6f, 1.6f}; std::memcpy(S.flowUboPtr[l], &u, sizeof(u)); }
|
||||
}
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
makeUbo(dd, dev, mp, sizeof(GMUBO), S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
|
||||
if (S.gmUboPtr[s]) { GMUBO g{}; std::memcpy(S.gmUboPtr[s], &g, sizeof(g)); }
|
||||
}
|
||||
|
||||
S.sLumaCurr = allocSet(dd, dev, pool, pLuma.setLayout);
|
||||
S.sLumaPrev = allocSet(dd, dev, pool, pLuma.setLayout);
|
||||
wImg(dd, dev, S.sLumaCurr, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[0].view, VK_NULL_HANDLE, GEN);
|
||||
wImg(dd, dev, S.sLumaPrev, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[0].view, VK_NULL_HANDLE, GEN);
|
||||
|
||||
S.sDownA.resize(kLevels - 1); S.sDownB.resize(kLevels - 1);
|
||||
for (int l = 1; l < kLevels; ++l) {
|
||||
S.sDownA[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
|
||||
S.sDownB[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
|
||||
wImg(dd, dev, S.sDownA[l-1], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[l-1].view, samp, GEN);
|
||||
wImg(dd, dev, S.sDownA[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[l].view, VK_NULL_HANDLE, GEN);
|
||||
wImg(dd, dev, S.sDownB[l-1], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[l-1].view, samp, GEN);
|
||||
wImg(dd, dev, S.sDownB[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[l].view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
S.sFlow.resize(kLevels);
|
||||
for (int l = fg->flowFinest(); l < kLevels; ++l) {
|
||||
S.sFlow[l] = allocSet(dd, dev, pool, pFlow.setLayout);
|
||||
int pred = (l + 1 < kLevels) ? l + 1 : l;
|
||||
wUbo(dd, dev, S.sFlow[l], 0, S.flowUbo[l], sizeof(FlowUBO));
|
||||
wImg(dd, dev, S.sFlow[l], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[l].view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlow[l], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrAs[l].view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlow[l], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[pred].view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlow[l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowLvl[l].view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
if (gmSsbo) {
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
if (!gmSsbo[s]) continue;
|
||||
S.sGmReduce[s] = allocSet(dd, dev, pool, pGmReduce.setLayout);
|
||||
wUbo (dd, dev, S.sGmReduce[s], 1, S.gmUbo[s], sizeof(GMUBO));
|
||||
wSsbo(dd, dev, S.sGmReduce[s], 16, gmSsbo[s], VK_WHOLE_SIZE);
|
||||
wImg (dd, dev, S.sGmReduce[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[FrameGen::kGmLevel].view, samp, GEN);
|
||||
wImg (dd, dev, S.sGmReduce[s], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[FrameGen::kGmLevel].view, samp, GEN);
|
||||
}
|
||||
}
|
||||
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
S.sGmPrewarp[s].assign(kLevels, VK_NULL_HANDLE);
|
||||
for (int l = fg->flowFinest(); l < kLevels; ++l) {
|
||||
S.sGmPrewarp[s][l] = allocSet(dd, dev, pool, pGmPrewarp.setLayout);
|
||||
wUbo(dd, dev, S.sGmPrewarp[s][l], 1, S.gmUbo[s], sizeof(GMUBO));
|
||||
wImg(dd, dev, S.sGmPrewarp[s][l], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[l].view, samp, GEN);
|
||||
wImg(dd, dev, S.sGmPrewarp[s][l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrAs[l].view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
}
|
||||
|
||||
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
|
||||
S.sExpand[s] = allocSet(dd, dev, pool, pExpand.setLayout);
|
||||
wUbo(dd, dev, S.sExpand[s], 0, S.expandUbo, sizeof(FlowUBO));
|
||||
wUbo(dd, dev, S.sExpand[s], 1, S.gmUbo[s], sizeof(GMUBO));
|
||||
wImg(dd, dev, S.sExpand[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[fg->flowFinest()].view, samp, GEN);
|
||||
wImg(dd, dev, S.sExpand[s], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expA.view, VK_NULL_HANDLE, GEN);
|
||||
wImg(dd, dev, S.sExpand[s], 49, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expB.view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
{
|
||||
const int F = fg->flowFinest();
|
||||
S.sFlowReg0 = allocSet(dd, dev, pool, pFlowReg.setLayout);
|
||||
S.sFlowRegAB = allocSet(dd, dev, pool, pFlowReg.setLayout);
|
||||
S.sFlowRegBA = allocSet(dd, dev, pool, pFlowReg.setLayout);
|
||||
VkDescriptorSet all3[3] = {S.sFlowReg0, S.sFlowRegAB, S.sFlowRegBA};
|
||||
for (VkDescriptorSet s : all3) {
|
||||
wUbo(dd, dev, s, 0, S.flowRegUbo, sizeof(FlowRegUBO));
|
||||
wImg(dd, dev, s, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
|
||||
wImg(dd, dev, s, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[F].view, samp, GEN);
|
||||
}
|
||||
wImg(dd, dev, S.sFlowReg0, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlowReg0, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
|
||||
wImg(dd, dev, S.sFlowRegAB, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegA.view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlowRegAB, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegB.view, VK_NULL_HANDLE, GEN);
|
||||
wImg(dd, dev, S.sFlowRegBA, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegB.view, samp, GEN);
|
||||
wImg(dd, dev, S.sFlowRegBA, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
for (int s = 0; s < FrameGen::kSynthSlots; ++s) {
|
||||
S.sSynth[s] = allocSet(dd, dev, pool, pSynth.setLayout);
|
||||
wUbo(dd, dev, S.sSynth[s], 0, S.synthUbo[s], sizeof(SynthUBO));
|
||||
wImg(dd, dev, S.sSynth[s], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expA.view, samp, GEN);
|
||||
wImg(dd, dev, S.sSynth[s], 35, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expB.view, samp, GEN);
|
||||
}
|
||||
|
||||
S.builtFor = ext; S.built = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_alpha.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_beta.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_chain.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_common.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#include "lsfg_delta.hpp"
|
||||
#include "lsfg_shaders.hpp"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk. Ported to WinNative and DroidDeck by @maxjivi05; only the
|
||||
// Vulkan dispatch differs here (see lsfg_vkd.h).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
// LSFG from Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk (PancakeTAS). Ported to WinNative and DroidDeck by @maxjivi05. GPL-3.0-or-later.
|
||||
// See lsfg_dll.h. PE resource walk + on-device SPIR-V cache for the Lossless
|
||||
// Scaling frame-generation chain.
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk (PancakeTAS). Ported to WinNative (lsfg_dll.c) and DroidDeck
|
||||
// by @maxjivi05. GPL-3.0-or-later.
|
||||
|
||||
#include "lsfg_dll.h"
|
||||
#include "lsfg_dxbc.h"
|
||||
@@ -20,9 +25,9 @@
|
||||
namespace lsfg {
|
||||
namespace {
|
||||
|
||||
|
||||
constexpr uint16_t kDosMagic = 0x5A4Du;
|
||||
constexpr uint32_t kPeSignature = 0x00004550u;
|
||||
// ---- PE constants ---------------------------------------------------------
|
||||
constexpr uint16_t kDosMagic = 0x5A4Du; // "MZ"
|
||||
constexpr uint32_t kPeSignature = 0x00004550u; // "PE\0\0"
|
||||
constexpr uint16_t kPe32Magic = 0x010Bu;
|
||||
constexpr uint16_t kPe32PlusMagic = 0x020Bu;
|
||||
|
||||
@@ -42,6 +47,7 @@ constexpr size_t kResourceEntrySize = 8u;
|
||||
constexpr uint32_t kResourceSubdirectoryBit = 0x80000000u;
|
||||
constexpr uint32_t kResourceTypeRcData = 10u;
|
||||
|
||||
// ---- SPIR-V constants -----------------------------------------------------
|
||||
constexpr uint32_t kSpirvMagic = 0x07230203u;
|
||||
constexpr size_t kSpirvHeaderWords = 5u;
|
||||
constexpr uint32_t kSpirvWordCountShift = 16u;
|
||||
@@ -58,7 +64,7 @@ constexpr uint32_t kVariantFp32Offset = 98u;
|
||||
constexpr uint32_t kMaxResourceId = 512u;
|
||||
constexpr size_t kMaxSpirvWords = 16u * 1024u * 1024u;
|
||||
|
||||
constexpr uint32_t kCacheMagic = 0x4C534642u;
|
||||
constexpr uint32_t kCacheMagic = 0x4C534642u; // "BFSL" little-endian
|
||||
constexpr uint32_t kCacheVersion = 1u;
|
||||
|
||||
struct CacheHeader {
|
||||
@@ -77,6 +83,7 @@ struct PeSection {
|
||||
uint32_t rawSize = 0;
|
||||
};
|
||||
|
||||
// Read-only mapping of the DLL. Closes itself; the DLL is never executed.
|
||||
class PeImage {
|
||||
public:
|
||||
~PeImage() { close(); }
|
||||
@@ -161,6 +168,7 @@ private:
|
||||
std::vector<PeSection> sections_;
|
||||
};
|
||||
|
||||
// RCDATA id -> blob, for the ids we care about.
|
||||
struct ResourceTable {
|
||||
const uint8_t* data[kMaxResourceId] = {};
|
||||
uint32_t size[kMaxResourceId] = {};
|
||||
@@ -209,6 +217,7 @@ bool resourceReadLeaf(const PeImage& img, size_t leafOffset,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Walk type -> name -> language and keep the first language of every RCDATA id.
|
||||
bool collectRcData(const PeImage& img, size_t resourceBase, ResourceTable& out) {
|
||||
size_t typeTotal = 0;
|
||||
if (!resourceEntryCount(img, resourceBase, typeTotal)) return false;
|
||||
@@ -247,7 +256,7 @@ bool collectRcData(const PeImage& img, size_t resourceBase, ResourceTable& out)
|
||||
if (!resourceReadLeaf(img, resourceBase + langOffset, blob, blobSize)) continue;
|
||||
out.data[nameId] = blob;
|
||||
out.size[nameId] = blobSize;
|
||||
break;
|
||||
break; // first language wins
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,6 +289,7 @@ DllStatus parseResources(PeImage& img, ResourceTable& table) {
|
||||
return DllStatus::Ok;
|
||||
}
|
||||
|
||||
// ---- SPIR-V adoption (precompiled-variant path) ---------------------------
|
||||
|
||||
bool isSpirvModule(const uint8_t* blob, uint32_t size) {
|
||||
if (!blob || size < kSpirvHeaderWords * sizeof(uint32_t)) return false;
|
||||
@@ -305,6 +315,9 @@ uint32_t lookupDescriptorSet(const std::vector<uint32_t>& w, uint32_t targetId)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Precompiled blobs are renumbered into set/binding order - that is the
|
||||
// convention they were built with. The DXBC path uses encounter order instead
|
||||
// (see lsfg_dxbc), which is what DXVK's output pairs with. Do not merge them.
|
||||
bool renumberBindingsSetOrder(std::vector<uint32_t>& w) {
|
||||
struct Slot { uint32_t set; uint32_t binding; size_t literalOffset; };
|
||||
std::vector<Slot> slots;
|
||||
@@ -386,6 +399,8 @@ bool writeCache(const std::string& cachePath, const CacheHeader& header, const M
|
||||
if (ok) ok = fsync(fileno(f)) == 0;
|
||||
fclose(f);
|
||||
|
||||
// Temp file + rename, so a failed or interrupted build can never leave a
|
||||
// half-written cache that would later be loaded as if it were complete.
|
||||
if (!ok || rename(tempPath.c_str(), cachePath.c_str()) != 0) {
|
||||
unlink(tempPath.c_str());
|
||||
return false;
|
||||
@@ -401,8 +416,9 @@ bool readCacheHeader(const std::string& cachePath, CacheHeader& out) {
|
||||
return ok && out.magic == kCacheMagic && out.version == kCacheVersion;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// ---- public API -----------------------------------------------------------
|
||||
|
||||
const std::vector<uint32_t>& shaderIds() {
|
||||
static const std::vector<uint32_t> ids = [] {
|
||||
@@ -568,6 +584,7 @@ DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// ---- SPIR-V version lowering (Vulkan 1.1/1.2 compat) ----------------------
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -576,14 +593,14 @@ constexpr uint32_t kSpirvOpExtInstImport= 11u;
|
||||
constexpr uint32_t kSpirvOpMemoryModel = 14u;
|
||||
constexpr uint32_t kSpirvOpCapability = 17u;
|
||||
|
||||
|
||||
|
||||
constexpr uint32_t kCapVulkanMemoryModel = 5345u;
|
||||
constexpr uint32_t kCapVulkanMemoryModelDeviceScope = 5346u;
|
||||
constexpr uint32_t kCapDemoteToHelperInvocation = 5379u;
|
||||
// Capabilities that are core in the version the translator emits but not in
|
||||
// the versions we lower to.
|
||||
constexpr uint32_t kCapVulkanMemoryModel = 5345u; // core from 1.5
|
||||
constexpr uint32_t kCapVulkanMemoryModelDeviceScope = 5346u; // core from 1.5
|
||||
constexpr uint32_t kCapDemoteToHelperInvocation = 5379u; // core from 1.6
|
||||
|
||||
std::vector<uint32_t> encodeOpExtension(const char* name) {
|
||||
const size_t len = strlen(name) + 1;
|
||||
const size_t len = strlen(name) + 1; // incl. NUL
|
||||
const size_t strWords = (len + 3) / 4;
|
||||
std::vector<uint32_t> ins(1 + strWords, 0u);
|
||||
ins[0] = ((uint32_t)(1 + strWords) << kSpirvWordCountShift) | kSpirvOpExtension;
|
||||
@@ -607,13 +624,17 @@ bool hasOpExtension(const std::vector<uint32_t>& w, const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
if (w.size() < kSpirvHeaderWords || w[0] != kSpirvMagic) return false;
|
||||
if (targetVersion < kSpirv14) return false;
|
||||
if (w[1] <= targetVersion) return true;
|
||||
if (w[1] <= targetVersion) return true; // already low enough
|
||||
|
||||
// Walk the preamble first and only touch the module once it is known to
|
||||
// be lowerable: note which extension-gated capabilities are declared and
|
||||
// where the capability block ends (OpExtension must follow every
|
||||
// OpCapability and precede OpExtInstImport / OpMemoryModel).
|
||||
bool needMemoryModel = false;
|
||||
size_t insertAt = kSpirvHeaderWords;
|
||||
size_t off = kSpirvHeaderWords;
|
||||
@@ -624,6 +645,9 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
if (opcode == kSpirvOpCapability && length >= 2) {
|
||||
const uint32_t cap = w[off + 1];
|
||||
if (cap == kCapVulkanMemoryModel || cap == kCapVulkanMemoryModelDeviceScope) needMemoryModel = true;
|
||||
// Core only in 1.6, and VK_EXT_shader_demote_to_helper_invocation
|
||||
// is never enabled on the compat path: refuse rather than hand the
|
||||
// driver a module that is not legal at the lowered version.
|
||||
if (cap == kCapDemoteToHelperInvocation) return false;
|
||||
insertAt = off + length;
|
||||
} else if (opcode == kSpirvOpExtension) {
|
||||
@@ -635,6 +659,7 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
off += length;
|
||||
}
|
||||
|
||||
// 1.5 has the memory model as core; only 1.4 needs it declared.
|
||||
if (targetVersion < kSpirv15 && needMemoryModel
|
||||
&& !hasOpExtension(w, "SPV_KHR_vulkan_memory_model")) {
|
||||
const auto e = encodeOpExtension("SPV_KHR_vulkan_memory_model");
|
||||
@@ -644,4 +669,4 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace lsfg
|
||||
@@ -1,6 +1,35 @@
|
||||
// LSFG from Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk (PancakeTAS). Ported to WinNative and DroidDeck by @maxjivi05. GPL-3.0-or-later.
|
||||
|
||||
#pragma once
|
||||
// ============================================================================
|
||||
// lsfg_dll - extract the Lossless Scaling frame-generation shader chain from
|
||||
// the user's own Lossless.dll, and cache it on device as SPIR-V.
|
||||
//
|
||||
// The 25 compute shaders that make up LSFG 3.1 are NOT redistributable. They
|
||||
// ship as RCDATA resources inside Lossless.dll from the user's own paid copy
|
||||
// of Lossless Scaling (Steam app 993090). Every implementation - upstream
|
||||
// lsfg-vk, LSFG-Android, eden, WinNative - requires the user to supply their
|
||||
// own DLL and extracts the resources on device. DroidDeck does the same, and
|
||||
// bundles nothing.
|
||||
//
|
||||
// The DLL is mmap'd READ-ONLY and parsed as data. It is never loaded as a
|
||||
// library and never executed.
|
||||
//
|
||||
// Resource layout, measured against real installs:
|
||||
// * The base chain lives at RCDATA ids 255, 256 and 280-302 (25 modules),
|
||||
// and is DXBC. It has always been DXBC.
|
||||
// * Lossless Scaling MAY additionally carry precompiled SPIR-V copies at
|
||||
// base+49 (native fp16) and base+98 (native fp32). No build currently
|
||||
// downloadable from Steam carries them - public buildId 19655272 was
|
||||
// measured with zero SPIR-V anywhere in the 311 MB install - so the DXBC
|
||||
// path is the normal path and the translator is not optional. The SPIR-V
|
||||
// path is kept because it costs nothing and a future build may ship them.
|
||||
//
|
||||
// Both producers fill the same shader-id -> SPIR-V words map, so everything
|
||||
// downstream of this file is source-agnostic.
|
||||
//
|
||||
// LSFG from the work of Camille LaVey / the Eden Emulator Project, following
|
||||
// upstream lsfg-vk (PancakeTAS). Ported to WinNative (lsfg_dll.c) and DroidDeck
|
||||
// by @maxjivi05. GPL-3.0-or-later.
|
||||
// ============================================================================
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -10,37 +39,44 @@ namespace lsfg {
|
||||
|
||||
enum class DllStatus {
|
||||
Ok = 0,
|
||||
NotInstalled,
|
||||
UnreadableFile,
|
||||
NotInstalled, // no such file / cannot open
|
||||
UnreadableFile, // opens but cannot be mapped or is empty
|
||||
NotPortableExecutable,
|
||||
MissingShaders,
|
||||
TranslationFailed,
|
||||
CacheUnusable
|
||||
MissingShaders, // parses, but the base chain is not all there
|
||||
TranslationFailed, // a shader failed DXBC->SPIR-V or SPIR-V adoption
|
||||
CacheUnusable // cache could not be written or read back
|
||||
};
|
||||
|
||||
// SPIR-V version words. The translator emits 1.6; a Vulkan 1.1 device takes
|
||||
// 1.4 (via VK_KHR_spirv_1_4) and a 1.2 device 1.5. One definition for the
|
||||
// probe, the engine and the shader loader.
|
||||
constexpr uint32_t kSpirv14 = 0x00010400u;
|
||||
constexpr uint32_t kSpirv15 = 0x00010500u;
|
||||
constexpr uint32_t kSpirv16 = 0x00010600u;
|
||||
|
||||
enum class Variant {
|
||||
None = 0,
|
||||
SpirvFp16,
|
||||
SpirvFp32,
|
||||
DxbcTranslated
|
||||
SpirvFp16, // precompiled fp16 blobs at base+49
|
||||
SpirvFp32, // precompiled fp32 blobs at base+98
|
||||
DxbcTranslated // base chain, translated on device (the normal case)
|
||||
};
|
||||
|
||||
// The 25 shader ids that make up the chain.
|
||||
constexpr uint32_t kShaderMipmaps = 255u;
|
||||
constexpr uint32_t kShaderGenerate = 256u;
|
||||
constexpr uint32_t kShaderChainFirst= 280u;
|
||||
constexpr uint32_t kShaderChainLast = 302u;
|
||||
constexpr uint32_t kShaderCount = 25u;
|
||||
|
||||
// Upstream spelling for the two ids the ported chain files refer to by name.
|
||||
// Kept so those files stay byte-close to their upstream form; these alias the
|
||||
// constants above rather than being a second source of truth.
|
||||
constexpr uint32_t LSFG_SHADER_MIPMAPS = kShaderMipmaps;
|
||||
constexpr uint32_t LSFG_SHADER_GENERATE = kShaderGenerate;
|
||||
|
||||
struct Module {
|
||||
uint32_t id = 0;
|
||||
std::vector<uint32_t> words;
|
||||
std::vector<uint32_t> words; // SPIR-V
|
||||
};
|
||||
|
||||
struct ModuleSet {
|
||||
@@ -51,19 +87,41 @@ struct ModuleSet {
|
||||
bool complete() const;
|
||||
};
|
||||
|
||||
// The canonical shader-id list, in dispatch-independent order.
|
||||
const std::vector<uint32_t>& shaderIds();
|
||||
|
||||
const char* statusName(DllStatus s);
|
||||
const char* variantName(Variant v);
|
||||
|
||||
// Does this file look like a usable Lossless.dll? Checks the base chain only,
|
||||
// so a DLL without the SPIR-V variants (i.e. every build on Steam today) is
|
||||
// reported as valid - the translator handles it.
|
||||
DllStatus validateDll(const std::string& dllPath);
|
||||
|
||||
// Parse the DLL, produce all 25 SPIR-V modules, and write them to cachePath
|
||||
// (via temp file + rename, so a failed build cannot leave a half-written
|
||||
// cache behind). Records which producer ran in the cache header.
|
||||
DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, bool preferFp16);
|
||||
|
||||
// Load a previously built cache.
|
||||
DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet);
|
||||
|
||||
// Which producer built an existing cache, without loading the modules.
|
||||
DllStatus cacheVariant(const std::string& cachePath, Variant& outVariant);
|
||||
|
||||
// Experimental Vulkan 1.1 / 1.2 compat: lower a DXBC-translated module's
|
||||
// declared SPIR-V version to `targetVersion` (kSpirv14 or kSpirv15). Below 1.5
|
||||
// the Vulkan memory model is not core, so SPV_KHR_vulkan_memory_model is
|
||||
// declared for it (the device side enables VK_KHR_vulkan_memory_model). The
|
||||
// cache is built without a device in hand, always at the translator's native
|
||||
// 1.6, so this runs at load time against whatever the device can take. A
|
||||
// module already at or below the target is left untouched. Returns false,
|
||||
// leaving the module untouched, for a malformed module, a target below 1.4, or
|
||||
// a module declaring DemoteToHelperInvocation: that capability is core only in
|
||||
// 1.6 and its extension is never enabled on the compat path, so such a module
|
||||
// cannot be made legal here (DXVK emits it for pixel-shader discard only, so
|
||||
// the 25 compute shaders never carry it). Whether the device then accepts the
|
||||
// result is vkCreateShaderModule's / vkCreateComputePipelines' verdict.
|
||||
bool downgradeSpirv(std::vector<uint32_t>& words, uint32_t targetVersion);
|
||||
|
||||
}
|
||||
} // namespace lsfg
|
||||
Loaded 100 of 2932 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user